オーディオのデバッグ

この記事では、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

コンパイル時の設定

  1. cd frameworks/av/services/audioflinger
  2. Configuration.h を編集します。
  3. #define TEE_SINK のコメントを削除します。
  4. libaudioflinger.so を再ビルドします。
  5. adb root
  6. adb remount
  7. 新しい libaudioflinger.so をデバイスの /system/lib にプッシュまたは同期します。

ランタイムの設定

  1. adb shell getprop | grep ro.debuggable
    出力が [ro.debuggable]: [1] であることを確認します。
  2. adb shell
  3. ls -ld /data/misc/audioserver

    出力が次のとおりであることを確認します。

    drwx------ media media ... media
    

    ディレクトリが存在しない場合は、次のように作成します。

    mkdir /data/misc/audioserver
    chown media:media /data/misc/audioserver
    
  4. echo af.tee=# > /data/local.prop
    ここで、af.tee の値は以下の数値です。
  5. chmod 644 /data/local.prop
  6. reboot

af.tee プロパティの値

af.tee の値は 0~7 の数値で、1 つの機能ごとに複数のビットの合計で表します。各ビットの簡単な説明については、AudioFlinger.cppAudioFlinger::AudioFlinger() のコードをご覧ください。

  • 1 = 入力
  • 2 = FastMixer の出力
  • 4 = トラックごとの AudioRecord と AudioTrack

ディープ バッファまたはノーマル ミキサーのビットはまだ存在しませんが、「4」を使用すると同様の結果が得られます。

データのテストと取得

  1. オーディオのテストを実行します。
  2. adb shell dumpsys media.audio_flinger
  3. dumpsys 出力で次のような行を検索します。
    tee copied to /data/misc/audioserver/20131010101147_2.wav
    これは PCM .wav ファイルです。
  4. 次に adb pull で、目的の /data/misc/audioserver/*.wav ファイルをプルします。トラック固有のダンプファイル名は dumpsys の出力には表示されませんが、トラックを閉じる際に /data/misc/audioserver に保存されます。
  5. 他のユーザーと共有する前に、ダンプファイルにプライバシー上の問題がないか確認します。

ヒント

より有用な結果を得るには、次の方法を試してください。

  • タップ音とキークリックを無効にして、テスト出力の中断を少なくします。
  • すべてのボリュームを最大にします。
  • 音の出るアプリやマイクから音を拾って録音するアプリで、テストに無関係なものについては無効にします。
  • トラック固有のダンプは、トラックを閉じたときにのみ保存されます。トラック固有のデータをダンプするには強制的にアプリを終了する必要があります。
  • テストの直後に dumpsys を実行します。なお、この場合は使用可能な記録容量に制限があります。
  • 紛失に備え、定期的にホストにダンプファイルをアップロードしてください。保持できるダンプファイルの数は限られており、上限に達すると古いダンプは削除されます。

復元

前述のように、ティーシンク機能を無効にする必要があります。 ビルドとデバイスを復元する方法は次のとおりです。

  1. ソースコードの変更を Configuration.h に戻します。
  2. libaudioflinger.so を再ビルドします。
  3. 復元した libaudioflinger.so をデバイスの /system/lib にプッシュまたは同期します。
  4. adb shell
  5. rm /data/local.prop
  6. rm /data/misc/audioserver/*.wav
  7. reboot

media.log

ALOGx マクロ

Android SDK での Java 言語の標準ロギング API は android.util.Log です。

これに対応する Android NDK の C 言語 API は、<android/log.h> で宣言されている __android_log_print です。

Android フレームワークのネイティブ部分では、ALOGEALOGWALOGIALOGV などの名称のマクロを使います。この記事では、<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 プロセスの関係を示したものです。

media.log 導入前のアーキテクチャ

図 1.media.log 導入前のアーキテクチャ

重要な点

  • initmediaserver をフォークして実行します。
  • initmediaserver の停止を検出し、必要な場合は再度フォークします。
  • ALOGx ログは表示されません。

以下の図は、アーキテクチャに media.log が追加された後のコンポーネント間の関係を示しています。

media.log 導入後のアーキテクチャ

図 2。media.log 導入後のアーキテクチャ

重要な変更点

  • クライアントは NBLOG API を使用してログエントリを作成し、共有メモリ内の循環バッファの末尾に追加します。
  • MediaLogService は、循環バッファの内容をいつでもダンプできます。
  • 循環バッファは、共有メモリの破損で MediaLogService がクラッシュしないように設計されており、破損の影響を受けていないバッファを同様にダンプできます。
  • 循環バッファは、新しいエントリの書き込みと既存のエントリの読み取りの両方について非ブロッキングであり、ロックフリーで使用できます。
  • 循環バッファの書き込み / 読み取り(オプションのタイムスタンプを除く)に、カーネルのシステムコールは不要です。

使用できる場所

Android 4.4 以降では、media.log システムを使用する AudioFlinger のログポイントは数か所のみです。新しい API は ALOGx のように簡単には使用できませんが、極端に難しいわけでもありません。必須となる場合に備えて、新しいロギング システムについて学ぶことをおすすめします。特に、FastMixerFastCapture スレッドなどの、ブロックが起こらず定期的かつ頻繁に実行する必要がある AudioFlinger スレッドに適しています。

使用方法

ログを追加

まず、コードにログを追加する必要があります。

FastMixerFastCapture スレッドでは、次のようにコードを使用します。

logWriter->log("string");
logWriter->logf("format", parameters);
logWriter->logTimestamp();

このように NBLog タイムラインは FastMixerFastCapture スレッドのみで使用されるため、相互に排他制御する必要はありません。

他の AudioFlinger スレッドでは、mNBLogWriter を以下のように使用します。

mNBLogWriter->log("string");
mNBLogWriter->logf("format", parameters);
mNBLogWriter->logTimestamp();

FastMixerFastCapture 以外のスレッドの場合、スレッドの 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 を指定します。メインの logcatmedia.log のダンプを確認できます。これには、クラッシュに至るまでのすべてのログが表示されます。

dumpsys media.log