月別アーカイブ: 2014年2月

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)」となるので、注意が必要です。

Unityでストップウォッチを作る その11 データの保存

Unityでストップウォッチを作るシリーズ半年ぶりの更新で、最後になります。今回はアプリを終了しても継続して計測できるようにデータを保存したいと思います。

Stopwatch.csに以下のコードを追加してください。ChangeState関数に関しては、最後に1行だけの追加になります。

//
// Stopwatch.csの一部
//
void Awake() {
    Load();
}
const string lastStopTimeKey = "LastStopTime";
const string startDateTimeKey = "StartDateTime";
const string stateKey = "State";
void Save() {
    string lastStopTimeString = lastStopTimeSpan.Ticks.ToString();
    string dateTimeString = startDateTime.Ticks.ToString();
    PlayerPrefs.SetString(lastStopTimeKey, lastStopTimeString);
    PlayerPrefs.SetString(startDateTimeKey, dateTimeString);
    PlayerPrefs.SetInt(stateKey, (int)state);
}
void Load() {
    if (PlayerPrefs.HasKey(lastStopTimeKey) &&
        PlayerPrefs.HasKey(startDateTimeKey) &&
        PlayerPrefs.HasKey(stateKey)) {
        string lastStopTimeString = PlayerPrefs.GetString(lastStopTimeKey);
        if (!string.IsNullOrEmpty(lastStopTimeString)) {
            lastStopTimeSpan = new TimeSpan(long.Parse(lastStopTimeString));
        }
        string dateTimeString = PlayerPrefs.GetString(startDateTimeKey);
        if (!string.IsNullOrEmpty(dateTimeString)) {
            startDateTime = new DateTime(long.Parse(dateTimeString));
        }
        state = (StopwatchState)PlayerPrefs.GetInt(stateKey);
    }
}
void ChangeState(ref bool circleAnim) {
    ButtonType buttonType = ButtonType.Background;
    
    // ... 中略
    
        FlashBackground(0);
    }
    
    // 次の行を追加
    Save();
}

解説

保存したいデータは3つだけですので、簡単に保存が出来るPlayerPrefsを使っています。ただし、PlayerPrefsに保存できる型は「int」「float」「string」の3つだけです。

Save()関数でデータの保存をしています。今回のコードで保存しているのはStopwatchクラスの変数の「state」「lastStopTime」および「startDateTime」の3つです。stateはintで大丈夫なのですが、他の2つはTicksというlong(リファレンスを見ると64bit)の値を保存したいので、intを使うのはちょっと不安です。なので、一旦文字列に変換して、stringで保存をしています。

Load()関数では保存したデータから読み込みをしています。long.Parseで文字列から数値に変換していますが、このParse関数は文字列から数値を探せなかったときに例外を発生させます。今回のコードのように何もしていない状態で例外が飛んでくると問題が起きますので、念のためにtryで囲って例外をキャッチしたら無視するなり、エラーダイアログを出すなりした方がよいと思います。

サンプルプロジェクト

これで最後という事で、プロジェクトをgithubにアップしてあります。
BigStopWatchForUnity

前へ