From ecc79aaeb66eb3acc00b3452e742fb55579bc202 Mon Sep 17 00:00:00 2001 From: impressionyang Date: Tue, 12 May 2026 20:08:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20PortAudio=20?= =?UTF-8?q?=E6=97=A0=E9=9F=B3=E9=A2=91=E8=AE=BE=E5=A4=87=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20+=20=E5=88=9D=E5=A7=8B=E5=8C=96=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/audio/audio_capture.cpp | 51 ++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/audio/audio_capture.cpp b/src/audio/audio_capture.cpp index 0a9c520..e5d1f7d 100644 --- a/src/audio/audio_capture.cpp +++ b/src/audio/audio_capture.cpp @@ -12,6 +12,27 @@ namespace impress { // 预分配缓冲区,避免在实时回调中分配内存 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 结构,供静态回调使用 struct CallbackContext { AudioCapture* owner = nullptr; @@ -72,17 +93,17 @@ QStringList AudioCapture::getDeviceList() { QStringList devices; devices << "默认设备"; #ifdef HAVE_PORTAUDIO - Pa_Terminate(); // 确保未初始化 - if (Pa_Initialize() == paNoError) { - int count = Pa_GetDeviceCount(); - for (int i = 0; i < count; ++i) { - const PaDeviceInfo* info = Pa_GetDeviceInfo(i); - if (info && info->maxInputChannels > 0) { - devices << QString("%1 (CH:%2, SR:%3)").arg( - info->name).arg(info->maxInputChannels).arg(info->defaultSampleRate); - } + if (!ensurePaInitialized()) { + LOG_ERROR(kTag, "PortAudio 初始化失败"); + return devices; + } + int count = Pa_GetDeviceCount(); + for (int i = 0; i < count; ++i) { + const PaDeviceInfo* info = Pa_GetDeviceInfo(i); + if (info && info->maxInputChannels > 0) { + devices << QString("%1 (CH:%2, SR:%3)").arg( + info->name).arg(info->maxInputChannels).arg(info->defaultSampleRate); } - Pa_Terminate(); } #endif return devices; @@ -95,22 +116,20 @@ bool AudioCapture::start(int deviceIndex, int sampleRate, int bufferSizeMs) { } #ifdef HAVE_PORTAUDIO - if (Pa_Initialize() != paNoError) { + if (!ensurePaInitialized()) { LOG_ERROR(kTag, "PortAudio 初始化失败"); return false; } int devIdx = deviceIndex < 0 ? Pa_GetDefaultInputDevice() : deviceIndex; if (devIdx < 0 || devIdx >= Pa_GetDeviceCount()) { - LOG_ERROR(kTag, QString("无效的音频设备索引: %1").arg(deviceIndex)); - Pa_Terminate(); + LOG_ERROR(kTag, QString("无效的音频设备索引: %1 (默认设备: %2)").arg(deviceIndex).arg(Pa_GetDefaultInputDevice())); return false; } const PaDeviceInfo* devInfo = Pa_GetDeviceInfo(devIdx); if (!devInfo || devInfo->maxInputChannels <= 0) { LOG_ERROR(kTag, "所选设备不是输入设备"); - Pa_Terminate(); return false; } @@ -131,7 +150,6 @@ bool AudioCapture::start(int deviceIndex, int sampleRate, int bufferSizeMs) { if (err != paNoError || !impl_->ctx.stream) { LOG_ERROR(kTag, QString("打开音频流失败: %1").arg(Pa_GetErrorText(err))); - Pa_Terminate(); return false; } @@ -140,7 +158,6 @@ bool AudioCapture::start(int deviceIndex, int sampleRate, int bufferSizeMs) { LOG_ERROR(kTag, QString("启动音频流失败: %1").arg(Pa_GetErrorText(err))); Pa_CloseStream(impl_->ctx.stream); impl_->ctx.stream = nullptr; - Pa_Terminate(); return false; } @@ -166,7 +183,7 @@ void AudioCapture::stop() { Pa_CloseStream(impl_->ctx.stream); impl_->ctx.stream = nullptr; } - Pa_Terminate(); + safePaTerminate(); #endif running_ = false;