Audio Unit框架(四)构建APP

音视频开发:OpenGL + OpenGL ES + Metal 系列文章汇总

在上文已经详细讲解了Audio Unit框架的原理和设计模式,本文将开始分析如何构建一个APP

1. 构建过程认识

构建步骤:

  1. 配置音频会话
  2. 指定音频单元
  3. 创建音频处理graph,然后获取音频单元
  4. 配置音频单元
  5. 连接音频单元节点
  6. 提供用户界面
  7. 初始化然后启动音频处理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

代码解释:

  1. 获取app 的单例音频会话对象的引用
  2. 请求硬件的采样率
  3. 请求我们想要的音频会话category。这里是录制和播放
  4. 激活音频会话
  5. 音频会话激活后,根据系统提供的实际采样率更新自己的采样率变量。

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)

步骤:

  1. 实例化AUGraph对象。该对象代表音频处理grahp
  2. 实例化一个或者多个AUNode 对象。每个类型代表grahp中的音频单元
  3. 将 AUNode 加入到 AUGraph中
  4. 打开图形并实例化音频单元
  5. 获取对音频单元的引用

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);

说明:

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

推荐阅读更多精彩内容