音视频开发:OpenGL + OpenGL ES + Metal 系列文章汇总
在上文已经详细讲解了Audio Unit框架的原理和设计模式,本文将开始分析如何构建一个APP
1. 构建过程认识
构建步骤:
- 配置音频会话
- 指定音频单元
- 创建音频处理graph,然后获取音频单元
- 配置音频单元
- 连接音频单元节点
- 提供用户界面
- 初始化然后启动音频处理graph
2. 配置音频会话
音频会话的特性在很大程度上决定了app的音频功能以及与系统其它部分的交互性。
1、指定要在app中使用的采样率:
self.graphSampleRate = 44100.0; // Hertz
2、设置首选采样率为硬件采样率:
音频会话对象请求系统使用您的首选采样率作为设备硬件的采样率。这里的目的是避免硬件和app之间的采样率转换。这可以最大程度的提高cpu的性能和音质,并最大限度的减少电池消耗。
NSError *audioSessionError = nil;
AVAudioSession *mySession = [AVAudioSession sharedInstance]; // 1
[mySession setPreferredHardwareSampleRate: graphSampleRate // 2
error: &audioSessionError];
[mySession setCategory: AVAudioSessionCategoryPlayAndRecord // 3
error: &audioSessionError];
[mySession setActive: YES // 4
error: &audioSessionError];
self.graphSampleRate = [mySession currentHardwareSampleRate]; // 5
代码解释:
- 获取app 的单例音频会话对象的引用
- 请求硬件的采样率
- 请求我们想要的音频会话category。这里是录制和播放
- 激活音频会话
- 音频会话激活后,根据系统提供的实际采样率更新自己的采样率变量。
3、音频硬件I/O 缓冲区持续时间
self.ioBufferDuration = 0.005;
[mySession setPreferredIOBufferDuration: ioBufferDuration
error: &audioSessionError];
说明:
- 在44.1KHZ采样率下,模式持续时间约为23ms。相当于1024个样本的slice大小
- 如果app的I/O 延迟至关重要,那么我们需要请求较小的持续时间,最低月为0.005毫秒
3. 指定音频单元
在运行时,音频会话配置代码后,其实app还是没有获取音频单元。
我们可以使用AudioComponentDescription指定音频单元。
获取到音频单元说明符,然后根据我们选择的模式构建音频处理graph。
AudioComponentDescription ioUnitDescription;
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
ioUnitDescription.componentFlagsMask = 0;
4. 创建音频处理graph,然后获取音频单元
AUGraph processingGraph;
NewAUGraph (&processingGraph);
AUNode ioNode;
AUNode mixerNode;
AUGraphAddNode (processingGraph, &ioUnitDesc, &ioNode);
AUGraphAddNode (processingGraph, &mixerDesc, &mixerNode)
步骤:
- 实例化AUGraph对象。该对象代表音频处理grahp
- 实例化一个或者多个AUNode 对象。每个类型代表grahp中的音频单元
- 将 AUNode 加入到 AUGraph中
- 打开图形并实例化音频单元
- 获取对音频单元的引用
AUGraphAddNode 函数调用使用音频单元说明符ioUnitDesc和mixerDesc。此时,graph 将被实例化,并拥有app中使用的结点。
打开graph 并实例化音频单元使用AUGraphOpen。
AUGraphOpen (processingGraph);
通过AUGraphNodeInfo函数获取对音频单元实例的引用
AudioUnit ioUnit;
AudioUnit mixerUnit;
AUGraphNodeInfo (processingGraph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo (processingGraph, mixerNode, NULL, &mixerUnit)
5. 配置音频单元
每个ios 音频单元都需要自己的配置。
- 默认情况下,远程I/O 单元已启用输出但是禁止输入。如果想app同时执行I/O 或者仅仅使用输入,那么必须要重新配置I/O 单元。
- 除了远程I/O 和语音处理I/O 单元外,所有的ios音频单元都需要配置其kAudioUnitProperty_MaximumFramesPerSlice属性。该属性确保音频单元准好相应于渲染调用产生足够数量的音频数据帧。
- 所有的音频单元都需要再输入和输出或者两者上定义其音频流格式。
具体可以看Audio Unit框架(一)框架认识和使用
写入并附加渲染回调函数:
对于采用渲染回调函数的设计模式,我们必须编写这些函数,然后将他们附加到正确的点上。
当音频不流动,我们可以使用音频单元API 立即附加渲染回调如下
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = &renderCallback;
callbackStruct.inputProcRefCon = soundStructArray;
AudioUnitSetProperty (
myIOUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0, // output element
&callbackStruct,
sizeof (callbackStruct)
);
通过使用音频处理graph API ,我们可以以现场安全的方式附加渲染回调,即使在音频流动时候也可以这样做。
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = &renderCallback;
callbackStruct.inputProcRefCon = soundStructArray;
AUGraphSetNodeInputCallback (
processingGraph,
myIONode,
0, // output element
&callbackStruct
);
// ... some time later
Boolean graphUpdated;
AUGraphUpdate (processingGraph, &graphUpdated);
6. 连接音频单元节点
在大多数情况下,使用音频处理graph API中的AUGraphConnectNodeInput和AUGraphDisconnectNodeInput函数建立或断开音频之间的连接是最好和最容易的。
这些函数是线程安全的,可以避免显示定义连接的编码开销,因为在不使用graph的时候必须这么做。
下列代码显示如何处理使用音频处理grahp API 混合器结点的输出连接到I/O 单元输出元件的输入
AudioUnitElement mixerUnitOutputBus = 0;
AudioUnitElement ioUnitOutputElement = 0;
AUGraphConnectNodeInput (
processingGraph,
mixerNode, // source node
mixerUnitOutputBus, // source node bus
iONode, // destination node
ioUnitOutputElement // desinatation node element
);
我们也可以使用音频单元属性机制直接建立和断开音频单元之间的连接。我们使用AudioUnitSetProperty函数的kAudioUnitProperty_MakeConnection属性,
如下,该方法要求我们为每个连接定义AudioUnitConnection结构以用做其属性值。。
AudioUnitElement mixerUnitOutputBus = 0;
AudioUnitElement ioUnitOutputElement = 0;
AudioUnitConnection mixerOutToIoUnitIn;
mixerOutToIoUnitIn.sourceAudioUnit = mixerUnitInstance;
mixerOutToIoUnitIn.sourceOutputNumber = mixerUnitOutputBus;
mixerOutToIoUnitIn.destInputNumber = ioUnitOutputElement;
AudioUnitSetProperty (
ioUnitInstance, // connection destination
kAudioUnitProperty_MakeConnection, // property key
kAudioUnitScope_Input, // destination scope
ioUnitOutputElement, // destination element
&mixerOutToIoUnitIn, // connection definition
sizeof (mixerOutToIoUnitIn)
);
7. 提供用户界面
到这里,我们构建的app已经完成构建和配置。
在许多情况下,我们需要提供一个用户界面,让用户微调音频行为。
我们可以定制用户界面以允许用户调整特定的音频单元参数,并在某些特使情况下调整音频单元属性。
8. 初始化然后启动音频处理graph
在开始音频流之前,我们必须通过调用AUGraphInitialize函数来初始化音频graph
OSStatus result = AUGraphInitialize (processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart (processingGraph);
// Some time later
AUGraphStop (processingGraph);
说明:
- 通过为每个音频单独的调用AudioUnitInitialize函数来初始化graph所拥有的音频单元
- 验证graph的连接和音频流数据格式
- 在音频单元连接上传播流格式