DSP

N32905音视频学习笔记-录音和播放

2019-07-13 16:03发布

陈生 微信:chenzhe_yj 提供开源硬件产品、嵌入式软硬件开发、技术咨询 淘宝店:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-18267726305.6.3fbb1d275b0ixd&id=554693753289   目录 准备工作: 1.N32905 audio简介 2.硬件设计 3.软件设计 3.1内核配置 3.2 audio录音和重放应用程序 4.编译 5.烧录运行 本例程将在Duckbill鸭嘴兽WIFI-Mini905板上学习录音和播放,可以实现录一段自己的声音然后播放,也可以播放本地PCM音频数据。

准备工作:

wifi音视频开发板一块

1.N32905 audio简介

N32905内部集成了音频DAC和SPU(声音处理单元),专用于音频处理。 音频DAC:支持16位立体声,可以直接驱动耳机。 SPU-声音处理单元:支持32个立体声通道;支持的原始音频格式有PCM8/PCM16/4-bit MDPCM/TONE;每一个通道支持7位音量控制;支持10波段均衡器;支持左右声道。  

2.硬件设计

录音电路主要由mic和滤波电路组成,如下图1.2.1所示。 图2.1 录音电路 播放电路采用了ft690音频功放,可以直接驱动喇叭,如下图1.2.2所示。 图2.2 播放电路

3.软件设计

3.1内核配置

3.2 audio录音和重放应用程序

代码路径:/duckbill/N32905/BSP/applications/audio/audio_demo.c。audio代码如下。 /* * * Audio录音和播放程序 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //PCM录音文件 char *pcmfiles[] = { "./8k.pcm", "./11.025k.pcm", "./16k.pcm", "./22.05k.pcm", "./24k.pcm", "./32k.pcm", "./44.1k.pcm", "./48k.pcm"}; //采样率 const int samplerate[] = { 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000}; int p_dsp, p_mixer; int r_dsp, r_mixer; static int rec_stop = 0; static volatile int rec_volume, play_volume; //关闭播放设备 int close_audio_play_device() { close(p_dsp); //关闭设备描述符p_dsp close(p_mixer); //关闭设备描述符p_mixer return 0; } //打开播放设备 int open_audio_play_device() { printf("***** "); p_dsp = open("/dev/dsp", O_RDWR); //打开播放设备dsp if( p_dsp < 0 ){ printf("Open dsp error "); return -1; } printf("==== "); p_mixer = open("/dev/mixer", O_RDWR); //打开播放设备mixer if( p_mixer < 0 ){ printf("Open mixer error "); return -1; } } //关闭录音设备 int close_audio_rec_device() { close(r_dsp); //关闭设备描述符r_dsp close(r_mixer); //关闭设备描述符r_mixer return 0; } //打开录音设备 int open_audio_rec_device() { r_dsp = open("/dev/dsp1", O_RDWR); //打开录音设备dsp1 if( r_dsp < 0 ){ printf("Open dsp error "); return -1; } // printf("Open dsp OK "); r_mixer = open("/dev/mixer1", O_RDWR); //打开录音设备mixer1 if( r_mixer < 0 ){ printf("Open mixer error "); return -1; } // printf("Open mixer OK "); } //双通道录音生成PCM文件 int record_to_pcm(char *file, int samplerate, int loop) { int i, status = 0, frag; int channel = 2, volume = 0x6464; FILE *fd; char *buffer; struct timeval time; struct timezone timezone; int nowsec; open_audio_rec_device(); //打开录音设备 fd = fopen(file, "w+"); //打开录音文件 if(fd == NULL) { printf("open %s error! ", file); return 0; } printf("open %s OK! ", file); // ioctl(r_mixer, MIXER_WRITE(SOUND_MIXER_MIC), &volume); //set MIC max volume ioctl(r_mixer, MIXER_WRITE(SOUND_MIXER_MIC), &rec_volume); //set MIC max volume status = ioctl(r_dsp, SNDCTL_DSP_SPEED, &samplerate); //设置采样率 if(status<0) { fclose(fd); printf("Sample rate not support "); return -1; } ioctl(r_dsp, SNDCTL_DSP_CHANNELS, &channel); //设置通道 ioctl(r_dsp, SNDCTL_DSP_GETBLKSIZE, &frag); //获取块的大小frag // printf("volume=%d ", volume); // printf("volume=%d ", rec_volume); printf("samplerate=%d ", samplerate); printf("channel=%d ", channel); printf("frag=%d ", frag); buffer = (char *)malloc(frag); //为buffer分配frag大小的内存 gettimeofday(&time, &timezone); nowsec = time.tv_sec; printf("begin recording! "); for(i=0;i ", file); open_audio_play_device(); //打开播放设备 FILE *fd; fd = fopen(file, "r+"); //打开播放文件 if(fd == NULL) { printf("open %s error! ", file); return 0; } data = 0x5050; oss_format=AFMT_S16_LE;/*standard 16bit little endian format, support this format only*/ sample_rate = samplerate; channels = 2; ioctl(p_dsp, SNDCTL_DSP_SETFMT, &oss_format); //设置16位小端格式 ioctl(p_mixer, MIXER_WRITE(SOUND_MIXER_PCM), &data); //设置PCM data ioctl(p_dsp, SNDCTL_DSP_SPEED, &sample_rate); //设置采样率 ioctl(p_dsp, SNDCTL_DSP_CHANNELS, &channels); //设置通道 int frag; ioctl(p_dsp, SNDCTL_DSP_GETBLKSIZE, &frag); //获取块的大小frag buffer = (char *)malloc(frag); //为buffer分配frag大小的内存 printf("frag=%d ", buffer); fread(buffer, 1, frag, fd); //从播放文件fd中读取frag大小数据到buffer中 while(!feof(fd)) { audio_buf_info info; do{ ioctl(p_dsp , SNDCTL_DSP_GETOSPACE , &info); //获取p_dsp剩余空间 usleep(100); }while(info.bytes < frag); //若剩余空间不够写入frag大小数据则等待 fd_set writefds; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO( &writefds ); FD_SET( p_dsp , &writefds ); tv.tv_sec = 0; tv.tv_usec = 0; select( p_dsp + 1 , NULL , &writefds , NULL, &tv ); //判断p_dsp是否可写,超时时间tv if( FD_ISSET( p_dsp, &writefds )) { write(p_dsp, buffer, frag); //从buffer中取frag大小数据写到播放设备p_dsp fread(buffer, 1, frag, fd); //从播放文件fd中读取frag大小数据到buffer中 } usleep(100); } int bytes; ioctl(p_dsp,SNDCTL_DSP_GETODELAY,&bytes); //获取p_dsp的延时时间 int delay = bytes / (sample_rate * 2 * channels); //设置延时时间 sleep(delay); printf("Stop Play "); fclose(fd); //关闭播放文件 free(buffer); //释放内存 close_audio_play_device(); //关闭播放设备 } //播放单通道PCM int play_pcm_single(char *file, int samplerate) { int data, oss_format, channels, sample_rate; int i; char *buffer; //printf("<=== %s ===> ", file); open_audio_play_device(); //打开播放设备 FILE *fd; fd = fopen(file, "r+"); //打开播放文件 if(fd == NULL) { printf("open %s error! ", file); return 0; } data = 0x5050; oss_format=AFMT_S16_LE;//standard 16bit little endian format, support this format only sample_rate = samplerate; channels = 1; ioctl(p_dsp, SNDCTL_DSP_SETFMT, &oss_format); //设置16位小端格式 ioctl(p_mixer, MIXER_WRITE(SOUND_MIXER_PCM), &data); //设置PCM data ioctl(p_dsp, SNDCTL_DSP_SPEED, &sample_rate); //设置采样率 ioctl(p_dsp, SNDCTL_DSP_CHANNELS, &channels); //设置通道 int frag; ioctl(p_dsp, SNDCTL_DSP_GETBLKSIZE, &frag); //获取块的大小frag buffer = (char *)malloc(frag); //为buffer分配frag大小的内存 printf("frag=%d ", buffer); fread(buffer, 1, frag, fd); //从播放文件fd中读取frag大小数据到buffer中 while(!feof(fd)) { audio_buf_info info; do{ ioctl(p_dsp , SNDCTL_DSP_GETOSPACE , &info); //获取p_dsp剩余空间 usleep(100); }while(info.bytes < frag); //若剩余空间不够写入frag大小数据则等待 fd_set writefds; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO( &writefds ); FD_SET( p_dsp , &writefds ); tv.tv_sec = 0; tv.tv_usec = 0; select( p_dsp + 1 , NULL , &writefds , NULL, &tv ); //判断p_dsp是否可写,超时时间tv if( FD_ISSET( p_dsp, &writefds )) { write(p_dsp, buffer, frag); //从buffer中取frag大小数据写到播放设备p_dsp fread(buffer, 1, frag, fd); //从播放文件fd中读取frag大小数据到buffer中 } usleep(100); } int bytes; ioctl(p_dsp,SNDCTL_DSP_GETODELAY,&bytes); //获取p_dsp的延时时间 int delay = bytes / (sample_rate * 2 * channels); //设置延时时间 sleep(delay); printf("Stop Play "); fclose(fd); //关闭播放文件 free(buffer); //释放内存 close_audio_play_device(); //关闭播放设备 } //录音 void * p_rec(void * arg) { printf("recording ... "); record_to_pcm("./rec.pcm", 16000, -1); } //播放 void * p_play(void * arg) { printf("playing ... "); play_pcm("./16k.pcm", 16000); rec_stop = 1; } int main() { int i, loop, sr; char path[256]; int buf; printf(" **** Audio Test Program **** "); printf("1. Play "); //1播放本地PCM歌曲 printf("2. Record and then Play "); //2录音后播放 printf("Select:"); scanf("%d", &i); //选择值保存在变量i if(i<1 || i >2) //若i不在1和2范围里则返回 return 0; if(i == 1) //播放本地PCM歌曲 { printf("Playing ... "); play_pcm("./16k.pcm", 16000); //for(i=0;i<8;i++) // play_pcm(pcmfiles[i], samplerate[i]); //依次播放8种不同采样率的PCM歌曲 } else if(i==2) //录音后播放 { printf(" Rec Seconds:"); scanf("%d", &loop); //选择录音时间,单位秒,保存在变量loop printf("Sample Rate(8000, 11025, 12000 or 16000):"); //选择采样率,保存在变量sr scanf("%d", &sr); printf(" *** Recording *** "); printf("SampleRate=%d, Time=%d sec ", sr, loop); record_to_pcm_single("./rec.pcm", sr, loop); //录音loop时间,采样率sr,录音文件保存在rec.pcm printf(" Done, Now play it ... "); getchar(); play_pcm_single("./rec.pcm", sr); //播放录音文件rec.pcm } else printf("wrong input "); return 0; } 程序流程:main函数一进来,等待用户输入选择,输入1代表播放本地PCM原始音频文件,调用play_pcm()执行;输入2代表录音和播放,调用record_to_pcm_single()和play_pcm_single()执行。 在play_pcm()、play_pcm_single()和record_to_pcm_single()中,每个函数都需要经过这几个步骤完成录音和播放:打开设备,初始化设备设置参数,然后从设备中读写数据,最后关闭设备。 注意:play_pcm()和play_pcm_single()区别在于前者是双通道,后者是单通道,record_to_pcm()和record_to_pcm_single()的区别也一样。在播放本地歌曲时,由于本地歌曲是双通道的格式,因此只能调用play_pcm(),若调用play_pcm_single()单通道则声音异常。

4.编译

在ubuntu下切换路径至/duckbill/N32905/applications/audio。 执行make,编译生成可执行文件audio_demo,audio_demo将会自动拷贝至例程文件系统目录 /duckbill/N32905/usr/TEST_mini905/mkFilesys下。 执行例程文件系统TEST_mini905/test_mini905/目录下的脚本mkjffs2.sh,生成我们所需的jffs2文件系统。

5.烧录运行

WIFI-Mini905开发板与电脑之间连接好usb电源线(也充当下载线)、usb转串口线,将拨码开关S1拨向Rec位,按下自锁开关K1,开发板通电,N32905进入烧录模式。 使用TurboWriter依次烧录 开发板光盘资料Mini905光盘资料BIN基础例程下的loader(SpiLoader_905.bin)、内核(Kernel.bin)以及刚刚生成的文件系统(TEST_mini905.jffs2.summary),烧录步骤与例程1一致。 烧录完成后将拨码开关S1拨向Nor位,开发板重新通电,电源指示灯亮,N32905进入正常启动模式,等待系统运行起来。 在串口超级终端输入./aucio_demo,执行音频应用程序,出现菜单,提示用户选择选项,在这里输入1,喇叭就可以播放悦耳的歌曲了。 等歌曲播放完成,重新执行./aucio_demo,选择2,接着根据提示信息输入相应的参数-录音时间和采样率,采样率越高,声音越清晰细腻。你就可以对着底板上的mic头说话,时间一到喇叭就把你刚才说的播放一次。   陈生 微信:chenzhe_yj 提供开源硬件产品、嵌入式软硬件开发、技术咨询 淘宝店:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-18267726305.6.3fbb1d275b0ixd&id=554693753289