在了解 iOS Core Audio 相关技术的时候,会遇到 bitrate、sample、frame 和 packet 等概念。由于业界在不同场合下使用 packet 和 frame 等词语会代表不同的含义,一不小心,很容易被绕进去。
本文讲述了 iOS Core Audio 中常用的音频概念定义,然后介绍一些容易造成概念混淆的场景以及一个实践 demo 案例,最后解答一些常见的问题。
讨论 iOS Core Audio,就要按照苹果的定义对音频相关概念进行理解。
首先,重点理解 sample, frame 和 packet 三个概念。在 API 文档中中苹果明确定义了 audio stream, channel, sample, frame, packet 和 sample rate 这些概念:
从上面文档定义,简单来说,可以这样理解:
从上面定义来看这三个概念互相独立,定义清晰。
然而在日常讨论中,会在多种场合下使用 frame 和 packet 两个词,但是各种场合下它们代表的含义是不同的,所以比较容易搞混。
举下面不同场景的例子来说明:
从上面例子可以看到,不同场景下都使用了 frame 和 packet 两个词语会代表不一样的含义。更糟糕的是,如果使用了中文“帧”,在某些语境下,到底是代表数据帧、音频帧、packet 还是 frame 呢,就更容易分不清楚了。
从上面的概念定义,我们搞清楚了 iOS Core Audio 中对 sample, frame 和 packet 的定义,其中 frame 和 packet 又很容易搞混。
下面我们通过分析QQ音乐一首歌的例子,在实践中理解 iOS Core Audio 中对 packet 的定义。
iOS Core Audio 在使用 AudioFileStreamOpen 解码音频文件时候,会要求我们注册 AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 两个回调,用于文件读取到属性和包的时候回调。
以QQ音乐中,《最长的电影》这首歌的MP3文件为例,我们每次传入 1000 个字节调用 AudioFileStreamParseBytes 方法,可以得到下面结果(已知音频帧从第 496 个字节开始)。
第1次传入 1000 个字节: 首先回调多次 AudioFileStream_PropertyListenerProc,证明文件前面496个字节是属性。 之后回调一次 AudioFileStream_PacketsProc,回调方法有1个packet,417个字节;二进制内容就是音频数据帧 fffb9044 0008024e…(文件位置:496),没有任何跟文件不一样的多余信息。 然后就没回调了,这时候处理了 496+417 = 913 个字节。传进去的1000个字节,剩下 1000 - 913 = 87 个没处理,因为不能构成一个完整的音频数据帧。
然后传入第2次 1000 个字节: 首先回调 AudioFileStream_PacketsProc,回调418个字节,1个 packet,二进制内容是 fffb9264 29000153…(文件位置913)。 之后第二次继续回调 AudioFileStream_PacketsProc,回调418个字节,1个 packet,二进制内容是 fffb9264 510d0260…(文件位置 913+418=1331)。
可以验证到,AudioStreamPacketDescription 说的 packet ,是指传入数据之后,分离出来的MP3 音频数据帧。注意的链接说这是 MPEG Audio Layer I/II/III frame header,而 iOS Core Audio 使用了 AudioStreamPacketDescription,这里就很容易把 frame 和 packet 的概念搞混。事实上他们是指同一样东西。
看一下 AudioStreamPacketDescription 的代码定义,可以看到它就是一个数据帧 packet,包含了多个音频帧 frame。
因此,在讨论 iOS Core Audio 相关概念的时候,遇到过的下列疑问都可以解答:
Q: iOS Core Audio 对于 packet 的明确定义是什么? A: 见上述讨论第(一)点。
Q: 为什么经常搞不清楚 packet 和 frame 的区别? A: 见上述讨论第(二)点、(三)实验,因为这两个概念在不同场景下代表不同的含义。
Q: 一个 packet 可以包含半个 frame 吗? A: 从上述(三)实验可以看到,iOS Core Audio 的 packet 至少包含一个 frame,譬如 PCM 就是一个 packet 一个 frame;数据传输的时候的 packet 跟 iOS Core Audio 讨论的概念不一样,有多种不同的协议,相信有些协议可以自己定义传输大小,传送半个帧然后组装成一个帧。
Q: AudioFileStreamParseBytes 每次解析多少个数据帧?可以控制吗? A: 可以控制,对于 CBR 音频格式,基本上你传入多少个字节的数据,就会回调分离多少个数据帧,除非是最后一个不包含完整的 frame;对于 VBR 音频格式,传入一次数据,可能会回调多次分类帧回调。相关说明见AudioFileStream_PacketsProc API 文档最下方。
文中链接 1: https://developer.apple.com/reference/coreaudio/audiostreambasicdescription “苹果 AudioStreamBasicDescription API 文档” 2: http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm “MPEG文件格式介绍” 3: https://en.wikipedia.org/wiki/MPEG_transport_stream “MPEG transport stream” 4: http://ffmpeg.org/ “FFmpeg官网” 5: http://www.ffmpeg.org/doxygen/2.7/structAVPacket.html “FFmpeg AVPacket 文档” 6: http://www.ffmpeg.org/doxygen/2.7/structAVFrame.html “FFmpeg AVFrame 文档” 7: https://developer.apple.com/reference/audiotoolbox/audiofilestream_packetsproc “AudioFileStream_PacketsProc API 文档”
(全文完)