iOS音频系列(三)--AudioQueue

本篇是AudioQueue的官方文档的笔记。Audio Queue Services可以play和record以下三类任何audio data:

  • Linear PCM.
  • Any compressed format supported natively on the Apple platform you are developing for.
  • Any other format for which a user has an installed codec.

对于最后一种类型,我们可以在使用AudioQueue同时自己将自己需要的format转化成LPCM。AudioQueue是对mic和speaker的高度抽象,同时可以非常简单的时间音频codecs。与此同时,它也有一些高级功能,例如多个音频的同步播放,回放等等。


About Audio Queues

这章会了解到audio queue的功能,结构体,以及内部运行的机理。具体的内容包括audio queues,audio queue buffers,audio queue会使用到的callback等。还有就是audio queue的状态以及参数。

What Is an Audio Queue?

An audio queue 是iOS中play和record audio的对象.底层是AudioQueueRef。Audio queue可以完成以下工作:

  • Connecting to audio hardware
  • Managing memory
  • Employing codecs, as needed, for compressed audio formats
  • Mediating recording or playback

Audio Queue Architecture

Audio queue的具体结构有以下几个部分构成:

  • A set of audio queue buffers, each of which is a temporary repository for some audio data
  • A buffer queue, an ordered list for the audio queue buffers
  • An audio queue callback function, that you write

根据我们使用audio queue的用途(record or play),具体的结构略有不同,仅仅只是callback函数函数的内容不同。

Audio Queues for Recording

一个用于record 的audio queue,需要使用AudioQueueNewInput方法创建,它的具体结构如图:

A recording audio queue
A recording audio queue

Audio Queues for Playback

一个用于play的audio queue,需要使用AudioQueueNewOutput函数创建,

A playback audio queue
A playback audio queue

Audio Queue Buffers

audio queue buffer的数据结构如下:

typedef struct AudioQueueBuffer {
    const UInt32   mAudioDataBytesCapacity;
    void *const    mAudioData;
    UInt32         mAudioDataByteSize;
    void           *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;

其中mAudioData字段表示这个buffer中的有用数据的地址,其他的字段用来辅助audio queue来管理使用这个buffer。一个audio queue可以使用任何数目的buffers。但是我们一般选择3个,比较好管理。

Audio queue通过下面的方式管理它们内部的buffers:

  • An audio queue allocates a buffer when you call the AudioQueueAllocateBuffer function.
  • When you release an audio queue by calling the AudioQueueDispose function, the queue releases its buffers.

The Buffer Queue and Enqueuing

buffer queue是由audio buffers组成的,是audio queue中的buffers。我们前面介绍了audio queue是如何使用callback管理内部的buffers。不论当前是用于record或者是pleyback,将buffer放到audio queue都是需要我们在callback函数中去手动调用的。

The Recording Process

The recording process
The recording process
  1. In step 1 , recording begins. The audio queue fills a buffer with acquired data.
  2. In step 2, the first buffer has been filled. The audio queue invokes the callback, handing it the full buffer (buffer 1). The callback (step 3) writes the contents of the buffer to an audio file. At the same time, the audio queue fills another buffer (buffer 2) with freshly acquired data.
  3. In step 4, the callback enqueues the buffer (buffer 1) that it has just written to disk, putting it in line to be filled again. The audio queue again invokes the callback (step 5), handing it the next full buffer (buffer 2). The callback (step 6) writes the contents of this buffer to the audio file. This looping steady state continues until the user stops the recording.

The Playback Process

The playback process
The playback process

Controlling the Playback Process

Audio queue buffers在queue是顺序播放的,我们可以通theAudioQueueEnqueueBufferWithParameters方法来进行控制

The Audio Queue Callback Function

Audio queue在运行过程中会不断的调用callback函数,通常间隔时间和audio queue buffer的大小相关,一般是几秒一次。

audio queue callback主要任务是将audio queue buffer归还给audio queue。callback中通过AudioQueueEnqueueBuffer方法将buffer加载到audio queue的最后。在playback中,可以使用AudioQueueEnqueueBufferWithParameters在enqueue的过程中进行更多的控制。

The Recording Audio Queue Callback Function

如果你仅仅使用audio queue去将record的audio data写入file system,callback的方法实现的原型如下:

AudioQueueInputCallback (
    void                               *inUserData,
    AudioQueueRef                      inAQ,
    AudioQueueBufferRef                inBuffer,
    const AudioTimeStamp               *inStartTime,
    UInt32                             inNumberPacketDescriptions,
    const AudioStreamPacketDescription *inPacketDescs
);

一个recording audio queue会触发我们注册的callback,会在callback的参数中传入所有需要的关于audio data的相关信息:

  • inUserData 是一个自定义的结构体,用来存储audio queueu以及audio queue buffer的状态信息,也包括AudioFileID,audio data format等。
  • inAQ 表示哪个audio queue触发这个callback。
  • inBuffer 是一个audio queue buffer,它的内容是由audio queue填充的,内部包括最新的audio data。并且这些audio data已经根据初始化时候传递的格式参数格式化好的数据。
  • inStartTime 表示这个buffer中的第一个采样的采样时间点,一般app中不太需要这个参数。
  • inNumberPacketDescriptions 表示inPacketDescs参数中的packet descriptions的个数。如果你是录入VBR format,audio queue就会在callback中提供这个参数,如果是CBR,audio queue就不会使用packet descriptions参数,这个参数会是NULL。
  • inPacketDescs 表示buffer中samples相关的一系列的packet descriptions。是否设置同上一个参数。

The Playback Audio Queue Callback Function

这个片段会介绍如果使用playing audio queue,那么callback应该的信息:

AudioQueueOutputCallback (
    void                  *inUserData,
    AudioQueueRef         inAQ,
    AudioQueueBufferRef   inBuffer
);

一个playback audio queue会触发这个callback,提供一些关于audio data的有用信息:

  • inUserData 见上
  • inAQ 表示哪个audio queue触发这个callback。
  • inBuffer 表示被audio queue设置为空的audio queue buffer,你需要在callback中将其内部信息填满,填充内容是你从AudioFile中读取的audio data。

Using Codecs and Audio Data Formats

我们日常使用Audio Queue Services时,都会使用codecs(audio data coding/decoding componets)用来在不同audio format之间进行转化。

每个audio queue都有一个audio data format,可以在AudioStreamBasicDescription结构体中得到。当我们在ASBD中指定了mFormatID以后,audio queue在向buffer中填充数据时候就会使用相应的codec。同样如果指定sample rate和channel count,audio queue也会同样。具体的过程见下图:

Audio format conversion during recording
Audio format conversion during recording
  • 第一步中,app会告知audio queue开始record,同时告诉它使用的的data format。
  • 第二步中,audio queue将获取到的new data使用codec转化成目标format。然后audio queue会调用callback函数,传入格式化以后的audio data。
  • 第三步中,callback函数会将格式化以后的audio data写入file中。

整个过程中,callback函数压根就不需要知道data fromat是什么。

Audio format conversion during playback
Audio format conversion during playback

在播放过程中,正好和录音过程相反,只需要在创建audio queue时候将data format告知即可。


Audio Queue Control and State

audio queue在创建和销毁的过程有一个声明周期。app需要管理它的声明周期,控制它的状态,具体控制状态的方法如下:

  • Start (AudioQueueStart).初始化audio queue用来record或者playback。
  • Prime (AudioQueuePrime).对于playback,在调用AudioQueueStart挚爱去哪调用确保数据可用,这个方法和record没有关系。
  • Stop (AudioQueueStop). 调用以后会重置audio queue,然后会停止record或者playback。在playback应用中,一般在没有audio data可以播放时候调用。
  • Pause (AudioQueuePause). 在record或者playback中调用这个方法不会影响到buffers。如果需要恢复,调用AudioQueueStart
  • Flush (AudioQueueFlush). 在enqueue最后一个audio queue buffer以后调用这个方法,确保所有的数据被record或者play(主要是在midst processing的数据)。
  • Reset (AudioQueueReset). 调用以后立即停止audio queue,然后将所有的buffers移除,重置所有的DSP状态等到。

在调用AudioQueueStop方法时候有两种模式:同步和异步。

  • Synchronous stopping happens immediately, without regard for previously buffered audio data.
  • Asynchronous stopping happens after all queued buffers have been played or recorded.

Recording Audio

当我们的record使用Audio Queue Services,存储的路径可以是磁盘上的任何地方,或者网络,或者内存中。这部分内容记录大多数的使用场景,存储在磁盘中。

具体的步骤如下:

  1. 定义一个结构体去存储状态,format,文件路径等信息。
  2. 完成audio queue callback函数,其中将record以后的数据进行存储。
  3. 为audio queue buffers计算出合适的大小,并且在file中写入magic cookies。
  4. 初始化自定义的结构体
  5. 创建recording audio queue,然后给它创建3个audio queue buffers,然后创建一个file用来存储record以后的audio data。
  6. 启动audio queue
  7. 当audio queue停止以后,dispose它以及buffers

具体的实现内容可以参考Apple官方文档:Recording Audio

Playing Audio

当我们使用Audio Queue Service去play audio时,音频源文件可以是任何在disk file或者memory中,这部分内容是如何用Audio Queue Service播放存储在disk上的audio file。

具体的步骤如下:

  1. 定义一个结构体管理Audio queue的状态,format,file path等
  2. 完成audio queue callback函数去进行实际的播放
  3. 创建一个函数用来计算最适合的audio queue buffer的大小
  4. 打开audio file,确定它的audio data format
  5. 创建audio queue,对它进行配置
  6. 为audio queue创建buffers,然后启动audio queue,当播放结束,callback让audio queue停止播放
  7. 销毁audio queue

具体的实现内容可以参考Apple官方文档:Playing Audio

可运行的Demo

请参考我的github: https://github.com/brownfeng/AudioQueueServiceDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容