本次调试基于全志v3s芯片linux系统,刚开始毫无头绪,即使都调试完成了,还是有很多地方不明白。
1、首先确保硬件和驱动都可以正常使用,用如下命令测试
录音测试
amixer cset numid=10,iface=MIXER,name='Audio main mic' 1
arecord -d 3 -f S16_LE -r 16000 tmp.wav
播放测试
amixer cset numid=1,iface=MIXER,name='Master Playback Volume' 63
amixer cset numid=17,iface=MIXER,name='Speaker Function' 0
aplay tmp.wav
如果没有这些命令,需要在编译文件系统时在buildroot里加进去,可以参考:https://blog.csdn.net/Jun626/article/details/100036595
如果配置完没有生成,就make clean再重新编译(我当时就是这个情况,配置了,没有生成)。
确定硬件和驱动没问题,就下一步。
2、通过编写程序读取音频流
2.1 准备工作
alsa的驱动框架,不像其他字符设备直接使用open、write、read等直接操作,而是需要alsa的动态库的API,来完成对alsa的参数配置,在上一步配置buildroot时会生成这个库,在编译时需要指定库和头文件的位置,也可以重新下载alsa-lib来自己编译,但是版本要和buildroot的alsa版本一样,要不然编译的程序和alsa相关命令不能同时使用。
alsa的编程指南看这里:https://blog.csdn.net/liuchen_csdn/article/details/52097088
官放说明看这里:https://users.suse.com/~mana/alsa090_howto.html#sect02
这里就直接上我改的代码:
这是一个录音后直接通过耳机播放的程序
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
int main(void){
/*Name of the PCM device ,like "default"*/
char *dev_name;
int rate = 44100;/*Sample rate*/
int exact_rate;/*Sample rate returned by*/
int dir;/*(1)exact_rate == rate --> dir=0,(2)exact_rate < rate --> dir=-1,(3)exact_rate > rate*/
long unsigned int periods = 2;/*Number of periods*/
int err;
int size;
int pcmreturn;
// snd_pcm_uframes_t periodsize = 8192;
snd_pcm_uframes_t periodsize = 1764;
snd_pcm_t *play_handle;
snd_pcm_t *capture_handle;
snd_pcm_stream_t play_stream = SND_PCM_STREAM_PLAYBACK;
snd_pcm_stream_t capture_stream = SND_PCM_STREAM_CAPTURE;
/*This structure contains information about */
/*the hardware and can be used to specify the */
/*configuration to be used for the PCM stream*/
snd_pcm_hw_params_t *hwparams;
snd_pcm_hw_params_t *c_hwparams;
/*Init dev_name, Of course, later you will make this configure*/
dev_name = strdup("default");
/*Allocate the snd_pcm_hw_params_t structure on the stack*/
snd_pcm_hw_params_alloca(&hwparams);
snd_pcm_hw_params_alloca(&c_hwparams);
FILE *out;
FILE *comm_fp_c = NULL;
FILE *comm_fp_p = NULL;
char comm_ret[100] = {'0'};
/*shell command*/
comm_fp_c = popen("amixer cset numid=10,iface=MIXER,name='Audio main mic' 1", "r");
if(comm_fp_c == NULL){
printf("shell error\n");
return 0;
}
while(fgets(comm_ret, sizeof(comm_ret) - 1, comm_fp_c) != NULL)
printf("amixer:\n%s\n", comm_ret);
memset(comm_ret, 0, sizeof(comm_ret));
pclose(comm_fp_c);
comm_fp_p = popen("amixer cset numid=17,iface=MIXER,name='Speaker Function' 0", "r");
if(comm_fp_p == NULL){
printf("shell error\n");
return 0;
}
while(fgets(comm_ret, sizeof(comm_ret) - 1, comm_fp_p) != NULL)
printf("amixer:\n%s\n", comm_ret);
pclose(comm_fp_p);
/* Open PCM. The last parameter of this function is the mode. */
/* If this is set to 0, the standard mode is used. Possible */
/* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC. */
/* If SND_PCM_NONBLOCK is used, read / write access to the */
/* PCM device will return immediately. If SND_PCM_ASYNC is */
/* specified, SIGIO will be emitted whenever a period has */
/* been completely processed by the soundcard. */
if (snd_pcm_open(&play_handle, dev_name, play_stream, 0) < 0) {
fprintf(stderr, "Error opening PCM device %s\n", dev_name);
return(-1);
}
if (snd_pcm_open(&capture_handle, dev_name, capture_stream, 0) < 0) {
fprintf(stderr, "Error opening PCM device %s\n", dev_name);
return(-1);
}
/* Init hwparams with full configuration space */
if (snd_pcm_hw_params_any(play_handle, hwparams) < 0) {
fprintf(stderr, "Can not configure this PCM device.\n");
return(-1);
}
if (snd_pcm_hw_params_any(capture_handle, c_hwparams) < 0) {
fprintf(stderr, "Can not configure this PCM device.\n");
return(-1);
}
/* Set access type. This can be either */
/* SND_PCM_ACCESS_RW_INTERLEAVED or */
/* SND_PCM_ACCESS_RW_NONINTERLEAVED. */
/* There are also access types for MMAPed */
/* access, but this is beyond the scope */
/* of this introduction. */
if (snd_pcm_hw_params_set_access(play_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
fprintf(stderr, "Error setting access.\n");
return(-1);
}
if (snd_pcm_hw_params_set_access(capture_handle, c_hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
fprintf(stderr, "Error setting access.\n");
return(-1);
}
/* Set sample format */
if (snd_pcm_hw_params_set_format(play_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
fprintf(stderr, "Error setting format.\n");
return(-1);
}
if (snd_pcm_hw_params_set_format(capture_handle, c_hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
fprintf(stderr, "Error setting format.\n");
return(-1);
}
exact_rate = rate;
if (snd_pcm_hw_params_set_rate_near(play_handle, hwparams, &exact_rate, 0) < 0) {
fprintf(stderr, "Error setting rate.\n");
return(-1);
}
if (rate != exact_rate) {
fprintf(stderr, "The rate %d Hz is not supported by your hardware.\
==> Using %d Hz instead.\n", rate, exact_rate);
}
exact_rate = rate;
if (snd_pcm_hw_params_set_rate_near(capture_handle, c_hwparams, &exact_rate, 0) < 0) {
fprintf(stderr, "Error setting rate.\n");
return(-1);
}
if (rate != exact_rate) {
fprintf(stderr, "The rate %d Hz is not supported by your hardware.\
==> Using %d Hz instead.\n", rate, exact_rate);
}
/* Set number of channels */
if (snd_pcm_hw_params_set_channels(play_handle, hwparams, 2) < 0) {
fprintf(stderr, "Error setting channels.\n");
return(-1);
}
if (snd_pcm_hw_params_set_channels(capture_handle, c_hwparams, 2) < 0) {
fprintf(stderr, "Error setting channels.\n");
return(-1);
}
/* Set number of periods. Periods used to be called fragments. */
if (snd_pcm_hw_params_set_periods(play_handle, hwparams, periods, 0) < 0) {
fprintf(stderr, "Error setting periods.\n");
return(-1);
}
if (snd_pcm_hw_params_set_periods(capture_handle, c_hwparams, periods, 0) < 0) {
fprintf(stderr, "Error setting periods.\n");
return(-1);
}
/* Set buffer size (in frames). The resulting latency is given by */
/* latency = periodsize * periods / (rate * bytes_per_frame) */
// if(snd_pcm_hw_params_get_buffer_size_max(hwparams,&periodsize) < 0)
// {
// fprintf(stderr,"Error get buffer size\n");
// }
// printf("periodsize=%lu\n",periodsize);
if (snd_pcm_hw_params_set_buffer_size(play_handle, hwparams, (periodsize * periods)) < 0) {
fprintf(stderr, "Error setting buffersize.\n");
return(-1);
}
if (snd_pcm_hw_params_set_buffer_size(capture_handle, c_hwparams, (periodsize * periods)) < 0) {
fprintf(stderr, "Error setting buffersize.\n");
return(-1);
}
/* Apply HW parameter settings to */
/* PCM device and prepare device */
if (snd_pcm_hw_params(play_handle, hwparams) < 0) {
fprintf(stderr, "Error setting HW params.\n");
return(-1);
}
if (snd_pcm_hw_params(capture_handle, c_hwparams) < 0) {
fprintf(stderr, "Error setting HW params.\n");
return(-1);
}
// snd_pcm_hw_params_free(hwparams);
// snd_pcm_hw_params_free(c_hwparams);
if ((err = snd_pcm_prepare (play_handle)) < 0) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_prepare (capture_handle)) < 0) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
}
snd_pcm_hw_params_get_period_size(c_hwparams, &periods,0);
printf("periods=%d\n",periods);
char *data_buf = (char*)malloc(periods);
if(!data_buf){
fprintf(stderr, "Cannot malloc buffer for data\n");
}
while(1)
{
pcmreturn = snd_pcm_readi( capture_handle, data_buf, periodsize);
if( pcmreturn == -EPIPE )
{
snd_pcm_prepare( capture_handle );
/*数据满了,没有被alsa读走*/
fprintf (stderr, "<<<<<<<<<<<<<<<<<<< Buffer Overrun >>>>>>>>>>>>>>>>>\n");
continue;
}else if( pcmreturn == -EBADFD ){
fprintf(stderr, "<<<<<<<<<<<<<<<<<<<< readi error -EBADFD >>>>>>>>>>>>>\n");
continue;
}else if( pcmreturn == -ESTRPIPE ){
fprintf(stderr, "<<<<<<<<<<<<<<<<<<<< readi error -ESTRPIPE >>>>>>>>>>>>>\n");
}else{
pcmreturn = snd_pcm_writei(play_handle, data_buf, pcmreturn);
if( pcmreturn == -EPIPE ){
/*在播放例子中,如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死"。这样的错误被称 为"underrun"*/
fprintf(stderr, "<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>\n");
snd_pcm_prepare( play_handle );
// continue;
}else if( pcmreturn == -ESTRPIPE ){
fprintf(stderr, "<<<<<<<<<<<< writei error -ESTRPIPE >>>>>>>>>>>\n");
}else if( pcmreturn == -EBADFD ){
fprintf(stderr, "<<<<<<<<<<<< writei error -EABDFD >>>>>>>>>>>>\n");
}
}
printf("pcmreturn = %d\n",pcmreturn);
}
}
在v3s上可以用,但在其他平台可能需要更改,如果需要单独录音或播放,可以把播放和录音分离出来。程序中open(“amixer cset numid=17,iface=MIXER,name=‘Speaker Function’ 0”, “r”);如果没有这一句,使用snd_pcm_writei播放录音时,只会读取前两帧,播放不了,这个命令的做用应该就是关闭扩音功能,从耳机输出。我在这里搞了好几才知道是这的问题。还要注意播放的音量是否为0。
int rate = 44100;/Sample rate/
snd_pcm_uframes_t periodsize = 1764;
当我把参数periodsize 设置为441时在本程序录音时,会出现Buffer Overrun 错误。但是在单独录音程序中没问题。这里似懂非懂的,还需要看看。
编译该程序的Makefile如下
SOURCE = $(wildcard *.c)
TARGETS = $(patsubst %.c, %, $(SOURCE))
CC = arm-buildroot-linux-gnueabihf-gcc
PREFIX=/usr/lib/alsa-lib
#CFLAGS = -Wall
all:$(TARGETS)
$(TARGETS):%:%.c
$(CC) $< -o $@ -I $(PREFIX)/include -L $(PREFIX)/lib -lasound
.PHONY:clean all
clean:
-rm -rf $(TARGETS)
接下来要把获取到的音频流,实现speex编码后,然后speex解码。下一篇