4. ALSA录音播放回环测试

任务目标

本案例实现一个ALSA全双工回环程序:从默认录音设备采集PCM数据,同时写入默认播放设备,形成实时监听效果。该程序用于验证音频输入到输出的物理通路是否正常,是音频调试中最基础的测试手段。

核心实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <alsa/asoundlib.h>

#define PCM_DEVICE "default"
#define SAMPLE_RATE 44100
#define CHANNELS 2
#define FRAME_SIZE (CHANNELS * 2)  /* 16bit stereo */
#define PERIOD_FRAMES 1024
#define BUFFER_FRAMES (PERIOD_FRAMES * 4)

static int set_hw_params(snd_pcm_t *pcm, snd_pcm_uframes_t *period, snd_pcm_uframes_t *buffer) {
    snd_pcm_hw_params_t *hw;
    snd_pcm_hw_params_alloca(&hw);
    int err;

    if ((err = snd_pcm_hw_params_any(pcm, hw)) < 0) return err;
    if ((err = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) return err;
    if ((err = snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16_LE)) < 0) return err;
    if ((err = snd_pcm_hw_params_set_channels(pcm, hw, CHANNELS)) < 0) return err;
    if ((err = snd_pcm_hw_params_set_rate_near(pcm, hw, &(unsigned int){SAMPLE_RATE}, 0)) < 0) return err;
    if ((err = snd_pcm_hw_params_set_period_size_near(pcm, hw, period, 0)) < 0) return err;
    if ((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hw, buffer)) < 0) return err;
    return snd_pcm_hw_params(pcm, hw);
}

int main(void) {
    snd_pcm_t *cap = NULL, *play = NULL;
    snd_pcm_uframes_t period = PERIOD_FRAMES, buffer = BUFFER_FRAMES;
    char *buf;
    int err;

    if ((err = snd_pcm_open(&cap, PCM_DEVICE, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
        fprintf(stderr, "capture open failed: %s\n", snd_strerror(err));
        return 1;
    }
    if ((err = snd_pcm_open(&play, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        fprintf(stderr, "playback open failed: %s\n", snd_strerror(err));
        snd_pcm_close(cap);
        return 1;
    }

    if ((err = set_hw_params(cap, &period, &buffer)) < 0) goto error;
    if ((err = set_hw_params(play, &period, &buffer)) < 0) goto error;

    buf = malloc(period * FRAME_SIZE);
    if (!buf) { err = -ENOMEM; goto error; }

    printf("ALSA loopback running: %d Hz, %d ch, period=%lu frames. Ctrl-C to stop.\n",
           SAMPLE_RATE, CHANNELS, (unsigned long)period);

    while (1) {
        err = snd_pcm_readi(cap, buf, period);
        if (err == -EPIPE) {
            snd_pcm_prepare(cap);
            continue;
        } else if (err < 0) {
            fprintf(stderr, "read error: %s\n", snd_strerror(err));
            break;
        }

        err = snd_pcm_writei(play, buf, err);
        if (err == -EPIPE) {
            snd_pcm_prepare(play);
            continue;
        } else if (err < 0) {
            fprintf(stderr, "write error: %s\n", snd_strerror(err));
            break;
        }
    }

    free(buf);
error:
    if (err < 0) fprintf(stderr, "ALSA error: %s\n", snd_strerror(err));
    snd_pcm_close(cap);
    snd_pcm_close(play);
    return err < 0 ? 1 : 0;
}

编译与运行

# 编译(需安装libasound2-dev)
gcc -o alsa_loopback alsa_loopback.c -lasound

# 运行(默认使用"default"设备,即系统当前输入/输出)
./alsa_loopback

验证方法

  1. 物理验证:将麦克风接入输入端口,耳机或音箱接入输出端口。对着麦克风说话或播放手机音乐,应能实时听到声音。
  2. 通路确认:程序启动后打印采样率、通道数和周期大小。若听到清晰无断裂的声音,说明录音→播放通路正常。
  3. 异常判断:若听到爆音或卡顿,可尝试增大BUFFER_FRAMES(如改为PERIOD_FRAMES * 8)后重新编译;若完全无声,检查系统默认录音/播放设备是否正确(可用arecord -l和aplay -l查看)。
  4. 退出方式:按Ctrl-C终止程序,终端应无错误输出。

关键说明

本程序使用阻塞I/O模式,snd_pcm_readi和snd_pcm_writei分别完成采集与播放。全双工的关键在于录音和播放使用独立的PCM句柄,且参数一致(采样率、通道数、格式)。出现-EPIPE(缓冲区欠载/溢出)时调用snd_pcm_prepare重置句柄后继续,保证长时间运行的稳定性。若需调整输入/输出设备,修改PCM_DEVICE宏为具体设备名(如"plughw:0,0")即可。