IOS和Android音频开发总结
最近因为项目需要对声音进行变声,所以边学习边做,发现音频的处理思路并不难,但是做起来还是有些繁琐的(比预期的)
趁着脑子还发热,赶紧把思路总结一下,记录下来。
主要讲三个部分
1,如何变声2,安卓实现变声3,ios实现变声
1.如何变声?
要想自己写一个变声的函数或者库出来,谈何容易,所以采用了大家普遍采用的库SoundTouch。
该库可以实现改变声音的速度,节拍,音调(这个最重要,可以把声音的音调调高调低,使之变成男生女生,可以参照汤姆猫),该库中对外提供方法的类为SoundTouch类,该类提供了许多方法,其中最重要的就是setPitch,setRate这几个调节声音参数的方法,可以通过设置参数大小实现各种效果,该类在使用前需要初始化一下,设置预制参数如下:
mSoundTouchInstance->setSetting(SETTING_USE_QUICKSEEK, 0); mSoundTouchInstance->setSetting(SETTING_USE_AA_FILTER, !(0)); mSoundTouchInstance->setSetting(SETTING_AA_FILTER_LENGTH, 32); mSoundTouchInstance->setSetting(SETTING_SEQUENCE_MS, 40); mSoundTouchInstance->setSetting(SETTING_SEEKWINDOW_MS, 16); mSoundTouchInstance->setSetting(SETTING_OVERLAP_MS, 8);
然后设置音频参数实现效果:
mSoundTouchInstance->setChannels(2); mSoundTouchInstance->setSampleRate(8000); mSoundTouchInstance->setPitch(2);
这里解释一下音频处理的几个参数,很重要。
声道:channals,可以是单声道和双声道,分别对应1,2
采样率:SampleRate 8000-44100不等,一般是常用的几个值,安卓里面好像44100是所有设备都支持的,所以设置成44100比较保险吧
每个声道的位数:bitsPerChannel 一般设置为16
每个帧的声道数 ChannelsPerFrame 对于pcm数据来说,这个是1
还有几个参数,对于安卓和ios可能说法不太一样,以上几个是都要用到的,比较重要,必须得掌握
2.Android中实现变声
因为项目要求录音要实时播放,所以需要采用读取音频数据流(PCM格式)来播放,采用的api是AudioRecorder和AudioTrack。这两个类相关资料较多,官方文档也比较详细。难点是Android中调用c++库需要使用到jni技术,这里就需要花一些力气将SoundTouch编成so库来使用了。对于这方面可以参考我的上一篇关于JNI的博客,也可以参考网上的资料,将SoundTouch类的几个重要函数对应到java层的Native函数,然后在java层调用。
以下是我写的代码的一部分
首先初始化AudioTrack和AudioRecord:
//初始化AudioTrack
int trbusize=AudioTrack.getMinBufferSize(RECORDER_SAMPLERATE,AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT); mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,RECORDER_SAMPLERATE, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, trbusize, AudioTrack.MODE_STREAM);
//初始化AudioRecord int rebusize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE,
AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT); mAudioRecord= new AudioRecord(MediaRecorder.AudioSource.MIC,RECORDER_SAMPLERATE,
AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, rebusize);
各项参数的含义可以参考api文档,需要注意的是,因为不同设备支持的参数可能不同,需要时可以写一个循环把所有可能的参数遍历一遍。
之后是录音和播放,可以分别放到两个线程里面。一般来说都是把录音数据保存到文件中,然后再进行播放,这样可以应付一般的录音需求。但不足之处在于,录音时间久了,文件会很大,如果在网络上实时播放的话这样肯定不行。解决方法就是将录音的数据传到一个缓冲区,然后播放时直接从缓冲区取走数据即可。这个缓冲区可以考虑用循环队列或者在java里面可以直接用一个LinkedList实现。
然后是变声部分:
while(isInstancePlaying){ if(l<21){ byte[] mbyte=new byte[64]; mAudioRecord.read(mbyte,0,64); SoundTouch.getSoundTouch().putSamples(mbyte,0,INPUT_LENGTH); SoundTouch.getSoundTouch().setPitchSemiTones(pitchTone); SoundTouch.getSoundTouch().receiveSamples(mbyte,INPUT_LENGTH); byteArray.add(mbyte); l=byteArray.size(); } else{ mAudioTrack.write(byteArray.getFirst(),0,64); byteArray.removeFirst(); l=byteArray.size(); }
代码中加粗的三个函数putSamples,setPitchSemiTones,receiveSamples.这三个都是native方法,在SoundTouch库中分别通过SoundTouch类提供的对应函数实现,
通过put和receive两个函数,mbyte这个数据块中的音频数据就实现了变声,变声的效果是通过中间的函数setPitchSemiTones()设置的。这里为了实现实时变声,我采用
了LinkedList作为一个缓冲区,l是其长度,当小于20时添加到byteArray的末尾,同时AudioTrack不断读取数组中的第一个元素来播放然后删除该元素,这样实现了实时的播放。
最后播放完要记得释放mAudioTrack和mAudioRecorder。通过stop和release方法实现。
3.IOS实现变声
因为本人之前没接触过ios所以做起来遇到了不少问题,还好最后解决了。
ios里面的音频处理比起安卓来说感觉要麻烦一些,用到的核心api就是AudioQueue,正在使用之前一定要好好理解一下它的原理,跟安卓不同的是ios播放和录音都是用的这个api。就相当于它一个东西实现了安卓中AudioRecorder和AudioTrack的功能,只不过在播放和录音过程中内部的流程有所变化。
核心思想:
Audio里面有自带的一个队列,首先用户创建若干个(3-6个左右都行)缓冲器用来装填音频数据,在自带队列中播放或录音完后使用用户自定义的回调函数进行处理,使得缓冲器能够被重新利用,并且可以在回调函数中实现用户自定义的一些功能,比如变声,写入文件等等操作。官方给了说明图比较详细,需要着重理解一下。
首先是录音的流程图:
然后是播放的流程图:
如何变声呢?
ios的变声不需要安卓的jni,因为oc语言可以和c++混编,所以这点相对来说要简单许多。流程如下:
首先在你的程序中实例化一个SoundTouch类,然后在初始化时将它的参数设置好(setSetting),之后在上面所述的回调函数里面就可以将录音得到的数据流进行处理然后选择保存到文件或者直接播放。思路就是这样,但是里面的函数的参数相对还是比较繁琐的,前面原理没理解的话这边就很难做下去了。
实时播放?
思路同Android,可以写一个循环队列用来缓存音频数据,然后边录音往里面传数据边播放,跟安卓不同的是这些操作需要放到相应的回调函数里面来实现,有个简单的办法是在录音的回调函数里面直接播放pcm数据。因为数据是一块一块的进来的,每使用完一次缓冲器才会调用一次回调函数,可以直接在回调函数里面进行播放。
以上就是两个平台上实现录音和实时播放的简单介绍,这里面的东西还是蛮多的,值得深入研究。