1. 使用ALSA库进行PCM录音
任务目标
编写一个C程序,通过ALSA库从默认音频设备采集PCM数据,并封装为WAV文件保存到磁盘。程序需支持指定录音时长,并在结束后正确写入WAV文件头。
环境依赖
- Linux系统(推荐Ubuntu/Debian)
- ALSA开发库:
sudo apt-get install libasound2-dev - 编译工具:
gcc
核心实现
程序使用ALSA的snd_pcm_*系列API完成设备打开、参数配置、数据读取,最后将PCM裸数据加上44字节WAV头写入文件。关键点:采样格式为S16_LE(16位小端),采样率44100Hz,单声道,录音时长由命令行参数指定。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <alsa/asoundlib.h>
#define SAMPLE_RATE 44100
#define CHANNELS 1
#define BITS_PER_SAMPLE 16
typedef struct {
char chunkID[4]; // "RIFF"
uint32_t chunkSize;
char format[4]; // "WAVE"
char subchunk1ID[4]; // "fmt "
uint32_t subchunk1Size;
uint16_t audioFormat;
uint16_t numChannels;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample;
char subchunk2ID[4]; // "data"
uint32_t subchunk2Size;
} WAVHeader;
void write_wav_header(FILE *fp, uint32_t data_size) {
WAVHeader hdr;
memcpy(hdr.chunkID, "RIFF", 4);
hdr.chunkSize = 36 + data_size;
memcpy(hdr.format, "WAVE", 4);
memcpy(hdr.subchunk1ID, "fmt ", 4);
hdr.subchunk1Size = 16;
hdr.audioFormat = 1; // PCM
hdr.numChannels = CHANNELS;
hdr.sampleRate = SAMPLE_RATE;
hdr.byteRate = SAMPLE_RATE * CHANNELS * BITS_PER_SAMPLE / 8;
hdr.blockAlign = CHANNELS * BITS_PER_SAMPLE / 8;
hdr.bitsPerSample = BITS_PER_SAMPLE;
memcpy(hdr.subchunk2ID, "data", 4);
hdr.subchunk2Size = data_size;
fwrite(&hdr, sizeof(WAVHeader), 1, fp);
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <output.wav> <seconds>\n", argv[0]);
return 1;
}
const char *filename = argv[1];
int seconds = atoi(argv[2]);
if (seconds <= 0) {
fprintf(stderr, "Seconds must be positive\n");
return 1;
}
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
int rc;
// 打开默认设备
rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
if (rc < 0) {
fprintf(stderr, "Unable to open PCM device: %s\n", snd_strerror(rc));
return 1;
}
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(handle, params);
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
snd_pcm_hw_params_set_rate_near(handle, params, &(unsigned int){SAMPLE_RATE}, 0);
snd_pcm_hw_params_set_period_size_near(handle, params, &(snd_pcm_uframes_t){1024}, 0);
rc = snd_pcm_hw_params(handle, params);
if (rc < 0) {
fprintf(stderr, "Unable to set hw params: %s\n", snd_strerror(rc));
snd_pcm_close(handle);
return 1;
}
snd_pcm_uframes_t period_size;
snd_pcm_hw_params_get_period_size(params, &period_size, 0);
int frame_size = CHANNELS * BITS_PER_SAMPLE / 8;
int period_bytes = period_size * frame_size;
FILE *fp = fopen(filename, "wb");
if (!fp) {
perror("fopen");
snd_pcm_close(handle);
return 1;
}
// 先写占位头,录音结束后回填
write_wav_header(fp, 0);
char *buffer = malloc(period_bytes);
if (!buffer) {
perror("malloc");
fclose(fp);
snd_pcm_close(handle);
return 1;
}
int total_frames = SAMPLE_RATE * seconds;
int frames_recorded = 0;
uint32_t data_bytes = 0;
while (frames_recorded < total_frames) {
int frames_to_read = period_size;
if (total_frames - frames_recorded < frames_to_read)
frames_to_read = total_frames - frames_recorded;
rc = snd_pcm_readi(handle, buffer, frames_to_read);
if (rc == -EPIPE) {
snd_pcm_prepare(handle);
continue;
} else if (rc < 0) {
fprintf(stderr, "Read error: %s\n", snd_strerror(rc));
break;
}
fwrite(buffer, frame_size, rc, fp);
frames_recorded += rc;
data_bytes += rc * frame_size;
}
// 回填正确的数据大小
fseek(fp, 0, SEEK_SET);
write_wav_header(fp, data_bytes);
free(buffer);
fclose(fp);
snd_pcm_close(handle);
printf("Recording complete: %d frames, %u bytes data\n", frames_recorded, data_bytes);
return 0;
}
编译与运行
gcc -o alsa_record alsa_record.c -lasound
./alsa_record test.wav 5 # 录制5秒音频
验证方法
- 文件头检查:执行
file test.wav,应输出包含WAVE audio的描述。 - 时长验证:使用
aplay test.wav播放,应听到约5秒的录音内容(环境需有音频输出设备)。 - 数据完整性:用
ls -l test.wav查看文件大小,应为44100×2×5 + 44 = 441044字节(若采样率被驱动调整则按实际值计算)。
常见问题
- 设备忙:若返回
Device or resource busy,检查是否有其他程序占用声卡,或改用plughw:0,0作为设备名。 - 录音无声:确认系统输入设备(如麦克风)未静音,可用
alsamixer检查捕获通道。 - 采样率不匹配:部分声卡不支持44100Hz,程序会自动选择最接近的采样率,可通过
snd_pcm_hw_params_get_rate获取实际值并调整WAV头。