Multi Route Audio

iOS6からの機能なので今さら感がありますが、拙作のTouch the Waveでも導入したMulti Route Audioの使い方をまとめておこうと思います。

Multi Route Audioとは

USBオーディオインターフェースやHDMIなどの外部出力を接続したときに、ヘッドホンへ別系統の音を出力できる機能です。外部出力2chとヘッドホン2chの計4ch出力する事が出来ます。残念ながらマルチイン・アウトが搭載されているオーディオインターフェースをマルチで使えるという機能ではなく、USBオーディオインターフェースはイン・アウト2chのみで、それとは別にヘッドホンにも別の音をだせるというだけの機能です。

ちなみにUSBオーディオインターフェースの繋げ方を知らない方に説明しておきますと、カメラコネクションキットまたはUSBカメラアダプタを使ってUSBオーディオインターフェースを接続すると、内蔵スピーカーではなくオーディオインターフェースから音を出す事が出来ます。市販されているたいていのUSBオーディオインターフェースは対応しているはずです。ただし、USB接続に対応しているのはiPadのみで、HDMI接続ならiPhone・iPad両方大丈夫です。また、バスパワーで動作するオーディオインターフェースはほぼ電源供給が足りないので、電源付きのUSBハブを介して接続する必要があります。

入力が2系統受けられる機能に関してはiOS5から搭載されているようですが、出力に関してはiOS6からの新機能です。今回のエントリでは出力のみ扱います。なお、WWDC2012のSession 505でも解説されていますので、英語に抵抗無い方はApple純正の解説をご覧いただくのも良いと思います。

音を出す前の準備として大まかに以下のような作業が必要になります。

① AudioSessionのCategoryをMultiRouteにする
② RouteChangeの通知を受け取る
③ 出力ポートの情報を取得する
④ 再生する音の出力チャンネルを設定する

AudioSessionのCategoryをMultiRouteにして、外部オーディオデバイスとヘッドホンを両方接続した状態で初めてMulti Route Audioが使えるようになります。

実際に音を出すのには、RemoteIOを使う方法と、AVAudioPlayerを使う方法の2つがあります。基本的にRemoteIOの方法を解説します。AVAudioPlayerは個人的に使わないので簡単に触れる程度にします。

AudioSessionのCategoryをMultiRouteにする

AVAudioSessionのCategoryにAVAudioSessionCategoryMultiRouteをセットし、通常オーディオを鳴らす前の準備と同じくAudioSessionをアクティブにしてください。

NSError *error = nil;
   
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryMultiRoute error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:&error];

RouteChangeの通知を受け取る

通知センターからAVAudioSessionRouteChangeNotificationの通知を受け取るように登録します。これで、オーディオデバイスやヘッドホンを抜き差ししたときに通知が飛んできます。

// 通知を登録する
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(routeChangeNotification:)
name:AVAudioSessionRouteChangeNotification object:nil];
// 通知を受け取る
- (void)routeChangeNotification:(NSNotification *)note
{
    NSDictionary *dict = note.userInfo;
    AVAudioSessionRouteChangeReason reason =
        [[dict objectForKey:AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
    
    // ... 通知を受け取った後の処理
}

出力ポートの情報を取得する

通知を受け取った後や、一番最初にセットアップをするときに出力ポートの情報を取得します。

// 現在接続されているオーディオルートの情報を取得する
AVAudioSessionRouteDescription *routeDesc =
    [[AVAudioSession sharedInstance] currentRoute];
// 出力の情報を取得する
NSArray *outputs = routeDesc.outputs;
    
for (AVAudioSessionPortDescription *portDesc in outputs) {
   NSString *portType = portDesc.portType; // AVAudioSessionPortHeadphonesなど
   NSString *portName = portDesc.portName;
   NSArray *channels = portDesc.channels; // AVAudioSessionChannelDescription
}

AudioSessionのcurrentRouteから現在のオーディオ接続状態をAVAudioSessionRouteDescriptionで取得できます。さらにそのAVAudioSessionRouteDescriptionのoutputsから現在の出力ポートの情報がAVAudioSessionPortDescriptionのNSArrayで取得できます。

ポートというのは基本的に接続されている機器です。USB Audioであったり、ヘッドホンであったり内蔵スピーカーの事です。出力であればだいたい2chずつチャンネルを持っています。

outputsが2つあればマルチで出力できるという事ですし、1つであれば1系統しか出力が無いという事です。AudioSessionでMultiRouteにCategoryがセットされていなければ、常に1つだけになります。CategoryがMultiRouteでかつマルチで出力できる接続があるときにoutputsが2つになります。

AVAudioPlayerで出力を選択するには、AVAudioSessionPortDescriptionのchannelsで取得できるAVAudioSessionChannelDescriptionをchannelAssignmentsにセットすれば良いようです。

RemoteIOのChannelMapを設定する

出力ポートの情報を取得してMulti Routeの状態であることが確認できたら、RemoteIOからどの出力へアサインするかをChannelMapで設定します。

UInt32 map[4] = {0, 1, -1, -1};//ひとつめのデバイスに出力
// UInt32 map[4] = {-1, -1, 0, 1};//ふたつめのデバイスに出力
// UInt32 map[4] = {0, 1, 0, 1};// 両方のデバイスに同じ音を出力
UInt32 size = 4 * sizeof(UInt32);
    
AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Output, 0, map, size);

重要な注意点として、RemoteIOはひとつにつき2chしか出力する事が出来ません。マルチ出力でUSB Audioとヘッドホンの合計4chへバラバラの音を出力したい場合は、RemoteIOを2つ作成する必要があります。ただ、同じ音を複数の出力へ割り当てる事は出来ますので、外部出力とヘッドホン出力で左右を入れ替えるというだけだったり(そんなことはしないと思いますが…)、モノラルを2系統出力するという用途であればRemoteIOひとつでも大丈夫です。

ChannelMapはUInt32型の配列で設定します。4chの出力がある状態ならば、要素数が4つの配列にRemoteIOのアウトのどのチャンネルを割り当てるのかを記述します。0と1にすればRemoteIOのそれぞれのチャンネルの音が出力されますが、-1にすれば出力されません。(符号無しの型なのに-1を記述するのはちょっと気持ち悪いですが、-1で設定するように指定されています)。なお、MultiRouteの状態でなければChannelMapの設定は無視されるようです。

ChannelMap配列の出力の順番は、AudioSessionのcurrentRouteのoutputsの順番と一致しています。ただ、接続するオーディオデバイスによって順番が変わったりしますので、ちゃんとポートの中身を判断して設定したり、ユーザーに設定をしてもらう必要があります。USBでしか試していないと「本線(USB)・ヘッドホン」の順番で固定だと思ってしまいますが、HDMIだと「ヘッドホン・本線(HDMI)」となるので、注意が必要です。

Multi Route Audio」への16件のフィードバック

  1. riki

    この記事を読ませていただき、ありがとうございました。
    私も、MultiRouteを試していますが、出力ボード情報の出力のところが上手く出力できないです。
    直接デバイスをMACに接続していなく、パソコンのエミレータで動かしてみましたが、
    input,outputの情報は、nillとなっています。
    デバイス情報を出力させるために、何かほかの条件が必要ありますか。
    ぜひ教えていただくようお願いいたします。

    返信
    1. yaso_san 投稿作成者

      Macで試そうとしているように見受けられますが、Mac上で動くiOSのシミュレータでは、AudioSessionは動かないので情報を取得する事は出来ませんし、MultiRouteの状態を試す事も出来ません。
      また、iOSの実機でMultiRouteとして動作させるときはXcodeと接続する事は難しいので、オーディオのIN・OUTの情報を取得するには、実機の画面に表示するか、テキストで保存してあとから見るかするなどの工夫が必要になると思います。

      返信
  2. riki

    なるほど、わかりました。
    ありがとうございました。
    試してみます。
    またよろしくお願いいたします。

    返信
  3. riki

    済みませんが、また質問させていただきます。
    実機でオーディオのIN・OUTの情報を取得しました。
    AudioSessionでMultiRouteにCategoryがセットしまして、USB Audioとヘッドホンを同時に接続して、outputsがUSBの出力情報しかありません。記事に書かれた手順で設定しましたので、情報出力の所に、何か設定が必要でしょうか?
    また、仮に2系統の情報を取れれば、channelAssignmentsの設定は、どう設定すればよいのでしょうか。
    WWDC2012のSession 505のPPTは、よくわかりませんのです。
    AVAudioSessionChannelDescription *desiredChannel =
    [[[outputs objectAtIndex:0] channels] objectAtIndex:0];
    後、RemotoIOの使用ですが、二つRemoIOを使う場合、それぞれ、出力デバイスを指定して、初期化することでしょうか?ガイドにこのようなやり方を載せていないため、できないと思いました。よければ、設定方法を教えていただけませんか。
    以上、よろしくお願いいたします。

    返信
    1. yaso_san 投稿作成者

      改めて手元のiPadで確認しましたが、この記事に書いてあるコードで取得したoutputsをそのままUILabelに表示すれば2系統のoutputの情報が取得できました。
      まず、他のMulti Route Audioに対応したアプリでちゃんとUSBとHPの2系統のサウンドが出力されている事は確認されていますでしょうか?Multi Routeが動作する接続をしているかどうかを試していないようでしたら、何かアプリを使用して確認する事をお勧めします。
      ひとつ思いついた原因としては、アプリが一旦バックグラウンドに移るとAudioSessionが非アクティブになるのでMulti Routeがリセットされている、などでしょうか。
      また、RemoteIOに関しては単純に2個作ってください。その上でそれぞれChannelMapで4chある出力先から2つを選択すれば良いです。

      返信
      1. riki

        ご回答ありがとうございました。
        すみませんが、2系統を同時表示されたという事でしょうか。私の場合、内蔵スピーカとHPと同時に接続された場合、HPの情報をしか表示されません。HPとUSBと同時に接続される場合、USBの情報をしか表示されないです。内蔵スピーカのみの場合、内蔵スピーカの情報を表示出来ます。
        下記のようになります。Channelが二つありますけど。なかなk上手く行かないです。
        宜しくお願い致します。
        ソースコード:
        for (AVAudioSessionPortDescription *portDesc in outputs) {
        NSString *portType = portDesc.portType; // AVAudioSessionPortHeadphonesなど
        label.text = [label.text stringByAppendingString:portType];
        NSString *portName = portDesc.portName;
        label.text = [label.text stringByAppendingString:portName];
        NSArray *channels = portDesc.channels; // AVAudioSessionChannelDescription
        NSString *temp;
        temp = [channels componentsJoinedByString:@”,\n”];
        label.text = [label.text stringByAppendingString:temp];
        NSLog(@”Type : %@ \n Name : %@ \n Channels : %@ \n”,portType,portName,channels);
        }
        その結果:
        Type : Headphones
        Name : ヘッドフォン
        Channels : (
        “”,
        “”
        )

        返信
        1. yaso_san 投稿作成者

          ログに書き出す所のコードを見てもうまくいかない原因はわかりませんので、さしつかえなければXcodeのプロジェクトをどちらかにあげていただくか、メールなどで送っていただければ何かお答えできるかもしれません。
          メールアドレスはこのページの上の「このサイトについて」にあります。

          返信
          1. riki

            Xcodeのプロジェクトをメールで送りましたので、
            宜しくお願い致します。

  4. yaso_san 投稿作成者

    頂いたXcodeプロジェクトそのままで実行して、問題なく2系統の出力の情報を取得できる事を確認しました。RemoteIOTestViewController.mの128行目で表示しているところでちゃんと2系統分の出力情報が確認できます。
    問題はその後の部分で、portType・portName・channelsというインスタンス変数は1個ずつしか無いので、テキストとして表示するときには最後のひとつのportDescriptionから取得した情報しか表示できていません。
    なお、RemoteIOで音を出力するようでしたら、AVAudioPlayerの話は全く関係ありません。また、AudioSessionに出力チャンネルを設定するということはできず、AudioSessionからは出力チャンネルの情報を取得できるだけです。出力チャンネルの設定はAudioSessionから取得した情報を元にRemoteIOのChannelMapで設定することになります。

    返信
    1. riki

      検証して頂きありがとうございました。自分もやってみましたけど、もしかして、2系統の意味が理解間違いました。私は、HPをさした状態で、HPの情報と内蔵スピーカの情報を一遍取得出来ると理解していますが、よろしいでしょうか。
      RemoteIOTestViewController.mの197行目に、RouteOutputをLog出力するようにしているけど、HP接続する時にHPの情報しか表示していません。ここも、変数の問題でしょうか。
      なかなか、2系統のイメージをつかんでいませんので、申し訳ないですが、一度2系統の情報を見せて頂けませんか?
      宜しくお願い致します。

      返信
      1. yaso_san 投稿作成者

        > 私は、HPをさした状態で、HPの情報と内蔵スピーカの情報を一遍取得出来ると理解していますが、よろしいでしょうか。
        いえ、HPをさした状態では基本的に内蔵スピーカから音は出ませんので情報を取得する事は出来ません。HPを外せば内蔵スピーカから音が出ますので、内蔵スピーカの情報を取得する事が出来ます。あくまで「今」接続されているポートの情報を取得する事しか出来ません。
        この記事の頭でも書いている事ですが、Multi Route Audioは「外部出力」と「ヘッドホン出力」に別系統で出力できるものです。「内蔵スピーカ」と「ヘッドホン」という組み合わせは、少なくとも現状では出来ませんことをご理解ください。
        RemoteIOTestViewController.mの197行目に関しては2系統あればちゃんとログに出ると思います。ログの出し方や変数の使い方についてはかなり基本的なことですので、別途ご自身で勉強してください。

        返信
  5. riki

    ご指摘ありがとうございました。
    2系統の情報を確認できました。
    >また、RemoteIOに関しては単純に2個作ってください。その上でそれぞれChannelMapで4chある出力先から2つを選択すれば良いです。
    済みませんが、ここは、もうちょっと聞かせていただきたいです。
    二つのRemoteIOを作るのですが、ChannelMapで4chを指定する際に、下記のIFを利用するため、RemoteIOは、一つしか指定できないかなとおもいますが?それとも、2回指定する意味でしょうか?
    また、二つのRemoteIOの出力は、システムが勝手に出力デバイスと繋ぎます?一つのRemoteIOには、2チャンネルしかないため、そこのChannelMapのチャンネルが、すべての出力チャンネルであって、_audioUnitの2チャンネルが、その中の一部であることでしょうか?
    AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Output, 0, map, size);
    また長い質問ですが、ぜひよろしくお願いいたします。

    返信
    1. yaso_san 投稿作成者

      間違ったコメントが飛んでいたらすみません。
      この記事の「RemoteIOのChannelMapを設定する」の項目に書いてある通りです。「//ひとつめのデバイスに出力」行に書いてある「{0,1,-1,-1}」を片方のRemoteIOに、「//ふたつめのデバイスに出力」の行の「{-1,-1,0,1}」をもう片方のRemoteIOにChannelMapとしてセットすれば良いはずです。

      返信
      1. riki

        コメント有難う御座いました。
        済みませんが、もうちょっと聞きたいですが、
        二つのデバイスと二つのRemotoIOとの対応関係が、システムが振り分けますか?
        調べたところで、OS Xの場合、AudioUnitのデバイスが指定できるそうですが、
        iOSの場合、できないようですので。

        返信
        1. yaso_san 投稿作成者

          ちょっと日本語が崩れていらっしゃるので質問のニュアンスは正確につかめませんが…。
          iOSはOS Xのように自由には設定できません。2つのデバイスのChannelMapの順番はシステムが決定します。デバイスとRemoteIOのチャンネルの対応関係はプログラマが設定します。

          返信
  6. riki

    日本語が下手なので、すみませんでした。
    ご回答有難う御座いました。
    デバイスとRemoteIOのチャンネルの対応関係の設定について、調べてみます。
    また宜しくお願い致します。

    返信

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です