开发板上有一个MIC,位置如下
板上的MIC接到RK809声卡上面。原理图如下所示
目前Linux下面音频采用的是ALSA(Advanced Linux Sound Architecture)框架。这个框架包含了内核和应用部分。
alsa里面有测试程序,比如用arecord进行录音。此命令的使用方式如下
比如如下的命令
arecord -D hw:0,0 --period-size=1024 --buffer-size=4096 -r 16000 -c 8 -f s32_le /tmp/r.wav
这个命令行的意思是,使用声卡0的设备0进行录音,缓冲大小为4096,周期大小为1024,采样率设置为16k,使用8个通道,格式为s32_le,输出文件为/tmp/r.wav
执行此命令之后会开始录音。录制下来的音频格式为wav,大多数播放器都支持此种音频格式。
如果使用C语言,需要使用板上的动态库。交叉编译的时候需要用到这些动态库和头文件,笔者已经把板上的动态库和头文件打包,可以在下面的链接下载
然后下面是一个demo,可以以缓冲大小4096,周期大小1024,采样率设置16k,格式S32_LE来进行录音,输出文件为/tmp/output.raw。 注释已经说明了相关API的使用方式,可以参考
#include <alsa/asoundlib.h>
#include <alsa/pcm.h>
#include <stdio.h>
#define CHANNELS 2
#define FSIZE 2 * CHANNELS
#define SOUND_CARD_DEVICE "hw:0,0"
int main()
{
int fd;
int ret = 0;
char output_filename[] = "/tmp/output.raw";
// 创建一个输出文件
fd = open(output_filename, O_WRONLY | O_CREAT, 0777);
if (fd < -1) {
printf("open file:%s fail.\n", output_filename);
exit(1);
}
snd_pcm_t *handle;
// 打开一个声卡,其中第二个参数是声卡号,可以通过 cat /proc/asound/cards 命令查看
// 第三个参数是指定以何种方式打开,这里使用的是 CAPTURE
ret = snd_pcm_open(&handle, SOUND_CARD_DEVICE, SND_PCM_STREAM_CAPTURE, 0);
if (ret < 0) {
printf("snd_pcm_open %s fail %d\n", SOUND_CARD_DEVICE, ret);
exit(1);
}
// 初始化 snd_pcm_hw_params_t 结构体类型参数
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_malloc(¶ms);
ret = snd_pcm_hw_params_any(handle, params);
if (ret < 0) {
printf("snd_pcm_hw_params_any %s fail %d \n", SOUND_CARD_DEVICE, ret);
exit(1);
}
// 设置多路数据 输出方式
// SND_PCM_ACCESS_RW_INTERLEAVED 交错模式 指的是每个周期(period)左右声道的数据交叉存放
// SND_PCM_ACCESS_RW_NONINTERLEAVED 非交错模式
ret = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (ret < 0) {
printf("snd_pcm_hw_params_set_access %s fail %d \n",SOUND_CARD_DEVICE, ret);
exit(1);
}
// 设置采样格式 也就是buf里面数据的排列方式
// SND_PCM_FORMAT_S16_LE 16位小端
// SND_PCM_FORMAT_S16_BE 16位大端
// SND_PCM_FORMAT_S32_LE 32位小端
// SND_PCM_FORMAT_S32_BE 32位大端
ret = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S32_LE);
if (ret < 0) {
printf("snd_pcm_hw_params_set_format %s fail %d \n",SOUND_CARD_DEVICE, ret);
exit(1);
}
// 设置声道数
ret = snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
if (ret < 0) {
printf("snd_pcm_hw_params_set_channels %s fail %d \n",SOUND_CARD_DEVICE, ret);
exit(1);
}
unsigned int val = 48000;
int dir;
// 设置采样率
ret = snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
if (ret < 0) {
printf("snd_pcm_hw_params_set_rate_near %s %d HZ fail %d \n", SOUND_CARD_DEVICE, val, ret);
exit(1);
}
// 设置缓冲 有两种方式,可以按size也可以按时间
#if 0
// 设置缓冲时间 周期时间
buffer_time = 50000;//单位us
period_time = 26315;
ret = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0);
if (ret < 0) {
printf("snd_pcm_hw_params_set_buffer_time_near %s %d fail %d \n", SOUND_CARD_DEVICE, buffer_time, ret);
exit(1);
}
ret = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0);
if (ret < 0) {
printf("snd_pcm_hw_params_set_period_time_near %s %d fail %d \n", SOUND_CARD_DEVICE, period_time, ret);
exit(1);
}
#else
snd_pcm_uframes_t buffer_size = 4096;
snd_pcm_uframes_t period_size = 1024;
ret = snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, &dir);
if (ret < 0) {
printf("snd_pcm_hw_params_set_period_size_near %s %d fail %d \n", SOUND_CARD_DEVICE, period_size, ret);
exit(1);
}
ret = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
if (ret < 0) {
printf("snd_pcm_hw_params_set_buffer_size_near %s %d fail %d \n", SOUND_CARD_DEVICE, buffer_size, ret);
exit(1);
}
#endif
// 让这些参数作用于PCM设备
ret = snd_pcm_hw_params(handle, params);
if (ret < 0) {
printf("snd_pcm_hw_params %s fail\n",SOUND_CARD_DEVICE);
exit(1);
}
// 再次获取当前声卡真实的周期buf size大小
snd_pcm_uframes_t frames;
snd_pcm_hw_params_get_period_size(params, &frames, &dir);
printf("period_size:%ld\n", frames);
int size;
// 1 frame = channels * sample_size.
size = frames * FSIZE; /* 2 bytes/sample, 1 channels */
printf("size:%d\n", size);
char *buffer;
buffer = (char *)malloc(size);
while (1) {
ret = snd_pcm_readi(handle, buffer, frames);
if (ret == -EPIPE) {
// EPIPE means overrun
fprintf(stderr, "overrun occurred\n");
ret = snd_pcm_prepare(handle);
if (ret < 0) {
printf("Failed to recover form overrun");
break;
}
} else if (ret < 0) {
fprintf(stderr, "error from read: %s\n", snd_strerror(ret));
break;
} else if (ret != (int)frames) {
// fprintf(stderr, "short read, read %d frames\n", ret);
usleep(1);
}
ret = write(fd, buffer, size);
if (ret < 0) {
perror("fail to write to audio file\n");
}
fflush(stdout);
}
close(fd);
snd_pcm_drain(handle);
snd_pcm_close(handle);
snd_pcm_hw_params_free(params);
free(buffer);
return 0;
}
编译时命令为
aarch64-linux-gcc mic_record.c -L./usr/lib/ -lasound -I./usr/include/ -o mic_record
注意编译器要用sdk下面 prebuilts/gcc/linux-x86/aarch64/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu,这样才能与板上自带的alsa库一致