月別アーカイブ: 2009年1月

Big StopWatch v1.1

Big StopWatchのv1.1が公開されました。今回はAppleの中の人とちょっとしたやり取りがあってからの公開という感じでした。「スナップモード」って何が違うの?って質問だけで、特にリジェクトになるという内容では無かったんですけど。

とりあえず、今回はバッテリー消費を抑えるだけのアップデートになります。v1.0だと何もしてなくても画面を更新していたりしたのですが、計測をストップしているときには更新しないようにしてます。短い時間を計測する使い方をする場合は効果があると思いますが、ずっと動かしっぱなしだと、まったく変わりません。

目盛り単位でカクカクと動くSnapモードを設定アプリ内で設定できるようになっています。これもアニメーションを少なくしてバッテリー消費を抑えるためです。ある意味こっちのほうがふつうのストップウォッチぽいですけど。

ちなみに、v2.0として出そうと思っているバージョンを作り始めています。ラップとか5秒前からのカウントダウンとか10年後も計り続けられるとか、機能的なものはだいたいできているのですが、一番困難を極めそうなのがポートレート表示です。ランドスケープ前提で作ってしまったので、縦にするといまいちバランスが悪いんですよねぇ。あんまり先の秒数も見えないですし。

Touch the Wave v0.5.4

Touch the Waveのv0.5.4が公開されました。今回の追加・修正点は、

・設定アプリでIOバッファサイズの設定が出来る
・非対応のAIFF・WAVファイルのダウンロードや再生を無視

という2点です。

iPod touch第2世代をご利用の方は、必ずIOバッファサイズを256に変更してください。1024だと今まで通りノイズが入ります。256にすることでノイズが入らないだけでなく、波形のスクロールも60fps近くのスムーズさになって、スクラッチのレスポンスも良くなってます。

逆に、iPhoneやiPod touch第1世代でご利用の方は、デフォルト状態の1024から変更しないでください。256にするとノイズが入ります。

本当はアプリ側で勝手に判断して切り替えたかったところですが、iPodにしろiPhoneにしろ、世代を判別する方法が見つからなかったので、しょうがなく環境設定という形にしました。

2つ目の方の修正ですが、v0.5.3までだと32ビットのファイルが非対応なのにダウンロードできてしまい、一度プレイリストで選択すると他のファイルも再生できなくなってしまうという状態でしたので、ダウンロード後のプレイリストの登録をしないようにして、既に登録されている非対応ファイルの再生も無視するようにしました。

ついでに、ダウンロード時のファイルの種類の判別や保存の仕方を内部的に変えています。自分で検証した限りでは問題ないと思うのですが、もし、以前はダウンロードできたのに出来なくなったという事がありましたらバグレポートをいただければと思います。

アプリのアップデート予定

Touch the WaveのiPod touch第2世代ノイズ対策版を提出してあります。特に問題なければ今週中にはアップされるんじゃないでしょうか。

ちょっと不本意ではあるのですが、設定アプリ内で「iPhoneまたはiPod touch第1世代」か「iPod touch第2世代」を選択してもらうという形になっています。できればユーザが設定しない形で解決したかったんですけど、世代の違いを判別する方法などが見つからなかったので、あきらめました。

Big StopWatchの方は、やっぱりバッテリーの消費が気になるので、うまいこと処理を軽減する方法を模索してます。ストップ中の画面更新を止めるだけでだいぶ違いますし。

レビューとか見てるとラップをつけて欲しいという要望が多かったりしますが、もともと積算さえしなくてもいいかなと思っていたくらいシンプルに保ってはいたかったので、少なくとも今の状態はシンプルモードで残しつつ、アドバンスドモードを追加していろいろ出来るようにするという方向で考えてはいます。

あ、あと数ヶ月先になると思いますが、マニアックなオーディオアプリ作ろうと思ってます。

AudioSession その2 AudioCategory

iPhoneのアプリでオーディオを再生・録音する場合に、iPodの音楽を鳴らしたままミックスして出したり、ロックした時にも音を出せるようにするには、AudioSessionSetProperty関数を使ってAudioCategoryというのを設定します。

AudioSessionSetPropertyはこんな感じで宣言されています。

extern OSStatus
AudioSessionSetProperty(AudioSessionPropertyID inID,
                        UInt32                 inDataSize,
                        const void             *inData)

AudioSessionSetPropertyの第一引数にはAudioSessionPropertyID、第二引数はプロパティのサイズ、第三引数には設定する値をポインタで渡します。CoreAudioではおなじみの構成です。

AudioSessionSetPropertyは色々とオーディオ関係の設定できますが、とりあえず今回はAudioCategoryの設定だけをやってみます。前回のコードのsetupAudioSessionメソッドを以下のように書き換えてください。

- (void)setupAudioSession
{
    AudioSessionInitialize(NULL, NULL, InterruptionListener, &outputUnit);
    
    UInt32 category = kAudioSessionCategory_AmbientSound;
    AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
    
    AudioSessionSetActive(true);
}

AudioCategoryのプロパティの型はUInt32と決まっていますので、その変数を作ってポインタで渡すという感じになります。プロパティの型の種類はものによってバラバラですが、ヘッダを辿るとコメントに書いてあります。

これで実行すると、iPodの音楽を鳴らした状態でアプリを起動しても音楽が止まらず、サイン波がミックスして再生されると思います。

プロパティのkAudioSessionCategory_AmbientSoundの部分をkAudioSessionCategory_MediaPlaybackに変えてみてアプリを実行すると、iPodの音楽はフェードアウトしてしまいますが、ロックしてスリープするときにサイン波は鳴りっぱなしになります(でも、このコードではピッチは下がってしまいますが)。

カテゴリの動作一覧はAudioSession Programing Guide内にありまして、日本語に置き換えてみるとこんな感じでしょうか。

PropertyID サイレントスイッチの効果(およびスクリーンロック時の消音) 他アプリのオーディオ再生 使用例
UserInterfaceSoundEffects ある する ユーザーインターフェースなど
AmbientSound ある する ゲーム効果音やバーチャル楽器など。iPodの音楽を再生しながら鳴らすもの
SoloAmbientSound ある しない デフォルト。ゲームサウンドなど。他アプリの音を再生しないもの
MediaPlayback ない しない オーディオファイル再生
LiveAudio ない しない ライブパフォーマンス系。バーチャル楽器など
RecordAudio ない しない 録音のみ
PlayAndRecord ない しない 再生と録音

何もカテゴリーを設定しないとSoloAmbientSoundになってしまいます。それと残念ながら、iPodの音楽を鳴らしながらスクリーンロック中に音を出すってことは出来ないみたいです。

※2009/10/7追記
OS3.0以降、MediaPlaybackなどのカテゴリでも、kAudioSessionProperty_OverrideCategoryMixWithOthersを使う事でiPodの音楽を流しながら音を出す事が出来るようになっています。また、LiveAudioとUserInterfaceSoundEffectsはDeprecatedになってます。

また、ちょっと分からない部分なんですが、RecordAudioにしていたからといって再生が出来なくなるという訳ではなかったりするので、結局のところAudioCategoryの設定は3パターンしかないようです。もし今の段階で違いが無かったとしても、今後のOSのバージョンアップで何か変化があるかもしれませんけど。

※追記 2009/1/25
AudioCategoryの種類によってルートチェンジのときに違いがあるみたいですね。それはまたちゃんと調べたらルートチェンジで1つエントリを書きます。

といったところでAudioCategoryはこの辺で。次回は、ずっと悩まされ続けてきたIOバッファあたりについて書いてみようかと思います。

BigStopWatch v1.0 公開しました

去年の7月にAppStoreへの提出を試すためにストップウォッチのアプリを作ってReady for Saleまで行っていたのですけど、あまりにも工夫も無くビジュアル的にもいまいちなやつだったので自らリジェクトしていまして、でもOpenGLもある程度使えるようになってきたのでゼロから作り直して公開しました。

bsw1.jpg

緑色の針が秒、赤色の針が分を指し示しています。一周はそれぞれ60秒と60分です。60分しか計れません。60分を過ぎると0分0秒に戻ります。

画面全体のどこかをタップするとスタートし、もう一回タップするとストップします。ストップしているときに右上に現れるRESETの文字をタップすれば0秒に戻り、それ以外の部分をタップするとそのまま続きを計測しはじめます。

一番の特徴は、画面下のアナログ的な表示をでっかくしているってことですね。もともとは画面にちょうど収まるように盤を表示していたのですが、0.1秒の目盛りなんて細かすぎて良くわからないので、こんな感じにしました。逆に右上に小さくして全体が見られるようにしています。

あと、Apple標準のストップウォッチでもそうなので当たり前ですが、ロックしようがアプリを終了しようが電源を切ろうが、それによって計測がリセットされるという事はありません。これは、計測開始のNSDateをユーザデフォルトにそのまま保存すれば簡単にできる事ですね。

味も素っ気も無い見た目のくせにアニメーションしまくってバッテリーの消耗が激しいアプリで、どんだけ実用的か分かりませんが、興味があればどうぞ。

ダウンロードは
こちら
から。

Touch the Wave v0.5.3

Touch the Waveのv0.5.3が公開されました。AppStoreの新機能にも書いてあるメインの修正点は、

・スクラッチ時のノイズを軽減
・アラーム等の後にアプリが終了する不具合を修正

でして、細かいところでは、

・プレイヤーのロック時にループボタンをダブルタップするとループ範囲がリセットされる
・音符ボタンがきれいになった
・ダウンロードしているファイル全体の容量が表示されるようになった

といったところです。

iPod touch 2Gで入ってしまうノイズはまだ解決していません。IOバッファサイズを256サンプルにすると入らないようなのですが、そうすると今度はiPhoneやiPod touch 1Gで処理が追いつかずノイズが入ってしまうという困った感じで止まっています。ちょっとしばらく解決できそうにない気がします。

AudioSession その1 Interruption

以前、RemoteIOでのオーディオ再生というエントリでRemoteIOでオーディオ再生をする方法を書きましたが、それだけだとiPhoneのアプリとしては設定が不足です。オーディオを扱うアプリでは、AudioSessionというAPIを使って、オーディオに関する情報の取得や設定を行います。SystemSoundServiceなんかのお手軽再生系では無用の様ですが、AudioUnitを使う場合には必須といえるでしょう。

ちなみに、AudioSession系の関数はシミュレータではエラーが出て一切受け付けてくれず、実機にしか効果が及びません。また、デフォルトのオーディオIOのバッファサイズは実機では1024サンプルですが、シミュレータではMacのオーディオインターフェースの設定そのままですので、オーディオの動作の検証は必ず実機で行いましょう。

※OS 3.0にはAVFoundationにAVAudioSessionというObjective-CでAudioSessionが扱えるフレームワークが追加されましたが、動作に?な部分がありますので、あえてC言語のフレームワークの使い方そのままで記述しておきます。

AudioSessionで出来る事はいろいろありますが、いくつかあげてみると

・iPodのミュージックの音を鳴らしたままにするか、止めてしまうかの設定
・スリープしたときにアプリの音声を停止するかの設定
・通話などが割り込んできて自分のアプリの音声が停止する時の通知を受ける
・ヘッドホンがささったりなど、オーディオのルーティングが変わったときの通知を受ける
・オーディオハードウェアの設定の変更や、設定されている情報の取得

といったところでしょうか。必ずやっておかなければいけないのは、外部から割り込みが入ってオーディオが停止する時と、その後復活する時の処理です。これを適切にやっておかないと、アプリに戻ってきたときに音声が再生できなくなります。

では、以前のエントリRemoteIOでのオーディオ再生のYKAudioOutput.mのコードに以下の関数とメソッドを追加してください。initは修正になります。

void InterruptionListener(void *inUserData,
                          UInt32 inInterruption)
{
    AudioUnit *remoteIO = (AudioUnit*)inUserData;
    if (inInterruption == kAudioSessionEndInterruption) {
        AudioSessionSetActive(true);
        AudioOutputUnitStart(*remoteIO);
        printf("EndInterruption\n");
    }
    
    if (inInterruption == kAudioSessionBeginInterruption) {
        AudioOutputUnitStop(*remoteIO);
        printf("BeginInterruption\n");
    }
}
- (void)setupAudioSession
{
    AudioSessionInitialize(NULL, NULL, InterruptionListener, &outputUnit);
    AudioSessionSetActive(true);
}
- (id) init
{
    self = [super init];
    if (self != nil) {
        [self setupAudioSession];
        [self setupOutputUnit];
    }
    return self;
}

setupAudioSessionメソッドでAudioSessionの設定を行っています。AudioSessionInitialize関数ではInterruptionListnerという関数を登録して初期化しています。以下がAudioToolbox/AudioServices.hの中にある宣言です。

extern OSStatus
AudioSessionInitialize(CFRunLoopRef                      inRunLoop,
                       CFStringRef                       inRunLoopMode,
                       AudioSessionInterruptionListener  inInterruptionListener,
                       void                              *inClientData)

1つ目と2つ目のRunLoopのところはそれぞれNULLを入れておけば、デフォルトで登録されます。4つ目のinClientDataは自由に使えるところなので、今回のコードではAudioUnitのポインタを渡してAudioUnitのスタートとストップを出来るようにしています。

AudioSessionInitializeで初期化が出来たら、AudioSessionSetActive(true)を呼ぶとAudioSessionが開始されます。

AudioSessionが開始されるとInterruptionListener関数がオーディオの割り込み時に呼ばれまして、inInterruptionにkAudioSessionBeginInterruptionがくれば割り込み開始(アプリのオーディオが停止)、kAudioSessionEndInterruptionなら割り込み終了(アプリのオーディオが復活)となりますので、それぞれに応じて処理するようにします。

割り込みが開始された場合にはAudioUnitをストップするだけで良いのですが、終了のときにはAudioSessionSetActiveを先に読んでおかないと、たぶんAudioUnitStartでアプリが落ちます。

※OS 3.0ではアプリが落ちたりする事は無いようですが、AudioSessionSetActiveを呼ばないとAudioUnitが再開しないのは変わらないようです。

最低限これだけやっておけば良いと思いますが、今回のコードだと、アプリ起動時にiPodのミュージックが停止したり、ロック時に音が止まったりとデフォルトの状態のままですので、その設定の仕方は次回やります。

iPod touchのInterruption Callback

iPhoneでアラームとか通話とかが割り込んできてアプリのオーディオが停止してしまうときの通知を受け取るにはAudioSessionでInterruption Callbackを登録するって感じなのですが、サンプルソースなどは以下のようなコードになってまして…

void rioInterruptionListener(void *inUserData, UInt32 inInterruption)
{
    AudioUnit *remoteIO = (AudioUnit*)inUserData;

    if (inInterruption == kAudioSessionEndInterruption) {
        AudioSessionSetActive(true);
        AudioOutputUnitStart(*remoteIO);
    }

    if (inInterruption == kAudioSessionBeginInterruption)
        AudioOutputUnitStop(*remoteIO);		
}

自分のアプリに戻ってきたときにはこのコールバックが呼ばれて、kAudioSessionEndInterruptionがinInterruptionに来る事になっていますが、1つ例外があるようです。

iPod touchでホームボタンをダブルタップすると、ミュージックをコントロールできる小さいウィンドウが現れます。そこで再生を押すとオーディオの割り込み開始のkAudioSessionBeginInterruptionは来ますが、そのあと停止してウィンドウを閉じてもkAudioSessionEndInterruptionは来ません。

とりあえず、戻ってくるところはUIApplicationのapplicationDidBecomeActive:で処理しないといけなさそうです。