mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
Add module: speaker capture
This commit is contained in:
@@ -8,7 +8,6 @@
|
|||||||
#define _SCREEN_CAPTURER_FACTORY_H_
|
#define _SCREEN_CAPTURER_FACTORY_H_
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
#include "screen_capturer_wgc.h"
|
#include "screen_capturer_wgc.h"
|
||||||
#elif __linux__
|
#elif __linux__
|
||||||
#include "screen_capturer_x11.h"
|
#include "screen_capturer_x11.h"
|
||||||
|
|||||||
@@ -108,6 +108,36 @@ int Render::StopScreenCapture() {
|
|||||||
return 0;
|
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() {
|
int Render::StartMouseControl() {
|
||||||
device_controller_factory_ = new DeviceControllerFactory();
|
device_controller_factory_ = new DeviceControllerFactory();
|
||||||
mouse_controller_ = (MouseController *)device_controller_factory_->Create(
|
mouse_controller_ = (MouseController *)device_controller_factory_->Create(
|
||||||
@@ -239,10 +269,10 @@ int Render::Run() {
|
|||||||
want_out.channels = 1;
|
want_out.channels = 1;
|
||||||
// want_out.silence = 0;
|
// want_out.silence = 0;
|
||||||
want_out.samples = 480;
|
want_out.samples = 480;
|
||||||
want_out.callback = SdlCaptureAudioOut;
|
want_out.callback = nullptr;
|
||||||
want_out.userdata = this;
|
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) {
|
if (output_dev_ == 0) {
|
||||||
SDL_Log("Failed to open input: %s", SDL_GetError());
|
SDL_Log("Failed to open input: %s", SDL_GetError());
|
||||||
// return 1;
|
// return 1;
|
||||||
@@ -301,10 +331,15 @@ int Render::Run() {
|
|||||||
// Screen capture
|
// Screen capture
|
||||||
screen_capturer_factory_ = new ScreenCapturerFactory();
|
screen_capturer_factory_ = new ScreenCapturerFactory();
|
||||||
|
|
||||||
|
// Speaker capture
|
||||||
|
// speaker_capturer_factory_ = new SpeakerCapturerFactory();
|
||||||
|
|
||||||
// Mouse control
|
// Mouse control
|
||||||
device_controller_factory_ = new DeviceControllerFactory();
|
device_controller_factory_ = new DeviceControllerFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartSpeakerCapture();
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
while (!exit_) {
|
while (!exit_) {
|
||||||
if (SignalStatus::SignalConnected == signal_status_ &&
|
if (SignalStatus::SignalConnected == signal_status_ &&
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "imgui_impl_sdl2.h"
|
#include "imgui_impl_sdl2.h"
|
||||||
#include "imgui_impl_sdlrenderer2.h"
|
#include "imgui_impl_sdlrenderer2.h"
|
||||||
#include "screen_capturer_factory.h"
|
#include "screen_capturer_factory.h"
|
||||||
|
#include "speaker_capturer_factory.h"
|
||||||
|
|
||||||
class Render {
|
class Render {
|
||||||
public:
|
public:
|
||||||
@@ -72,6 +73,9 @@ class Render {
|
|||||||
int StartScreenCapture();
|
int StartScreenCapture();
|
||||||
int StopScreenCapture();
|
int StopScreenCapture();
|
||||||
|
|
||||||
|
int StartSpeakerCapture();
|
||||||
|
int StopSpeakerCapture();
|
||||||
|
|
||||||
int StartMouseControl();
|
int StartMouseControl();
|
||||||
int StopMouseControl();
|
int StopMouseControl();
|
||||||
|
|
||||||
@@ -213,6 +217,8 @@ class Render {
|
|||||||
private:
|
private:
|
||||||
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
|
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
|
||||||
ScreenCapturer *screen_capturer_ = nullptr;
|
ScreenCapturer *screen_capturer_ = nullptr;
|
||||||
|
SpeakerCapturerFactory *speaker_capturer_factory_ = nullptr;
|
||||||
|
SpeakerCapturer *speaker_capturer_ = nullptr;
|
||||||
DeviceControllerFactory *device_controller_factory_ = nullptr;
|
DeviceControllerFactory *device_controller_factory_ = nullptr;
|
||||||
MouseController *mouse_controller_ = nullptr;
|
MouseController *mouse_controller_ = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -78,24 +78,24 @@ int Render::ProcessMouseKeyEven(SDL_Event &ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Render::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
|
void Render::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
|
||||||
// Render *render = (Render *)userdata;
|
Render *render = (Render *)userdata;
|
||||||
// if (1) {
|
if (1) {
|
||||||
// if ("Connected" == render->connection_status_str_) {
|
if ("Connected" == render->connection_status_str_) {
|
||||||
// SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len);
|
SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len);
|
||||||
// }
|
}
|
||||||
// } else {
|
} else {
|
||||||
// memcpy(render->audio_buffer_, stream, len);
|
memcpy(render->audio_buffer_, stream, len);
|
||||||
// render->audio_len_ = len;
|
render->audio_len_ = len;
|
||||||
// SDL_Delay(10);
|
SDL_Delay(10);
|
||||||
// render->audio_buffer_fresh_ = true;
|
render->audio_buffer_fresh_ = true;
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Render::SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) {
|
void Render::SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) {
|
||||||
Render *render = (Render *)userdata;
|
// Render *render = (Render *)userdata;
|
||||||
if ("Connected" == render->connection_status_str_) {
|
// if ("Connected" == render->connection_status_str_) {
|
||||||
SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len);
|
// SendData(render->peer_, DATA_TYPE::AUDIO, (const char *)stream, len);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// if (!render->audio_buffer_fresh_) {
|
// if (!render->audio_buffer_fresh_) {
|
||||||
// return;
|
// return;
|
||||||
|
|||||||
26
src/speaker_capturer/speaker_capturer.h
Normal file
26
src/speaker_capturer/speaker_capturer.h
Normal 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
|
||||||
32
src/speaker_capturer/speaker_capturer_factory.h
Normal file
32
src/speaker_capturer/speaker_capturer_factory.h
Normal 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
|
||||||
190
src/speaker_capturer/windows/speaker_capturer_wasapi.cpp
Normal file
190
src/speaker_capturer/windows/speaker_capturer_wasapi.cpp
Normal 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; }
|
||||||
61
src/speaker_capturer/windows/speaker_capturer_wasapi.h
Normal file
61
src/speaker_capturer/windows/speaker_capturer_wasapi.h
Normal 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
|
||||||
303
test/audio_capture/audio_capture_wasapi.cpp
Normal file
303
test/audio_capture/audio_capture_wasapi.cpp
Normal 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(¤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;
|
||||||
|
}
|
||||||
86
test/audio_capture/miniaudio.cpp
Normal file
86
test/audio_capture/miniaudio.cpp
Normal 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;
|
||||||
|
}
|
||||||
2
thirdparty/projectx
vendored
2
thirdparty/projectx
vendored
Submodule thirdparty/projectx updated: 792a286899...e73f9b3457
33
xmake.lua
33
xmake.lua
@@ -3,6 +3,7 @@ set_license("LGPL-3.0")
|
|||||||
|
|
||||||
set_version("0.0.1")
|
set_version("0.0.1")
|
||||||
add_defines("RD_VERSION=\"0.0.1\"");
|
add_defines("RD_VERSION=\"0.0.1\"");
|
||||||
|
add_defines("MINIAUDIO_IMPLEMENTATION")
|
||||||
|
|
||||||
add_rules("mode.release", "mode.debug")
|
add_rules("mode.release", "mode.debug")
|
||||||
set_languages("c++17")
|
set_languages("c++17")
|
||||||
@@ -77,6 +78,23 @@ target("screen_capturer")
|
|||||||
add_includedirs("src/screen_capturer/linux", {public = true})
|
add_includedirs("src/screen_capturer/linux", {public = true})
|
||||||
end
|
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")
|
target("device_controller")
|
||||||
set_kind("object")
|
set_kind("object")
|
||||||
add_deps("rd_log")
|
add_deps("rd_log")
|
||||||
@@ -115,7 +133,7 @@ target("original_version")
|
|||||||
|
|
||||||
target("single_window")
|
target("single_window")
|
||||||
set_kind("object")
|
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
|
if is_os("macosx") then
|
||||||
add_packages("ffmpeg")
|
add_packages("ffmpeg")
|
||||||
elseif is_os("linux") then
|
elseif is_os("linux") then
|
||||||
@@ -137,6 +155,19 @@ target("remote_desk")
|
|||||||
end
|
end
|
||||||
add_files("src/gui/main.cpp")
|
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")
|
-- target("screen_capturer")
|
||||||
-- set_kind("binary")
|
-- set_kind("binary")
|
||||||
-- add_packages("sdl2", "imgui", "ffmpeg", "openh264")
|
-- add_packages("sdl2", "imgui", "ffmpeg", "openh264")
|
||||||
|
|||||||
Reference in New Issue
Block a user