iOS Audio Unit(二)

作者: MasonFu | 来源:发表于2016-07-19 15:45 被阅读3333次

在上一篇文章中介绍了iOS Audio Unit一些相关概念和基本用法,接下来通过阅读github上国外的一个开源的unit音频引擎框架源码,学习和理解Audio Unit技术在实际项目开发时可以有那些方式的应用场景,在代码结构和设计上有哪些考虑等等,加深自己对这块技术的理解,并且将其转化融入到自己的项目体系中去。我接触到的Audio方面的比较强大的开源库有AudioKitTheAmazingAudioEngineEZAudioDouAudioStreamer,这些框架的侧重点各有不同,这里以TheAmazingAudioEngine为例,进行详细讲解。

TheAmazingAudioEngine

该框架是一个能同时运用在iOS、MAC OS上音频类APP的比较成熟的音频技术框架,其构建基于Core Audio Remote IO system,具备良好的性能及和低延迟处理能力,与AudioKit开源库提供丰富的工具集的方式不同,AmazingAudioEngine提供了包含音频采集、音效处理、播放等一整套解决方案,通过它可以快速的搭建出音频创作类的集成度高的APP。如果对音频处理理解不够深入的话,或者需要更基础的可灵活搭建特定场景的音频技术方案的话,使用AudioKit会更适合些。

下图是TheAmazingAudioEngine开源框架的概括类图,后面会分别详细介绍下几个核心模块的设计思路,以及如何将这些技术应用到自己的设计方案中去。

AmazingAudioEngineDiagram.jpg

主要模块

AEBufferStack

该类是以栈的形式来存储和封装音频数据的缓冲池,在整个Engine流程里的各个环节之间交互的单元,例如向栈中压入缓冲采集音频数据;对当前存在的缓冲进行效果处理、解析、录制等;混合多路音频缓冲;输出缓冲到渲染;在所有处理结束后弹出释放缓冲。AEBufferStack相关结构定义如下:

const UInt32 AEBufferStackMaxFramesPerSlice = 4096;
static const int kDefaultPoolSize = 16;

typedef struct _AEBufferStackBufferLinkedList {
    void * buffer;
    struct _AEBufferStackBufferLinkedList * next;
} AEBufferStackPoolEntry;

typedef struct {
    void * bytes;
    AEBufferStackPoolEntry * free;
    AEBufferStackPoolEntry * used;
} AEBufferStackPool;

struct AEBufferStack {
    int                poolSize;
    int                maxChannelsPerBuffer;
    UInt32             frameCount;
    int                stackCount;
    AEBufferStackPool  audioPool;
    AEBufferStackPool  bufferListPool;
};

在AEBufferStack.h及.m文件中定义了许多操作Stack结构的helper方法,因为该BufferStatck是通过单链表结构实现,在压栈出栈等操作都会涉及到相应链接的节点操作,在外部使用基本只面对Stack中的AudioBufferList对象而不用考虑更多细节,该对象即AudioUnit中Render回调的基本单位。

AERenderer

该渲染类是整个引擎的核心类,主要负责驱动整个音频处理流程,通常位于音频生产和处理之间,简单说就是一个串连各种处理操作的驱动者,子渲染AESubRenderer也时常会在类似变速处理的场景下以中间处理流程的形式被使用。该类主要向外暴露AERenderLoopBlock回调,使用者在该回调内,对传递过来的AudioRenderContext中的音频数据施加处理,例如,创建一个AERenderer实例,并将其传入到AudioUnitOutput中,AudioUnitOutput运行后产生render callback回调到renderer提供的LoopBlock当中,你可以在该block中对即将输出的buffer进行Effect效果处理,回调结束后buffer被送到硬件播放。

AEAudioUnitOutput

该类是AEIOAudioUnit(真正封装AudioUnit的核心类)对外的封装类,与AERenderer结合,主要负责在Engine处理流程中音频数据的播放和采集,当然Engine框架中也提供了单独的AEAudioUnitInputModule来处理采集,因为在MAC OS与iOS上AudioUnit的设计方式不同(iOS平台上IO Unit的输入输出由单独的Unit提供,而在MAC上是两个独立的Unit提供)。

AEAudioFileOutput

该类封装了音频数据写入文件操作,与AERenderer结合,在写入文件之前可对音频数据进行效果处理,样例代码如下:

- (NSError *)createTestFile {
    AERenderer * renderer = [AERenderer new];
    
    __block NSError * error = nil;
    AEAudioFileOutput * output = [[AEAudioFileOutput alloc] initWithRenderer:renderer URL:self.fileURL type:AEAudioFileTypeAIFFInt16 sampleRate:44100.0 channelCount:1 error:&error];
    if ( !output ) {
        return error;
    }
    
    AEOscillatorModule * osc = [[AEOscillatorModule alloc] initWithRenderer:renderer];
    osc.frequency = 440.0;
    renderer.block = ^(const AERenderContext * context) {
        AEModuleProcess(osc, context);
        AERenderContextOutput(context, 1);
    };
    
    __block BOOL done = NO;
    [output runForDuration:kTestFileLength completionBlock:^(NSError * e){
        done = YES;
        error = e;
    }];
    while ( !done ) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    }
    [output finishWriting];
    return error;
}

AEModule

音频处理的基础单元,作为基类提供了统一的AEModuleProcessFunc处理方法地址,在render处理流程中通过调用AEModuleProcess(osc, context)传入具体module实例及音频buffer上下文进行同步音频处理,下面是其主要的派生类处理单元:

AEAudioUnitModule

继承自AEMoudle,音频基础单元与音效单元模块的基类,例如AEAudioFilePlayerModule、AEBandpassModule等都派生于此类,该基类封装了统一的AudioUnit创建、配置、处理入口(AEMoudle),在具体的处理模块中只需要指定其AudioUnitType及相关的配置参数即可。

AEMixerModule

混音处理单元,该类中持有普通处理模块(例如AEAudioFilePlayerModule)数组,在处理过程中会枚举其数组中的所有普通模块并且调用其processFunc函数,同时逐个混缩到StackBuffer中,并且可以逐个对音频单元进行音量及平衡处理。

AEAudioFilePlayerModule

音频播放类型的Unit模块,继承自AEModule,提供iOS平台所支持的全部格式的音频播放能力,主要集中在AEModuleProcessFunc,处理过程中,不断从音频文件中读取数据压入StackBuffer中,而且提供loop循环播放及开头结尾淡入淡出功能。

AEBandpassModule

音效处理模块之一,派生自AEAudioUnitModule,内部只定义了与Bandpass效果相关的参数设置和读取方法,其它音频处理模块与之类似。

AEManagedValue

封装对objective-c以及void*内存指针的操作和生命周期管理,通过pthread_mutex_t以及OSAtomic方式保证多线程访问安全性,以及在实时处理线程中避免加锁操作。

AERealtimeWatchdog

实时性看门狗,通过重载alloc、dealloc、socket send/recv等系统API调用,监听Audio Unit的工作线程AURemoteIO::IOThread中是否存在这些影响实时性的方法调用,并把这些调用打印出来,以alloc方法为例,下面是核心代码实现:

// Signatures for the functions we'll override
typedef void * (*malloc_t)(size_t);

// Overrides
void * malloc(size_t sz) {
    static malloc_t funcptr = NULL;
    if ( !funcptr ) funcptr = (malloc_t) dlsym(RTLD_NEXT, "malloc");
    if ( AERealtimeWatchdogIsOnRealtimeThread() ) AERealtimeWatchdogUnsafeActivityWarning("malloc");
    return funcptr(sz);
}

// print function
static void AERealtimeWatchdogUnsafeActivityWarning(const char * activity) {
#ifndef REPORT_EVERY_INFRACTION
static BOOL once = NO;
if ( !once ) {
    once = YES;
#endif
    
    printf("AERealtimeWatchdog: Caught unsafe %s on realtime thread. Put a breakpoint on %s to debug\n",
           activity, __FUNCTION__);
    
#ifndef REPORT_EVERY_INFRACTION
    }
#endif
}

// confirm the current thread is AURemote IO thread
BOOL AERealtimeWatchdogIsOnRealtimeThread(void);
BOOL AERealtimeWatchdogIsOnRealtimeThread(void) {
pthread_t thread = pthread_self();

char name[21] = {0};
if ( pthread_getname_np(thread, name, sizeof(name)) == 0 && !strcmp(name, "AURemoteIO::IOThread") ) {
    return YES;
}

return NO;

}

AEAudioBufferListUtilities

该工具集封装了针对AudioBufferList常用基础操作和Stack操作,例如初始化、拷贝、释放等。

AEDSPUtilties

该工具集封装了一些Accelerate.framework\vDSP.h中提供的对音频信号处理的算法,例如增益处理、平滑处理、混音等操作,接口以AudioBufferList暴露方便调用

AEMainThreadEndpoint

封装了AECircularBuffer与NSThread类,提供主线程与音频处理线程进行消息或者数据传递的机制,CircularBuffer作为message的载体,在MainThreadEndPoint中实现了NSThread的main方法,通过semophore同步处理事件,由于CircularBuffer内部采用原子操作,保证多线程操作安全性

TheAmazingAudioEngine样例流程

下面的样例代码主要完成了3个音频文件的实时播放及录音功能,中间先对file1音频进行了带通效果处理,然后将file2和file3音频先混音,然后进行了延时效果处理,最后将所有buffer数据混音输出到硬件播放,并且录制到指定URL文件中

 // Create our renderer and output
 AERenderer * renderer = [AERenderer new];
 self.output = [[AEAudioUnitOutput alloc] initWithRenderer:renderer];
 
 // Create the players
 AEAudioFilePlayerModule * file1 = [[AEAudioFilePlayerModule alloc] initWithRenderer:renderer URL:url1 error:NULL];
 AEAudioFilePlayerModule * file2 = [[AEAudioFilePlayerModule alloc] initWithRenderer:renderer URL:url2 error:NULL];
 AEAudioFilePlayerModule * file3 = [[AEAudioFilePlayerModule alloc] initWithRenderer:renderer URL:url3 error:NULL];
 
 // Create the filters
 AEBandpassModule * filter1 = [[AEBandpassModule alloc]  initWithRenderer:renderer];
 AEDelayModule * filter2 = [[AEDelayModule alloc] initWithRenderer:renderer];
 
 // Create the recorder
 AEAudioFileRecorderModule * recorder = [[AEAudioFileRecorderModule alloc] initWithRenderer:renderer URL:outputUrl error:NULL];


 renderer.block = ^(const AERenderContext * _Nonnull context)     {
 AEModuleProcess(file1, context);     // Run player (pushes 1)
 AEModuleProcess(filter1, context);   // Run filter (edits top buffer)

 AEModuleProcess(file2, context);     // Run player (pushes 1)
 AEModuleProcess(file3, context);     // Run player (pushes 1)
 AEBufferStackMix(context->stack, 2); // Mix top 2 buffers

 AEModuleProcess(filter2, context);   // Run filter (edits top buffer)

 AERenderContextOutput(context, 1);   // Put top buffer onto output
 AEModuleProcess(recorder, context);  // Run recorder (uses top buffer)
};

[self.output start:NULL];

一些体会

通过对上面这个TheAmazingAudioEngine框架的学习和技术点的概要梳理,发现能学习到很多东西,很多技术点也是第一次碰到,非常有趣,这篇文章并没有仔细深挖,毕竟每个点都可以展开写一篇文章了,如果你感兴趣,自己下载阅读下代码吧。

另外,在音频这块的技术领域,还是要细分到很多方面的,例如专业音频播放APP、专业创作类、VOIP电话类、大型游戏类APP等等,不同类型的产品对所需音频技术的侧重点不尽相同,例如TheAmazingAudioEngine就是偏音频创作类APP的架构设计。没有万能的随处可用的引擎框架和设计,作为开发人员,还是应该将平台音频技术不同的点都学习透彻,零活运用到目标场景中去。

相关文章

网友评论

  • Crazy_Init:TAAE有两个,第一个已经不维护了,简称TAAE1;第二个是TAAE2,部分维护,本文介绍的是TAAE2,望后来者悉知。
    源码在https://github.com/TheAmazingAudioEngine可找到
  • 177badd0da8a:刚刚接触ios开发, 有个问题想请教一下,如果是做相对专业一点的音频播放器,用哪个框架更合理一些呢?
  • cjy027:我以前跟师傅说音频好像很简单,视频挺麻烦,难怪他当时跟我说恰恰相反。。
  • __阳阳:TheAmazingAudioEngine这个框架怎么集成呢, 有没有相关教程, 我集成一直报错解决不了, 求大神帮帮忙!
  • __阳阳:我怎么没找到AEAudioUnitOutput这个类呢?
  • 诸子百家谁的天下:一开始接触,觉得iOS这个音频都可以开一个学科了,强大到不可思议!

本文标题:iOS Audio Unit(二)

本文链接:https://www.haomeiwen.com/subject/qbwmjttx.html