若该文为原创文章,转载请注明原文出处。
一、介绍
音频是我们最常用到的功能,音频也是 linux 和安卓的重点应用场合。
测试使用的是ATK-DLR3568板子,板载外挂RK809 CODEC芯片,RK官方驱动是写好的,不用在自己重新写。测试应用层录音和放音使用的是ALSA方式。
二、硬件原理图
I2S 接口一共用到了 5 根线,这个 5 根线用于 RK3568 与 RK809 之间的音频数据收
发。
三、音频驱动使能
RK 官方已经写好了 RK809 CODEC 驱动,直接配置内核使能 RK809 CODEC 驱动即可。
打开kernel/arch/arm64/configs/rockchip_linux_defconfig
修改RK817为y, 重新编译kenel.RK官方把RK809 与 RK817 CODEC 部分驱动代码做了兼容。
驱动源码路径:kernel/sound/soc/codecs/rk817_codec.c
四、设备树配置
RK809 CODEC芯片的I2S连接到RK3568的i2c0, 内核使能后,需要通过I2C配置一系列参数。
1、I2C设备树配置
设备树路径: arch/arm64/boot/dts/rockchip/rk3568-evb.dtsi
2、声卡设备树配置
配置声卡主要是为了和RK809驱动关联。
声卡驱动文件路径为 sound/soc/generic/simple-card.c
五、声卡设置与测试
alsa-utils 自带了 amixer 这个声卡设置工具,使用amixer测试录音和放音。
1、使用 amixer 设置声卡
amixer cset name='Capture MIC Path' 'Main Mic'
2、使用 arecord 录制音频
arecord -r 44100 -f S16_LE -d 10 record.wav
录制一段 10 秒音频, 采样率 44.1K,S16_LE 格式进行采样,
3、播放音频
aplay record.wav
六、开机自动配置声卡
声卡设置的保存通过 alsactl 工具来完成,此工具也是 alsa-utils 编译出来的。alsactl 默认将
声卡配置文件保存在/var/lib/alsa 目录下。
使用 amixer 设置声卡,然后输入如下命令保存声卡设置
alsactl -f /var/lib/alsa/asound.state store //保存声卡设置
如果要使用 asound.state 中的配置信息来配置声卡,执行如下命令即可:
alsactl -f /var/lib/alsa/asound.state restore
创建自启动文件
/etc/init.d/S98alactl
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
start_restore()
{if [ -f "/var/lib/alsa/asound.state" ]; then/usr/sbin/alsactl -f /var/lib/alsa/asound.state restore &fi
}
stop_restore()
{echo -n ""
}
case "$1" instart)echo -n "ALSA: Restoring mixer setting......"start_restoreecho "done.";;stop)stop_restoreecho "done.";;restart|reload)echo -n "stoping restore... "stop_restore && sleep .3echo "done."echo -n "starting restore... "start_restoreecho "done.";;*)echo "Usage: $0 {start|stop|restart}"exit 1esacexit 0
chmod 777 /etc/init.d/S98alactl # 赋予文件可执行权限
修改/etc/init.d/rcS,添加执行文件命令,上电后会自动配置声卡
七、使用ALSA录制音频和放音
ALSA程序网上很多,不多做介绍,直接上代码
录制音频alsa_pcm_rec.c
/*进行音频采集,采集pcm数据并直接保存pcm数据音频参数:声道数: 1采样位数: 16bitLE格式t、LE格式采样频率: 44100Hz运行示例:$ gcc linux_pcm_save.c -lasound$ ./a.out
*/#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <signal.h>/** 指定音频的格式,其他常用格式:* SND_PCM_FORMAT_U24_LE、* SND_PCM_FORMAT_U32_LE*/
#define AudioFormat SND_PCM_FORMAT_S16_LE
/* 1单声道 2立体声*/
#define AUDIO_CHANNEL_SET 1
/** 音频采样率,常用的采样频率:* 44100Hz 、16000HZ、8000HZ、48000HZ、22050HZ*/
#define AUDIO_RATE_SET 44100#define PCM_FILE "test.pcm"FILE *pcm_data_file = NULL;
int run_flag = 0;void exit_sighandler(int sig)
{run_flag = 1;
}int main(int argc, char *argv[])
{int i;int err;char *buffer;int buffer_frames = 1024;unsigned int rate = AUDIO_RATE_SET;snd_pcm_t *capture_handle; // 一个指向PCM设备的 句柄snd_pcm_hw_params_t *hw_params; //此结构包含有关硬件的信息,可用于指定PCM流的配置/* 注册信号捕获退出接口 */signal(2, exit_sighandler);/* PCM的采样格式在pcm.h文件里有定义 */snd_pcm_format_t format = AudioFormat; // 采样位数:16bit、LE格式/*打开音频采集卡硬件,并判断硬件是否打开成功,若打开失败则打印出错误提示* SND_PCM_STREAM_PLAYBACK 输出流* SND_PCM_STREAM_CAPTURE 输入流*/if((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0){printf("无法打开音频设备.\n");exit(1);}printf("音频接口打开成功.\n");/* 创建一个保存PCM数据的文件 */if((pcm_data_file = fopen(PCM_FILE, "wb")) == NULL){printf("无法创建音频文件.\n");exit(1);}printf(" 用于录制的音频文件已打开.\n");/* 分配硬件参数结构对象,并判断是否分配成功 */if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0){printf("无法分配硬件参数结构.\n");exit(1);}printf("硬件参数结构分配成功.\n");/* 按照默认设置对硬件对象进行设置,并判断是否成功 */if((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0){printf("无法初始化硬件参数结构.\n");exit(1);}printf("硬件参数结构初始化成功.\n");/*设置数据为交叉模式,并判断是否设置成功interleaved/non interleaved:交叉/非交叉模式。表示在多声道数据传输的过程中是采样交叉的模式还是非交叉的模式。对多声道数据,如果采样交叉模式,使用一块buffer即可,其中各声道的数据交叉传输;如果使用非交叉模式,需要为各声道分别分配一个buffer,各声道数据分别传输。*/if((err = snd_pcm_hw_params_set_access (capture_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0){printf("无法设置访问类型(%s)\n",snd_strerror(err));exit(1);}printf("访问类型设置成功.\n");/* 设置数据编码格式,并判断是否设置成功 */if((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0){printf("无法设置格式.\n");exit(1);}printf("PCM数据格式设置成功.\n");/* 设置采样频率,并判断是否设置成功 */if((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0){printf("无法设置采样率.\n");exit(1);}printf("采样率设置成功.\n");/* 设置声道,并判断是否设置成功 */if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, AUDIO_CHANNEL_SET)) < 0){printf("无法设置声道参数.\n");exit(1);}printf("声道参数设置成功.\n");/* 将配置写入驱动程序中,并判断是否配置成功 */if((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0){printf("无法向驱动程序设置参数.\n");exit(1);}printf("参数设置成功.\n");/* 使采集卡处于空闲状态 */snd_pcm_hw_params_free(hw_params);/* 准备音频接口,并判断是否准备好 */if((err = snd_pcm_prepare(capture_handle)) < 0){printf("无法使用音频接口.\n");exit(1);}printf("音频接口准备好.\n");/* 配置一个数据缓冲区用来缓冲数据 *//* snd_pcm_format_width(format) 获取样本格式对应的大小(单位是:bit) */int frame_byte = snd_pcm_format_width(format)/8;buffer = malloc(buffer_frames*frame_byte*AUDIO_CHANNEL_SET);printf("缓冲区分配成功.\n");/* 开始采集音频PCM数据 */printf("开始采集数据...\n");while(1){/* 从声卡设备读取一帧音频数据 2048字节 */if((err=snd_pcm_readi(capture_handle, buffer, buffer_frames)) != buffer_frames){printf("从音频接口读取失败.\n");exit(1);}/* 写数据到文件: 音频的每帧数据样本大小是16位=2个字节 */fwrite(buffer, (buffer_frames*AUDIO_CHANNEL_SET), frame_byte, pcm_data_file);if(run_flag){printf("停止采集.\n");break;}}/* 释放数据缓冲区 */free(buffer);/* 关闭音频采集卡硬件 */snd_pcm_close(capture_handle);/* 关闭文件流 */fclose(pcm_data_file);return 0;
}
播放音频alsa_pcm_play.c
/** 进行音频采集,读取存放pcm数据文件通过声卡进行播放'* 音频参数:* 声道数: 1* 采样位叔: 16bit、LE格式* 采样频率: 44100Hz* 运行救命:* $ gcc alsa_pcm_play.c -lasound* $ ./a.out test.pcm*/#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <signal.h>/* 指定音频的格式,其他常用格式:** SND_PCM_FORMAT_U24_LE、* SND_PCM_FORMAT_U32_LE*/
#define AudioFormat SND_PCM_FORMAT_S16_LE
/* 1单声道 2立体声 */
#define AUDIO_CHANNEL_SET 1
/* 音频采样率,常用的采样频率:* 44100Hz 、16000HZ、8000HZ、48000HZ、22050HZ*/
#define AUDIO_RATE_SET 44100FILE *pcm_data_file = NULL;int run_flag = 0;void exit_sighandler(int sig)
{run_flag = 1;
}int main(int argc, char *argv[])
{int i;int err;char *buffer;int buffer_frames = 1024;unsigned int rate = AUDIO_RATE_SET;snd_pcm_t *capture_handle; // 一个指向PCM设备的句柄snd_pcm_hw_params_t *hw_params; //此结构包含有关硬件的信息,可用于指定PCM流的配置/* 注册信号捕获退出接口 */signal(2, exit_sighandler);/* PCM的采样格式在pcm.h文件里有定义 */snd_pcm_format_t format = AudioFormat;/*打开音频采集卡硬件,并判断硬件是否打开成功,若打开失败则打印出错误提示* SND_PCM_STREAM_PLAYBACK 输出流* SND_PCM_STREAM_CAPTURE 输入流*/if((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0){printf("无法打开音频设备.\n");exit(1);}printf("音频接口打开.\n");/* 打开存放PCM数据的文件 */if((pcm_data_file = fopen(argv[1], "rb")) == NULL){printf("无法打开音频文件.\n");exit(1);}printf("用于播放的音频文件已打开.\n");/* 分配硬件参数结构对象, 并判断是否分配成功 */if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0){printf("无法分配参数结构.\n");exit(1);}printf("硬件参数结构已分配成功.\n");/* 按照默认设置对硬件对象进行设置,并判断是否设置成功 */if((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0){printf("无法初始化硬件参数结构.\n");exit(1);}printf("硬件参数结构初始化成功.\n");/*设置数据为交叉模式,并判断是否设置成功interleaved/non interleaved:交叉/非交叉模式。表示在多声道数据传输的过程中是采样交叉的模式还是非交叉的模式。对多声道数据,如果采样交叉模式,使用一块buffer即可,其中各声道的数据交叉传输;如果使用非交叉模式,需要为各声道分别分配一个buffer,各声道数据分别传输。*/if((err = snd_pcm_hw_params_set_access (capture_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0){printf("无法设置访问类型.\n");exit(1);}printf("访问类型访问.\n");/* 设置数据编码格式,并判断是否设置成功 */if((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0){printf("无法设置格式.\n");exit(1);}printf("PCM数据格式设置成功.\n");/* 设置采样频率,并判断是否设置成功 */if((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0){printf("无法设置采样率.\n");exit(1);}printf("采样率设置成功.\n");/* 设置声道,并判断是否设置成功 */if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, AUDIO_CHANNEL_SET)) < 0){printf("无法设置声道数.\n");exit(1);}printf("声道数设置成功.\n");/* 将配置写入驱动程序中,并判断是否配置成功 */if((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0){printf("无法向驱动程序设置参数.\n");exit(1);}printf("参数设置成功.\n");/* 使采集卡处理空闲状态 */snd_pcm_hw_params_free(hw_params);/* 准备音频接口, 并判断是否准备好 */if((err = snd_pcm_prepare(capture_handle)) < 0){printf("无法使用音频接口.\n");exit(1);}printf("音频接口准备好.\n");/* 配置一个数据缓冲区用来缓冲数据 *//* snd_pcm_format_width(format) 获取样本格式对应的大小(单位是:bit) */int frame_byte = snd_pcm_format_width(format)/8;buffer = malloc(buffer_frames*frame_byte*AUDIO_CHANNEL_SET);printf("缓冲区分配成功.\n");/* 开始播放音频数据...*/printf("开始播放音频数据...\n");int read_cnt;while(1){read_cnt = fread(buffer, 1, frame_byte*(buffer_frames*AUDIO_CHANNEL_SET), pcm_data_file);if(read_cnt <= 0)break;/* 向声卡设备写一帧音频数据: 2048字节 */if((err = snd_pcm_writei(capture_handle, buffer, buffer_frames)) != buffer_frames){printf("向音频接口写数据失败.\n");exit(1);}if(run_flag){printf("停止播放.\n");break;}}printf("播放完成.\n");/* 释放数据缓冲区 */free(buffer);/* 关闭音频采集卡硬件 */snd_pcm_close(capture_handle);/* 关闭文件流 */fclose(pcm_data_file);return 0;
}
编译:
/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-g++ alsa_pcm_rec.c -o alsa_pcm_play -lasound
/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-g++ alsa_pcm_play.c -o alsa_pcm_play -lasound
生成的可执行文件,通过ADB或TFTP等方式上传到板子运行。
测试录音和放音正常。
如有侵权,或需要完整代码,请及时联系博主。