mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-19 10:23:10 +08:00
[fix] prevent system audio capture hangs across desktop platforms
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user