この記事では、Android オーディオのデバッグを行う際のヒントとコツを紹介します。
ティーシンク
「ティーシンク」はカスタムビルドでのみ使用できる AudioFlinger デバッグ機能です。後で解析を行うために直近の音声の短い断片を保持します。これにより、実際に再生または録音された音声を予測されていた音声と比較できます。
プライバシー保護のため、デフォルトで、ティーシンクはコンパイル時および実行時の両方で無効になっています。ティーシンクを使用するには、再コンパイルしたうえでプロパティを設定して有効にする必要があります。デバッグが完了したら、必ずこの機能を無効にしてください。製品版のビルドでティーシンクを有効のままにしないでください。
このセクションの手順は、Android 7.x 以降を対象としています。Android 5.x、6.x の場合は、/data/misc/audioserver
を /data/misc/media
に置き換えください。それに加えて、userdebug ビルドまたは eng ビルドを使用する必要があります。userdebug ビルドを使用する場合は、次のように verity を無効にします。
adb root && adb disable-verity && adb reboot
コンパイル時の設定
cd frameworks/av/services/audioflinger
Configuration.h
を編集します。#define TEE_SINK
のコメントを削除します。libaudioflinger.so
を再ビルドします。adb root
adb remount
- 新しい
libaudioflinger.so
をデバイスの/system/lib
にプッシュまたは同期します。
ランタイムの設定
adb shell getprop | grep ro.debuggable
出力が[ro.debuggable]: [1]
であることを確認します。adb shell
ls -ld /data/misc/audioserver
出力が次のとおりであることを確認します。
drwx------ media media ... media
ディレクトリが存在しない場合は、次のように作成します。
mkdir /data/misc/audioserver
chown media:media /data/misc/audioserver
echo af.tee=# > /data/local.prop
ここで、af.tee
の値は以下の数値です。chmod 644 /data/local.prop
reboot
af.tee プロパティの値
af.tee
の値は 0~7 の数値で、1 つの機能ごとに複数のビットの合計で表します。各ビットの簡単な説明については、AudioFlinger.cpp
の AudioFlinger::AudioFlinger()
のコードをご覧ください。
- 1 = 入力
- 2 = FastMixer の出力
- 4 = トラックごとの AudioRecord と AudioTrack
ディープ バッファまたはノーマル ミキサーのビットはまだ存在しませんが、「4」を使用すると同様の結果が得られます。
データのテストと取得
- オーディオのテストを実行します。
adb shell dumpsys media.audio_flinger
dumpsys
出力で次のような行を検索します。
tee copied to /data/misc/audioserver/20131010101147_2.wav
これは PCM .wav ファイルです。- 次に
adb pull
で、目的の/data/misc/audioserver/*.wav
ファイルをプルします。トラック固有のダンプファイル名はdumpsys
の出力には表示されませんが、トラックを閉じる際に/data/misc/audioserver
に保存されます。 - 他のユーザーと共有する前に、ダンプファイルにプライバシー上の問題がないか確認します。
ヒント
より有用な結果を得るには、次の方法を試してください。
- タップ音とキークリックを無効にして、テスト出力の中断を少なくします。
- すべてのボリュームを最大にします。
- 音の出るアプリやマイクから音を拾って録音するアプリで、テストに無関係なものについては無効にします。
- トラック固有のダンプは、トラックを閉じたときにのみ保存されます。トラック固有のデータをダンプするには強制的にアプリを終了する必要があります。
- テストの直後に
dumpsys
を実行します。なお、この場合は使用可能な記録容量に制限があります。 - 紛失に備え、定期的にホストにダンプファイルをアップロードしてください。保持できるダンプファイルの数は限られており、上限に達すると古いダンプは削除されます。
復元
前述のように、ティーシンク機能を無効にする必要があります。 ビルドとデバイスを復元する方法は次のとおりです。
- ソースコードの変更を
Configuration.h
に戻します。 libaudioflinger.so
を再ビルドします。- 復元した
libaudioflinger.so
をデバイスの/system/lib
にプッシュまたは同期します。 adb shell
rm /data/local.prop
rm /data/misc/audioserver/*.wav
reboot
media.log
ALOGx マクロ
Android SDK での Java 言語の標準ロギング API は android.util.Log です。
これに対応する Android NDK の C 言語 API は、<android/log.h>
で宣言されている __android_log_print
です。
Android フレームワークのネイティブ部分では、ALOGE
、ALOGW
、ALOGI
、ALOGV
などの名称のマクロを使います。この記事では、<utils/Log.h>
で宣言されているこれらのマクロを総称して「ALOGx
」と呼びます。
これらの API はすべて使いやすく理解しやすいため、Android プラットフォーム全体で使われています。特に、AudioFlinger サウンド サーバーを含む mediaserver
プロセスでは、ALOGx
が幅広く使用されます。
ただ、ALOGx
や同様のマクロにはいくつかの制約があります。
-
「ログスパム」の影響に対して敏感です。ログバッファは共有リソースであるため、無関係なログエントリが原因でオーバーフローしやすく、それによって情報が失われることがあります。
ALOGV
バリアントは、デフォルトでコンパイル時には無効になりますが、有効の場合、ログスパムが発生する場合があります。 -
基盤となるカーネルのシステムコールがブロックされる場合があります。その結果、優先度の逆転が発生し、その後に測定に混乱が生じたり、測定値が不正確になったりする可能性があります。
FastMixer
およびFastCapture
のようなタイムクリティカルなスレッドでは、この点に特に注意する必要があります。 - ログスパムを減らすために特定のログが無効になっている場合、そのログによって記録できるはずの情報は失われます。特定のログを調べる必要があったことが後でわかっても、ログを遡及的に有効にすることはできません。
NBLOG、media.log、MediaLogService
NBLOG
API、関連する media.log
プロセス、MediaLogService
サービスは、併用することでメディア用のロギング システムを新たに構築し、上記の問題に対処するように設計されています。ここでは上記 3 つの総称として「media.log」という用語を使っていますが、厳密には NBLOG
が C++ ロギング API、media.log
が Linux のプロセス名、MediaLogService
がログを調べるための Android バインダー サービスのことを意味しています。
media.log
の「タイムライン」は、相対的な順序が保持される一連のログエントリです。慣例的に、各スレッドには独自のタイムラインを使用する必要があります。
メリット
media.log
システムには以下のメリットがあります。
- 必要がない限り、メインログを大量に記録しません。
mediaserver
がクラッシュまたはハングしても、調査が可能です。- タイムラインごとに非ブロッキングです。
- パフォーマンスの低下を抑えられます (ロギングが構成されていない場合、無駄なログは一切発生しません)。
アーキテクチャ
次の図は、media.log
を導入する前の mediaserver
プロセスと init
プロセスの関係を示したものです。
重要な点
init
がmediaserver
をフォークして実行します。init
はmediaserver
の停止を検出し、必要な場合は再度フォークします。ALOGx
ログは表示されません。
以下の図は、media.log
がアーキテクチャに追加された後のコンポーネント間の関係を示しています。
重要な変更点
- クライアントは
NBLOG
API を使用してログエントリを作成し、共有メモリ内の循環バッファの末尾に追加します。 MediaLogService
は、循環バッファの内容をいつでもダンプできます。- 循環バッファは、共有メモリの破損で
MediaLogService
がクラッシュしないように設計されており、破損の影響を受けていないバッファを同様にダンプできます。 - 循環バッファは、新しいエントリの書き込みと既存のエントリの読み取りの両方について非ブロッキングであり、ロックフリーで使用できます。
- 循環バッファの書き込み / 読み取り(オプションのタイムスタンプを除く)に、カーネルのシステムコールは不要です。
使用できる場所
Android 4.4 以降では、media.log
システムを使用する AudioFlinger のログポイントは数か所のみです。新しい API は ALOGx
のように簡単には使用できませんが、極端に難しいわけでもありません。必須となる場合に備えて、新しいロギング システムについて学ぶことをおすすめします。特に、FastMixer
や FastCapture
スレッドなどの、ブロックが起こらず定期的かつ頻繁に実行する必要がある AudioFlinger スレッドに適しています。
使用方法
ログを追加
まず、コードにログを追加する必要があります。
FastMixer
と FastCapture
スレッドでは、次のようにコードを使用します。
logWriter->log("string"); logWriter->logf("format", parameters); logWriter->logTimestamp();
このように NBLog
タイムラインは FastMixer
と FastCapture
スレッドのみで使用されるため、相互に排他制御する必要はありません。
他の AudioFlinger スレッドでは、mNBLogWriter
を以下のように使用します。
mNBLogWriter->log("string"); mNBLogWriter->logf("format", parameters); mNBLogWriter->logTimestamp();
FastMixer
と FastCapture
以外のスレッドの場合、スレッドの NBLog
タイムラインは、スレッド自体と Binder の操作の両方で使用できます。NBLog::Writer
ではタイムラインごとに暗黙的な相互排他が行われないため、スレッドのミューテックスの mLock
が保持されるコンテキスト内に、すべてのログがあることを必ず確認してください。
ログを追加したら、AudioFlinger を再ビルドします。
注意: タイムラインはミューテックスを省略するように設計されているため、スレッドの安全性を確保するためには、スレッドごとに独立した NBLog::Writer
タイムラインが必要になります。複数のスレッドで同じタイムラインを使用する場合は、既存のミューテックスで保護できます(上記の mLock
の箇所を参照)。または、NBLog::Writer
の代わりに NBLog::LockedWriter
ラッパーを使用できます。ただし、この API の主なメリットである非ブロック動作は無効になります。
完全な NBLog
API は、frameworks/av/include/media/nbaio/NBLog.h
にあります。
media.log を有効にする
デフォルトで、media.log
は無効に設定されています。ro.test_harness
プロパティが 1
の場合にのみ有効です。次の方法で有効にできます。
adb root
adb shell
echo ro.test_harness=1 > /data/local.prop
chmod 644 /data/local.prop
reboot
再起動中に接続が失われるため、次のように入力します。
adb shell
ps media
コマンドを実行すると、次の 2 つのプロセスが表示されます。
- media.log
- mediaserver
後で使用するため、mediaserver
のプロセス ID をメモします。
タイムラインの表示
ログダンプはいつでも手動でリクエストできます。 このコマンドは、すべての有効なタイムラインと最近のタイムラインのログを表示し、クリアします。
dumpsys media.log
設計上、タイムラインは独立しており、タイムラインを統合する機能はありません。
mediaserver 停止後のログの復元
mediaserver
プロセスを kill -9 #
で強制終了します。# にはあらかじめ控えておいたプロセス ID を指定します。メインの logcat
で media.log
のダンプを確認できます。これには、クラッシュに至るまでのすべてのログが表示されます。
dumpsys media.log