Add module: speaker capture

This commit is contained in:
dijunkun
2024-07-24 16:16:13 +08:00
parent f446154747
commit a99a4230af
12 changed files with 789 additions and 20 deletions

View File

@@ -8,7 +8,6 @@
#define _SCREEN_CAPTURER_FACTORY_H_
#ifdef _WIN32
#include "screen_capturer_wgc.h"
#elif __linux__
#include "screen_capturer_x11.h"

View File

@@ -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_ &&

View File

@@ -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;

View File

@@ -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;
// }
}
void Render::SdlCaptureAudioOut(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;
}
}
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);
// }
// if (!render->audio_buffer_fresh_) {
// return;

View File

@@ -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 <functional>
class SpeakerCapturer {
public:
typedef std::function<void(unsigned char *, size_t)> 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

View File

@@ -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

View File

@@ -0,0 +1,190 @@
#include "speaker_capturer_wasapi.h"
#include <algorithm>
#include <climits>
#include <iostream>
#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<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;
}
}
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<BYTE>(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; }

View File

@@ -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 <Audioclient.h>
#include <Devicetopology.h>
#include <Endpointvolume.h>
#include <Mmdeviceapi.h>
#include <thread>
#include <vector>
#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<BYTE> 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<std::thread> capture_thread_ = nullptr;
};
#endif

View File

@@ -0,0 +1,303 @@
// 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(&current_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;
}

View File

@@ -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 <stdio.h>
#include <stdlib.h>
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;
}

View File

@@ -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")