2017年3月8日更新:
TheAmazingAudioEngine這個Framework,作者Michael由于工作和生活(要當爹了)等原因,已經很少更新、維護(seldomly receive updates)。作者建議使用AudioKit(暫時沒有用過)。所以各位客官,自行甄別是否使用。具體詳見。
另外,之前有部分朋友發來簡信交流提問,因為一直在忙,沒有一一回復,非常抱歉。不過,我建議提問的朋友,把你們具體遇到的問題,表述清楚,減少溝通成本,我也方便回復。郵箱比較常用:aeq2005@163.com,謝謝大家。
本文適讀對象:
- 第一次用TheAmazingAudioEngine實現音效的讀者。
- 第一次用TheAmazingAudioEngine實現音頻播放、錄制的讀者。
- 想了解iOS音頻開發框架概況的讀者。
概述
TheAmazingAudioEngine是Michael Tyson開源的iOS第三方音頻框架。很多音頻類APP應用這個框架作開發。
應用這個框架,可以比較方便地實現iOS音頻開發中的各種音效的實現。
iOS開發中的音頻框架
開始之前,制作了這張圖,或許可以更清楚地了解iOS開發中各種音頻框架以及其結構關系。(基于官方文檔 Using Audio 及objc中國 音頻API一覽 一文整理。如有謬誤,請斧正,謝謝。)

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調整類似下圖:
可以通過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框架進行音頻播放、錄制、音效實現的一次簡單實踐分享。
當然,這個框架能做的事情還有很多,有時間的朋友可以繼續發掘。
尊重勞動成果,轉載請注明出處,謝謝。