构建 Audio Unit 应用程序

news2024/12/26 11:51:56

构建 Audio Unit 应用程序

  • 构建 Audio Unit 应用程序
    • 从选择设计模式开始
      • I/O Pass Through
      • I/O Without a Render Callback Function
      • I/O with a Render Callback Function
      • Output-Only with a Render Callback Function
      • 其他设计模式
    • 构建应用程序
      • 配置 audio session
      • 指定 audio unit
      • 创建 audio processing graph
      • 配置 audio unit
      • 编写并绑定渲染回调函数
      • 连接 audio unit nodes
      • 提供用户界面
      • 初始化然后开启 audio processing graph
    • Debug 小技巧

构建 Audio Unit 应用程序

使用 Audio Unit 构建应用程序主要步骤是选择一个设计模式,然后编写代码来实现该模式。

从选择设计模式开始

在 iOS 应用程序中,Audio Unit 有很多基本设计模式。每种模式都有的共同特征:

  • 仅仅只有一个 I/O 单元。

  • 在整个 audio processing graph 中使用单一的音频流格式,尽管该格式可能存在变化。

  • 要求在特定位置设置流格式或部分流格式。

正确设置流格式对于建立音频数据流至关重要。这些模式大多依赖于音频单元连接提供的音频流格式从源到目的地的自动传播。合理利用这种传播的特性可以减少了编写和维护的代码量。同时,必须保证清楚了解每种模式需要如何进行设置。例如,必须在 iPod EQ 单元的输入和输出上设置完整的流格式。

在大多数情况下,设计模式都会使用 AUGraph。虽然可以在不使用 graph 的情况下实现这些模式中的任何一种,但使用 graph 可以简化代码并支持动态重新配置。

I/O Pass Through

I/O Pass Through 模式将传入的音频直接发送到输出硬件,没有处理音频数据的选项。虽然这没有什么实际价值,但基于这种模式构建 Audio Unit 应用程序是验证和巩固对 Audio Unit 概念的理解的好方法。图 2-1 说明了这种模式。

请添加图片描述

如图所示,音频输入硬件将其流格式强加在 Remote I/O unit 的 input element 的外向一侧。开发者需要指定要在此元素的内侧使用的格式, 音频单元内部将根据需要执行格式转换。为了避免不必要的采样率转换,在定义流格式时最好使用音频硬件的采样率。我们也不必指定 Remote I/O unit 的 output element 的流格式,因为流格式会通过连接从 input element 传给 output element。同理,传给硬件的流将会根据硬件需要完成一次自动转换。

I/O Without a Render Callback Function

可以在 Remote I/O unit 的元素之间添加一个或多个其他音频单元,例如,使用多通道混音器单元将传入的麦克风音频定位在立体声域中,或提供输出音量控制。在这个设计模式中,仍然没有渲染回调函数,如图 2-2 所示。这简化了模式,但限制了其效用。如果没有渲染回调函数,就无法直接操作音频。

请添加图片描述

在此模式中,可以像在 I/O Pass Through 模式中一样配置 Remote I/O unit 的两个元素。要设置多通道混音器单元,必须在混音器输出上设置流格式的采样率。混音器的输入流格式通过音频单元连接从 Remote I/O unit 的 input element 的输出中传播,自动建立。同样,Remote I/O unit 的 output element 输入范围的流格式由音频单元连接建立,这要归功于混音器单元输出的传播。

在这种模式的任何情况下,每当使用 I/O unit 以外的其他音频单元时,必须设置 kAudioUnitProperty_MaximumFramesPerSlice 属性。与 I/O Without a Render Callback Function 模式一样,无需配置任何音频数据缓冲区。

I/O with a Render Callback Function

通过在 Remote I/O unit 的输入和输出元素之间放置渲染回调函数,可以在传入音频到达输出硬件之前进行操作,例如:使用渲染回调函数来调整输出音量,还可以添加颤音、环调制、回声或其他效果。这种模式如图 2-3 所示。

请添加图片描述

如图所示,此模式使用 Remote I/O unit 的两个元素。将渲染回调函数附加到 output element 的input scope。当该元素需要另一组音频数据时,系统会触发回调。反过来,回调通过调用 Remote I/O unit 的 input element 的渲染回调函数来获得新的音频数据。

与其他 I/O 模式一样,您必须在 Remote I/O unit 上明确启用输入,因为默认情况下,输入是禁用的。而且无需配置任何音频数据缓冲区。

请注意,当使用渲染回调函数建立从一个音频单元到另一个音频单元的音频路径时,回调取代了音频单元连接。

Output-Only with a Render Callback Function

在最简单的情况下,这种模式涉及一个直接连接到 Remote I/O unit 的 output element 的 input scope 的渲染回调函数,如图 2-4 所示。

请添加图片描述

可以利用此模式完成复杂的音频结构。例如,将几个声音混合在一起,然后通过设备的输出硬件播放它们。图 2-5 显示了这种情况。

请添加图片描述

在图中,需要在 iPod EQ 的输入和输出上设置完整的流格式,多通道混音器只需要在其输出上设置正确的采样率。正如前面说到的,完整的音频流格式信息会在传递的过程中自动赋值。

对于每个多通道混合器单元输入,要设置完整的流格式。对于 input 0,需要显式设置它的流格式。对于 input 1,流格式由音频单元连接从 iPod EQ 单元的输出传播。一般来说,必须单独考虑每个音频单元的流格式需求。

其他设计模式

Audio Unit 还有另外两种主要设计模式:

  1. Input-only with a Render Callback Function:回调函数由应用程序调用,将音频数据传给 Remote I/O unit 的 input element。然而,在大多数情况下,对于这样的应用程序来说,更好的选择是使用输入音频队列对象(使用 AudioQueueNewInput 函数实例化的 AudioQueueRef 类型),使用音频队列对象提供了更大的灵活性,因为它的渲染回调功能不在实时线程上。

  2. Generic Output unit:离线音频处理。与 Remote I/O unit 不同,该音频单元无法连接到设备的音频硬件。当使用它向应用程序发送音频时,它仅仅取决于应用程序调用其渲染方法。

构建应用程序

无论选择哪种设计模式,构建 Audio Unit 应用程序的步骤基本相同:

  1. 配置 audio session。
  2. 指定 audio unit。
  3. 创建 audio processing graph,然后获取 audio unit。
  4. 配置 audio unit。
  5. 连接 audio unit nodes。
  6. 提供用户界面。
  7. 初始化然后开启 audio processing graph。

配置 audio session

构建 Audio Unit 应用程序的第一步与任何 iOS 音频应用程序的步骤相同:配置音频会话。音频会话是应用程序和硬件交互的中介,它的特征在很大程度上决定了应用程序的音频功能及其与系统其他部分的交互性。首先指定要在应用程序中使用的采样率,如下所示:

self.graphSampleRate = 44100.0; // 单位:赫兹

接下来,使用音频会话对象请求系统使用指定采样率作为设备硬件采样率。这里的目的是避免硬件和应用程序之间的采样率转换。这可以最大限度地提高 CPU 性能和音质,并最大限度地减少功耗。

NSError *audioSessionError = nil;
// 获取 audio session 单例对象
AVAudioSession *mySession = [AVAudioSession sharedInstance];
// 请求当前设备硬件使用的采样率
[mySession setPreferredHardwareSampleRate: graphSampleRate
                                    error: &audioSessionError];
// 设置音频分类,AVAudioSessionCategoryPlayAndRecord 指的是支持音频输入与输出
[mySession setCategory: AVAudioSessionCategoryPlayAndRecord
                                    error: &audioSessionError];
// 激活 audio session
[mySession setActive: YES error: &audioSessionError];
// 激活会话后更新采样率
self.graphSampleRate = [mySession currentHardwareSampleRate];

还可以配置其他硬件特征:音频硬件 I/O 缓冲区持续时间。采样率在 44.1kHz 的延时约为 23ms,相当于每次采集 1024 个采样点。如果 I/O 延迟在应用程序中至关重要,可以设置更短的 duration,低至约 0.005ms(相当于 256 个采样点),如下所示:

self.ioBufferDuration = 0.005;
[mySession setPreferredIOBufferDuration: ioBufferDuration
                                  error: &audioSessionError];

有关如何配置和使用音频会话对象的完整说明,请参阅:Audio Session Programming Guide。

指定 audio unit

在运行时,配置音频会话后,应用程序尚未获取音频单元。可以使用 AudioComponentDescription 结构来得到一个指定的音频单元,然后根据音频单元说明符和选择的设计模式构建一个 audio processing graph。

创建 audio processing graph

在此步骤中,将创建设计模式的骨架。具体来说,有以下几步:

  1. 实例化 AUGraph 对象,该实例代表 audio processing graph。
  2. 实例化一个或多个 AUNode 对象,每个对象代表 graph 中的一个音频单元。
  3. 添加 nodes 到 graph。
  4. 打开 graph 并且实例化 audio units。
  5. 获得 audio units 引用。

下面代码显示了如何对包含 Remote I/O unit 和多通道混合器单元的 graph 执行这些步骤。假设已经为每个音频单元定义了 AudioComponentDescription 结构。

AUGraph processingGraph;
NewAUGraph (&processingGraph);
 
AUNode ioNode;
AUNode mixerNode;
 // 已经为每个音频单元定义了 AudioComponentDescription 结构
AUGraphAddNode(processingGraph, &ioUnitDesc, &ioNode);
AUGraphAddNode(processingGraph, &mixerDesc, &mixerNode);

AUGraphAddNode 函数调用使用音频单元说明符 ioUnitDesc 和 mixerDesc。此时,图形被实例化,并拥有您将在应用程序中使用的节点。要打开 graph 并实例化音频单元,请调用 AUGraphOpen:

AUGraphOpen (processingGraph);

然后,通过 AUGraphNodeInfo 函数获取对音频单元实例的引用,如下所示:

AudioUnit ioUnit;
AudioUnit mixerUnit;
 
AUGraphNodeInfo(processingGraph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo(processingGraph, mixerNode, NULL, &mixerUnit);

ioUnit 和 mixerUnit 变量现在保存对图形中音频单元实例的引用,允许对它们进行配置和互连音频单元。

配置 audio unit

每个 iOS 音频单元都需要自己的配置,这里介绍一些常见的配置:

  • 默认情况下,Remote I/O unit 启用输出并禁用输入。如果同时执行 I/O,或仅使用输入,必须相应地重新配置 I/O unit。

  • 除 Remote I/O unit 和 Voice-Processing I/O unit 外,所有 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)
);

也可以使用 audio processing 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);

连接 audio unit nodes

在大多数情况下,最好使用 audio processing graph API 中的 AUGraphConnectNodeInput 和 AUGraphDisconnectNodeInput 函数建立或断开音频单元之间的连接。这些函数是线程安全的,避免了显式定义连接的编码开销。

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

提供用户界面

在许多情况下,需要提供一个用户界面,允许用户调整特定的音频单元参数,并在某些特殊情况下调整音频单元属性,比如:要更改 iPod EQ单元的活动均衡曲线,需要更改 kAudioUnitProperty_PresentPreset 属性的值。无论哪种情况,用户界面还应提供有关当前设置的视觉反馈。

初始化然后开启 audio processing graph

在开始音频流之前,必须通过调用 AUGraphInitialize 函数来初始化 audio processing graph。这个关键步骤:

  1. 通过为每个单元单独自动调用 AudioUnitInitialize 函数来初始化 graph 拥有的音频单元(如果要在不使用 graph 的情况下构建处理链,则必须依次显式初始化每个音频单元)。
  2. 验证 graph 的连接和音频数据流格式。
  3. 跨音频单元连接,传播流格式。
OSStatus result = AUGraphInitialize(processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart(processingGraph);
// Some time later
AUGraphStop(processingGraph);

Debug 小技巧

  • 通过函数返回值可以检查调用是否成功。

  • 注意函数调用之间的依赖性。例如,只有在成功初始化 audio processing graph 后,才能启动它。检查 AUGraphInitialize 的返回值。如果函数成功返回,就可以启动图表。如果失败了,利用 CAShow 函数将 graph 的状态打印到控制台。

  • 确保您将每个 AudioStreamBasicDescription 结构初始化为 0,如下所示:AudioStreamBasicDescription stereoStreamFormat = {0};。将 ASBD 的字段初始化为 0 可确保没有字段包含垃圾数据(注意:作为类声明中的实例变量,其字段会自动初始化为 0,无需自己初始化它们)。

  • 可以将 AudioStreamBasicDescription 结构的字段值打印到 Xcode 控制台,这在开发过程中非常有用。

    - (void) printASBD: (AudioStreamBasicDescription) asbd {
        char formatIDString[5];
        UInt32 formatID = CFSwapInt32HostToBig(asbd.mFormatID);
        bcopy(&formatID, formatIDString, 4);
        formatIDString[4] = '\0';
    
        NSLog (@"  Sample Rate:         %10.0f",  asbd.mSampleRate);
        NSLog (@"  Format ID:           %10s",    formatIDString);
        NSLog (@"  Format Flags:        %10X",    asbd.mFormatFlags);
        NSLog (@"  Bytes per Packet:    %10d",    asbd.mBytesPerPacket);
        NSLog (@"  Frames per Packet:   %10d",    asbd.mFramesPerPacket);
        NSLog (@"  Bytes per Frame:     %10d",    asbd.mBytesPerFrame);
        NSLog (@"  Channels per Frame:  %10d",    asbd.mChannelsPerFrame);
        NSLog (@"  Bits per Channel:    %10d",    asbd.mBitsPerChannel);
    }
    

    这种方法可以快速揭示 ASBD 中的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1876627.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分享一个导出数据到 Excel 的解决方案

前言 许多业务场景下需要处理和分析大量的数据,而 Excel 是广泛使用的文件格式,几乎所有人都能打开和查看 Excel 文件,因此将数据库中的原始数据处理后,导出到 Excel 是一个很常见的功能,对于数据管理、分析、备份、展…

汽车电子工程师入门系列——CAN 规范系列通读

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

【一生一芯】笔记

文章目录 一级目录二级目录三级目录缓存的验证 一级目录 二级目录 三级目录 缓存的验证

c++习题04-忙碌的工人

目录 一,问题 二,思路 1,图形 2,分析 3,伪代码 三,代码 一,问题 二,思路 1,图形 根据题目,绘制出来的图形如下👇 之后再绘制甲经过楼梯…

Hadoop 安装与伪分布的搭建

目录 1 SSH免密登录 1.1 修改主机名称 1.2 修改hosts文件 1.3 创建hadoop用户 1.4 生成密钥对免密登录 2 搭建hadoop环境与jdk环境 2.1 将下载好的压缩包进行解压 2.2 编写hadoop环境变量脚本文件 2.3 修改hadoop配置文件,指定jdk路径 2.4 查看环境是否搭建完成 3 …

文华财经通达信同花顺期货通盘立方博易大师主图指标公式源码

买线:EMA(C,2); 卖线:EMA(SLOPE(C,21)*20C,42); BU:CROSS(买线,卖线); SEL:CROSS(卖线,买线); STICKLINE1(买线>卖线,LOW,MIN(O,C),0.1,1),COLORRED; STICKLINE1(买线>卖线,MAX(O,C),HIGH,0.1,1),COLORRED; STICKLINE(买线>卖线,CLOSE,OPEN,8,1),COLORRED; STI…

解锁iCloud的全能潜力:从新手到专家的终极指南!

在今天这个数字化日益发达的时代,云服务已经成为我们生活中不可或缺的一部分。苹果公司的iCloud服务,作为一个集成的云服务平台,为用户提供了数据存储、备份、同步等多样化的功能。通过本文,我们将深入探讨如何高效利用iCloud&…

itext生成pdf文件demo示例

需求 在PDF文件中植入一些信息(pdf模版) 制作模版 可以看到下面红色箭头标注位置,这都是我们需要动态写入数据的表单域,可以使用wps等工具来制作 点击编辑表单,可以给对应空间添加表单域,表单域名称是ke…

【React】代码简化与拓展安装

安装如下拓展: 只需敲击rcc即可搭建框架

SPFA的拓展应用

spfa的拓展应用——负环 理论 01分数规划负环:一个环边权之和小于零 求负环的基本方法,基于SPFA: 都是基于抽屉原理,如果超过n条边,那一定有两个点相同,那就一定存在一个环 (1) 统计每个点入队次数,如…

linux 下neo4j的安装

一、neo4j简介 Neo4j 是一个高性能的 NoSQL 图形数据库,它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j 也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。 neo4j与jdk版本对应 neo4j的版本需要与jdk版本相适配,否则容易出现安装失…

个人搭建cppreference网站

近日,由于购买的腾讯云服务器要过期了,之前在服务器搭建的cppreference也要重新搭建,故写下此文章 cppreference的访问速度也慢,故自己WSL子系统简单搭键一下是个不错的选择 环境准备 首先,自己先安装Nginx,在网上找安装教程即可下载cppreference网站资源包:https://pan.baidu…

Redis基础教程(一):redis数据类型

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝&#x1f49…

【sqlmap命令学习及测试dvwa_SQL_Injection】

文章目录 1.sqlmap命令及 不同级别探索 能否注入命令option1.1 low等级1.2 Medium等级1. 3 High等级 2. 注入流程2.1 数据库2.2 指定数据库表名2.3 指定表的 字段名2.4 内容2.5 当前用户信息2.6 用户密码2.7 其他 1.sqlmap命令及 不同级别探索 能否注入 命令option sqlmap -u…

无刷直流电机(BLDCM)位置识别SVPWM控制

无刷直流电机,即BLDCM,在各个行业应用非常广泛。我们最熟悉的是在四轴飞行器中的应用,其中的电机基本都是BLDCM。除此之外,汽车电子、家用电器、航空航天、办公自动化、机器人等领域都有重要应用。 梯形波/方波无刷直流电机被称为…

【爆肝34万字】从零开始学Python第2天: 判断语句【入门到放弃】

目录 前言判断语句True、False简单使用作用 比较运算符引入比较运算符的分类比较运算符的结果示例代码总结 逻辑运算符引入逻辑运算符的简单使用逻辑运算符与比较运算符一起使用特殊情况下的逻辑运算符 if 判断语句引入基本使用案例演示案例补充随堂练习 else 判断子句引入else…

2024年度总结:不可错过的隧道IP网站评估推荐

随着网络技术的飞速发展,隧道IP服务成为了许多企业和个人在进行网络活动时的得力助手。作为专业的测评团队,我们经过一整年的深入研究和测试,为大家带来了三款备受瞩目的隧道IP网站推荐——品易HTTP、极光HTTP和一G代理。接下来,我…

AI产品经理面试

把优秀当习惯把优秀当习惯肯定不是口头说说,那有什么判断标准吗? 当我做完一件事儿的时候,我会看它有没有突破我的舒适圈、能不能惊艳到我自己。这就是我的判断标准。 在自我介绍和经历介绍时,面试者应该注重以下几个方面&#xf…

字节码编程ASM之插桩调用其他类的静态方法

写在前面 源码 。 本文看下通过ASM如何实现插桩调用其他类的静态方法。 1:编码 假定有如下的类: public class PayController {public void pay(int userId, int payAmount) {System.out.println("用户:" userId ", 调用…

GPIO和PIN

文章目录 1 GPIO和Pin1.1 GPIO和Pin基础概念1.2 GPIO输入模式1.3 GPIO输出模式1.4 GPIO的HAL库1.4.1 一些HAL库表示1.4.2 HAL库常用GPIO函数1.4.3 GPIO点亮led灯程序例子 1 GPIO和Pin 1.1 GPIO和Pin基础概念 ​ 单片机有很多的引脚,为了操控每一个引脚&#xff0c…