fix: 修复 PortAudio 无音频设备检测问题 + 初始化生命周期

1. 重新编译 PortAudio 启用 ALSA 支持(原先编译时未启用任何音频后端,
   导致设备数为 0,Pa_GetDefaultInputDevice() 返回 -1)
2. 修复 getDeviceList() 中 Pa_Terminate/Pa_Initialize 顺序错误
   (先 Terminate 再 Initialize 会导致状态异常)
3. 使用全局 gPaInitialized 标志管理 PortAudio 生命周期,
   避免多次 start/stop 后重复初始化或意外终止

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alvin Young 2026-05-12 20:08:56 +08:00
parent 6cb73b43a8
commit ecc79aaeb6

View File

@ -12,6 +12,27 @@ namespace impress {
// 预分配缓冲区,避免在实时回调中分配内存 // 预分配缓冲区,避免在实时回调中分配内存
static constexpr int kMaxBufferSize = 8192; static constexpr int kMaxBufferSize = 8192;
// 全局 PortAudio 初始化状态
static bool gPaInitialized = false;
/** 安全初始化 PortAudio多次调用不报错 */
static bool ensurePaInitialized() {
if (gPaInitialized) return true;
if (Pa_Initialize() == paNoError) {
gPaInitialized = true;
return true;
}
return false;
}
/** 安全终止 PortAudio */
static void safePaTerminate() {
if (gPaInitialized) {
Pa_Terminate();
gPaInitialized = false;
}
}
// 回调上下文:独立于 Impl 的 POD 结构,供静态回调使用 // 回调上下文:独立于 Impl 的 POD 结构,供静态回调使用
struct CallbackContext { struct CallbackContext {
AudioCapture* owner = nullptr; AudioCapture* owner = nullptr;
@ -72,8 +93,10 @@ QStringList AudioCapture::getDeviceList() {
QStringList devices; QStringList devices;
devices << "默认设备"; devices << "默认设备";
#ifdef HAVE_PORTAUDIO #ifdef HAVE_PORTAUDIO
Pa_Terminate(); // 确保未初始化 if (!ensurePaInitialized()) {
if (Pa_Initialize() == paNoError) { LOG_ERROR(kTag, "PortAudio 初始化失败");
return devices;
}
int count = Pa_GetDeviceCount(); int count = Pa_GetDeviceCount();
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
const PaDeviceInfo* info = Pa_GetDeviceInfo(i); const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
@ -82,8 +105,6 @@ QStringList AudioCapture::getDeviceList() {
info->name).arg(info->maxInputChannels).arg(info->defaultSampleRate); info->name).arg(info->maxInputChannels).arg(info->defaultSampleRate);
} }
} }
Pa_Terminate();
}
#endif #endif
return devices; return devices;
} }
@ -95,22 +116,20 @@ bool AudioCapture::start(int deviceIndex, int sampleRate, int bufferSizeMs) {
} }
#ifdef HAVE_PORTAUDIO #ifdef HAVE_PORTAUDIO
if (Pa_Initialize() != paNoError) { if (!ensurePaInitialized()) {
LOG_ERROR(kTag, "PortAudio 初始化失败"); LOG_ERROR(kTag, "PortAudio 初始化失败");
return false; return false;
} }
int devIdx = deviceIndex < 0 ? Pa_GetDefaultInputDevice() : deviceIndex; int devIdx = deviceIndex < 0 ? Pa_GetDefaultInputDevice() : deviceIndex;
if (devIdx < 0 || devIdx >= Pa_GetDeviceCount()) { if (devIdx < 0 || devIdx >= Pa_GetDeviceCount()) {
LOG_ERROR(kTag, QString("无效的音频设备索引: %1").arg(deviceIndex)); LOG_ERROR(kTag, QString("无效的音频设备索引: %1 (默认设备: %2)").arg(deviceIndex).arg(Pa_GetDefaultInputDevice()));
Pa_Terminate();
return false; return false;
} }
const PaDeviceInfo* devInfo = Pa_GetDeviceInfo(devIdx); const PaDeviceInfo* devInfo = Pa_GetDeviceInfo(devIdx);
if (!devInfo || devInfo->maxInputChannels <= 0) { if (!devInfo || devInfo->maxInputChannels <= 0) {
LOG_ERROR(kTag, "所选设备不是输入设备"); LOG_ERROR(kTag, "所选设备不是输入设备");
Pa_Terminate();
return false; return false;
} }
@ -131,7 +150,6 @@ bool AudioCapture::start(int deviceIndex, int sampleRate, int bufferSizeMs) {
if (err != paNoError || !impl_->ctx.stream) { if (err != paNoError || !impl_->ctx.stream) {
LOG_ERROR(kTag, QString("打开音频流失败: %1").arg(Pa_GetErrorText(err))); LOG_ERROR(kTag, QString("打开音频流失败: %1").arg(Pa_GetErrorText(err)));
Pa_Terminate();
return false; return false;
} }
@ -140,7 +158,6 @@ bool AudioCapture::start(int deviceIndex, int sampleRate, int bufferSizeMs) {
LOG_ERROR(kTag, QString("启动音频流失败: %1").arg(Pa_GetErrorText(err))); LOG_ERROR(kTag, QString("启动音频流失败: %1").arg(Pa_GetErrorText(err)));
Pa_CloseStream(impl_->ctx.stream); Pa_CloseStream(impl_->ctx.stream);
impl_->ctx.stream = nullptr; impl_->ctx.stream = nullptr;
Pa_Terminate();
return false; return false;
} }
@ -166,7 +183,7 @@ void AudioCapture::stop() {
Pa_CloseStream(impl_->ctx.stream); Pa_CloseStream(impl_->ctx.stream);
impl_->ctx.stream = nullptr; impl_->ctx.stream = nullptr;
} }
Pa_Terminate(); safePaTerminate();
#endif #endif
running_ = false; running_ = false;