From a99a4230af0fca4663966fca27bd56953de4e890 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 24 Jul 2024 16:16:13 +0800 Subject: [PATCH] Add module: speaker capture --- src/screen_capturer/screen_capturer_factory.h | 1 - src/single_window/render.cpp | 39 ++- src/single_window/render.h | 6 + src/single_window/render_callback_func.cpp | 30 +- src/speaker_capturer/speaker_capturer.h | 26 ++ .../speaker_capturer_factory.h | 32 ++ .../windows/speaker_capturer_wasapi.cpp | 190 +++++++++++ .../windows/speaker_capturer_wasapi.h | 61 ++++ test/audio_capture/audio_capture_wasapi.cpp | 303 ++++++++++++++++++ test/audio_capture/miniaudio.cpp | 86 +++++ thirdparty/projectx | 2 +- xmake.lua | 33 +- 12 files changed, 789 insertions(+), 20 deletions(-) create mode 100644 src/speaker_capturer/speaker_capturer.h create mode 100644 src/speaker_capturer/speaker_capturer_factory.h create mode 100644 src/speaker_capturer/windows/speaker_capturer_wasapi.cpp create mode 100644 src/speaker_capturer/windows/speaker_capturer_wasapi.h create mode 100644 test/audio_capture/audio_capture_wasapi.cpp create mode 100644 test/audio_capture/miniaudio.cpp diff --git a/src/screen_capturer/screen_capturer_factory.h b/src/screen_capturer/screen_capturer_factory.h index 55c0fad..cdd8927 100644 --- a/src/screen_capturer/screen_capturer_factory.h +++ b/src/screen_capturer/screen_capturer_factory.h @@ -8,7 +8,6 @@ #define _SCREEN_CAPTURER_FACTORY_H_ #ifdef _WIN32 - #include "screen_capturer_wgc.h" #elif __linux__ #include "screen_capturer_x11.h" diff --git a/src/single_window/render.cpp b/src/single_window/render.cpp index 51f159b..ff7c9c9 100644 --- a/src/single_window/render.cpp +++ b/src/single_window/render.cpp @@ -108,6 +108,36 @@ int Render::StopScreenCapture() { return 0; } +int Render::StartSpeakerCapture() { + speaker_capturer_ = (SpeakerCapturer *)speaker_capturer_factory_->Create(); + + int speaker_capturer_init_ret = + speaker_capturer_->Init([this](unsigned char *data, size_t size) -> void { + SendData(peer_, DATA_TYPE::AUDIO, (const char *)data, size); + }); + + if (0 == speaker_capturer_init_ret) { + speaker_capturer_->Start(); + } else { + speaker_capturer_->Destroy(); + delete speaker_capturer_; + speaker_capturer_ = nullptr; + } + + return 0; +} + +int Render::StopSpeakerCapture() { + if (speaker_capturer_) { + LOG_INFO("Destroy speaker capturer") + speaker_capturer_->Destroy(); + delete speaker_capturer_; + speaker_capturer_ = nullptr; + } + + return 0; +} + int Render::StartMouseControl() { device_controller_factory_ = new DeviceControllerFactory(); mouse_controller_ = (MouseController *)device_controller_factory_->Create( @@ -239,10 +269,10 @@ int Render::Run() { want_out.channels = 1; // want_out.silence = 0; want_out.samples = 480; - want_out.callback = SdlCaptureAudioOut; + want_out.callback = nullptr; want_out.userdata = this; - output_dev_ = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out, 0); + output_dev_ = SDL_OpenAudioDevice(nullptr, 0, &want_out, NULL, 0); if (output_dev_ == 0) { SDL_Log("Failed to open input: %s", SDL_GetError()); // return 1; @@ -301,10 +331,15 @@ int Render::Run() { // Screen capture screen_capturer_factory_ = new ScreenCapturerFactory(); + // Speaker capture + // speaker_capturer_factory_ = new SpeakerCapturerFactory(); + // Mouse control device_controller_factory_ = new DeviceControllerFactory(); } + // StartSpeakerCapture(); + // Main loop while (!exit_) { if (SignalStatus::SignalConnected == signal_status_ && diff --git a/src/single_window/render.h b/src/single_window/render.h index 870b3bc..95d531a 100644 --- a/src/single_window/render.h +++ b/src/single_window/render.h @@ -20,6 +20,7 @@ #include "imgui_impl_sdl2.h" #include "imgui_impl_sdlrenderer2.h" #include "screen_capturer_factory.h" +#include "speaker_capturer_factory.h" class Render { public: @@ -72,6 +73,9 @@ class Render { int StartScreenCapture(); int StopScreenCapture(); + int StartSpeakerCapture(); + int StopSpeakerCapture(); + int StartMouseControl(); int StopMouseControl(); @@ -213,6 +217,8 @@ class Render { private: ScreenCapturerFactory *screen_capturer_factory_ = nullptr; ScreenCapturer *screen_capturer_ = nullptr; + SpeakerCapturerFactory *speaker_capturer_factory_ = nullptr; + SpeakerCapturer *speaker_capturer_ = nullptr; DeviceControllerFactory *device_controller_factory_ = nullptr; MouseController *mouse_controller_ = nullptr; diff --git a/src/single_window/render_callback_func.cpp b/src/single_window/render_callback_func.cpp index 9b76976..2484b8b 100644 --- a/src/single_window/render_callback_func.cpp +++ b/src/single_window/render_callback_func.cpp @@ -78,24 +78,24 @@ int Render::ProcessMouseKeyEven(SDL_Event &ev) { } void Render::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) { - // Render *render = (Render *)userdata; - // if (1) { - // if ("Connected" == render->connection_status_str_) { - // SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len); - // } - // } else { - // memcpy(render->audio_buffer_, stream, len); - // render->audio_len_ = len; - // SDL_Delay(10); - // render->audio_buffer_fresh_ = true; - // } + Render *render = (Render *)userdata; + if (1) { + if ("Connected" == render->connection_status_str_) { + SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len); + } + } else { + memcpy(render->audio_buffer_, stream, len); + render->audio_len_ = len; + SDL_Delay(10); + render->audio_buffer_fresh_ = true; + } } void Render::SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) { - Render *render = (Render *)userdata; - if ("Connected" == render->connection_status_str_) { - SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len); - } + // Render *render = (Render *)userdata; + // if ("Connected" == render->connection_status_str_) { + // SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len); + // } // if (!render->audio_buffer_fresh_) { // return; diff --git a/src/speaker_capturer/speaker_capturer.h b/src/speaker_capturer/speaker_capturer.h new file mode 100644 index 0000000..3166409 --- /dev/null +++ b/src/speaker_capturer/speaker_capturer.h @@ -0,0 +1,26 @@ +/* + * @Author: DI JUNKUN + * @Date: 2024-07-22 + * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _SPEAKER_CAPTURER_H_ +#define _SPEAKER_CAPTURER_H_ + +#include + +class SpeakerCapturer { + public: + typedef std::function speaker_data_cb; + + public: + virtual ~SpeakerCapturer() {} + + public: + virtual int Init(speaker_data_cb cb) = 0; + virtual int Destroy() = 0; + virtual int Start() = 0; + virtual int Stop() = 0; +}; + +#endif \ No newline at end of file diff --git a/src/speaker_capturer/speaker_capturer_factory.h b/src/speaker_capturer/speaker_capturer_factory.h new file mode 100644 index 0000000..8c9a8fe --- /dev/null +++ b/src/speaker_capturer/speaker_capturer_factory.h @@ -0,0 +1,32 @@ +/* + * @Author: DI JUNKUN + * @Date: 2024-07-22 + * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _SPEAKER_CAPTURER_FACTORY_H_ +#define _SPEAKER_CAPTURER_FACTORY_H_ + +#ifdef _WIN32 +#include "speaker_capturer_wasapi.h" +#elif __linux__ +#elif __APPLE__ +#endif + +class SpeakerCapturerFactory { + public: + virtual ~SpeakerCapturerFactory() {} + + public: + SpeakerCapturer* Create() { +#ifdef _WIN32 + return new SpeakerCapturerWasapi(); +#elif __linux__ +#elif __APPLE__ +#else + return nullptr; +#endif + } +}; + +#endif \ No newline at end of file diff --git a/src/speaker_capturer/windows/speaker_capturer_wasapi.cpp b/src/speaker_capturer/windows/speaker_capturer_wasapi.cpp new file mode 100644 index 0000000..37cddd0 --- /dev/null +++ b/src/speaker_capturer/windows/speaker_capturer_wasapi.cpp @@ -0,0 +1,190 @@ +#include "speaker_capturer_wasapi.h" + +#include +#include +#include + +#define REFTIMES_PER_SEC 10000000 +#define REFTIMES_PER_MILLISEC 10000 + +#define SAVE_AUDIO_FILE 0 + +#define CHECK_HR(hres) \ + if (FAILED(hres)) { \ + return -1; \ + } + +#define SAFE_RELEASE(punk) \ + if ((punk) != nullptr) { \ + (punk)->Release(); \ + (punk) = nullptr; \ + } + +const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); +const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); + +SpeakerCapturerWasapi::SpeakerCapturerWasapi() {} + +SpeakerCapturerWasapi::~SpeakerCapturerWasapi() { + if (inited_ && capture_thread_->joinable()) { + capture_thread_->join(); + inited_ = false; + } + + CoTaskMemFree(pwfx); + SAFE_RELEASE(pEnumerator) + SAFE_RELEASE(pDevice) + SAFE_RELEASE(pAudioClient) + SAFE_RELEASE(pCaptureClient) + + if (SAVE_AUDIO_FILE) { + fclose(fp); + } + + // if (pData_dst) delete pData_dst; + // pData_dst = nullptr; +} + +int SpeakerCapturerWasapi::Init(speaker_data_cb cb) { + cb_ = cb; + + if (SAVE_AUDIO_FILE) { + fopen_s(&fp, "system_audio.pcm", "wb"); + } + + HRESULT hr; + + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, + IID_IMMDeviceEnumerator, (void **)&pEnumerator); + CHECK_HR(hr) + + hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, + &pDevice); // 输出 + CHECK_HR(hr) + + hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, + (void **)&pAudioClient); + CHECK_HR(hr) + + hr = pAudioClient->GetMixFormat(&pwfx); + CHECK_HR(hr) + + // Change to 16bit + 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; + } else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast(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; + } + } + + hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, pwfx, + nullptr); + CHECK_HR(hr) + + // Get the size of the allocated buffer. + hr = pAudioClient->GetBufferSize(&bufferFrameCount); + CHECK_HR(hr) + + hr = pAudioClient->GetService(IID_IAudioCaptureClient, + (void **)&pCaptureClient); + CHECK_HR(hr) + + // Show audio info + { + 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); + } + + hnsActualDuration = + (double)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec; + + // pData_dst = new BYTE[960]; + + inited_ = true; + + return 0; +} + +int SpeakerCapturerWasapi::Start() { + HRESULT hr; + hr = pAudioClient->Start(); + CHECK_HR(hr) + + capture_thread_.reset(new std::thread([this]() { + HRESULT hr; + + // Each loop fills about half of the shared buffer. + while (1) { + // Sleep for half the buffer duration. + Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 4); + + hr = pCaptureClient->GetNextPacketSize(&packetLength); + CHECK_HR(hr) + + while (packetLength != 0) { + // Get the available data in the shared buffer. + hr = pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, + nullptr, &ts); + CHECK_HR(hr) + + // flags equals to 2 means silence, set data to nullptr + if (flags == AUDCLNT_BUFFERFLAGS_SILENT) { + pData = nullptr; + } + + if (pData != nullptr) { + size_t size = numFramesAvailable * pwfx->nBlockAlign; + + for (int i = 0; i < size / 2; i++) { + BYTE left = pData[i * 2]; + BYTE right = pData[i * 2 + 1]; + // Right channel only? + BYTE monoSample = right; + + pData_dst[i] = static_cast(monoSample); + } + + cb_(pData_dst, size / 2); + + if (SAVE_AUDIO_FILE) { + fwrite(pData_dst, size / 2, 1, fp); + } + } + + hr = pCaptureClient->ReleaseBuffer(numFramesAvailable); + CHECK_HR(hr) + + hr = pCaptureClient->GetNextPacketSize(&packetLength); + CHECK_HR(hr) + } + } + })); + + return 0; +} + +int SpeakerCapturerWasapi::Stop() { + HRESULT hr; + hr = pAudioClient->Stop(); + CHECK_HR(hr) + return 0; +} + +int SpeakerCapturerWasapi::Destroy() { return 0; } + +int SpeakerCapturerWasapi::Pause() { return 0; } diff --git a/src/speaker_capturer/windows/speaker_capturer_wasapi.h b/src/speaker_capturer/windows/speaker_capturer_wasapi.h new file mode 100644 index 0000000..3bfafab --- /dev/null +++ b/src/speaker_capturer/windows/speaker_capturer_wasapi.h @@ -0,0 +1,61 @@ +/* + * @Author: DI JUNKUN + * @Date: 2024-07-22 + * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _SPEAKER_CAPTURER_WASAPI_H_ +#define _SPEAKER_CAPTURER_WASAPI_H_ + +#include +#include +#include +#include + +#include +#include + +#include "speaker_capturer.h" + +class SpeakerCapturerWasapi : public SpeakerCapturer { + public: + SpeakerCapturerWasapi(); + ~SpeakerCapturerWasapi(); + + public: + virtual int Init(speaker_data_cb cb); + virtual int Destroy(); + virtual int Start(); + virtual int Stop(); + + int Pause(); + int Resume(); + + private: + speaker_data_cb cb_ = nullptr; + + private: + REFERENCE_TIME hnsActualDuration; + UINT32 bufferFrameCount; + UINT32 numFramesAvailable; + BYTE *pData; + // std::vector pData_dst; + BYTE pData_dst[960]; + DWORD flags; + + // REFERENCE_TIME hnsRequestedDuration = 10000000; + IMMDeviceEnumerator *pEnumerator = NULL; + IMMDevice *pDevice = NULL; + IAudioClient *pAudioClient = NULL; + IAudioCaptureClient *pCaptureClient = NULL; + WAVEFORMATEX *pwfx = NULL; + UINT32 packetLength = 0; + UINT64 pos, ts; + FILE *fp; + + bool inited_ = false; + // thread + std::unique_ptr capture_thread_ = nullptr; +}; + +#endif \ No newline at end of file diff --git a/test/audio_capture/audio_capture_wasapi.cpp b/test/audio_capture/audio_capture_wasapi.cpp new file mode 100644 index 0000000..9013412 --- /dev/null +++ b/test/audio_capture/audio_capture_wasapi.cpp @@ -0,0 +1,303 @@ +// MyAudioSink.cpp : 定义控制台应用程序的入口点。 +// + +// #define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include + +#include +//----------------------------------------------------------- +// 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(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; +} \ No newline at end of file diff --git a/test/audio_capture/miniaudio.cpp b/test/audio_capture/miniaudio.cpp new file mode 100644 index 0000000..8169e13 --- /dev/null +++ b/test/audio_capture/miniaudio.cpp @@ -0,0 +1,86 @@ +/* +Demonstrates how to implement loopback recording. + +This example simply captures data from your default playback device until you +press Enter. The output is saved to the file specified on the command line. + +Loopback mode is when you record audio that is played from a given speaker. It +is only supported on WASAPI, but can be used indirectly with PulseAudio by +choosing the appropriate loopback device after enumeration. + +To use loopback mode you just need to set the device type to +ma_device_type_loopback and set the capture device config properties. The output +buffer in the callback will be null whereas the input buffer will be valid. +*/ +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio.h" + +#include +#include + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, + ma_uint32 frameCount) { + ma_encoder* pEncoder = (ma_encoder*)pDevice->pUserData; + MA_ASSERT(pEncoder != NULL); + + ma_encoder_write_pcm_frames(pEncoder, pInput, frameCount, NULL); + + (void)pOutput; +} + +int main(int argc, char** argv) { + ma_result result; + ma_encoder_config encoderConfig; + ma_encoder encoder; + ma_device_config deviceConfig; + ma_device device; + + /* Loopback mode is currently only supported on WASAPI. */ + ma_backend backends[] = {ma_backend_wasapi}; + + if (argc < 2) { + printf("No output file.\n"); + return -1; + } + + encoderConfig = + ma_encoder_config_init(ma_encoding_format_wav, ma_format_s16, 1, 48000); + + if (ma_encoder_init_file(argv[1], &encoderConfig, &encoder) != MA_SUCCESS) { + printf("Failed to initialize output file.\n"); + return -1; + } + + deviceConfig = ma_device_config_init(ma_device_type_loopback); + deviceConfig.capture.pDeviceID = + NULL; /* Use default device for this example. Set this to the ID of a + _playback_ device if you want to capture from a specific device. + */ + deviceConfig.capture.format = encoder.config.format; + deviceConfig.capture.channels = encoder.config.channels; + deviceConfig.sampleRate = encoder.config.sampleRate; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &encoder; + + result = ma_device_init_ex(backends, sizeof(backends) / sizeof(backends[0]), + NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + printf("Failed to initialize loopback device.\n"); + return -2; + } + + result = ma_device_start(&device); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to start device.\n"); + return -3; + } + + printf("Press Enter to stop recording...\n"); + getchar(); + + ma_device_uninit(&device); + ma_encoder_uninit(&encoder); + + return 0; +} \ No newline at end of file diff --git a/thirdparty/projectx b/thirdparty/projectx index 792a286..e73f9b3 160000 --- a/thirdparty/projectx +++ b/thirdparty/projectx @@ -1 +1 @@ -Subproject commit 792a2868998039defbfb6ecba330428d462a1bb5 +Subproject commit e73f9b34571e456594600244e580f648518e8ede diff --git a/xmake.lua b/xmake.lua index 6a22e1c..f404ed7 100644 --- a/xmake.lua +++ b/xmake.lua @@ -3,6 +3,7 @@ set_license("LGPL-3.0") set_version("0.0.1") add_defines("RD_VERSION=\"0.0.1\""); +add_defines("MINIAUDIO_IMPLEMENTATION") add_rules("mode.release", "mode.debug") set_languages("c++17") @@ -77,6 +78,23 @@ target("screen_capturer") add_includedirs("src/screen_capturer/linux", {public = true}) end +target("speaker_capturer") + set_kind("object") + add_deps("rd_log") + add_includedirs("src/speaker_capturer", {public = true}) + if is_os("windows") then + add_files("src/speaker_capturer/windows/*.cpp") + add_includedirs("src/speaker_capturer/windows", {public = true}) + elseif is_os("macosx") then + add_packages("ffmpeg") + add_files("src/speaker_capturer/macosx/*.cpp") + add_includedirs("src/speaker_capturer/macosx", {public = true}) + elseif is_os("linux") then + add_packages("ffmpeg") + add_files("src/speaker_capturer/linux/*.cpp") + add_includedirs("src/speaker_capturer/linux", {public = true}) + end + target("device_controller") set_kind("object") add_deps("rd_log") @@ -115,7 +133,7 @@ target("original_version") target("single_window") set_kind("object") - add_deps("rd_log", "common", "localization", "config_center", "projectx", "screen_capturer", "device_controller") + add_deps("rd_log", "common", "localization", "config_center", "projectx", "screen_capturer", "speaker_capturer", "device_controller") if is_os("macosx") then add_packages("ffmpeg") elseif is_os("linux") then @@ -137,6 +155,19 @@ target("remote_desk") end add_files("src/gui/main.cpp") +target("ra") + set_kind("binary") + if is_os("windows") then + add_files("test/audio_capture/audio_capture_wasapi.cpp") + end + +target("m") + set_kind("binary") + add_packages("miniaudio") + if is_os("windows") then + add_files("test/audio_capture/miniaudio.cpp") + end + -- target("screen_capturer") -- set_kind("binary") -- add_packages("sdl2", "imgui", "ffmpeg", "openh264")