iOS第三方音頻框架TheAmazingAudioEngine使用及音效實現(xiàn)介紹

2017年3月8日更新:

TheAmazingAudioEngine這個Framework帽借,作者Michael由于工作和生活(要當?shù)耍┑仍颍呀?jīng)很少更新、維護(seldomly receive updates)宋下。作者建議使用AudioKit(暫時沒有用過)造挽。所以各位客官,自行甄別是否使用褐荷。具體詳見勾效。

另外,之前有部分朋友發(fā)來簡信交流提問叛甫,因為一直在忙层宫,沒有一一回復,非常抱歉其监。不過萌腿,我建議提問的朋友,把你們具體遇到的問題抖苦,表述清楚毁菱,減少溝通成本,我也方便回復睛约。郵箱比較常用:aeq2005@163.com鼎俘,謝謝大家。


本文適讀對象:

  • 第一次用TheAmazingAudioEngine實現(xiàn)音效的讀者辩涝。
  • 第一次用TheAmazingAudioEngine實現(xiàn)音頻播放贸伐、錄制的讀者。
  • 想了解iOS音頻開發(fā)框架概況的讀者怔揩。

概述

TheAmazingAudioEngineMichael Tyson開源的iOS第三方音頻框架捉邢。很多音頻類APP應用這個框架作開發(fā)脯丝。

應用這個框架,可以比較方便地實現(xiàn)iOS音頻開發(fā)中的各種音效的實現(xiàn)伏伐。

iOS開發(fā)中的音頻框架

開始之前宠进,制作了這張圖,或許可以更清楚地了解iOS開發(fā)中各種音頻框架以及其結(jié)構(gòu)關(guān)系藐翎。(基于官方文檔 Using Audio 及objc中國 音頻API一覽 一文整理材蹬。如有謬誤,請斧正吝镣,謝謝堤器。)

iOS下各種音頻框架
iOS下各種音頻框架

TheAmazingAudioEngine就是基于AudioUnit框架、AudioToolBox框架末贾、AVFoundation框架的封裝闸溃,使其更方便使用。

音頻的播放

這部分和官方AVAudioPalyer以及AVAudioEngine都比較類似拱撵,拿到文件路徑辉川、或者音頻buffer,調(diào)用相關(guān)方法播放即可拴测,這里舉例文件的播放乓旗。
具體步驟:

  • 創(chuàng)建AEAudioController對象;
  • 拿到音頻的路徑(一個NSURL對象)集索;
  • 根據(jù)音頻路徑創(chuàng)建AEAudioFilePlayer對象寸齐;
  • 通過AEAudioController的addChannels:方法將AEAudioFilePlayer對象add到AEAudioController對象中即可。
    范例如下:
#pragma mark - 音頻播放
- (void)playNewSongCH1:(NSURL *)songURL {
    if (_selectedSongCH1Player) {
        [_audioController removeChannels:@[_selectedSongCH1Player]];
        _selectedSongCH1Player = nil;
    }
    
    // 創(chuàng)建AEAudioFilePlayer對象
    _selectedSongCH1Player = [[AEAudioFilePlayer alloc] initWithURL:songURL error:nil];
    
    // 進行播放
    [_audioController addChannels:@[_selectedSongCH1Player]];
}

關(guān)于音頻文件路徑的獲取抄谐,如果是直接拖進Xcode的文件,利用文件名及后綴即可創(chuàng)建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
        // 備注:這里已經(jīng)將播放歌曲的方法playNewSongCH1:封裝到自定義的engine類中
        [[HNMCManager shareManager].engine playNewSongCH1:mediaItemCollection.representativeItem.assetURL];
    } 
    else {
        [[HNMCManager shareManager].engine playNewSongCH2:mediaItemCollection.representativeItem.assetURL];
    }
    
    [self dismissViewControllerAnimated:YES completion:nil];
}

音頻的錄制

普通錄制(錄完再播)

步驟:

  • 創(chuàng)建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];
    
    // 如果文件不存在,結(jié)束
    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;
    }
    
    // 播放結(jié)束后發(fā)送一個播放結(jié)束通告(可選步驟)
    __weak HNAudioEngine *weakSelf = self;
    _ch1RecorderPlayer.completionBlock = ^{
        weakSelf.ch1RecorderPlayer = nil;
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationPlayRecordCH1Completed object:nil];
    };
    
    // 進行播放
    [_audioController addChannels:@[_ch1RecorderPlayer]];
}

邊錄邊播

利用TheAmazingAudioEngine中的AEPlaythroughChannel對象祠锣,可以方便地實現(xiàn)邊錄邊播酷窥。應用場景,想象一下:可以將手機連上音箱伴网,手機就變成一個擴音器了(當然蓬推,應該還有很多噪音、回響之類要處理的)澡腾。

代碼比較簡單:

#pragma mark 同步錄播(邊錄邊播)相關(guān)
- (void)setupCH1playthroughChannelBeginRecording {
    // 實例化AEPlaythroughChannel對象
    _ch1playthroughChannel = [[AEPlaythroughChannel alloc] init];
    
    // 利用addInputReceiver:方法add到AEAudioController對象中
    [_audioController addInputReceiver:_ch1playthroughChannel];
    
    // 利用addChannels:方法add到AEAudioController對象中
    // 我理解:上一行是為了錄制,這一行是為了播放
    [_audioController addChannels:@[_ch1playthroughChannel]];
}

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

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

音效的實現(xiàn)

所有音效都是基于AEAudioUnitFilter類實現(xiàn)的沸伏。

TheAmazingAudioEngine上的音效比蘋果官方的AVAudioEngine豐富且容易實現(xiàn)糕珊。

總的步驟:

  • 創(chuàng)建AEAudioUnitFilter或其子類對象
  • 用AEAudioController的addFilter:方法將Filter對象add到AEAudioController對象中
  • 設(shè)置相關(guān)屬性值,實現(xiàn)音效的控制

舉例:

實現(xiàn)高通音效

該框架有現(xiàn)成的高通音效類:

#pragma mark 高通音效
- (void)setupFilterHighPass:(double)cutoffFrequency {
    // 創(chuàng)建并添加AEAudioUnitFilter實例
    [self addHighpassFilter];
    
    // 設(shè)置相關(guān)屬性值毅糟,達到音效的控制
    _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];
        }
    }
}

實現(xiàn)EQ調(diào)整

因為本來對音頻相關(guān)領(lǐng)域的概念红选、知識不太了解,實現(xiàn)EQ調(diào)整還頗費了一番周折姆另。需要實現(xiàn)的EQ調(diào)整類似下圖:

要實現(xiàn)10段EQ的音效調(diào)整
要實現(xiàn)10段EQ的音效調(diào)整

可以通過AEParametricEqFilter類實現(xiàn)喇肋,該類也是AEAudioUnitFilter的子類,要實現(xiàn)10段EQ值的調(diào)整迹辐,就要創(chuàng)建10個AEParametricEqFilter對象蝶防,給centerFrequency屬性賦值20Hz-20000Hz之間的值(取決于你要調(diào)整哪個頻率的聲音)。而具體音效調(diào)整右核,則是調(diào)整增益值(通過gain屬性)慧脱,值范圍:-20dB to 20dB。

#pragma mark EQ音效
// 創(chuàng)建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: {
            // 設(shè)置需要調(diào)整的頻率贺喝,并將傳入的增益值gain賦值給gain屬性菱鸥,達到音效調(diào)整效果
            [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框架進行音頻播放、錄制躏鱼、音效實現(xiàn)的一次簡單實踐分享氮采。

當然,這個框架能做的事情還有很多染苛,有時間的朋友可以繼續(xù)發(fā)掘鹊漠。

尊重勞動成果,轉(zhuǎn)載請注明出處茶行,謝謝躯概。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市畔师,隨后出現(xiàn)的幾起案子娶靡,更是在濱河造成了極大的恐慌,老刑警劉巖看锉,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姿锭,死亡現(xiàn)場離奇詭異,居然都是意外死亡伯铣,警方通過查閱死者的電腦和手機呻此,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腔寡,“玉大人焚鲜,你說我怎么就攤上這事。” “怎么了恃泪?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵郑兴,是天一觀的道長。 經(jīng)常有香客問我贝乎,道長情连,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任览效,我火速辦了婚禮却舀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锤灿。我一直安慰自己挽拔,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布但校。 她就那樣靜靜地躺著螃诅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪状囱。 梳的紋絲不亂的頭發(fā)上术裸,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音亭枷,去河邊找鬼袭艺。 笑死,一個胖子當著我的面吹牛叨粘,可吹牛的內(nèi)容都是我干的猾编。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼升敲,長吁一口氣:“原來是場噩夢啊……” “哼答倡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驴党,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤苇羡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鼻弧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡锦茁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年攘轩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片码俩。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡度帮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笨篷,我是刑警寧澤瞳秽,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站率翅,受9級特大地震影響练俐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冕臭,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一腺晾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辜贵,春花似錦悯蝉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厚棵,卻和暖如春蕉世,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窟感。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工讨彼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柿祈。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓哈误,卻偏偏與公主長得像,于是被迫代替她去往敵國和親躏嚎。 傳聞我的和親對象是個殘疾皇子蜜自,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容