[转]IOS、安卓IM语音聊天开发初探部分心得——本地音频处理篇
最近的项目的内容开始涉及到一定的IM语音对讲的内容,而笔者从未接触过此类开发,也只是在摸索中一点点探索学习,几日下来略有了一点收获,以博客的形式跟诸位看官分享
先说明一下什么是IM语音聊天,IM全称Instant Messenger,即时通讯,简单的来说就是MSN ,QQ一类的聊天软件。而IM语音聊天即是使用语音音频来代替传统文字交流的方式进行沟通交流,目前市面上的语音IM根据聊天的方式又几种不同的方式,一种是即时发言犹如电话通信一样的语音通信,其中比较具有代表的软件有UCTalk,YY语音等,此类语音软件以PC平台为主,另外一种则是先进行录音之后发送形式与传统文字IM的形式略有相似之处的聊天方式,比较具有代表性的软件有微信,陌陌等,此类的语音软件大多出现在移动端,PC平台上同时也具有少量的使用。还有一种则可以视为介于两者中间的,模仿传统对讲机形式进行轮流发言的聊天方式,这种方式只在PC台上少量使用出现过,较前两者使用的范围比较小。
本次我们做的是移动端的项目,于是也难免于俗套使用先录制后发送的方式实现语音聊天,鄙人才疏学浅了解不深,所以可能是主观上的臆断,感觉以录制发送的形式实现的语音IM实现起来从技术将要简单很多,毕竟不会设计流媒体的问题。
既然是语音聊天难免涉及到一些音频相关的问题,笔者是负责IOS端开发的,所以大部分的内容以IOS角度为主,IOS提供的AVFoundation框架可以实现大部分系统声音服务不支持的超过30秒的音频播放功能,同时还提供了录音功能。而我们主要使用到的是AVAudioRecorder与AVAudioPlayer两个类,通过名字我们就可以判断出,前者是提供音频录制服务而后者则是提供播放服务。AVAudioRecorder以各种不同的格式将声音录制到内存或设备本地文件中。录音过程可再应用程序执行其他功能时持续进行。而AVAudioPlayer能够播放任意长度的音频。使用这个类可以实现游戏配乐和其他复杂的音频应用程序。可以全名控制播放过程,包括同时播放多个音频文件等。无疑IOS提供的音频服务是强大以及便利的。再使用AVFoundation框架之前必须要将AVFoundation.framework与CoreAudio.framework加入到项目中,再导入两个接口文件。
import
import
现在我我们来详细解读一下者两段代码的含义,首先是音频录制的代码,我们先后声明并且定义了一下几样东西,创建音频的参数键值对MyRecordParam,一个路径数组pathArray,一个Docment路径字符串DocmentPath以及我们这一步的主角AVAudioRecorder对象MyRecorder。
我们先来解释一下路径的获取,至于音频参数,重头戏需要放在后面不是么~
NSSearchPathForDirectoriesInDomains是IOS中一个搜索路径的方法,它三个参数前两个为枚举,而最后一个参数为BOOL类型,第一个参数的枚举列表如下:
enum {
NSApplicationDirectory = 1,//Supported applications (/Applications)
NSDemoApplicationDirectory,//Unsupported applications and demonstration versions
NSDeveloperApplicationDirectory,//Developer applications (/Developer/Applications)
NSAdminApplicationDirectory,//System and network administration applications
NSLibraryDirectory,//Various user-visible documentation, support, and configuration files (/Library)
NSDeveloperDirectory,//Developer resources (/Developer)
NSUserDirectory,//User home directories (/Users)
NSDocumentationDirectory,//
NSDocumentDirectory,//
NSCoreServiceDirectory,//Location of core services (System/Library/CoreServices)
NSAutosavedInformationDirectory = 11,//Location of user’s autosaved documents Library/Autosave Information
NSDesktopDirectory = 12,//
NSCachesDirectory = 13,//Location of discardable cache files (Library/Caches)
NSApplicationSupportDirectory = 14,//Location of application support files (Library/Application Support)
NSDownloadsDirectory = 15,//
NSInputMethodsDirectory = 16,//
NSMoviesDirectory = 17,//
NSMusicDirectory = 18,//
NSPicturesDirectory = 19,//
NSPrinterDescriptionDirectory = 20,//
NSSharedPublicDirectory = 21,//
NSPreferencePanesDirectory = 22,//
NSItemReplacementDirectory = 99,//
NSAllApplicationsDirectory = 100,//
NSAllLibrariesDirectory = 101//
};
其每一项代表一种希望获取到的目录类型,这其中不只是IOS中的目录类型,也有MAC下的路径类型,没错,就跟你想的一样,这个函数并非IOS下专用。
第二个参数的枚举列表如下
enum {
NSUserDomainMask = 1,//用户主目录中
NSLocalDomainMask = 2,//当前机器中
NSNetworkDomainMask = 4,//网络中可见的主机
NSSystemDomainMask = 8,//系统目录,不可修改(/System)
NSAllDomainsMask = 0x0ffff,//全部
};
第二个参数代表要搜索路径的位置,本机?亦或是当前程序,还是局域网连接到的其他电脑。
第三个参数是一个BOOL值他代表是否将返回完整路径
而返回的路径中并不包含文件名,我们一定要记住再路径结尾处加上我们想要的文件名,别忘了我们是要创建一个音频文件。
当然此函数搜索的结果可能又很多条路径,因为根据你的参数不同他返回的路径甚至可能包含其他电脑上的(具体本人未测,有心人可进一步测试,也希望其讲结果与大家分享)所以他的结果是一个数组,而我们要取得的路径目的极为明确,就是程序的Docment路径,并且可以更加肯定是我们的程序只有一个Docment路径,所以我们直接取得了第一条返回记录。
不得不说的是AVAudioRecorder的设计者是个好人,没错,他没有将构造函数的参数设置成一大堆参数,那让人看起来头疼,但其实他用了一个更加让人头疼的方法,没错他让你去手动设置一个参数键值对,你甚至不知道建值对中该填什么参数,哪些参数…这对于习惯看参数列表直接调用方法的人无疑是个噩梦(尤其是当他们英文文档阅读能力低下时- -),目前我所掌握的参数键的相关资料如下:
AVSampleRateKey, //采样率
AVFormatIDKey,//音频编码格式
AVLinearPCMBitDepthKey,//采样位数 默认 16
AVNumberOfChannelsKey,//通道的数目
AVLinearPCMIsBigEndianKey,//大端还是小端 是内存的组织方式
AVLinearPCMIsFloatKey,//采样信号是整数还是浮点数
AVEncoderAudioQualityKey,//音频编码质量
鉴于考虑到可能各位看官对于我们所要给出的参数并不了解,所以在此我们来依次解释一下每一个参数的含义
首先是采样率,简单地说就是通过波形采样的方法记录1秒钟长度的声音,需要多少个数据。44KHz采样率的声音就是要花费44000个数据来描述1秒钟的声音波形。原则上采样率越高,声音的质量越好。
编码格式可以理解为每种音频格式不同的编解码方式,鄙人对于此了解的也不是非常多(能熟知所有编解码的人一定是偶像级的超人!)而IOS下这些编码方式被集中到一个枚举中,而我们本次代码中所使用的编码格式是WAV文件的格式,想要使用其他的编码格式就在成功导入AVFouncation框架之后即可通过Xcode的自动提示找到以kAudioFormat开头的各种枚举的名称。
采样位数即采样值或取样值,是用来衡量声音波动变化的参数,是指声卡在采集和播放声音文件时所使用数字声音信号的二进制位数。声卡的位客观地反映了数字声音信号对输入声音信号描述的准确程度。
通道数目应该很好理解了,1意味着单声道声音,2指立体声,4是指四个声道等等。
接下来的AVLinearPCMIsBigEndianKey是指再内存中音频的存储模式,在计算机中,通常采用的字节存储机制主要有两种:big-endian和little-endian,即大端模式和小端模式。这个参数为BOOL值,YES为大端,NO为小端。关于大端和小端相关到两个关键词,MSB以及LSB。MSB:Most Significant Bit( 最高有效位),LSB:Least Significant Bit (最低有效位)你可以理解为一段数据再内存中的起始位置以及终止位置,大端模式就是MSB存放在最低端的地址上。而小端口模式就是LSB存放在最低端的地址上。
在Big-Endian中,对于bit序列中的序号编排方式如下(以双字节数0x8B8A为例):bit | casino
0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15——MSB———————————-LSB
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |——————————————–
在Little-Endian中,对于bit序列中的序号编排和Big-Endian刚好相反,其方式如下(以双字节数0x8B8A为例):
bit | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0——MSB———————————–LSBval | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |———————————————
总之可以理解为在内存中正反两种存储顺序。
对于采样信号是整数还是浮点数也是一个BOOL类型的参数,本人并未理解会影响到的含义,或许是设计到音频信号再解析时候的精准度但并不敢确定还有待设定,所以也没有设置这个参数。
最后一个参数音频编码质量比较好理解了,这个参数又是一个枚举,我们可以找到以AVAudioQuality开头的High、Low、Medium、Max、Min五种设置。介于参数键值对的解释暂时就是这些了,如果又看官得到了一些其他参数的资料或者对于我的解释有纠正补充的欢迎指教。
AVAudioRecorder构造函数中的最后一个参数为一个出参,用以保存音频录制的错误信息,如果不想保存错误的信息直接设置为空即可
接下来我们的AVAudioRecorder对象好像看起来成功创建完成了,慢着,不对,为什么会提示错误?仔细看一下,原来是路径的问题,别担心,其实只是AVAudioRecorder跟你开了个小玩笑,因为他实在太懒了,都懒得把字符串路径转换成URL了,所以我们得手动转换一下~没错使用NSURL的静态方法 urlWithString即可解决~接下来只要调用record方法即可开始录音。而以上代码只是实例,建议在编写程序的时候,我们的AVAudioRecorder对象要声名在类内属性中。否则需要结束录音时无法调用到AVAudioRecorder的stop方法。
AVAudioRecorder对象创建起来或许会比较麻烦,但是使用起来确很方便,只要再录音开始时调用record,暂停的时候调用pause方法,而结束的时候调用stop方法就可以了。
相对于录音,播放音频可以说简单的可以,我们在创建AVAudioPlayer对象的时候只需要将之前录音的文件路径给它并且给一个空的错误出参便可轻松的创建出一个AVAudioPlayer对象,使用起来也是那么的方便只要调用一下play函数即可~~他和AVAudioRecorder一样也可以提供暂停和停止的功能,那它是否可以支持进度调节呢?想知道的话不如用自动提示打开他的方法列表看看呢一切看起来好像都很简单,我们就要轻松愉快的大功告成了,可当你运行程序之后却惊奇的发现为什么播放不出声音!使用iTools一类的软件检查一下程序的Docment目录,没错啊!文件在啊,莫非我录制错了?等等等等上万种可能就这样出现在你的脑中!好吧我真不忍心看你像我一样没头没脑的研究几个小时只是因为你没有把实例代码中AVAudioPlayer的声明放到类属性中。。。没错,如果你再某个方法中声明了它并且调用播放函数你就会发现怎么样也播放不出声音,如果你在调用play方法的位置设置下断点再仔细听的话可能会听到一小段声音,为什么呢?因为你刚调用了play的方法你的AVAudioPlayer对象就被释放了,当然什么也没有了也就播放不出声音了使用AVAudioPlayer就把它的对象放入类内属性吧!
wav没错我们的成功的录制并播放了一个wav,就在你开心的时候你会发现一个问题,纳尼!?安卓不支持wav!!!这可怎么办?难道要让本来就少的可怜的用户还要永隔安卓与IOS的大洋两岸么!一切问题都会有解决方法的!但是,预知后事如何,请听下回分解