Visualizerを使ってイコライザーや波形を表示する


今日は特別に、夜子まま(@yokmama)さんより寄稿いただきました!Android2.3の新機能Visualizerについて、解説してもらいました。同様に音声に関する記事は以前のEqualizerクラスを使って音質の調整を行うがあります。参考に。

夜子ままさん(Twitterより)


Re:Kayo-System Co.,Ltd. 代表 夜子まま Androidのアプリケーション開発にシフトしていってるWEBシステム屋です。 Android関連の仕事は、本の執筆、講演等です、仕事ほしいです・・・http://kayosystem.blogspot.com/


約束は守るんだからね!
去年(H22年)の12月頭にAndroid2.3が発表されました。
こんな年末なんてことしてくれるんだと、予想はしていても、これは大変だ!ということで。
急いで2.3の資料を集めはじめたのですが、あちこち調べるも当然ないですよね!
そんな都合のよい資料は・・・・
やっぱり、英語の公式資料が少しある程度です。
ソースコードもまだないし。
そういうわけで、こういう場合は自分たちで作っていかなきゃ!
そうおもいました。
それで、日ごろお世話になっています、テックブースター様に、私にも記事を書かせてください。とお願いしたのですが・・・
あわわ、もう1月?あれからもう一ヶ月もたっています!
だけど何も書いていない!
ごめんなさい、OverScrollについて書くといっていたのになんという体たらく。

(解説は続きからどうぞ)

うぐぐ、どうでもいい風になる前に、なんとか時間を作って体裁だけでも取り繕わねば、羊に殺される~。

そういうわけで、先日、adroid2.3で追加された音楽関係の調査をしたので、その記事をエントリーしたいと思います。
時代はエコですよね。

第一回「Visualizerを使って再生中の音楽の波形を表示しちゃえ!」

Androidで気軽に音楽を再生するにはMediaPlayerを使えばいいです。
ところが、このMediaPlayer、高機能すぎて?再生の上っ面しか触れませんでした。
車のナビみたいに、走れ!止まれ!右みろ?左みろ?みたいな。
ようするに、エンジンの中身をみたり、実際に運転したりしたりといったことは出来なくて。
もし、やるとしたらそれはもう血みどろの作業を強いられていたわけです。
(この例、余計混乱したかな?だとしたらそれでOK!です)

ところが、Android2.3では、そういう突っ込んだことも出来るようにと、いくつかのAPIが追加されました。
私自身まだ全部を熟知していないので、ここはざっくりとしか説明しませんが。
大まかにいうと。

  • AudioEffect関連のクラスを追加しました。
  • AudioEffectはMediaPlayerとリンクして、データをいじくったり、覗いたりできるクラス達です

ということです。

どんなことができるかというと、コンポなどでおなじみのイコライザーや、再生中の音楽の波形を表示したりできますよーということです。
これはすごい!
実行した画像はこんなの

このサンプル自体は、Andoridの開発者向けの公式サイトよりダウンロードできますので興味のあるかたはどうぞ。

TestFxプロジェクトをダウンロード

それでは、実際に使ってみましょう。
というわけでこのエントリーです。
今回は、波形部分の表示に使われているVisualizerを使ってみます。
登場してくるクラス達

  • Visualizer
  • Visualizer.OnDataCaptureListener
  • MediaPlayer
  • 表示用のView

え?こんなに?と思う人もいるかもしれませんが、そうなんですよ。
MediaPlayerを使わなきゃいけないのは当然なのですが、問題は表示用のView
波形の表示につかうVisualizerは、単にMediaPlayerと連結をして仲立ちをしてくれるだけで、実際の表示部分は自前で実装しなきゃならんのです。
そりゃそうか、そうですよね。

でも、そんなの説明していたら、めちゃくちゃ長いエントリーになってしまうので?
ここではそのへんをはしょっちゃいます。
肝心要の、処理の部分だけ抜き出して説明しますので、詳しくはソースコードをダウンロードしてみてください。

それでは、手順ですが。
まず、MediaPlayerを初期化します。
つぎに、MediaPlayerのAudioSessionIdを取得します。これは2.3で追加された新しいAPIで、MediaPlayerとAudioFXを連結されるために使用します。
そして、取得したAudioSessionIdをもとにVisualizerを生成します。

生成をしたら、次はデータを取得するための準備をします。
データはVisualizer.OnDataCaptureListenerをVisualizerに設定し、その実装メソッドのonWaveFormDataCaptureあるいは、onFftDataCaptureでデータを取得します。

取得したデータは再生中の音声データなので、これを画面に表示すると。

こんな画面になります。

というようなことを次のリストでしています。

private void initAudio() {
    try {
        //メディアプレイヤーの初期化
        mMediaPlayer = MediaPlayer.create(this, R.raw.test_cbr);
        //これは再生が終了したときのイベント処理をするリスナー設定
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            public void onCompletion(MediaPlayer mediaPlayer) {
                //再生がおわったらVisualizerを無効にする
                mVisualizer.setEnabled(false);
            }
        });

        //再生開始
        mMediaPlayer.start();

        //Visualizerの初期化
        mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
        //これおまじない、一回無効にしないと、有効になってくれないので
        mVisualizer.setEnabled(false);
        //音声データをキャプチャするサイズを設定
        mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
        //キャプチャしたデータを定期的に取得するリスナーを設定
        mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
            //Wave形式のキャプチャーデータ
            public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
                    int samplingRate) {
                mVisualizerView.updateVisualizer(bytes);
            }

            //高速フーリエ変換のキャプチャーデータ
            public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
                    int samplingRate) {
            }
        },
        Visualizer.getMaxCaptureRate() / 2, //キャプチャーデータの取得レート(ミリヘルツ)
        true,//これがTrueだとonWaveFormDataCaptureにとんでくる
        false);//これがTrueだとonFftDataCaptureにとんでくる
        mVisualizer.setEnabled(true);
    } catch (Exception e) {
        Log.e("TestVisualizer", "initAudio", e);
    }
}

ポイントは、Visualizerの初期化前に一度、setEnabled(false);をしないといけないこと。
これはちょっとした落とし穴です。
このサンプルでは、MediaPlayerを終了しているので問題はないのですが、サービスなどでMediaPlayerを管理している場合はVisualizer.getCaptureSizeRangeでエラーが発生します。

java.lang.IllegalStateException: setCaptureSize() called in wrong state: 2
     at android.media.audiofx.Visualizer.setCaptureSize(Visualizer.java:281)
     at jp.co.se.android.chapter09.VisualizeActivity.setPlayDisp(VisualizeActivity.java:67)
     at jp.co.se.android.chapter09.ControlActivity.setPlayDisp(ControlActivity.java:281)
     at jp.co.se.android.chapter09.ControlActivity.update(ControlActivity.java:416)
     at jp.co.se.android.chapter09.ControlActivity$1.onServiceConnected(ControlActivity.java:512)
     at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1064)
     at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1081)
     at android.os.Handler.handleCallback(Handler.java:587)
     at android.os.Handler.dispatchMessage(Handler.java:92)
     at android.os.Looper.loop(Looper.java:123)
     at android.app.ActivityThread.main(ActivityThread.java:3647)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:507)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
     at dalvik.system.NativeStart.main(Native Method)

原因は、Visualizerが内部でもっているステータスがSTATE_INITIALIZEDになっていないとCaptureSizeの取得時にエラーになるようなのですが。
次のリストはVisualizer.javaのsetCaptureSizeの処理です。

public int setCaptureSize(int size)
throws IllegalStateException {
    synchronized (mStateLock) {
        if (mState != STATE_INITIALIZED) {
            throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState));
        }
        return native_setCaptureSize(size);
    }
}

まさしく、このIllegalStateExceptionです。
mStateは、Visualizerのコンストラクタ
をよびだしたときにSTATE_INITIALIZEDになるはずなのですが、どうやらAndroidOS2.3では問題があるらしく、VisualizerのReleaseでnative_release()をやってるのに、native_getEnabled()の値は変化しないみたいで、MediaPlayerを解放しないかぎり一回セットしたらずっとtrueがかえってくるみたいです。

if (native_getEnabled()) {
    mState = STATE_ENABLED;
} else {
    mState = STATE_INITIALIZED;
}

このコードはコンストラクタ内でmStateに状態を設定している箇所。
だから、Visualizerを生成したら念のためsetEnabled(false);をしておいたほうがよいというわけです。これは将来修正される内容かもしれないので。
バッドノウハウとしておぼえておくといいでしょう。

AndroidManifest.xml

最後に、AndroidManifest.xmlに次のパーミッションを追加してください。

<uses-permission android:name="android.permission.RECORD_AUDIO" />

これも結構わすれがちです。

以上でVisualizerの説明は終わりです、簡単そうで面倒くさいです。
MediaPlayerは、けっこう扱いがややこしいクラスなので、ラッパークラスやらサービスやらでがちがちに作ってしまっていたりします。これからAudioFXを取り入れる場合には、そのへんをよく考慮しておかないとプログラムがぐちゃぐちゃになってしまうので十分注意してください。

次は、イコライザーですね。
1月はちょっと忙しいので2月あたりにエントリーできれば、、と思っています。
それか、優秀なテックブースターの方ならやってしまわれるかも?

それから、添付のプログラムは十分にテストできていないので何か問題があるかもしれません。
怪しいところがあればご指摘いただけばと思います。

それではこのへんで。

TestVisualizerプロジェクトのダウンロード

#プロジェクト添付の音楽ファイルは
http://incompetech.com/
ここからダウンロードしたCreative Commonsな音楽データです。