iOS Core Audio 相关概念的深入理解

(这原本是几年在腾讯任职时写的老文,翻硬盘找到,所以重载于此)

在了解 iOS Core Audio 相关技术的时候,会遇到 bitrate、sample、frame 和 packet 等概念。由于业界在不同场合下使用 packet 和 frame 等词语会代表不同的含义,一不小心,很容易被绕进去。

本文讲述了 iOS Core Audio 中常用的音频概念定义,然后介绍一些容易造成概念混淆的场景以及一个实践 demo 案例,最后解答一些常见的问题。

(一) iOS Core Audio 音频概念定义

讨论 iOS Core Audio,就要按照苹果的定义对音频相关概念进行理解。

首先,重点理解 sample, frame 和 packet 三个概念。在 API 文档中中苹果明确定义了 audio stream, channel, sample, frame, packet 和 sample rate 这些概念:

  • A sample is single numerical value for a single audio channel in an audio stream.
  • A frame is a collection of time-coincident samples. For instance, a linear PCM stereo sound file has two samples per frame, one for the left channel and one for the right channel.
  • A packet is a collection of one or more contiguous frames. A packet defines the smallest meaningful set of frames for a given audio data format, and is the smallest data unit for which time can be measured. In linear PCM audio, a packet holds a single frame. In compressed formats, it typically holds more; in some formats, the number of frames per packet varies.

从上面文档定义,简单来说,可以这样理解:

  • sample 是一个声道的一个采样。
  • frame 是最小单位时间点包含的一个或多个声音采样,最小单位时间点取决于声音采样设备,是一个时间点多个采样的集合。譬如,双声道的音频文件,一个时间点有两个声道,一个 frame 就包括两个采样。
  • packet 是一个或多个 frame 的集合,一个 packet 包含多少个 frame,是由声音文件格式决定的。譬如 PCM 文件格式中一个 packet 包含 1 个frame。而 MP3 文件格式中一个 packet 包含 1152 个 frames。

从上面定义来看这三个概念互相独立,定义清晰。

(二)这些概念为什么容易搞混?

然而在日常讨论中,会在多种场合下使用 framepacket 两个词,但是各种场合下它们代表的含义是不同的,所以比较容易搞混。

举下面不同场景的例子来说明:

  1. 在讨论 MPEG 格式的时候,如网上常见的MPEG文件格式介绍,把 MPEG 一个 header + payload (帧头 + 数据内容)的数据结构叫做一个 frame (MPEG数据帧),一个 MPEG 数据帧包含了多个音频帧。事实上这个东东在上述 iOS Core Audio 定义中,却又被称为一个 packet。所以两份文档中,分别使用了 packet 和 frame 两个词指代同一个概念
  2. 网络传输音频的时候,会把音频数据进行打包发送,这个时候也用到 packet 的概念,他有自己独立的 packet header 定义,又跟 iOS Core Audio 定义的 packet 不是同一个了。
  3. 在讨论计算机网络时,硬件数据帧称为 frame,而数据链路层将 frame 打包成 packet 之后提供给上层网络层使用。 这里 frame 和 packet 的概念又跟音频讨论中的含义不一样了。
  4. FFmpeg 是一个音/视频编码解码及转换的开源软件。它定义了两个结构体,AVPacket 一份代表经过压缩的音频/视频数据,AVFrame 代表一份解压后的一个音频/视频数据。视频一个 AVPacket 通常只包含一个 AVFrame,经过压缩的音频 AVPacket 通常包括多个 AVFrame。可以看到 FFmpeg 在处理音频和视频时,对 packet 和 frame 概念的使用跟 iOS Core Audio 基本一致。

从上面例子可以看到,不同场景下都使用了 frame 和 packet 两个词语会代表不一样的含义。更糟糕的是,如果使用了中文“帧”,在某些语境下,到底是代表数据帧、音频帧、packet 还是 frame 呢,就更容易分不清楚了。

(三)DEMO:QQ音乐一首歌的音频数据帧

从上面的概念定义,我们搞清楚了 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。

AudioStreamPacketDescription 定义

(四)答疑

因此,在讨论 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 文档最下方。

(全文完)

Posted 2020-03-07

More writing at jakehao.com