358 lines
13 KiB
C++
358 lines
13 KiB
C++
/**
|
||
* @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;
|
||
}
|