IOS Audio Queue Architecture

        本文是对关于IOS 音频队列介绍的翻译,只是起到一个入门的作用,为大家进一步研究音频队列的使用起到一个抛砖引玉的作用。之前公司要做一个播放器,由于一些原因我们没有使用苹果的视频播放类,而是决定自己来做音视频同步。客户端接受wifi发出的音视频数据流,由ffmpeg解复用后,音频由ios设备硬解码播放,视频由ffmpeg解码后形成图片由UIImageview显示。对于如何使用音频队列,网上已经有一个很好地例子,我觉得就算不使用音频队列,研究这个类也是很有益处的。https://github.com/mattgallagher/AudioStreamer(只需要看AudioStreamer.h 、AudioStreamer.m文件)。我自己也在研究,翻译这篇官方文档也是为了加深自己的理解,以及练习英文水平,如有不当之处,请高手指正。如要转载,请标明出处。



关于音频队列

本章我们学习关于音频队列的性能,体系结构以及内部工作方式。你将看到关于音频队列,音频队列缓存,以及用于录音或者回放的音频队列回调函数的介绍。你也会看到关于音频队列的状态和参数的内容。这章结束时,对于需要高效使用这项技术来说,你将从理论上有所了解。

 

 

什么是音频队列?

           在iOS或者Mac OS X系统中,audio queue是用于录音和回放音频的一个软件对象。它由AudioQueueRef 不透明的数据类型代表,在AudioQueue.h头文件中声明。

Audio queue做如下工作:

· 与音频硬件连接

· 管理内存

· 对于压缩的音频格式,如果需要,使用编解码器

· 调解录音或者回放。

 

    在你的应用程序中,你可以使用audio queue 和 Core Audio 接口,以及一部分相对小量的定制代码,来创建一个完全数字化的音频录音或者回放解决方案。

 

音频队列体系结构

  所有音频队列都有相同的常规结构,包含下列部分:

   ·一组audio queue buffers, 每一块缓存都是一些音频数据的临时存储仓库。

  ·一个bufferqueue,就是一个排序了的音频队列缓存列表。

  ·一个audio queue callback函数,这个需要你来写。

 

取决于音频队列是用于录音还是回放,audio queue体系结构是有所不同的。这些差异主要在音频队列如何与输入和输出连接,以及回调函数扮演什么样的角色。

 

音频队列用于录音

           一个录音音频队列,由AudioQueueNewInput函数创建,结构由图1-1显示。

    图1-1 一个录音音频队列

 

 

   录音队列的输入端最具代表性的就是连接外部音频硬件,比如一个麦克风。例如,在IOS设备中,音频来自由用户安装的麦克风或者耳机麦克风连接的设备。对于Mac OS X的默认实例,音频来自系统默认的音频输入设备,它由用户在系统偏好中设置。

          

   录音音频队列的输出端使用由你来写的回调函数。当录制到磁盘上时,回调函数将来自音频队列的新音频数据缓存写到一个音频文件中。然而,录制音频队列可以用于别的方面。例如,你也可以在一个实时音频分析器中使用。在这个例子中,你的回调函数可以直接提供音频数据给你的应用程序而不是写到磁盘上。

关于这个回调,如果你想了解更多,请看“The RecordingAudio Queue Callback Function”.

每个音频队列,不论是用于录音还是回放,都有一个或更多的音频队列缓存。这些缓存是在一个特殊的被叫做缓存队列的序列中排序。在图中,音频队列缓存的编号是根据它们被填装的顺序,这与它们被转交给回调的顺序是相同的。对于音频队列如何使用它的缓存,如果你想了解更多,请看“The Buffer Queueand Enqueuing”.

 

音频队列用于回放

回放音频队列(由AudioQueueNewOutput函数创建)的结构显示如下图1-2

图1-2一个回放音频队列


 

在一个回放音频队列中,回调是在输入端。回调负责从磁盘上(或者别的源上)获得音频数据并转交数据给音频队列。回放的回调也告诉音频队列,当没有更多数据播放时,停止播放。对于这个回调,你想要了解更多,请看“The PlaybackAudio Queue Callback Function”。

典型的回调音频队列输出端连接外部音频硬件,比如扬声器。在IOS设备中,音频到达用户选择的设备,如接收器或者耳机。在Mac OS X中默认音频到达系统默认的音频输出设备,这是由用户在系统偏好中设置的。

 

音频队列缓存

audio queue buffer 是一个类型为AudioQueueBuffer的数据结构,在头文件AudioQueue.h中声明。

typedef struct AudioQueueBuffer {

 

    const UInt32   mAudioDataBytesCapacity;

 

    void *const    mAudioData;

 

    UInt32         mAudioDataByteSize;

 

    void           *mUserData;

 

} AudioQueueBuffer;

 

typedef AudioQueueBuffer *AudioQueueBufferRef;

   mAudioData字段,在代码列表中高亮显示,指向缓存本身:一块暂时充当被播放或者被录制的音频数据容器的内存。其他字段的信息帮助一个音频队列管理缓存。

   一个音频队列可以使用任何个数的缓存。你的应用程序来指定其个数。一个典型的个数是3。这要求其中一个处于忙碌之中,也就是说,当另外一个正在填装新的音频数据时,这个正在向磁盘上写数据。如有必要,第三块缓存用于抵消如磁盘I/O延迟此类事件。图1-3描述了这些。

音频队列完成缓存的内存管理。

· 当调用AudioQueueAllocateBuffer函数时,一个音频队列分配了一块缓存。

· 当要释放一个音频队列时通过调用AudioQueueDispose函数,队列释放了缓存。

 

           把这些加入你的应用程序中,可以提高录制和回放特性的健壮性。也帮助优化资源的使用。

           想要一个完整的AudioQueueBuffer数据结构描述,请看Audio QueueServices Reference.

 

 

缓存队列和入队

缓存队列就像它们的名字是给予音频队列的,用来确保音频队列服务。你看到的缓存队列——一个排序了的缓存列表——在“Audio QueueArchitecture”中详述。这篇文章中你会了解到在录制或者回放期间,一个音频队列对象是如何与你的回调函数一起来管理缓存队列的。尤其是,你会了解入队,一个音频队列缓存加入一个缓存队列。不论你是实现录制还是回放,入队都是你的回调函数要完成的任务。

 

录音过程

当录音的时候,一个音频队列缓存被音频数据填满,音频数据获得自输入设备,例如一个麦克风。在缓存队列中的剩余缓存在当前缓存之后线性排列,轮流等待被音频数据填满。

音频队列转交填满的音频数据缓存给你的回调,按照它们获得的顺序。图1-3描绘了当使用一个音频队列时,录音是如何工作的。

  

图1-3 录制过程

 

在图1-3的步骤1中,录制开始。音频队列用获得的数据填满一个缓存。

步骤2中,第一个缓存被填满。音频队列调用回调,传递这个装满的缓存给音频队列(buffer1)。回调(步骤3)将缓存的内容写到一个音频文件中。与此同时,音频队列用刚获得的数据填满另一个缓存(buffer2)。

在步骤4中,回调将已经写入磁盘的缓存入队,放在线性队列中等待再次被填满。音频队列再次调用回调(步骤5),将下一个装满的缓存转交 (buffer2)。回调(步骤6)将这个缓存的内容写入音频文件。这个循环稳定的状态一直持续直到用户停止录制。

 

回放过程

当播放时,一个音频队列缓存正在发送数据给一个输出设备,例如扬声器。在缓存队列中的剩余缓存在当前缓存之后线性排列,等待轮流被播放。

音频队列转交音频数据的播放缓存给你的回调,按照它们被播放的顺序。回调读新的音频数据进入缓存,接着将它入队。图1-4描绘了当使用音频队列时,回调是如何工作的。

图1-4 回调过程


图1-4中的步骤1中,应用程序填装回调音频队列。应用程序为每个音频队列缓存调用回调一次,就装满这个缓存,并把它添加到缓存队列里。当你的应用程序调用AudioQueueStart函数(步骤2)时,填装确保回调可以立即开始。

在步骤3中,音频队列发送第一个缓存(buffer1)给输出。

一旦第一个缓存已经播放,回调音频队列进入一个稳定状态的循环。音频队列开始播放下一个缓存(buffer2,步骤4),并调用回调(步骤5),转交刚刚播放了的缓存(buffer1)给音频队列。回调(步骤6)用来自音频文件的数据填满缓存,接着为了入队回放。

控制回放过程

音频队列缓存总是以他们入队的顺序播放。然而,音频队列服务通过AudioQueueEnqueueBufferWithParameters函数给你提供一些对回放进程的控制。这个函数提供如下功能:

·  为一个缓存设置精确的回调时间。这允许你支持同步。

·  在一个音频队列缓存的开始或者结束修剪帧。这允许你删除开始或者结尾的静默。

·  设置回放增益的缓存颗粒度。(意思应该是通过设置缓存的大小,来改变回放的效果)

 

对于更多关于设置回放增益,请看”Audio Queue Parameters”。对于AudioQueueEnqueueBufferWithParameters函数更完整的描述,请看Audio Queue Services Reference.

 

音频队列回调函数

通常,你的应用程序工作的大部分在使用音频队列服务,这些服务包含写一个音频队列回调函数。

在录制或者回放过程中,一个音频队列回调被拥有它的音频队列重复调用。两次调用之间的时间取决于音频队列缓存的容量。通常在半秒到几秒之间。

不论是为了录制还是回放,一个音频队列回调的责任是返回音频队列缓存给缓存队列。

回调通过调用AudioQueueEnqueueBuffer函数增加一个缓存到缓存队列的末尾。对于回放,你可以使用AudioQueueEnqueueBufferWithParameters函数来代替。如果需要更多的控制,详细内容请看”Controlling thePlayback Process.”

 

录制音频队列回调函数

这部分介绍回调,你应该为通常的录制音频到一个磁盘文件实例来写回调。此处是一个录制音频队列回调的原型,详细描述在AudioQueue.h头文件中。

AudioQueueInputCallback (

    void                               *inUserData,

    AudioQueueRef                      inAQ,

    AudioQueueBufferRef                inBuffer,

    const AudioTimeStamp               *inStartTime,

    UInt32                             inNumberPacketDescriptions,

    const AudioStreamPacketDescription *inPacketDescs

);

一个录制音频队列,调用你的回调函数,回调函数提供一切将一组音频数据写入音频文件的需要。

·   inUserData 通常是一个定制的结构,你创建它用于包含音频队列和它的缓存的状态信息,一个音频文件对象(类型为AudioFileID),文件的音频数据格式化信息,代表你将要写入的文件。

· inAQ 调用回调的音频队列。

· inBuffer 音频队列缓存,由音频队列来填充,包含你的回调需要写入磁盘的新数据。数据已经被格式化,根据你在定制的结构(传递进inUserData的参数)中指定的格式来格式化。更多关于这些内容的信息,请看“Using Codecs andAudio Data Formats”。

· inStartTime 在缓存中第一个采样的采样时间。对于录音,你的回调不使用这个参数。

· inNumberPacketDescriptions 在inPacketDescs参数中描述包的个数。如果你录音生成一个VBR(变比特率)格式,音频队列为这个参数提供一个值给你的回调,接着把它传给AudioFileWritePackets函数。CBR(常量比特率)格式不使用描述包。对于一个CBR格式的录音,音频队列要设置这个参数和inPacketDescs参数为NULL。

· inPacketDescs 是一组与在缓存中的采样相关联的描述包。同样,音频队列为这个参数提供值,如果音频数据是一个VBR格式,你的回调将这个值传给AudioFileWritePackets函数(在AudioFile.h头文件中)。

 

关于录音回调,需要更多信息,请看“Recording Audio”这个文档,还有Audio QueueServices Reference.

 

回放音频队列的回调函数

这部分介绍回调函数,你要为从一个磁盘文件播放音频的通用实例写一个回调函数。此处是一个回放音频队列回调的原型,在AudioQueue.h文件中描述:

AudioQueueOutputCallback (

 

    void                  *inUserData,

 

    AudioQueueRef         inAQ,

 

    AudioQueueBufferRef   inBuffer

 

);

一个回放音频队列,在调用你的回调函数时,提供回调所需要的,从音频文件读下一组的音频数据。

·     inUserData 通常是一个定制的结构。你创建它来包含音频队列和它的缓存的状态信息,一个音频文件对象(类型为AudioFileID)代表你将要写的文件(原文是写,我觉得应该是读),还有文件的音频格式化信息。

           在回放音频队列实例中,你的回调使用这个结构中一段区域记录当前包的索引。

· inAQ 是调用回调的音频队列

· inBuffer是一个音频队列缓存,音频队列可以获得,你的回调填满从文件中读取的将要被播放的下组数据。

 

如果你的应用程序正在回放VBR数据,回调需要为正在读取的音频数据获得包信息。通过调用AudioFileReadPackets函数来做到,AudioFileReadPackets在AudioFile.h文件中声明。回调接着把这个包信息放置在定制的数据结构中,以使回放音频队列可以获得。

更多关于回放回调信息,请看“Playing Audio”文档,还有Audio QueueServices Reference.

 

 

使用编解码器和音频数据格式

音频队列服务采用编解码器(音频数据的编码/解码组件)根据需要来在音频格式间转换。你的录制或者回放应用可以使用任何音频格式,因为有一个安装好的编解码器。你不需要写定制好的编码器来处理不同音频格式。特别是,你的回调不需要知道数据格式。

 

接下来你将看到这是如何工作的。每一个音频队列有一个音频数据格式,在一个AudioStreamBasicDescription结构中有描述。当你指定格式——在这个结构的mFormatID区域中——音频队列使用合适的编码器。你接着要指定采样率和通道个数,这就是所有的了。你将要在“Recording Audio”和“Playing Audio”看到设置音频数据格式的实例。

在图1-5中显示了录音音频队列利用一个安装了的编解码器。

图1-5在录音期间音频格式的转化

 

在图1-5中的第一步骤,你的应用程序告诉一个音频队列开始录音,同时告诉应用要使用的数据格式。步骤2中,音频队列使用一个编解码器,根据你指定的格式,获得新的音频数据并转化它。音频队列接着调用回调,转交给它包含合适的格式化音频数据的缓存。步骤3,你的回调将格式化的音频数据写在磁盘上。同样,你的回调不需要知道数据格式。

图1-6显示了一个回放音频队列使用一个安装的编解码器。


图1-6 回放期间的音频格式转化


图1-6的步骤1中,你的应用程序告诉一个音频队列开始播放,也告诉它将要播放的音频文件包含的数据格式。步骤2中音频队列调用你的回调,回调从音频文件中读数据。回调以初始的格式给音频队列转交数据。在步骤3中,音频队列使用合适的编解码器,

接着发送音频一直到目的地。

音频队列可以使用任何安装的编解码器,不论是本地给Mac OS X,还是由第三方提供。

指定一个编解码器来使用,你提供它的四个字符的编码器ID给一个音频队列的AudioStreamBasicDescription结构。在“Recording Audio”你将看到有关这些的一个例子。

Mac OS X包含一个很广的音频编解码器的范围,在CoreAudioTypes.h头文件中和Core Audio Data TypesReference文档中,以格式化ID枚举列出。你可以决定在系统中可以获得的编解码器,通过使用在Audio 工具包的AudioFormat.h 头文件中的接口。通过使用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 ServicesReference,包括更多关于音频队列的同步和异步停止。

 

 

音频队列参数

音频队列可调整的设置叫做参数。每一个参数有一个枚举常量作为它的关键字,一个浮点数作为它的值。参数通常用于回放,而不是录制。

在Mac OS X v10.5,这个唯一的可获得的音频队列参数是为了增益。通过使用kAudioQueueParam_Volume常量,这个参数的值被设置或者被取回,它有一个可获得的范围,0.0为静音,1.0为完整增益。

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

· 每个音频队列,使用AudioQueueSetParameter函数。这允许你为一个音频队列直接改变设置。这样的改变直接有效。

· 每个音频队列缓存,使用AudioQueueEnqueueBufferWithParameters函数。这允许你有效地指定音频队列设置,当你将他们入队时,由音频队列缓存执行。当音频队列缓存开始播放时,这样的改变开始起作用。

 

在这两个例子中,直到你改变它们,对于音频队列的参数设置依然有效。

你可以使用音频队列的当前参数值在任何时候,通过使用AudioQueueGetParameter函数

对于获得和设置参数值的函数的完整描述,请看Audio QueueServices Reference。

 

 

 


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值