AudioQueue 知识

什么是音频队列?

在iOS和Mac OS X中,音频队列是一个用来录制和播放音频的软件对象,他用AudioQueueRef这个不透明数据类型来表示,该类型在AudioQueue.h头文件中声明。

音频队列完成以下工作:

连接音频硬件

内存管理

根据需要为已压缩的音频格式引入编码器

媒体的录制或播放

你可以将音频队列配合其他Core Audio的接口使用,再加上相对少量的自定义代码就可以在你的应用程序中创建一套完整的数字音频录制或播放解决方案。

音频队列架构

所有的音频队列都含有相同的基础结构,包含以下几部分:

一组音频队列缓冲区(audio queue buffers),每个音频队列缓冲区都是一个存储音频数据的临时仓库

一个缓冲区队列(buffer queue),一个包含音频队列缓冲区的有序列表

一个你自己编写的音频队列回调函数(audio queue callback)

    它的架构很大程度上依赖于这个音频队列是用来录制还是用来播放的。不同之处在于音频队列如何连接到它的输入和输入,还有它的回调函数所扮演的角色。

用来录制的音频队列

录制用音频队列,用AudioQueueNewInput函数创建,拥有如图1-1的结构

图示 1-1  录制用音频队列

    录制用音频队列的输入端一般连接到外部的音频硬件上,比如说麦克风。在iOS中,音频来自于由用户连接的设备--内置的麦克风或者耳机麦克风,比如说,在Mac OS X默认情况下,音频来自于由用户在系统首选项中设置的系统默认音频输入设备。

    录制用音频队列的输入端利用了你自己写的回调函数,当录制音频到磁盘上的时候,回调函数将存有从音频队列中接收到的新的音频数据的缓冲区写入到音频文件中。然而,录制用音频队列也可以用其他方法来使用。你也可以使用其中一种,比如说,在一个实时的分析仪中,在这种情况下,你的回调函数会直接向你的应用程序提供音频数据,而不是将它写入磁盘。

    你将在“记录用音频队列回调函数”中学到更多关于这个回调的知识。

每一个音频队列——无论是录制用还是播放用——都有一个或多个音频队列缓冲区。这些缓冲区排列在一个特殊的被称为缓冲区队列(buffer queue)的序列中。如图所示,音频队列缓冲区是按照他们被填充的顺序编号的——这也是和把他们交付给回调函数的顺序是相同的。你将在“缓冲区队列和入队”中学到如何音频队列是如何使用它的缓冲区的。

用来播放的音频队列

播放用音频队列(用AudioQueueNewOutput函数创建)拥有如图1-2所示的结构

图示 1-2  播放用音频队列

在播放用音频队列中,回调函数是在输入端的,这个回调函数的职责就是从磁盘(或其他来源)中获取音频数据,然后将它交付给音频队列。当没有更多音频数据需要播放的时候告诉音频队列停止,你将在“播放用音频队列回调函数”中学到更多关于这个回调函数的知识。

播放用音频队列的输出端一般都是连接到外部的音频设备的,比如说扬声器。在iOS中,音频通过用户选择的设备播放——比如说,接受者是耳机。在Mac OS X中,默认情况下,音频会通过用户在系统首选项中设置的默认音频输出设备中输出。

音频队列缓冲区

音频队列缓冲区(audio queue buffer)是一个AudioQueueBuffer类型的数据结构,声明于AudioQueue.h头文件。

[cpp]view plaincopy

<span style="font-size:12px;">typedef struct AudioQueueBuffer {  

const UInt32   mAudioDataBytesCapacity;  

void *const    mAudioData;</strong>  

    UInt32         mAudioDataByteSize;  

void           *mUserData;  

} AudioQueueBuffer;  

typedef AudioQueueBuffer *AudioQueueBufferRef;</span>  

    上述代码中高亮的mAudioData域,指向了缓冲区本身:一个用来当作暂时存放录制或播放音频数据的容器的内存,其他域中的数据用来辅助音频队列管理这个缓冲区。

    音频队列可以使用任意数量的缓冲区。你的应用程序制定它的数量。一般情况下这个数字是3。这样就可以让给一个忙于将数据写入磁盘,同时另一个在填充新的音频数据,第三个缓冲区在需要做磁盘I/O延迟补偿的时候可用。图示1-3演示了这个过程。

音频队列负责对它的缓冲区进行内存管理

当你调用AudioQueueAllocateBuffer函数的时候音频队列创建了一个缓冲区

当你通过调用AudioQueueDispose函数释放一个音频队列的时候,这个音频队列释放掉它拥有的缓冲区

    这提高了你添加到应用程序中的录制和播放功能的鲁棒性。同时它也帮助你优化了资源的使用。

关于AudioQueueBuffer数据结构的完整描述,请参照音频队列服务参考(Audio Queue Services Reference)

缓冲区队列和入队

传递给音频队列的缓冲区队列,就如它的名字一样,就是事实上的音频队列服务(Audio Queue Services),你见过缓冲区队列——一个缓冲区的有序列表——在“音频队列架构”中你学到了音频队列对象如何配合回调函数在录制或播放的过程中管理缓冲区队列。特别的,你将学习到入队(enqueuing),缓冲区队列对音频队列缓冲区的附加操作。无论你正在实现录制或者播放,入队都是你在回调函数中需要执行的任务。

录制过程

当进行录制的时候,一个音频队列缓冲区被填充了从列入例如麦克风的输入设备种获取的音频数据。缓冲区队列中的其他缓冲区将在当前缓冲区的后面依次排队等待被填充音频数据。

音频队列将按照缓冲区填充的顺序把已填充过音频数据的缓冲区交付给你的回调函数。图示1-3演示了当使用音频队列的时候这个录制过程是如何工作的。

图示 1-3录制过程

    在图示1-3中的第一步,录制开始,音频队列用获取到的数据填充缓冲区。

    第二步,第一个缓冲区填充完毕,音频队列调用回调函数来处理这个被填充满的缓冲区(缓冲区一)。回调函数(第三步)将缓冲区的内容写到音频文件中。同时,音频队列将另一个缓冲区(缓冲区二)填充新获取到的数据。

    在第四步,回调函数将刚刚写入磁盘的缓冲区(缓冲区一)入队,使它重新重新回到被填充的队列。音频队列再一次调用回调函数(第五步),处理下一个填充完毕的缓冲区(缓冲区二)。回调函数(第六步)将这个缓冲区的内容写入到音频文件。这种稳定状态会一直持续到用户停止录制。

播放过程

当进行播放的时候,音频队列缓冲区将被传送到像扬声器这样的输出设备。缓冲区队列中其他的缓冲区讲按顺序排在当前缓冲区后面等待播放。

音频队列将已经播放过的音频数据按照他们播放的顺序交付给你的回调函数,回调函数将新的音频数据读取到一个缓冲区中,然后将它入队。图示1-4演示了当使用音频队列时播放是如何工作的

图示 1-4播放过程

图示1-4中的第一步,应用程序启动播放用音频队列,应用程序对每一个音频队列缓冲区调用回调函数,填充这些缓冲区并且将它们加入缓冲区队列。启动操作会确保播放可以立即执行当你的应用程序调用AudioQueueStart函数之后。

    在第三步,音频队列将第一个缓冲区(缓冲区一)交付给输出。

    当第一个缓冲区被播放完毕之后,播放用音频队列就进入了一个稳定的循环状态。音频队列开始播放下一个缓冲区(第四步,缓冲区二)然后调用回调函数(第五步),处理刚刚播放完的那个缓冲区(缓冲区一)。这个回调函数(第六步)从音频文件中读取数据填充缓冲区然后将他们入队用于播放。

控制播放过程

音频队列缓冲区总是按照他们入队的顺序进行播放,然而,在播放过程中,音频队列服务为你提供了AudioQueueEnqueueBufferWithParameters函数来进行一些控制,这个函数有以下功能:

设置缓冲区的精确播放时间,这可以让你支持同步

截断音频队列缓冲区开头或结尾的帧(frame),这可以让你移除开头或结尾的静音

在缓冲区的粒度上设置播放增益

    关于更多播放增益的知识,请看"音频队列参数"(Audio Queue Parameters.),如果要对Audio Queue Parameters.函数的完整描述,请参照“音频队列服务参考”(Audio Queue Parameters.)。

音频队列回调函数

一般来说,使用音频队列服务的大部分编程任务都在编程音频队列回调函数上。

    在录制或播放过程中,音频队列将反复的调用它所拥有的音频队列回调函数。调用的时间间隔取决于音频队列缓冲区的容量,并且一般来一说这个时间在半秒或者几秒。

无论对于录制或者播放,音频队列回调的一个职责就是返回一个缓冲区队列的音频队列缓冲区。回调函数使用AudioQueueEnqueueBuffer函数将一个缓冲区加入到缓冲区队列的末尾。对于播放来说,你也可以使用AudioQueueEnqueueBufferWithParameters函数来获得更多的控制,就像“控制播放过程”中描述的一样。

录制用音频队列回调函数

本节介绍了一般情况下——将音频录制到磁盘上,这种情况的回调函数。这里是这个录制用回调函数的原型,就和AudioQueue.h头文件中声明的一样:

[cpp]view plaincopy

AudioQueueInputCallback (  

void                               *inUserData,  

    AudioQueueRef                      inAQ,  

    AudioQueueBufferRef                inBuffer,  

const AudioTimeStamp               *inStartTime,  

    UInt32                             inNumberPacketDescriptions,  

const AudioStreamPacketDescription *inPacketDescs  

);  

    录制用音频队列,在调用回调函数的时候,提供了回调函数将下一组音频数据写入到文件的一切信息。

inUserData,通常是一个你创建用来保存音频队列和它的缓冲区状态信息的自定义结构,一个音频文件对象 (AudioFileID类型)代表你正在写入的文件,还有这个文件的音频格式信息。

inAQ是调用回调函数的音频队列

inBuffer是一个被音频队列填充新的音频数据的音频队列缓冲区,它包含了回调函数写入文件所需要的新数据。. 数据已经根据你在自己指定的自定义结构(由inUserData参数传入)中指定的格式格式化。关于此点的更多信息,请参照“使用编码器和音频数据格式”

inStartTime是缓冲区中的一采样的参考时间,对于基本的录制,你的毁掉函数不会使用这个参数

inNumberPacketDescriptions是inPacketDescs参数中包描述符(packet descriptions)的数量,如果你正在录制一个VBR(可变比特率(variable bitrate))格式, 音频队列将会提供这个参数给你的回调函数,这个参数可以让你传递给AudioFileWritePackets函数. CBR (常量比特率(constant bitrate)) 格式不使用包描述符。对于CBR录制,音频队列会设置这个参数并且将inPacketDescs这个参数设置为NULL

inPacketDescs是一组对应于缓冲区中采样的包描述符,音频队列提供了这个参数的值,如果音频文件是VBR格式的,你的回调函数可以将这个值传递给AudioFileWritePackets函数(声明于AudioFile.h头文件中)

如果要了解更多关于录制用回调函数的信息,请参照本文档的“录制音频”,并且参照音频队列服务参考(Audio Queue Services Reference.)

播放用音频队列回调函数

本节介绍了一般情况下——从磁盘文件播放音频,这种情况的回调函数。 这里是这个播放用回调函数的原型,就和AudioQueue.h头文件中声明的一样:

[cpp]view plaincopy

AudioQueueOutputCallback (  

void                  *inUserData,  

    AudioQueueRef         inAQ,  

    AudioQueueBufferRef   inBuffer  

);  

播放用音频队列,在调用回调函数的时候,提供了回调函数将下一组音频数据进行读取进行播放的信息。

inUserData域,一般来说是一个你创建的包含音频队列和它的缓冲区的的状态信息的自定义结构,一个音频文件对象 (AudioFileID类型) 代表了你要写入的文件和文件的音频数据格式信息。

在播放音频队列的情况下,回调函数会在这个结构中用一个域保持对当前包的索引

inAQ域是调用这个回调函数的音频队列

inBuffer域是一个音频队列缓冲区,是一个有音频队列变成可用状态的音频队列缓冲区,你的回调函数将把它填充上下一组要进行播放的音频数据。

    如果你的应用程序在播放VBR数据,回调函数需要得到正在播放的音频数据的包数据,它通过调用AudioFileReadPackets函数来完成这个任务,这个函数声明于AudioFile.h头文件,回调函数随后将包信息放到自定义的数据结构中以使得它对播放用音频队列可用。

关于播放回调的更多信息,请看本文档的“播放音频”(Playing Audio),并且参照音频队列服务参考(Audio Queue Services Reference.)

使用编码和音频数据格式

音频队列服务根据在不同的音频格式之间转换的时候会根据需要使用编码器(音频数据编码/解码组件)。你的录制或播放程序可以使用任意已经安装过相应编码器的格式,不需要写自定义的代码来处理各种各样的音频格式。特别的,你的回调函数不需要知道数据格式。

现在来讲解一下这是如何工作的,每一个音频队列在AudioStreamBasicDescription结构中都有一个域代表了音频数据格式。当你在mFormatID域中指定了它的格式的时候——音频队列会使用相应的解码器。然后你指定采样率和声道数,这些就是所有你需要做的。你将会在“录制音频”和“播放音频”中看到如何设置音频数据格式的示例。

录制用音频队列按照图示1-5中的流程使用已安装的编码器。

图示 1-5  在录制音频的时候进行音频格式转换

    在图示1-5中的第一步,你的应用程序告诉音频队列开始录制,同时也告诉它所要使用的音频格式。在第二部,音频队列获取新的音频数据,并且根据你指定的格式使用相应的编码器转换音频数据。然后音频队列调用回调函数,将适当的格式化过的音频数据放进缓冲区中。第三步,回调函数将格式化后的音频数据写入磁盘。再次,你的回调函数不需要了解数据格式。

播放用音频队列按照图示1-6的流程使用已安装的编码器。

图示 1-6在播放过程中进行音频格式转换

在图示1-6的第一步中,你的应用程序告诉音频队列开始播放,同时也告诉了它将要播放放的音频文件的数据格式。在第二步,音频队列调用回调函数来从音频文件中读取音频数据。回调函数按照它的原始格式将音频数据交付给音频队列。在第三步,音频队列使用对应的解码器将音频交付给目标输出。

音频队列可以使用任意已安装的编码器,无论是Mac OS X原生的还是第三方的。你可以通过指定音频队列的AudioStreamBasicDescription结构中四字节的编码ID来指定将要使用的编码器。你将会在“录制音频”中看到这个字段的使用示例。

Mac OS X包含大量的编码器,他们都在CoreAudioTypes.h头文件中的format IDs枚举值中列出了,并且记录在核心音频数据类型参考(Core Audio Data Types Reference)中。你可以通过调用AudioFormat.h头文件中的接口来查询当前系统可用的编码器。在Audio Toolbox框架中。你可以使用Fiendishthngs应用程序来显示系统的编码器,相应的示例代码在网址http://developer.apple.com/samplecode/Fiendishthngs/.

音频队列的控制和状态

音频队列的生命周期在创建和处理之间。你的应用程序管理它的生命周期——并且控制音频队列的状态——通过使用AudioQueue.h头文件中的六个函数:

Start(AudioQueueStart). 调用它来初始化录制或者播放

Prime(AudioQueuePrime). 对于播放, 在调用AudioQueueStart之前调用这个函数,用来确定音频队列中立刻就有可用的数据来播放。这个函数不在录制中使用

Stop(AudioQueueStop).调用这个函数来重置音频队列 (参考下面对AudioQueueReset的描述),然后停止录制或播放。一个播放用音频队列回调函数当它没有更多的数据播放的时候会调用这个函数

Pause(AudioQueuePause).调用这个函数可以在不影响缓冲区和不重置音频队列的情况下停止录制或播放。如果需要恢复,调用AudioQueueStart函数

Flush(AudioQueueFlush). 在将最后一个音频队列缓冲区入队之后调用,来确保所有缓存过的数据,也包括处理的中间数据,得到录制或播放

Reset(AudioQueueReset). 调用这个函数可以立即让音频队列静音。移除之前调度过的缓冲区,并且重置所有解码器和DSP状态

    你可以在同步或异步模式下使用AudioQueueStop函数:

同步立刻停止,不考虑之前缓冲的音频数据

异步在所有已入队的缓冲区播放或录制完毕之后再停止

参照Audio Queue Services Reference来获取所有这些函数的完整描述,也包含了更多关于同步和异步停止音频队列的信息

音频队列参数(Audio Queue Parameters)

音频队列有一个称作参数(parameters)的可调整设置。每个参数都有一个枚举值作为它的键,一个浮点数作为它的值。参数一般于播放,不用于录制。

在Mac OS X10.5中,只有一个音频队列参数就是播放增益。可以通过使用kAudioQueueParam_Volume常量来获取或设置它的值,它的有效范围在0.0(静音)到1.0(单位增益)

你的应用程序可以通过两种方法来设置音频队列参数:

对于每一个音频队列,使用AudioQueueSetParameter函数,这可以让你直接改变音频队列的设置,这个改变是立刻生效的

对于每一个音频队列缓冲区,调用AudioQueueEnqueueBufferWithParameters函数。这可以让你在将音频队列缓冲区入队的时候设置音频队列设置。这种改变只会在播放这个音频队列缓冲区的时候生效。

这两种情况下,音频队列的参数设置会一直保留到你改变它们为止。

你可以通过调用AudioQueueGetParameter函数来获取音频队列当前的参数。参考Audio Queue Services Reference来获得这个函数的完整描述和获取和设置参数值的方法


转载自:CSDN地址

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

推荐阅读更多精彩内容