投稿者「yuki」のアーカイブ

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:で処理しないといけなさそうです。

Touch the Wave v0.5.2

Touch the Waveのv0.5.2が公開されました。iTunes Connectクリスマス休暇前に滑り込みセーフでした。

ちょっとした変更だけなんですが、メインどころは

・ループオンオフ時に波形が一部表示されなくなることがあったのを修正
・プレイヤー画面上部のボタンのタッチ範囲を拡大
・波形のスクロールをよりスムーズに

といったところなのですが、波形のスクロールに関しては、以前のエントリに書いていた60fpsではなく45fps程度に設定しています(あくまでタイマーが設定されている間隔なので、iPod touch第一世代だとそこまで出ていないと思います)。今後60fpsまであげられるかどうかはノイズ対策後のパフォーマンス次第です。一度60fpsを体験してしまうと30fps程度じゃ満足できなくなっちゃいますんで。

あと、ちょっと隠し機能っぽくしてしまいましたが、

・再生ボタン長押しで逆再生をやめて、ロック時にダブルタップで再生方向を逆転
・ロック時に音符ボタンをダブルタップで、ファイル名に記述されているBPMへリセット

という風に変更しています。逆再生の必要性は疑問が残っているのですが、まあ、おまけ機能ってことで。

あと最近気がついた不具合で、アラームが割り込まれた後にアプリが終了するっていうのがあります。今のバージョンだとOS2.0の時に対策したままだったので、久しぶりにアラームを鳴らしてみたらストンと落ちてしまいました。次のアップデートのタイミングで修正します。

iPod touch 2Gのノイズ対策に関しては、2Gはノイズが出ないようにはできたですが、そうすると今度は逆に1Gの性能が追いつかないようでノイズが入るようになってしまい、なんとか調整してみようとしているところです。うまいこと描画系をOpenGLに移行できれば何とかなりそうなんですけど、まだちょっと模索中です。

文字列のイメージを作成する

OpenGL ESで文字列を表示しようと思ったのですが、OpenGL自体にそんな機能は無さそうなので、テクスチャに文字列を描画する方法を調べてみました。

テクスチャをCGContextから作って貼付けるところはDev Centerのサンプルソースを参考にしていただくとして、その前段階でCGImageの文字列を作成するコードです。

CGRect imageRect; //文字列用の画像の大きさ
CGContextRef bitmapContext; //文字列用のコンテキスト
Byte *bitmapBuffer; //コンテキストのバッファ

というのが用意されているとして、以下のような感じです。

memset(bitmapBuffer, 0, imageRect.size.width * imageRect.size.height * 4);
    
CGContextSetRGBFillColor (bitmapContext, 0.0, 0.0, 0.0, 1.0);
CGContextFillRect (bitmapContext, imageRect);
    
UIGraphicsPushContext(bitmapContext);
    
UIFont *tFont = [UIFont systemFontOfSize:40];
[[UIColor whiteColor] set];
[@"Test String 1234567890" drawAtPoint:CGPointMake(0, 0) withFont:tFont];
[@"日本語もOK!" drawAtPoint:CGPointMake(0, 44) withFont:tFont];
    
UIGraphicsPopContext();
    
CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
//ここでテクスチャに描画する
CGImageRelease(image);

NSStringの描画メソッドはコンテキストを指定する事が出来ないので、UIGraphicPushContext()でbitmapContextをカレントのコンテキストにしています。実際にテクスチャに貼付けて表示してみるとこんな感じ。

drawtext.jpg

ちなみに、ランドスケープで左上原点にしてみるってこともやっていたので、こんな位置に表示されています。

これでパフォーマンス的にどうなのかは実際にアプリに組み込んでみないとまだ分かりませんけど。

OpenGLとRemoteIOとiPod 2Gでのノイズの件

※このエントリの内容を修正しました。

放置するといっていたiPod touch第2世代のノイズですが、どうやらOpenGLのViewをWindow全体に貼付けるようにしておけば入らないようです。

と思って一度書いたのですが、関係ありませんでした。AudioSessionのkAudioSessionProperty_PreferredHardwareIOBufferDurationの値を小さくする事で解決できそうな感じです。たぶんビューあたりをいじってノイズが消えたと思っていたのはたまたまだったのだと思います。どちらにしろ、OpenGL使わなければノイズは入らないんですけどねぇ。

それはそれとして、ここ数ヶ月はずっと開発モードだったので、勉強モードに切り替えてOpenGLを中心にちゃんと習得しようかなぁと思います。今月あたまに頼んでた「MOBILE 3D GRAPHICS」って本もちょうど届いたところなんで。

ちなみに、Touch the Waveのv0.5.2をApp Storeに提出してあります。前回のエントリの修正に加えて、波形表示が60fpsのなめらかでスムーズなスクロールになってます。最初に実機で動かしていた頃は何の変化も無かった「Compile for Thumb」外しが、ここに来てかなり効果を発揮して、60fpsでもタッチを邪魔しなくなってきました。そのかわり、電池の消耗はより激しくなっているかもしれません…。

ダウンロード方法について

Touch the Waveへのオーディオファイルのダウンロード方法をAppsのアプリ紹介ページに載せました。Macでのやり方のみです。

Windowsの方は自力でお願いします。Vistaとかになると、さわったことがないのでよくわかりません。とりあえずXPのHomeEditionでApacheをインストールして同じ事が出来るのは確認しています。ただ、ネットワーク系にある程度詳しくないと難しいかもしれません。

ところで、また今週あたり微妙なアップデートを出そうかと思っています。プレイヤー画面の上半分にある9個の小さいボタンのタッチ範囲を広げます。なんか誰かの動画を見ていて結構外してるなぁと思いましたんで。

それと、ループをオン・オフしたときに波形が1秒分表示されない事がたまにあるので、これも修正します。

Touch the Wave v0.5.1

Touch the Wave v0.5.1が公開されました。今回の修正は、

・波形が再生位置とずれているのを修正
・スピード変化時の音質を改善

です。

波形の修正の方は、波形を描画するときにfloatにしないといけないところをintにしていたので、その分ずれが出ていたという感じでした。音質の方は、単純に直線補間でやってます。他に軽くて高音質な補間方法があればいいんですけど、とりあえず聞いた感じ問題無さそうなので、これでいいかなと思っています。

前のエントリに書いたiPod touch第2世代でノイズが入る件ですが、当面放置する事にしました。Quartzでの直接描画を試してみたのですが、パフォーマンスも安定性もいまいちでした。なので、今現在、正常動作を確認しているのはiPod touch第1世代のみとなります。

iPhoneではちゃんと動いてるんですかね?メモリの空きがiPodに比べて少ないのが気になるところではありますが。

修正とiPod 2Gでの不具合

Touch the Waveの波形表示が再生位置とちょっとずれていたので、修正したものをApp Storeに提出してあります。何も無ければ2〜3日後くらいには公開されるんじゃないでしょうか。

それはそれとして、音質向上のための修正をしていたらv0.5をiPod touch第2世代で使っているとノイズがプチプチと入っている事が判明してしまいました。v0.4ではCore Animationを使っている事で数秒間の動作停止が起きることがあって、それを修正するためにv0.5ではOpenGLに変更したんですけど、どうやらそっちも相性が悪かったようです。

別に特別な処理をしているからという訳ではなく、何も描画していないOpenGLのビューを一枚表示しておいて、RemoteIOでシンプルにサイン波だけを鳴らしてみると、それだけでプチプチいっちゃいます。なのでTouch the Waveではプレイリストとダウンロードの画面では大丈夫なんですけど、肝心のプレイヤーでノイズが入ってしまうという困った感じです。

まさか僕のデバイスだけの故障とかじゃないですよねぇ。第1世代では何事も無く再生できちゃうんですけど。

まあ、iPod touch第2世代はもともとロスレスの再生がプツプツとぎれるって不具合がありますから。全体的にパフォーマンスはアップしているけどオーディオは逆に弱くなっているんでしょうか。

回避策としてあと残されているのは、Quartzですかね。試してみてパフォーマンス的に問題無さそうならそっちに切り替えます。せっかくのiPhoneでのプログラミングなんだからCore AnimationとかOpenGLとか使ってみたかったのに、結局Quartzに落ち着くとしたらもったいないなぁと思いますけど。それでも駄目ならOS側の修正アップデートを待つしかないって感じです。

Touch the Wave v0.5 公開しました

Touch the Wave v0.5を公開しました!

最近の審査にかかる時間は、他の方々も見ているとだいたい3〜4日くらいのようですね。リジェクトされたときが土曜の夜に提出して水曜の夜にリターン、今回が日曜の夜に出して木曜の朝にOKという感じでした。

ver0.5に関しては今までのエントリーでもこまごまとアップデート内容は書いてきましたが、まとめておこうと思います。

・−20%〜+20%のスピード可変が出来るように変更しました
・再生中の早送りと巻き戻しを一時的なスピードアップ・ダウンに変更しました
・波形のズーム以外の操作をロックする機能を追加しました
・HTMLファイル内のリンク先のオーディオファイルを連続でダウンロードできるようにしました
・プレイヤーとプレイリストのパフォーマンスが向上しました
・パフォーマンスの向上によりプレイヤー画面更新レートのスライダーを廃止しました
・ボタンをタッチしたのがわかるように枠を表示するようにしました
・アプリ動作中に自動ロックがかからないようにしました
・アプリを起動したときに前回終了した場所にちゃんと戻るようにしました
・iPod touch第2世代でまれに再生が停止する不具合を修正しました
・音符ボタンを一回タッチで、ファイル名に書き込んでいるBPMへリセットするようにしました。書き込んでいなければ以前と同じくループ範囲で設定されます。

その他の微調整はいろいろとあったと思いますが忘れました。何よりパフォーマンスが向上したので、使える度が結構アップしていると思います。

ちなみに、ダウンロードしたときに同じファイル名のものが既にある場合は上書きしなくなりました。フォルダごとダウンロードしていてエラーで止まったりキャンセルしたときに、そのまま続けられるようにと考えまして。

近いうちにアプリ紹介ページにダウンロードのインストラクション的なものを書こうかなと思っています。

機能的なところは今回のアップデートで出来上がってきたと思うので、次のバージョンアップは質をあげる方向になります。スピード変更時の音質向上は(とりあえず直線補間ですが)ほぼできていまして、あと再生停止時にはもうちょっと波形を精細に表示したいなぁと思っています。