Files
NaviGlassClient/avaota_app_demo/src/audio/audio_capture.cpp
2025-12-31 15:13:39 +08:00

358 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file audio_capture.cpp
* @brief 音频采集实现 - 基于 ALSA
* @date 2024-11-24
* @platform Avaota F1 (V821 / RISC-V)
*/
#include "audio_capture.h"
#include "../utils/logger.h"
#include <cstring>
#include <errno.h>
/**
* @brief 构造函数
*/
AudioCapture::AudioCapture(const std::string& device, int sample_rate, int channels)
: m_device(device)
, m_sample_rate(sample_rate)
, m_channels(channels)
, m_pcm_handle(nullptr)
{
}
/**
* @brief 析构函数
*/
AudioCapture::~AudioCapture() {
if (m_pcm_handle) {
snd_pcm_drain(m_pcm_handle);
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
LOG_INFO("[AudioCapture] Device closed");
}
}
/**
* @brief 配置混音器以启用麦克风输入
* @param card_name 声卡名称,如 "hw:0"
* @return true 如果成功
*/
bool AudioCapture::setup_mixer(const char* card_name) {
snd_mixer_t *mixer = nullptr;
snd_mixer_selem_id_t *sid = nullptr;
int err;
// 打开混音器
err = snd_mixer_open(&mixer, 0);
if (err < 0) {
LOG_WARN("[AudioCapture] Cannot open mixer: %s", snd_strerror(err));
return false;
}
// 附加到声卡
err = snd_mixer_attach(mixer, card_name);
if (err < 0) {
LOG_WARN("[AudioCapture] Cannot attach mixer to %s: %s", card_name, snd_strerror(err));
snd_mixer_close(mixer);
return false;
}
// 注册混音器
err = snd_mixer_selem_register(mixer, NULL, NULL);
if (err < 0) {
LOG_WARN("[AudioCapture] Cannot register mixer: %s", snd_strerror(err));
snd_mixer_close(mixer);
return false;
}
// 加载混音器元素
err = snd_mixer_load(mixer);
if (err < 0) {
LOG_WARN("[AudioCapture] Cannot load mixer: %s", snd_strerror(err));
snd_mixer_close(mixer);
return false;
}
LOG_INFO("[AudioCapture] Mixer opened successfully, scanning controls...");
// 遍历所有混音器元素并打印信息
snd_mixer_selem_id_alloca(&sid);
snd_mixer_elem_t *elem;
int control_count = 0;
for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) {
if (!snd_mixer_selem_is_active(elem)) continue;
snd_mixer_selem_get_id(elem, sid);
const char *name = snd_mixer_selem_id_get_name(sid);
control_count++;
// 检查是否有捕获能力
bool has_capture = snd_mixer_selem_has_capture_volume(elem) ||
snd_mixer_selem_has_capture_switch(elem);
if (has_capture) {
LOG_INFO("[AudioCapture] Found capture control: '%s'", name);
// 尝试启用捕获开关
if (snd_mixer_selem_has_capture_switch(elem)) {
snd_mixer_selem_set_capture_switch_all(elem, 1);
LOG_INFO("[AudioCapture] Enabled capture switch for '%s'", name);
}
// 尝试设置捕获音量到最大
if (snd_mixer_selem_has_capture_volume(elem)) {
long min, max;
snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
snd_mixer_selem_set_capture_volume_all(elem, max);
LOG_INFO("[AudioCapture] Set capture volume to max (%ld) for '%s'", max, name);
}
}
// 尝试匹配常见的麦克风相关控制项
if (strstr(name, "MIC") || strstr(name, "Mic") || strstr(name, "mic") ||
strstr(name, "ADC") || strstr(name, "Capture") || strstr(name, "Input") ||
strstr(name, "Line") || strstr(name, "LINEIN")) {
LOG_INFO("[AudioCapture] Found potential mic control: '%s'", name);
// Day 23 fix: Restore playback volume to MAX for ADC/LINEOUT.
// On some codecs (e.g. AC108/ES8388), "Playback Volume" on ADC actually controls
// the digital gain of the ADC signal *before* it splits to capture/loopback.
// Setting it to 0 silences the capture. We will deal with echo via software if needed.
if (snd_mixer_selem_has_playback_volume(elem)) {
long min, max;
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
// Day 23: Set to 80% to balance capture signal vs loopback noise
long target = min + (max - min) * 0.8;
snd_mixer_selem_set_playback_volume_all(elem, target);
LOG_INFO("[AudioCapture] Set playback volume to 80%% (%ld/%ld) for '%s'", target, max, name);
}
// Enable playback switch (to ensure hardware is powered/active)
if (snd_mixer_selem_has_playback_switch(elem)) {
snd_mixer_selem_set_playback_switch_all(elem, 1); // Enable
LOG_INFO("[AudioCapture] Enabled playback switch for '%s' (required for capture)", name);
}
}
}
LOG_INFO("[AudioCapture] Scanned %d mixer controls", control_count);
snd_mixer_close(mixer);
return true;
}
/**
* @brief 初始化 ALSA 采集设备
*/
bool AudioCapture::init() {
int err;
// 0. 首先配置混音器,启用麦克风输入
LOG_INFO("[AudioCapture] Setting up mixer for microphone...");
setup_mixer("hw:0");
// 1. 打开 PCM 设备 (CAPTURE 模式,非阻塞)
// 使用 SND_PCM_NONBLOCK 避免 read 阻塞
err = snd_pcm_open(&m_pcm_handle, m_device.c_str(),
SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot open device '%s': %s",
m_device.c_str(), snd_strerror(err));
return false;
}
LOG_INFO("[AudioCapture] Opened device: %s (non-blocking)", m_device.c_str());
// 2. 分配硬件参数对象
snd_pcm_hw_params_t* hw_params;
snd_pcm_hw_params_alloca(&hw_params);
// 3. 初始化硬件参数
err = snd_pcm_hw_params_any(m_pcm_handle, hw_params);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot initialize hw params: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
// 4. 设置访问模式 (交织模式)
err = snd_pcm_hw_params_set_access(m_pcm_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot set access type: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
// 5. 设置采样格式 (16-bit signed little-endian)
err = snd_pcm_hw_params_set_format(m_pcm_handle, hw_params,
SND_PCM_FORMAT_S16_LE);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot set sample format: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
// 6. 设置采样率
unsigned int actual_rate = m_sample_rate;
err = snd_pcm_hw_params_set_rate_near(m_pcm_handle, hw_params,
&actual_rate, 0);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot set sample rate: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
if (actual_rate != (unsigned int)m_sample_rate) {
LOG_WARN("[AudioCapture] Sample rate %d Hz not supported, using %d Hz",
m_sample_rate, actual_rate);
}
// 7. 设置声道数
err = snd_pcm_hw_params_set_channels(m_pcm_handle, hw_params, m_channels);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot set channel count: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
// 8. 设置缓冲区大小 (增大以提高稳定性)
// Period: 480 frames (30ms @ 16kHz)
// Buffer: 4800 frames (300ms @ 16kHz)
// Day 22 优化: 使用20ms包(320 samples)与服务器ASR期望一致
snd_pcm_uframes_t period_size = 320; // 20ms @ 16kHz = 320 samples
snd_pcm_uframes_t buffer_size = 4800;
err = snd_pcm_hw_params_set_period_size_near(m_pcm_handle, hw_params,
&period_size, 0);
if (err < 0) {
LOG_WARN("[AudioCapture] Cannot set period size: %s", snd_strerror(err));
}
err = snd_pcm_hw_params_set_buffer_size_near(m_pcm_handle, hw_params,
&buffer_size);
if (err < 0) {
LOG_WARN("[AudioCapture] Cannot set buffer size: %s", snd_strerror(err));
}
// 9. 应用硬件参数
err = snd_pcm_hw_params(m_pcm_handle, hw_params);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot apply hw params: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
// 10. 准备设备
err = snd_pcm_prepare(m_pcm_handle);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot prepare device: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
// 11. 启动捕获流 (非阻塞模式必须显式启动)
err = snd_pcm_start(m_pcm_handle);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot start capture: %s", snd_strerror(err));
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
return false;
}
LOG_INFO("[AudioCapture] Initialized: %d Hz, %d channels, S16_LE",
actual_rate, m_channels);
LOG_INFO("[AudioCapture] Period: %lu frames, Buffer: %lu frames",
period_size, buffer_size);
LOG_INFO("[AudioCapture] Capture stream started");
return true;
}
/**
* @brief 读取 PCM 数据
* @param buffer 输出缓冲区 (int16_t 数组)
* @param frames 需要读取的帧数
* @return 实际读取的帧数,<0 表示错误
*/
snd_pcm_sframes_t AudioCapture::read(int16_t* buffer, snd_pcm_uframes_t frames) {
if (!m_pcm_handle) {
LOG_ERROR("[AudioCapture] Device not initialized");
return -1;
}
snd_pcm_sframes_t frames_read = snd_pcm_readi(m_pcm_handle, buffer, frames);
if (frames_read < 0) {
// 错误处理
if (frames_read == -EAGAIN) {
// 非阻塞模式:没有可用数据,返回 0 让调用者等待
return 0;
} else if (frames_read == -EPIPE) {
// Overrun (缓冲区溢出)
LOG_WARN("[AudioCapture] Overrun occurred, recovering...");
snd_pcm_prepare(m_pcm_handle);
return 0; // 本次读取失败,下次重试
} else if (frames_read == -ESTRPIPE) {
// Suspend (设备挂起)
LOG_WARN("[AudioCapture] Device suspended, resuming...");
while ((frames_read = snd_pcm_resume(m_pcm_handle)) == -EAGAIN) {
usleep(100000); // 等待 100ms
}
if (frames_read < 0) {
// 恢复失败,重新准备
frames_read = snd_pcm_prepare(m_pcm_handle);
if (frames_read < 0) {
LOG_ERROR("[AudioCapture] Cannot recover from suspend: %s",
snd_strerror(frames_read));
return frames_read;
}
}
return 0;
} else {
// 其他错误
LOG_ERROR("[AudioCapture] Read error: %s", snd_strerror(frames_read));
// 尝试自动恢复
int err = snd_pcm_recover(m_pcm_handle, frames_read, 0);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot recover: %s, attempting to reinitialize...", snd_strerror(err));
// 关闭设备
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
// 等待一下再重新初始化
usleep(500000); // 500ms
// 重新初始化
if (!init()) {
LOG_ERROR("[AudioCapture] Failed to reinitialize device");
return -1; // 彻底失败
}
LOG_INFO("[AudioCapture] Device reinitialized successfully");
return 0; // 本次读取失败,但设备已恢复
}
return 0;
}
}
// 成功读取
if (frames_read != (snd_pcm_sframes_t)frames) {
LOG_DEBUG("[AudioCapture] Short read: expected %lu, got %ld frames",
frames, frames_read);
}
return frames_read;
}