mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-27 04:35:34 +08:00
303 lines
9.4 KiB
C++
303 lines
9.4 KiB
C++
// MyAudioSink.cpp : 定义控制台应用程序的入口点。
|
||
//
|
||
|
||
// #define _CRT_SECURE_NO_WARNINGS
|
||
|
||
#include <Audioclient.h>
|
||
#include <Devicetopology.h>
|
||
#include <Endpointvolume.h>
|
||
#include <Mmdeviceapi.h>
|
||
#include <tchar.h>
|
||
|
||
#include <iostream>
|
||
//-----------------------------------------------------------
|
||
// Record an audio stream from the default audio capture
|
||
// device. The RecordAudioStream function allocates a shared
|
||
// buffer big enough to hold one second of PCM audio data.
|
||
// The function uses this buffer to stream data from the
|
||
// capture device. The main loop runs every 1/2 second.
|
||
//-----------------------------------------------------------
|
||
|
||
// REFERENCE_TIME time units per second and per millisecond
|
||
#define REFTIMES_PER_SEC 10000000
|
||
#define REFTIMES_PER_MILLISEC 10000
|
||
|
||
#define EXIT_ON_ERROR(hres) \
|
||
if (FAILED(hres)) { \
|
||
goto Exit; \
|
||
}
|
||
|
||
#define SAFE_RELEASE(punk) \
|
||
if ((punk) != NULL) { \
|
||
(punk)->Release(); \
|
||
(punk) = NULL; \
|
||
}
|
||
|
||
#define IS_INPUT_DEVICE 0 // 切换输入和输出音频设备
|
||
|
||
#define BUFFER_TIME_100NS (5 * 10000000)
|
||
|
||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
|
||
|
||
const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
|
||
const IID IID_IAudioVolumeLevel = __uuidof(IAudioVolumeLevel);
|
||
const IID IID_IPart = __uuidof(IPart);
|
||
const IID IID_IConnector = __uuidof(IConnector);
|
||
const IID IID_IAudioEndpointVolume = __uuidof(IAudioEndpointVolume);
|
||
|
||
class MyAudioSink {
|
||
public:
|
||
// WAVEFORMATEX *pwfx = NULL;
|
||
int SetFormat(WAVEFORMATEX *pwfx);
|
||
|
||
int CopyData(SHORT *pData, UINT32 numFramesAvailable, BOOL *pbDone);
|
||
};
|
||
|
||
int MyAudioSink::SetFormat(WAVEFORMATEX *pwfx) {
|
||
printf("wFormatTag is %x\n", pwfx->wFormatTag);
|
||
printf("nChannels is %x\n", pwfx->nChannels);
|
||
printf("nSamplesPerSec is %d\n", pwfx->nSamplesPerSec);
|
||
printf("nAvgBytesPerSec is %d\n", pwfx->nAvgBytesPerSec);
|
||
printf("wBitsPerSample is %d\n", pwfx->wBitsPerSample);
|
||
|
||
return 0;
|
||
}
|
||
|
||
FILE *fp;
|
||
|
||
int MyAudioSink::CopyData(SHORT *pData, UINT32 numFramesAvailable,
|
||
BOOL *pbDone) {
|
||
if (pData != NULL) {
|
||
size_t t = sizeof(SHORT);
|
||
for (int i = 0; i < numFramesAvailable / t; i++) {
|
||
double dbVal = pData[i];
|
||
pData[i] = dbVal; // 可以通过不同的分母来控制声音大小
|
||
}
|
||
fwrite(pData, numFramesAvailable, 1, fp);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/// pwfx->nSamplesPerSec = 44100;
|
||
/// 不支持修改采样率, 看来只能等得到数据之后再 swr 转换了
|
||
BOOL AdjustFormatTo16Bits(WAVEFORMATEX *pwfx) {
|
||
BOOL bRet(FALSE);
|
||
|
||
if (pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
|
||
pwfx->wFormatTag = WAVE_FORMAT_PCM;
|
||
pwfx->wBitsPerSample = 16;
|
||
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
|
||
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
|
||
|
||
bRet = TRUE;
|
||
} else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||
PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx);
|
||
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) {
|
||
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||
pEx->Samples.wValidBitsPerSample = 16;
|
||
pwfx->wBitsPerSample = 16;
|
||
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
|
||
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
|
||
|
||
bRet = TRUE;
|
||
}
|
||
}
|
||
|
||
return bRet;
|
||
}
|
||
|
||
typedef unsigned long long uint64_t;
|
||
static bool have_clockfreq = false;
|
||
static LARGE_INTEGER clock_freq;
|
||
static inline uint64_t get_clockfreq(void) {
|
||
if (!have_clockfreq) QueryPerformanceFrequency(&clock_freq);
|
||
return clock_freq.QuadPart;
|
||
}
|
||
uint64_t os_gettime_ns(void) {
|
||
LARGE_INTEGER current_time;
|
||
double time_val;
|
||
|
||
QueryPerformanceCounter(¤t_time);
|
||
time_val = (double)current_time.QuadPart;
|
||
time_val *= 1000000000.0;
|
||
time_val /= (double)get_clockfreq();
|
||
|
||
return (uint64_t)time_val;
|
||
}
|
||
|
||
HRESULT RecordAudioStream(MyAudioSink *pMySink) {
|
||
HRESULT hr;
|
||
REFERENCE_TIME hnsActualDuration;
|
||
UINT32 bufferFrameCount;
|
||
UINT32 numFramesAvailable;
|
||
BYTE *pData;
|
||
DWORD flags;
|
||
REFERENCE_TIME hnsDefaultDevicePeriod(0);
|
||
|
||
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
|
||
IMMDeviceEnumerator *pEnumerator = NULL;
|
||
IMMDevice *pDevice = NULL;
|
||
IAudioClient *pAudioClient = NULL;
|
||
IAudioCaptureClient *pCaptureClient = NULL;
|
||
WAVEFORMATEX *pwfx = NULL;
|
||
UINT32 packetLength = 0;
|
||
BOOL bDone = FALSE;
|
||
HANDLE hTimerWakeUp = NULL;
|
||
UINT64 pos, ts;
|
||
|
||
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||
IID_IMMDeviceEnumerator, (void **)&pEnumerator);
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
if (IS_INPUT_DEVICE)
|
||
hr = pEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications,
|
||
&pDevice); // 输入
|
||
else
|
||
hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole,
|
||
&pDevice); // 输出
|
||
|
||
// wchar_t *w_id;
|
||
// os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
|
||
// hr = pEnumerator->GetDevice(w_id, &pDevice);
|
||
// bfree(w_id);
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL,
|
||
(void **)&pAudioClient);
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
hr = pAudioClient->GetMixFormat(&pwfx);
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
// The GetDevicePeriod method retrieves the length of the periodic interval
|
||
// separating successive processing passes by the audio engine on the data in
|
||
// the endpoint buffer.
|
||
hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
AdjustFormatTo16Bits(pwfx);
|
||
|
||
// 平时创建定时器使用的是WINAPI SetTimer,不过该函数一般用于有界面的时候。
|
||
// 无界面的情况下,可以选择微软提供的CreateWaitableTimer和SetWaitableTimer
|
||
// API。
|
||
hTimerWakeUp = CreateWaitableTimer(NULL, FALSE, NULL);
|
||
|
||
DWORD flag;
|
||
if (IS_INPUT_DEVICE)
|
||
flag = 0;
|
||
else
|
||
flag = AUDCLNT_STREAMFLAGS_LOOPBACK;
|
||
|
||
if (IS_INPUT_DEVICE)
|
||
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, flag /*0*/, 0, 0,
|
||
pwfx, NULL); // 输入
|
||
else
|
||
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, flag /*0*/, 0, 0,
|
||
pwfx, NULL); // 输出
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
// Get the size of the allocated buffer.
|
||
hr = pAudioClient->GetBufferSize(&bufferFrameCount);
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
hr = pAudioClient->GetService(IID_IAudioCaptureClient,
|
||
(void **)&pCaptureClient);
|
||
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
LARGE_INTEGER liFirstFire;
|
||
liFirstFire.QuadPart =
|
||
-hnsDefaultDevicePeriod / 2; // negative means relative time
|
||
LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 /
|
||
(10 * 1000); // convert to milliseconds
|
||
|
||
BOOL bOK = SetWaitableTimer(hTimerWakeUp, &liFirstFire, lTimeBetweenFires,
|
||
NULL, NULL, FALSE);
|
||
|
||
// Notify the audio sink which format to use.
|
||
hr = pMySink->SetFormat(pwfx);
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
// Calculate the actual duration of the allocated buffer.
|
||
hnsActualDuration =
|
||
(double)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
|
||
|
||
/*************************************************************/
|
||
hr = pAudioClient->Start(); // Start recording.
|
||
EXIT_ON_ERROR(hr)
|
||
HANDLE waitArray[1] = {/*htemp hEventStop,*/ hTimerWakeUp};
|
||
|
||
// Each loop fills about half of the shared buffer.
|
||
while (bDone == FALSE) {
|
||
// Sleep for half the buffer duration.
|
||
// Sleep(hnsActualDuration/REFTIMES_PER_MILLISEC/2);//这句貌似不加也可以
|
||
// WaitForSingleObject(hTimerWakeUp,INFINITE);
|
||
int a = sizeof(waitArray);
|
||
int aa = sizeof(waitArray[0]);
|
||
WaitForMultipleObjects(sizeof(waitArray) / sizeof(waitArray[0]), waitArray,
|
||
FALSE, INFINITE);
|
||
// WaitForMultipleObjects(sizeof(waitArray) / sizeof(waitArray[0]),
|
||
// waitArray, FALSE, INFINITE);
|
||
|
||
hr = pCaptureClient->GetNextPacketSize(&packetLength);
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
while (packetLength != 0) {
|
||
// Get the available data in the shared buffer.
|
||
hr = pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, NULL,
|
||
&ts);
|
||
ts = ts * 100;
|
||
uint64_t timestamp =
|
||
os_gettime_ns(); // ts是设备时间,timestamp是系统时间
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
// 位运算,flags的标志符为2(静音状态)时,将pData置为NULL
|
||
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
|
||
pData = NULL; // Tell CopyData to write silence.
|
||
}
|
||
|
||
// Copy the available capture data to the audio sink.
|
||
hr = pMySink->CopyData((SHORT *)pData,
|
||
numFramesAvailable * pwfx->nBlockAlign, &bDone);
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
hr = pCaptureClient->GetNextPacketSize(&packetLength);
|
||
EXIT_ON_ERROR(hr)
|
||
}
|
||
}
|
||
|
||
hr = pAudioClient->Stop(); // Stop recording.
|
||
EXIT_ON_ERROR(hr)
|
||
|
||
Exit:
|
||
CoTaskMemFree(pwfx);
|
||
SAFE_RELEASE(pEnumerator)
|
||
SAFE_RELEASE(pDevice)
|
||
SAFE_RELEASE(pAudioClient)
|
||
SAFE_RELEASE(pCaptureClient)
|
||
|
||
return hr;
|
||
}
|
||
|
||
int _tmain(int argc, _TCHAR *argv[]) {
|
||
fopen_s(&fp, "record.pcm", "wb");
|
||
CoInitialize(NULL);
|
||
MyAudioSink test;
|
||
|
||
RecordAudioStream(&test);
|
||
|
||
return 0;
|
||
} |