iOS第三方音频框架TheAmazingAudioEngine使用及音效实现介绍

2017年3月8日更新:

TheAmazingAudioEngine这个Framework,作者Michael由于工作和生活(要当爹了)等原因,已经很少更新、维护(seldomly receive updates)。作者建议使用AudioKit(暂时没有用过)。所以各位客官,自行甄别是否使用。具体详见

另外,之前有部分朋友发来简信交流提问,因为一直在忙,没有一一回复,非常抱歉。不过,我建议提问的朋友,把你们具体遇到的问题,表述清楚,减少沟通成本,我也方便回复。邮箱比较常用:aeq2005@163.com,谢谢大家。


本文适读对象:

  • 第一次用TheAmazingAudioEngine实现音效的读者。
  • 第一次用TheAmazingAudioEngine实现音频播放、录制的读者。
  • 想了解iOS音频开发框架概况的读者。

概述

TheAmazingAudioEngineMichael Tyson开源的iOS第三方音频框架。很多音频类APP应用这个框架作开发。

应用这个框架,可以比较方便地实现iOS音频开发中的各种音效的实现。

iOS开发中的音频框架

开始之前,制作了这张图,或许可以更清楚地了解iOS开发中各种音频框架以及其结构关系。(基于官方文档 Using Audio 及objc中国 音频API一览 一文整理。如有谬误,请斧正,谢谢。)

iOS下各种音频框架
iOS下各种音频框架

TheAmazingAudioEngine就是基于AudioUnit框架、AudioToolBox框架、AVFoundation框架的封装,使其更方便使用。

音频的播放

这部分和官方AVAudioPalyer以及AVAudioEngine都比较类似,拿到文件路径、或者音频buffer,调用相关方法播放即可,这里举例文件的播放。
具体步骤:

  • 创建AEAudioController对象;
  • 拿到音频的路径(一个NSURL对象);
  • 根据音频路径创建AEAudioFilePlayer对象;
  • 通过AEAudioController的addChannels:方法将AEAudioFilePlayer对象add到AEAudioController对象中即可。
    范例如下:
#pragma mark - 音频播放
- (void)playNewSongCH1:(NSURL *)songURL {
    if (_selectedSongCH1Player) {
        [_audioController removeChannels:@[_selectedSongCH1Player]];
        _selectedSongCH1Player = nil;
    }
    
    // 创建AEAudioFilePlayer对象
    _selectedSongCH1Player = [[AEAudioFilePlayer alloc] initWithURL:songURL error:nil];
    
    // 进行播放
    [_audioController addChannels:@[_selectedSongCH1Player]];
}

关于音频文件路径的获取,如果是直接拖进Xcode的文件,利用文件名及后缀即可创建NSURL对象,如下:

// 歌曲名和后缀名
static NSString *audioFileName   = @"leftRightTest";
static NSString *audioFileFormat = @"mp3";

NSURL *songURL = [[NSBundle mainBundle] URLForResource:audioFileName
                                         withExtension:audioFileFormat];

如果是想拿手机中的歌曲,则通过MPMediaPickerController的委托方法mediaPicker:didPickMediaItems:方法获得,如下:

#pragma mark - MPMediaPickerControllerDelegate
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {

    // 我这里要播放两首歌,所以有两个MPMediaPickerController对象,这里作一个判断
    if (mediaPicker == _mediaCH1PickerController) {
    
        // mediaItemCollection.representativeItem.assetURL这一句即可拿到使用者选择歌曲的URL
        // 备注:这里已经将播放歌曲的方法playNewSongCH1:封装到自定义的engine类中
        [[HNMCManager shareManager].engine playNewSongCH1:mediaItemCollection.representativeItem.assetURL];
    } 
    else {
        [[HNMCManager shareManager].engine playNewSongCH2:mediaItemCollection.representativeItem.assetURL];
    }
    
    [self dismissViewControllerAnimated:YES completion:nil];
}

音频的录制

普通录制(录完再播)

步骤:

  • 创建AERecorder对象;
  • 获取录音文件的保存路径;
  • 通过AEAudioController的addInputReceiver:方法(录制麦克风的声音)或addOutputReceiver:方法(录制手机喇叭的声音)将AERecorder对象add到AEAudioController对象中。

范例:

// 保存的录音文件名字
static NSString *ch1RecorderFileName = @"ch1Recording.m4a";

#pragma mark - 开始录音
- (void)setupCH1RecorderBeginRecording {
    // 实例化AERecorder对象
    _ch1Recorder = [[AERecorder alloc] initWithAudioController:_audioController];
    
    // 获取录制后文件存放的路径
    NSString *filePath = [self getFilePathWithFileName:ch1RecorderFileName];
    
    NSError *error = nil;
    if (![_ch1Recorder beginRecordingToFileAtPath:filePath fileType:kAudioFileM4AType error:&error]) {
        return;
    }
    
    // 同时录制输入及输出通道的声音(即既录人声,也录手机播放的声音)
    [_audioController addInputReceiver:_ch1Recorder];
    [_audioController addOutputReceiver:_ch1Recorder];
}

#pragma mark Helper Method
- (NSString *)getFilePathWithFileName:(NSString *)fileName {
    NSString *documentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *filePath = [documentsFolder stringByAppendingPathComponent:fileName];
    return filePath;
}

#pragma mark - 停止录音
- (void)stopCH1Recording {
    if (_ch1Recorder) {
        [_ch1Recorder finishRecording];
        [_audioController removeInputReceiver:_ch1Recorder];
        [_audioController removeOutputReceiver:_ch1Recorder];
        _ch1Recorder = nil;
    }
}

#pragma mark - 播放录音
- (void)playRecordCH1 {
    // 通过文件名拿到文件路径
    NSString *filePath = [self getFilePathWithFileName:ch1RecorderFileName];
    
    // 如果文件不存在,结束
    if ( ![[NSFileManager defaultManager] fileExistsAtPath:filePath] ) {
        return;
    }
    
    NSError *error = nil;
    
    // 利用AEAudioFilePlayer对象进行播放
    _ch1RecorderPlayer = [[AEAudioFilePlayer alloc] initWithURL:[NSURL fileURLWithPath:filePath] error:&error];
    if (!_ch1RecorderPlayer) {
        [[[UIAlertView alloc] initWithTitle:@"Error"
                                    message:[NSString stringWithFormat:@"Couldn't start playback: %@", [error localizedDescription]]
                                   delegate:nil
                          cancelButtonTitle:nil
                          otherButtonTitles:@"OK", nil] show];
        return;
    }
    
    // 播放结束后发送一个播放结束通告(可选步骤)
    __weak HNAudioEngine *weakSelf = self;
    _ch1RecorderPlayer.completionBlock = ^{
        weakSelf.ch1RecorderPlayer = nil;
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationPlayRecordCH1Completed object:nil];
    };
    
    // 进行播放
    [_audioController addChannels:@[_ch1RecorderPlayer]];
}

边录边播

利用TheAmazingAudioEngine中的AEPlaythroughChannel对象,可以方便地实现边录边播。应用场景,想象一下:可以将手机连上音箱,手机就变成一个扩音器了(当然,应该还有很多噪音、回响之类要处理的)。

代码比较简单:

#pragma mark 同步录播(边录边播)相关
- (void)setupCH1playthroughChannelBeginRecording {
    // 实例化AEPlaythroughChannel对象
    _ch1playthroughChannel = [[AEPlaythroughChannel alloc] init];
    
    // 利用addInputReceiver:方法add到AEAudioController对象中
    [_audioController addInputReceiver:_ch1playthroughChannel];
    
    // 利用addChannels:方法add到AEAudioController对象中
    // 我理解:上一行是为了录制,这一行是为了播放
    [_audioController addChannels:@[_ch1playthroughChannel]];
}

#pragma mark 设置音量
- (void)setupCH1playthroughChannelVolume:(double)volume {
    if (_ch1playthroughChannel) {
        _ch1playthroughChannel.volume = volume;
    }
}

#pragma mark 停止
- (void)stopCH1Playthrough {
    if (_ch1playthroughChannel) {
        [_audioController removeInputReceiver:_ch1playthroughChannel];
        [_audioController removeChannels:@[_ch1playthroughChannel]];
        _ch1playthroughChannel = nil;
    }
}

音效的实现

所有音效都是基于AEAudioUnitFilter类实现的。

TheAmazingAudioEngine上的音效比苹果官方的AVAudioEngine丰富且容易实现。

总的步骤:

  • 创建AEAudioUnitFilter或其子类对象
  • 用AEAudioController的addFilter:方法将Filter对象add到AEAudioController对象中
  • 设置相关属性值,实现音效的控制

举例:

实现高通音效

该框架有现成的高通音效类:

#pragma mark 高通音效
- (void)setupFilterHighPass:(double)cutoffFrequency {
    // 创建并添加AEAudioUnitFilter实例
    [self addHighpassFilter];
    
    // 设置相关属性值,达到音效的控制
    _highPassFilter.cutoffFrequency = cutoffFrequency;
}

- (void)addHighpassFilter {
     // _highPassFilter是AEHighPassFilter类的实例
    // AEHighPassFilter是AEAudioUnitFilter的子类
    if (!_highPassFilter) {
        _highPassFilter = [[AEHighPassFilter alloc] init];
        [_audioController addFilter:_highPassFilter];
    } else {
        if ( ![_audioController.filters containsObject:_highPassFilter] ) {
            [_audioController addFilter:_highPassFilter];
        }
    }
}

实现EQ调整

因为本来对音频相关领域的概念、知识不太了解,实现EQ调整还颇费了一番周折。需要实现的EQ调整类似下图:

要实现10段EQ的音效调整
要实现10段EQ的音效调整

可以通过AEParametricEqFilter类实现,该类也是AEAudioUnitFilter的子类,要实现10段EQ值的调整,就要创建10个AEParametricEqFilter对象,给centerFrequency属性赋值20Hz-20000Hz之间的值(取决于你要调整哪个频率的声音)。而具体音效调整,则是调整增益值(通过gain属性),值范围:-20dB to 20dB。

#pragma mark EQ音效
// 创建10个AEParametricEqFilter对象
- (void)creatEqFliters {
    _eq20HzFilter  = [[AEParametricEqFilter alloc] init];
    _eq50HzFilter  = [[AEParametricEqFilter alloc] init];
    _eq100HzFilter = [[AEParametricEqFilter alloc] init];
    _eq200HzFilter = [[AEParametricEqFilter alloc] init];
    _eq500HzFilter = [[AEParametricEqFilter alloc] init];
    _eq1kFilter    = [[AEParametricEqFilter alloc] init];
    _eq2kFilter    = [[AEParametricEqFilter alloc] init];
    _eq5kFilter    = [[AEParametricEqFilter alloc] init];
    _eq10kFilter   = [[AEParametricEqFilter alloc] init];
    _eq20kFilter   = [[AEParametricEqFilter alloc] init];
    _eqFilters     = @[_eq20HzFilter, _eq50HzFilter, _eq100HzFilter, _eq200HzFilter, _eq500HzFilter, _eq1kFilter, _eq2kFilter, _eq5kFilter, _eq10kFilter, _eq20kFilter];
}

- (void)setupFilterEq:(NSInteger)eqType value:(double)gain {
    switch (eqType) {
        case EQ_20Hz: {
            // 设置需要调整的频率,并将传入的增益值gain赋值给gain属性,达到音效调整效果
            [self setupEqFilter:_eq20HzFilter centerFrequency:20 gain:gain];
            break;
        }
        case EQ_50Hz: {
            [self setupEqFilter:_eq50HzFilter centerFrequency:50 gain:gain];
            break;
        }
        case EQ_100Hz: {
            [self setupEqFilter:_eq100HzFilter centerFrequency:100 gain:gain];
            break;
        }
        case EQ_200Hz: {
            [self setupEqFilter:_eq200HzFilter centerFrequency:200 gain:gain];
            break;
        }
        case EQ_500Hz: {
            [self setupEqFilter:_eq500HzFilter centerFrequency:500 gain:gain];
            break;
        }
        case EQ_1K: {
            [self setupEqFilter:_eq1kFilter centerFrequency:1000 gain:gain];
            break;
        }
        case EQ_2K: {
            [self setupEqFilter:_eq2kFilter centerFrequency:2000 gain:gain];
            break;
        }
        case EQ_5K: {
            [self setupEqFilter:_eq5kFilter centerFrequency:5000 gain:gain];
            break;
        }
        case EQ_10K: {
            [self setupEqFilter:_eq10kFilter centerFrequency:10000 gain:gain];
            break;
        }
        case EQ_20K: {
            [self setupEqFilter:_eq20kFilter centerFrequency:20000 gain:gain];
            break;
        }
    }
}

- (void)setupEqFilter:(AEParametricEqFilter *)eqFilter centerFrequency:(double)centerFrequency gain:(double)gain {
    if ( ![_audioController.filters containsObject:eqFilter] ) {
        for (AEParametricEqFilter *existEqFilter in _eqFilters) {
            if (eqFilter == existEqFilter) {
                [_audioController addFilter:eqFilter];
                break;
            }
        }
    }
    
    eqFilter.centerFrequency = centerFrequency;
    eqFilter.qFactor         = 1.0;
    eqFilter.gain            = gain;
}

以上就是应用TheAmazingAudioEngine框架进行音频播放、录制、音效实现的一次简单实践分享。

当然,这个框架能做的事情还有很多,有时间的朋友可以继续发掘。

尊重劳动成果,转载请注明出处,谢谢。

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

推荐阅读更多精彩内容