Android 4.4 KitKatで追加されたLoudnessEnhancerを試してみる


今日は11月11日、「Android 4.4 KitKat 冬コミ原稿リレー」で僕こと「夜子まま」のターンです。
技術系ブログで、余計な前振りは必要ない!というのはわかるけど、書かずにはいられない。いや書かせてほしい!

だって、当初この企画「Android Advent Calendar」だったんです。僕は去年サボってしまったので、今年は書きたいなぁーということで立候補したのですが、スケジュールが決まり確認すると、え?締め切り来週の月曜日?「うそ!」という状況なのです。

そして、Advent Calendarって12月だよね?11月じゃ違うでしょう。というツッコミもあり名前も「冬コミ原稿リレー」に変わってしまったわけで、「うぉおおおい(心の叫び)」、いやぁ、やると言ったんだからやりますよ。となると、12月のAdventCalendarは別にたちが上がるわけで、今年は書きたいというマイルールがあるならもっかい書かなければならない。。いや無理だろうな・・・

さてと、みっともない愚痴はこの辺にして、僕が紹介するのはKitKatで新たに追加されたLoudnessEnhancerです。

なぜこれか?というと、実は僕は趣味で音楽プレイヤーを作っているため、毎回新しいSDKがでると真っ先にチェックするのは音楽関係の機能だったりします。なので、音に直接関係のあるLoudnessEnhancerなるものがどういうものなのか?は調べずにはいられない。

完全に俺得です。冬コミで販売する内容も自分が書きたいこと!なので、音楽攻めでいく予定です。

というわけで早速調べたことを解説します。

KitKatのMediaまわりで追加された機能

以下、MediaPlayer絡みで主だった変更をハイライトから抜粋

・Audio Tunneling to DSP
これはMediaPlayerなどからの再生からハードに搭載されているDSP(Digital Signal Processor)を経由して再生するようにしたので低消費電力になったよ。ということです。非常にありがたいのですが、これを検証するとなるとDSP有りと無しでそれ以外はスペックが同じ端末を準備し、そして音楽を再生しながらログを最終し、消費電力にどの程度違いがあるのか?ということを検証しないといけないとおもったので関わらないことにしました。
もしかしたらソフトで切り替えができるといった方法があるのかもしれないけども、そこまで調べるのはやめました。どこからかOn/Offの方法について報告があれば調べてみたい。

・Audio monitoring
これは、再生中のオーディオのPeakとRMSの計測ができるようになった。ということです。このネタは本記事に含めます。

・Loudness enhancer
これが、本記事のメインである、ラウドネスエンハンサーです。詳しくは後ほど

MeasurePeakRMS

現在再生中のオーディオのPeakとRMSの取得が可能になりました。
具体的には、再生中のオーディオにアタッチされたVisualizerに計測モードを設定し、任意のタイミングで取得します。

private void codeSample1(int audioSessionId){
    mVisualizer = new Visualizer(audioSessionId);
    mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
    mVisualizer.setEnabled(true);

    mTimer = new Timer();
    mTimer.schedule(new TimerTask() {
        @Override
        public void run() {
            MeasurementPeakRms measurement = new MeasurementPeakRms();
            mVisualizer.getMeasurementPeakRms(measurement);
            Log.d(TAG, "PEAK:"+measurement.mPeak+" RMS:"+measurement.mRms);
        }
    }, 100, 100);
}

3行目で setMeasurementMode(mode) を指定しています。このモードには二種類あり、

Visualizer.MEASUREMENT_MODE_NONE 計測しない
Visualizer.MEASUREMENT_MODE_PEAK_RMS 計測する

計測をするなら Visualizer.MEASUREMENT_MODE_PEAK_RMS を指定しておく必要があります。
上記のプログラムを実行すると次のようなログが出力されます。

11-11 12:44:40.160: D/LevelMeterMain(10195): PEAK:-2415 RMS:-3701
11-11 12:44:40.260: D/LevelMeterMain(10195): PEAK:-2415 RMS:-3626
11-11 12:44:40.370: D/LevelMeterMain(10195): PEAK:-2415 RMS:-3609
11-11 12:44:40.460: D/LevelMeterMain(10195): PEAK:-2415 RMS:-3677

PEAK/RMSの範囲は-96dB〜0dBです。マイナスなので感覚として受け入れづらいですが、0dBで最も音が大きく聞こえ、-96dBで無音になります。

どうしてこの範囲になるのかについて、それは僕が音に関して素人なので説明できないのですが、色々調べたり聞いたりしてみたところデシベル(L)は下記の数式のように2点間の電圧比(V1、V0)の対数から求めることができます。(参考:http://www.kawakawa.net/note/fresh/chap1/chap1.html

L = 20log10(V1/V0)

そして、実際にPeakの値を求めている箇所は下記のコードです。

if (peak_u16 == 0) {
    p_int_reply_data[MEASUREMENT_IDX_PEAK] = -9600; //-96dB
} else {
    p_int_reply_data[MEASUREMENT_IDX_PEAK] = (int32_t) (2000 * log10(peak_u16 / 32767.0f));
}

これをみるとV1には計測された電圧peak_u16が入り、V0には32767(16bitの最大値?)が入っています。20でなく2000になっているのは100倍することで小数点第二位以下を切り捨て整数に揃えるためかと予想されます。最大値が-9600となっている理由はわかりません。さすがにシリアル通信の9600bpsとは無関係だとおもいますが・・・(震え声)
人間に聞こえる音が96dB程度だからなのかな?とか、そもそそもAndroid端末で出力できる音が96dBでリミットがかかっているからなのかな?実際には定かでありません。この辺りについて詳しい人の助言が求められるところ。

(追記:Twitterで助言をいただきました。9600の理由は20*log(1/(2^15))を計算した値が-96.3295986になるためです。ずっと2^15のところを32767で計算をしていたため数字があわなくて困っていました。ありがとうございます。)

ちなみに、PeakとRMSの違いですが、Peakは一定周期内で計測された最大値であるのに対し、RMSはそれぞれの瞬間の値を自乗し平均をとりその平方根になります。詳しくはこのサイトに解説があります。(参考:http://www.g200kg.com/jp/docs/dic/rms.html

下記のコードをみると、RMSは各計測地点におけるRMS値の平均を求め平方根を求めているのがわかります。

/* only use actual measurements, otherwise the first RMS measure happening before
 * MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS have been played will always be artificially
 * low */
uint32_t i;
for (i=0 ; i < visu_ctxt->meas_wndw_size_in_buffers ; i++) {
    if (visu_ctxt->past_meas[i].is_valid) {
        if (visu_ctxt->past_meas[i].peak_u16 > peak_u16) {
            peak_u16 = visu_ctxt->past_meas[i].peak_u16;
        }
        sum_rms_squared += visu_ctxt->past_meas[i].rms_squared;
        nb_valid_meas++;
    }
}
float rms = nb_valid_meas == 0 ? 0.0f : sqrtf(sum_rms_squared / nb_valid_meas);

というわけで、Peak/RMSに関するお話はここまで、次はLoudnessEnhancerを使ってみます。

LoudnessEnhancer

LoudnessEnhancerは音に厚みを加える事ができるオーディオ効果です。厚みといってもピンと来ない人も多いかと思います。
実際、僕自身もなんとなくでしか理解してなく、説明をしろといわれてもうまく説明できないです。身近なものでいえばAVコンポなんかについてますが、最近はAVコンポをもっている人も少ない気もします。
僕の思い出としては、高校生のときに親に買ってもらったAVコンポにこのラウドネスというものがついていて、深夜にこれをONにしたらいきなり大音量になって慌ててボリュームを小さくした記憶があり、今だと恥ずかしい音楽が流れて大惨事かもしれないね。
このようにラウドネス効果は小さい音を大きくしてくれます。しかし、そうすると音が大きくなるならマスターボリュームと何が違うの?と思うかもしれません。
実際にラウドネスはそういう質問がよくされるエフェクトのようです。
分かりやすい例では、安っぽいヘッドフォンやスピーカーを使っていると有り難いものだったりします。
どういうことかというと、安っぽいヘッドフォンだと小さい音はよく聴こえないし、大きい音は割れて耳障りになったりします。
そういうときにラウドネスを使うと、聴こえなかった小さい音とか割れた大きい音を調整し聴こえやすいようにしてくれるため、なんとなく高級ヘッドフォンを手にした気分になれるオーディオ効果なのです。
と、このように説明をすると音の厚みとはどこにいったの?と思うかもしれないですが、実際のところラウドネスは安いヘッドフォンのためだけのものはなく、そもそも人間の耳をヘッドフォンだとすると、耳にだって聞こえやすい周波数というものが存在します。
とくに小さい周波数の音や大きい周波数の音は他の周波数と同様に音量を上げても聞き取りにくいです。なので実際に人間にとって感覚的に音の聞こえる感じを各周波数毎に合わせるように考えだされたのがラウドネスと呼ばれるものです。
このラウドネスに従い音量をあげると、どの周波数も同じように音量があがるため、いままで聞こえにくかった音が明瞭になり、音に厚みがでるように感じるというわけです。(参考:http://www.g200kg.com/jp/docs/dic/loudness.html

と、前置はこの辺にして実際に使ってみましょう。

//ラウドネスエフェクトの生成
mLoudness = new LoudnessEnhancer(mAudioSessionId);

seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

<省略>

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress,
            boolean fromUser) {
		//シークバーの値をラウドネス値として設定
        mLoudness.setTargetGain(progress);
        TextView textPos = (TextView) ((ViewGroup) seekBar.getParent())
                .findViewById(R.id.textPos);
        textPos.setText(Integer.toString(progress));
    }
});

checkbox1.setChecked(mLoudness.getEnabled());
checkbox1.setOnCheckedChangeListener(new OnCheckedChangeListener() {

    @Override
    public void onCheckedChanged(CompoundButton buttonView,
            boolean isChecked) {
		//ラウドネス効果のOn/Off
        mLoudness.setEnabled(isChecked);
    }
});

LoudnessEnhancerは他のBassBoostやEqulizerと同じようにエフェクトをかけたいオーディオのセッションIDをコンストラクターに与えて生成し、setEnable(boolean)で有効・無効にします。その後、setTargetGain(int)でラウドネス効果を与えています。
設定できる値は整数(マイナスも設定できます)0以上の整数です。ラウドネスの単位はmBとなっています。実際に値を与えるとPeak/RMSの値が設定された値だけ引かれているような動作をしました。Peak/RMSと連動するならプログラムとしては扱いやすいかなと思います。

ゴール

さて、これでひと通りの解説は終わってしまったのですが、このままだと他のNFCやPrintingFramework,StorageAccessとくらべて見劣りし過ぎです。
しかも、このネタをこのまま同人誌にもっていってもネタ不足感が否めません。僕の章だけで2,3ページということもありえる。
それでも同人ならありなのですが、これだと羊会にのこのこ顔をだしても肉一切れだけ皿におかれるという仕打ちがまっているかもしれません。(切実)
仕方がないので、肉10切れ程度には膨らませるべく、今回はこれらを用いてアプリを1つ作ることにします。

タイトル「なんちゃってラウドネスコントロール」

これは音楽プレイヤーに組み込むもので、僕の持っているAndroid本体の音量ボタンではあまり小さい音量の設定が出来ないし、大きい音を設定すると音割れするのでその辺をラウドネスを使ってうまくカバーするようにします。具体的には、今のLaoudneEnhancerだとマスターボリュームとラウドネスが自動的に連動してくれないので、その辺はプログラムで頑張る。
その頑張る部分をなんちゃってで実装してみようかなとおもっています。

実装

た、たた、ただいま実装中!
実装中のコードはこちらです。

えーと、コードの解説は同人誌でやる予定です。
それから、実装中のアプリの実行画面はこんなかんじ!
アプリではボリュームと連動していないので、単にラウドネスが大きくなったり小さくなったりするだけです、ラウドネスを調節するとPeak/RMSが変化するのがわかると思います。

スクリーンショット 2013-11-11 17.04.15

メーターがかっこいいですよ。冬コミに期待してください。

ではでは、次の方よろしくお願いします。

One Comment