前言
本来说好是要在第三篇中讲AudioFileStream
和AudioQueue
,但写着写着发现光AudioFileStream
就好多内容,最后还是决定分篇介绍,这篇先来说一下AudioFileStream
,下一篇计划说一下和AudioFileStream
类似的AudioFile
,下下篇再来说AudioQueue
。
在本篇那种将会提到计算音频时长duration和音频seek的方法,这些方法对于CBR编码形式的音频文件可以做到比较精确而对于VBR编码形式的会存在较大的误差(关于CBR和VBR,请看本系列的第一篇),具体讲到duration和seek时会再进行说明。
AudioFileStream介绍
在第一篇中说到AudioFileStreamer
时提到它的作用是用来读取采样率、码率、时长等基本信息以及分离音频帧。那么在官方文档中Apple是这样描述的:
To play streamed audio content, such as from a network connection, use Audio File Stream Services in concert with Audio Queue Services. Audio File Stream Services parses audio packets and metadata from common audio file container formats in a network bitstream. You can also use it to parse packets and metadata from on-disk files
根据Apple的描述AudioFileStreamer
用在流播放中,当然不仅限于网络流,本地文件同样可以用它来读取信息和分离音频帧。AudioFileStreamer
的主要数据是文件数据而不是文件路径,所以数据的读取需要使用者自行实现,
支持的文件格式有:
- MPEG-1 Audio Layer 3, used for .mp3 files
- MPEG-2 ADTS, used for the .aac audio data format
- AIFC
- AIFF
- CAF
- MPEG-4, used for .m4a, .mp4, and .3gp files
- NeXT
- WAVE
上述格式是iOS、MacOSX所支持的音频格式,这类格式可以被系统提供的API解码,如果想要解码其他的音频格式(如OGG、APE、FLAC)就需要自己实现解码器了。
初始化AudioFileStream
第一步,自然是要生成一个AudioFileStream
的实例:
1 2 3 4 5 |
|
第一个参数和之前的AudioSession的初始化方法一样是一个上下文对象;
第二个参数AudioFileStream_PropertyListenerProc
是歌曲信息解析的回调,每解析出一个歌曲信息都会进行一次回调;
第三个参数AudioFileStream_PacketsProc
是分离帧的回调,每解析出一部分帧就会进行一次回调;
第四个参数AudioFileTypeID
是文件类型的提示,这个参数来帮助AudioFileStream
对文件格式进行解析。这个参数在文件信息不完整(例如信息有缺陷)时尤其有用,它可以给与AudioFileStream
一定的提示,帮助其绕过文件中的错误或者缺失从而成功解析文件。所以在确定文件类型的情况下建议各位还是填上这个参数,如果无法确定可以传入0(原理上应该和这篇博文近似);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
第五个参数是返回的AudioFileStream实例对应的AudioFileStreamID
,这个ID需要保存起来作为后续一些方法的参数使用;
返回值用来判断是否成功初始化(OSSStatus == noErr)。
解析数据
在初始化完成之后,只要拿到文件数据就可以进行解析了。解析时调用方法:
1 2 3 4 |
|
第一个参数AudioFileStreamID
,即初始化时返回的ID;
第二个参数inDataByteSize,本次解析的数据长度;
第三个参数inData,本次解析的数据;
第四个参数是说本次的解析和上一次解析是否是连续的关系,如果是连续的传入0,否则传入kAudioFileStreamParseFlag_Discontinuity
。
这里需要插入解释一下何谓“连续”。在第一篇中我们提到过形如MP3的数据都以帧的形式存在的,解析时也需要以帧为单位解析。但在解码之前我们不可能知道每个帧的边界在第几个字节,所以就会出现这样的情况:我们传给AudioFileStreamParseBytes的数据在解析完成之后会有一部分数据余下来,这部分数据是接下去那一帧的前半部分,如果再次有数据输入需要继续解析时就必须要用到前一次解析余下来的数据才能保证帧数据完整,所以在正常播放的情况下传入0即可。目前知道的需要传入kAudioFileStreamParseFlag_Discontinuity
的情况有两个,一个是在seek完毕之后显然seek后的数据和之前的数据完全无关;另一个是开源播放器AudioStreamer的作者@Matt Gallagher曾在自己的blog中提到过的:
the Audio File Stream Services hit me with a nasty bug: AudioFileStreamParseBytes will crash when trying to parse a streaming MP3.
In this case, if we pass the kAudioFileStreamParseFlag_Discontinuity flag to AudioFileStreamParseBytes on every invocation between receiving kAudioFileStreamProperty_ReadyToProducePackets and the first successful call to MyPacketsProc, then AudioFileStreamParseBytes will be extra cautious in its approach and won't crash.
Matt发布这篇blog是在2008年,这个Bug年代相当久远了,而且原因未知,究竟是否修复也不得而知,而且由于环境不同(比如测试用的mp3文件和所处的iOS系统)无法重现这个问题,所以我个人觉得还是按照Matt的work around在回调得到kAudioFileStreamProperty_ReadyToProducePackets
之后,在正常解析第一帧之前都传入kAudioFileStreamParseFlag_Discontinuity
比较好。
回到之前的内容,AudioFileStreamParseBytes
方法的返回值表示当前的数据是否被正常解析,如果OSStatus的值不是noErr则表示解析不成功,其中错误码包括:
1 2 3 4 5 6 7 8 9 10 11 1 |