AVAudioEngine完成即時錄音與播放功能

AVAudioEngine完成即時錄音與播放功能

來源:502 AVAudioEngine in Practice

這次WWDC 2014上推出在iOS8上才有的AVAudioEngine,它將以往AUGraph實作上很煩雜的設定簡化,所以使用上可能對於AUGraph及Remote IO操作要有點了解操作上更得心應手,相關知識參考Audio Processing Graph(AUGraph)完成即時錄音與播放功能。接下來請務必將Xcode升級至6.0以上版本才能使用AVAudioEngine功能。

加入AVFoundation Framework

首先建立Project後確認AVFoundation Framework是否加入:

未加入請參考下面的圖示加入:

+新增framework,

輸入關鍵字AVFoundation

選擇Framework並加入

宣告AVAudioEngine

AVAudioEngine 功能非常多,此篇要實作出即時錄音與播放功能為原則將程式寫出來,與先前的文章Audio Processing Graph(AUGraph)完成即時錄音與播放功能比較差異,看看AVAudioEngine簡化過程有多少?

首先記得先引入標頭檔:

//-----------start-----------
#import <AVFoundation/AVFoundation.h>
//------------end------------

接下來宣告AVAudioEngine

//-----------start-----------
    AVAudioEngine *engine = [[AVAudioEngine alloc] init];
//------------end------------

上面程式完成宣告及初始化動作,往後整個過程都依靠AVAudioEngine提供的方法來完成。(它太強大了)

取得輸入節點

AVAudioEngine將每個裝置都化成一個節點,節點除了硬體的麥克風、喇叭之外,還有提供軟體的裝置,如:Delay、Mixer…等,所以取得輸入裝置執行方法inputNode就能取得:

//-----------start-----------
    AVAudioEngine *engine = [[AVAudioEngine alloc] init];
    AVAudioInputNode *input = [engine inputNode];
//------------end------------

連接節點

AVAudioEngine提供方法connect,讓我們像接音響的方式將許多節點串接起來產生效果,示意圖如下:

圖中需要另外介紹的方法是mainMixerNode,這是一個允許多輸入及單一輸出接口的節點,本身初始化時AVAudioEngine已經將mainMixerNode與輸出端連接,所以我們要將輸入節點與它連接後就能完成即時錄音與播放功能,接續之前的程式後增加輸入節點與mainMixerNode的連接,

//-----------start-----------
    AVAudioEngine *engine = [[AVAudioEngine alloc] init];
    AVAudioInputNode *input = [engine inputNode];
    [engine connect:input to:[engine mainMixerNode] format:[input inputFormatForBus:0]];
//------------end------------

上面程式中的format為輸入音訊格式,這裡使用的是以輸入裝置格式為主,如此一來輸入、輸出格式就能統一,接下來執行啟動方法後就能正常的聽到麥克風聲音完整的從喇叭播放。

啟動、停止

前面程式將相關準備都準備完成,接下來只需要啟動AVAudioEngine讓它開始工作:

//-----------start-----------
    AVAudioEngine *engine = [[AVAudioEngine alloc] init];
    AVAudioInputNode *input = [engine inputNode];
    [engine connect:input to:[engine mainMixerNode] format:[input inputFormatForBus:0]];
    NSError *error;
    [engine startAndReturnError:&error];
//------------end------------

沒錯,短短的這幾行程式就能取代先那篇文章Audio Processing Graph(AUGraph)完成即時錄音與播放功能的功能,是不是非常方便?

啟動後要將AVAudioEngine停止需要執行它的停止方法:

//-----------start-----------
[engine stop]
//------------end------------

此篇很簡略的完成功能,往後有其他深入的相關心得再另外介紹,AVAudioEngine只是AVFoundation framework新增的其中一個功能,可以參考官網提供的AV Foundation Framework Reference更清楚了解新增加與舊有的功能。

參考資料

AV Foundation Framework Reference

502 AVAudioEngine in Practice

  • FocusOn

    Danny您好,我曾在github上拜读过您的Audio Processing Graph(AUGraph)完成即时录音与播放功能的demo,我觉得您介绍得很通俗易懂,最近有看到您的这篇介绍AVAudioEngine的文章,也觉得相当不错。

    我在实践的过程中,碰到一个问题: 在接入外置蓝牙2.1EDR HFP输入设备和插入带麦克风的耳机的情况下,如何实现从外置蓝牙2.1EDR HFP输入,同时完成即时录音与播放功能 ? 好像在iOS的AVAudioEngine无法将input设置成BluetoothHFP?

    期待您的回复,谢谢!

  • FocusOn

    谢谢您的回复,我设置过AVAudioSessionCategoryOptions 为AVAudioSessionCategoryOptionAllowBluetooth,但程序启动和连接蓝牙HFP成功后,我点击play按钮,并没有声音传入耳机(我的环境是:外置蓝牙2.1EDR HFP输入设备和插入带麦克风的耳机的情况下),我代码如下:

    import “ViewController.h"

    import <AVFoundation/AVFoundation.h>

    @interface ViewController () { AVAudioEngine *engine; AVAudioSession *audioSession; } @end

    @implementation ViewController

    • (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib.

    }

    • (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }
    • (IBAction)play:(id)sender {

      NSError *error = nil; audioSession = [AVAudioSession sharedInstance]; if(![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&error]) { NSLog(@"init AudioSession failed %@",error); } if (![audioSession setActive:YES error:&error]) { NSLog(@"active AudioSession failed %@",error); }

      NSArray *inputArray = [audioSession availableInputs]; NSLog(@"now available inputs are : %@", inputArray);

      for (AVAudioSessionPortDescription *inputDesc in inputArray) {

      if ([inputDesc.portType isEqualToString:AVAudioSessionPortBluetoothHFP]) {
          NSError *myerror;
          NSLog(@"set bluetooth input");
          if (![audioSession setPreferredInput:inputDesc error:&myerror]) {
              NSLog(@" setPreferredInput failed %@",myerror);
          }
      }`
      

      }

      engine = [[AVAudioEngine alloc] init]; AVAudioInputNode *input = [engine inputNode];

      [engine connect:input to:[engine mainMixerNode] format:[input inputFormatForBus:0]];

      [engine startAndReturnError:&error]; }

    • (IBAction)stop:(id)sender { [engine stop]; }

    @end

    • 你的Bluetooth HFP 有自帶麥克風及擴音設備的話,會被iOS視為"1″組音訊設備,然而你插入的"耳機+麥克風"也被視為一組設備,印象中iOS好像只能以"組"來切換,而不能各別指定輸入/輸出為不同組(只有iphone內建麥克風可以指定要用哪個當輸入),所以就你的設備來看,您切到HFP時,"耳機+麥克風"是不會輸出聲音至耳機(這是我在iOS7之前實驗的結果供您參考)

      • FocusOn

        If an application uses the setPreferredInput:error: method to select a Bluetooth HFP input, the output will automatically be changed to the Bluetooth HFP output. 我 从苹果开发者网上找到上面这段话,iOS确实把output changed to Bluetooth HFP上的擴音設備了, 但我的Bluetooth HFP设备只带了麦克风没有擴音設備,您知道iOS是否提供了修改或设置output到iphone的方法吗?

        • 目前看資訊你可能只能利用AVAudioSession中的overrideOutputAudioPort的method試試暫時把ourput指到speaker或是在設定Category中的option也加入(AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth)