最近在搞DS上的开发,正好需要用到音频方面的功能。但是libnds音频方面问题实在是多,就我这两天遇到的问题总结一下。
DS的有两个CPU,ARM7和ARM9。其中只有ARM7是能访问音频相关的控制寄存器的。我们一般编写程序主要在ARM9端,而需要用到音频功能时通过一些IPC手段发送消息到ARM7,然后ARM7再访问音频相关寄存器。
在现在的较新版本libnds上,默认的模板已经做好了安装音频相关FIFO机制的准备工作。
ARM7端的模板代码就有:
installSoundFIFO();
并且音频相关的寄存器操作都被封装在一些函数中,现在我们可以在ARM9端简单地调用它们。
详细的接口就在libnds/include/nds/arm9/sound.h中
我们要注意两个函数,首先是:
void soundEnable(void);
这个函数的作用是开启音频设备,播放声音前必须先调用这个函数。
接下来是:
/*! \fn int soundPlaySample(const void* data, SoundFormat format, u32 dataSize, u16 freq, u8 volume, u8 pan, bool loop, u16 loopPoint);
\brief Plays a sound in the specified format at the specified frequency.
\param data A pointer to the sound data
\param format The format of the data (only 16-bit and 8-bit pcm and ADPCM formats are supported by this function)
\param dataSize The size in bytes of the sound data
\param freq The frequency in Hz of the sample
\param volume The channel volume. 0 to 127 (min to max)
\param pan The channel pan 0 to 127 (left to right with 64 being centered)
\param loop If true, the sample will loop playing once then repeating starting at the offset stored in loopPoint
\param loopPoint The offset for the sample loop to restart when repeating
\return An integer id coresponding to the channel of playback. This value can be used to pause, resume, or kill the sound
as well as adjust volume, pan, and frequency
*/
int soundPlaySample(const void* data, SoundFormat format, u32 dataSize, u16 freq, u8 volume, u8 pan, bool loop, u16 loopPoint);
关于如何调用,每个参数的作用都写的很清楚,包括常量也能在sound.h里找到,问题是:
\param dataSize The size in bytes of the sound data
这里明确说明dataSize的单位是byte,也就是说这个是按字节的。但是又有另一种说法,这个参数的单位为“字”,32bit处理器上一个字是32 bit=4 bytes,因为DS内部的确是按字来算音频的长度,为了搞清这一点,可以看看libnds的代码:
int soundPlaySample(const void* data, SoundFormat format, u32 dataSize, u16 freq, u8 volume, u8 pan, bool loop, u16 loopPoint){
FifoMessage msg;
msg.type = SOUND_PLAY_MESSAGE;
msg.SoundPlay.data = data;
msg.SoundPlay.freq = freq;
msg.SoundPlay.volume = volume;
msg.SoundPlay.pan = pan;
msg.SoundPlay.loop = loop;
msg.SoundPlay.format = format;
msg.SoundPlay.loopPoint = loopPoint;
msg.SoundPlay.dataSize = dataSize >> 2;
fifoSendDatamsg(FIFO_SOUND, sizeof(msg), (u8*)&msg);
while(!fifoCheckValue32(FIFO_SOUND));
return (int)fifoGetValue32(FIFO_SOUND);
}
可以看到libnds自动对长度进行了处理,所以大家在使用的时候可以放心地用字节来衡量自己的数据长度。
data指针指向的是数据开头的部分,那么这个所谓的数据是什么数据呢?这个数据应该是DS自己支持的一种RAW的数据,一般的从WAV直接提取出来的采样是不能直接播放的,要么通过sox之类的工具进行转换,要么在代码中即时转换。
接下来是我碰到的一个很诡异的问题,就是每当调用soundPlaySample的时候,系统自动卡死,我对音频这块调试了半天都找不到原因,最后调试到其他部分才找到使得系统崩溃的地方。
我用了这段代码进行测试:
iprintf("Debug1\n");
soundEnable();
char *fake_buf = (char *)malloc(400);
if (fake_buf == NULL)
iprintf("malloc failed\n");
memset(fake_buf, 0, 400);
soundPlaySample(fake_buf, SoundFormat_8Bit, 400, 22050, 127, 64, 0, 0);
iprintf("Debug2\n");
屏幕上是可以显示出Debug1和Debug2的,并没有卡死在soundPlaySample上。而在我的另一个项目中确只能显示Debug1,而卡死在soundPlaySample上,理由很简单,就是我设置了一下中断:
irqInit();
irqSet(IRQ_VBLANK, vblank_handler);
irqEnable(IRQ_VBLANK);
结果导致不能调用soundPlaySample了,现在只能排除错误,至于错误原因还是不明白。
在DS中有这么个需求,比如播放一首很长的音乐,或者干脆需要播放的东西是流式的,类似即时音频通信,soundPlaySample支持的数据长度是有限的,或者就算支持无限我们也无法一次性获得所有数据,怎么办?关键就在这个函数的loop参数上,这个参数的意思是重复播放,也就是播放到结尾的时候自动将播放指针定位为loopPoint的地方。那么,我们就需要将data指针指向一个足够大的缓冲区,通过硬件计时器或者其他手段精确计算机器播放到什么位置了,然后用新数据填充已经播放过的位置,这样就可以实现连续播放了。