[fix] prevent system audio capture hangs across desktop platforms

This commit is contained in:
dijunkun
2026-09-09 11:34:47 +08:00
parent b87ab025be
commit bd0a7aa2f6
16 changed files with 743 additions and 575 deletions
@@ -1,104 +1,95 @@
#include "speaker_capturer_wasapi.h"
#include <utility>
#include "rd_log.h"
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#define SAVE_AUDIO_FILE 0
namespace crossdesk {
static ma_device_config device_config_;
static ma_device device_;
static ma_format format_ = ma_format_s16;
static ma_uint32 sample_rate_ = ma_standard_sample_rate_48000;
static ma_uint32 channels_ = 1;
static FILE* fp_ = nullptr;
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
ma_uint32 frameCount) {
SpeakerCapturerWasapi* ptr = (SpeakerCapturerWasapi*)pDevice->pUserData;
if (ptr) {
if (SAVE_AUDIO_FILE) {
fwrite(pInput, frameCount * ma_get_bytes_per_frame(format_, channels_), 1,
fp_);
}
ptr->GetCallback()((unsigned char*)pInput,
frameCount * ma_get_bytes_per_frame(format_, channels_),
"audio");
}
(void)pOutput;
}
SpeakerCapturerWasapi::speaker_data_cb SpeakerCapturerWasapi::GetCallback() {
return cb_;
}
SpeakerCapturerWasapi::SpeakerCapturerWasapi() {}
// Each attempt owns its device. A failed/old session must never uninitialize
// a device belonging to another capturer or reuse already released storage.
class SpeakerCapturerWasapi::Impl {
public:
ma_device device{};
bool initialized = false;
speaker_data_cb callback;
};
SpeakerCapturerWasapi::SpeakerCapturerWasapi() : impl_(new Impl) {}
SpeakerCapturerWasapi::~SpeakerCapturerWasapi() {
if (SAVE_AUDIO_FILE) {
fclose(fp_);
}
Destroy();
delete impl_;
}
int SpeakerCapturerWasapi::Init(speaker_data_cb cb) {
if (inited_) {
return 0;
}
cb_ = cb;
if (SAVE_AUDIO_FILE) {
fopen_s(&fp_, "system_audio.pcm", "wb");
}
ma_result result;
if (impl_->initialized) return 0;
impl_->callback = std::move(cb);
ma_device_config config = ma_device_config_init(ma_device_type_loopback);
config.capture.pDeviceID =
nullptr; // Resolve the current default each attempt.
config.capture.format = ma_format_s16;
config.capture.channels = 1;
config.sampleRate = 48000;
config.pUserData = impl_;
config.dataCallback = [](ma_device* device, void*, const void* input,
ma_uint32 frames) {
auto* state = static_cast<Impl*>(device->pUserData);
if (state && state->callback && input && frames > 0)
state->callback((unsigned char*)input, frames * sizeof(int16_t), "audio");
};
ma_backend backends[] = {ma_backend_wasapi};
device_config_ = ma_device_config_init(ma_device_type_loopback);
device_config_.capture.pDeviceID = NULL;
device_config_.capture.format = format_;
device_config_.capture.channels = channels_;
device_config_.sampleRate = sample_rate_;
device_config_.dataCallback = data_callback;
device_config_.pUserData = this;
result = ma_device_init_ex(backends, sizeof(backends) / sizeof(backends[0]),
NULL, &device_config_, &device_);
const ma_result result =
ma_device_init_ex(backends, 1, nullptr, &config, &impl_->device);
if (result != MA_SUCCESS) {
LOG_ERROR("Failed to initialize loopback device");
impl_->callback = nullptr;
LOG_ERROR("Failed to initialize WASAPI loopback device: {}",
static_cast<int>(result));
return -1;
}
inited_ = true;
impl_->initialized = true;
return 0;
}
int SpeakerCapturerWasapi::Start() {
ma_result result = ma_device_start(&device_);
if (!impl_->initialized) return -1;
if (IsRunning()) return 0;
const ma_result result = ma_device_start(&impl_->device);
if (result != MA_SUCCESS) {
ma_device_uninit(&device_);
LOG_ERROR("Failed to start device");
LOG_ERROR("Failed to start WASAPI loopback device: {}",
static_cast<int>(result));
Destroy();
return -1;
}
return 0;
}
int SpeakerCapturerWasapi::Stop() {
ma_device_stop(&device_);
if (!impl_->initialized || !IsRunning()) return 0;
const ma_result result = ma_device_stop(&impl_->device);
if (result != MA_SUCCESS) {
LOG_WARN("Failed to stop WASAPI loopback device: {}",
static_cast<int>(result));
return -1;
}
return 0;
}
int SpeakerCapturerWasapi::Destroy() {
ma_device_uninit(&device_);
if (impl_->initialized) {
// ma_device_uninit also stops and drains the device callback thread.
ma_device_uninit(&impl_->device);
impl_->initialized = false;
}
impl_->callback = nullptr;
return 0;
}
int SpeakerCapturerWasapi::Pause() { return 0; }
} // namespace crossdesk
bool SpeakerCapturerWasapi::IsRunning() const {
return impl_->initialized && ma_device_is_started(&impl_->device);
}
int SpeakerCapturerWasapi::Pause() { return Stop(); }
int SpeakerCapturerWasapi::Resume() { return Start(); }
} // namespace crossdesk
@@ -14,24 +14,21 @@ namespace crossdesk {
class SpeakerCapturerWasapi : public SpeakerCapturer {
public:
SpeakerCapturerWasapi();
~SpeakerCapturerWasapi();
~SpeakerCapturerWasapi() override;
public:
virtual int Init(speaker_data_cb cb);
virtual int Destroy();
virtual int Start();
virtual int Stop();
int Init(speaker_data_cb cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
bool IsRunning() const override;
int Pause();
int Resume();
speaker_data_cb GetCallback();
private:
speaker_data_cb cb_ = nullptr;
private:
bool inited_ = false;
class Impl;
Impl* impl_ = nullptr;
};
} // namespace crossdesk
#endif