3. 音频采集与播放
任务目标
在嵌入式Linux设备上,通过ALSA(Advanced Linux Sound Architecture)接口完成一条最小音频通路:从麦克风采集PCM音频数据,立即写入声卡播放设备,实现回声侦听(听到自己说话的声音),以此验证音频采集与播放硬件链路正常。
环境与依赖
- Linux内核已启用ALSA驱动,设备节点存在:
/dev/snd/pcmC0D0c(采集)与/dev/snd/pcmC0D0p(播放) - 安装ALSA用户态库及头文件:
sudo apt-get install libasound2-dev - 编译工具:
gcc
核心实现
以下代码打开默认采集设备(default,通常为麦克风)与默认播放设备(default,通常为扬声器),配置为16bit单声道、采样率16000Hz,以周期(period)为单位循环读取PCM数据并原样写入播放设备。缓冲区大小设为8个period,以降低因调度延迟导致的音频断裂。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>
#define SAMPLE_RATE 16000
#define CHANNELS 1
#define PERIOD_SIZE 512 /* 每个period的帧数 */
#define PERIODS 8 /* 缓冲区包含的period数 */
static int set_hw_params(snd_pcm_t *pcm, snd_pcm_uframes_t *period_size) {
snd_pcm_hw_params_t *hw;
snd_pcm_hw_params_alloca(&hw);
if (snd_pcm_hw_params_any(pcm) < 0) return -1;
if (snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) return -1;
if (snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16_LE) < 0) return -1;
if (snd_pcm_hw_params_set_channels(pcm, hw, CHANNELS) < 0) return -1;
unsigned int rate = SAMPLE_RATE;
if (snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, 0) < 0) return -1;
if (snd_pcm_hw_params_set_period_size_near(pcm, hw, period_size, 0) < 0) return -1;
if (snd_pcm_hw_params_set_periods(pcm, hw, PERIODS, 0) < 0) return -1;
if (snd_pcm_hw_params(pcm, hw) < 0) return -1;
return 0;
}
int main(void) {
snd_pcm_t *capture = NULL, *playback = NULL;
int err;
/* 打开采集与播放设备 */
if ((err = snd_pcm_open(&capture, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf(stderr, "无法打开采集设备: %s\n", snd_strerror(err));
return 1;
}
if ((err = snd_pcm_open(&playback, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf(stderr, "无法打开播放设备: %s\n", snd_strerror(err));
snd_pcm_close(capture);
return 1;
}
/* 配置硬件参数 */
snd_pcm_uframes_t period_size = PERIOD_SIZE;
if (set_hw_params(capture, &period_size) < 0 ||
set_hw_params(playback, &period_size) < 0) {
fprintf(stderr, "硬件参数配置失败\n");
snd_pcm_close(capture);
snd_pcm_close(playback);
return 1;
}
/* 分配缓冲区:一个period的帧数 × 2字节(16bit) × 通道数 */
size_t bytes_per_frame = 2 * CHANNELS;
char *buf = malloc(period_size * bytes_per_frame);
if (!buf) {
fprintf(stderr, "内存分配失败\n");
snd_pcm_close(capture);
snd_pcm_close(playback);
return 1;
}
printf("开始音频回环测试(按 Ctrl+C 退出)...\n");
printf("参数: %u Hz, %d 通道, 16bit, period=%lu 帧\n",
SAMPLE_RATE, CHANNELS, (unsigned long)period_size);
/* 主循环:采集→播放 */
while (1) {
snd_pcm_sframes_t n = snd_pcm_readi(capture, buf, period_size);
if (n == -EPIPE) {
snd_pcm_prepare(capture); /* 处理欠载 */
continue;
} else if (n < 0) {
fprintf(stderr, "采集错误: %s\n", snd_strerror(n));
break;
}
snd_pcm_sframes_t w = snd_pcm_writei(playback, buf, n);
if (w == -EPIPE) {
snd_pcm_prepare(playback); /* 处理欠载 */
} else if (w < 0) {
fprintf(stderr, "播放错误: %s\n", snd_strerror(w));
break;
}
}
free(buf);
snd_pcm_drain(playback);
snd_pcm_close(capture);
snd_pcm_close(playback);
return 0;
}
编译与运行
gcc -o alsa_loop alsa_loop.c -lasound
./alsa_loop
验证方法
- 运行程序后,对着麦克风说话,若能立即从扬声器/耳机听到自己的声音(有轻微延迟属正常),说明采集与播放通路正常。
- 程序持续运行不退出,且终端无错误输出,说明PCM数据流未中断。
- 按
Ctrl+C终止程序,若终端无“采集错误”或“播放错误”打印,则判定通过。
常见问题
- 打开设备失败:确认声卡驱动已加载,可用
aplay -l和arecord -l查看可用设备;若默认设备不对,将代码中的"default"替换为具体设备名(如"plughw:0,0")。 - 听到爆音或卡顿:可增大
PERIODS至16,或使用snd_pcm_hw_params_set_buffer_time_near增加缓冲时间;若CPU负载高,可尝试将PERIOD_SIZE增至1024。 - 只有采集或只有播放:检查麦克风/扬声器是否被其他程序占用(如PulseAudio),可临时停止音频服务:
sudo systemctl stop pulseaudio。