陈生
微信: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音视频开发板一块
N32905内部集成了音频DAC和SPU(声音处理单元),专用于音频处理。
音频DAC:支持16位立体声,可以直接驱动耳机。
SPU-声音处理单元:支持32个立体声通道;支持的原始音频格式有PCM8/PCM16/4-bit MDPCM/TONE;每一个通道支持7位音量控制;支持10波段均衡器;支持左右声道。
录音电路主要由mic和滤波电路组成,如下图1.2.1所示。

图2.1 录音电路
播放电路采用了ft690音频功放,可以直接驱动喇叭,如下图1.2.2所示。

图2.2 播放电路
代码路径:/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()单通道则声音异常。
在ubuntu下切换路径至/duckbill/N32905/applications/audio。
执行make,编译生成可执行文件audio_demo,audio_demo将会自动拷贝至例程文件系统目录 /duckbill/N32905/usr/TEST_mini905/mkFilesys下。
执行例程文件系统TEST_mini905/test_mini905/目录下的脚本mkjffs2.sh,生成我们所需的jffs2文件系统。
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