Audio-Debugging

In diesem Artikel werden einige Tipps und Tricks zum Debuggen von Android-Audio beschrieben.

T-Stück Waschbecken

Die „Tee-Senke“ ist eine AudioFlinger-Debugging-Funktion, die nur in benutzerdefinierten Builds verfügbar ist und dazu dient, ein kurzes Fragment aktueller Audiodaten für eine spätere Analyse aufzubewahren. Dies ermöglicht einen Vergleich zwischen dem, was tatsächlich abgespielt oder aufgenommen wurde, und dem, was erwartet wurde.

Aus Datenschutzgründen ist die Tee-Senke sowohl zur Kompilierungszeit als auch zur Laufzeit standardmäßig deaktiviert. Um die Tee-Senke verwenden zu können, müssen Sie sie durch erneutes Kompilieren und Festlegen einer Eigenschaft aktivieren. Stellen Sie sicher, dass Sie diese Funktion deaktivieren, nachdem Sie mit dem Debuggen fertig sind. Die Tee-Senke sollte in Produktions-Builds nicht aktiviert bleiben.

Die Anweisungen in diesem Abschnitt gelten für Android 7.x und höher. Ersetzen Sie für Android 5.x und 6.x /data/misc/audioserver durch /data/misc/media . Darüber hinaus müssen Sie einen Userdebug- oder Eng-Build verwenden. Wenn Sie einen Userdebug-Build verwenden, deaktivieren Sie Verity mit:

adb root && adb disable-verity && adb reboot

Einrichtung zur Kompilierungszeit

  1. cd frameworks/av/services/audioflinger
  2. Bearbeiten Sie Configuration.h .
  3. Kommentieren Sie #define TEE_SINK aus.
  4. Erstellen Sie libaudioflinger.so neu.
  5. adb root
  6. adb remount
  7. Übertragen oder synchronisieren Sie die neue libaudioflinger.so mit /system/lib des Geräts.

Laufzeit-Setup

  1. adb shell getprop | grep ro.debuggable
    Bestätigen Sie, dass die Ausgabe lautet: [ro.debuggable]: [1]
  2. adb shell
  3. ls -ld /data/misc/audioserver

    Bestätigen Sie, dass die Ausgabe wie folgt lautet:

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

    Wenn das Verzeichnis nicht existiert, erstellen Sie es wie folgt:

    mkdir /data/misc/audioserver
    chown media:media /data/misc/audioserver
    
  4. echo af.tee=# > /data/local.prop
    Dabei ist der af.tee Wert eine unten beschriebene Zahl.
  5. chmod 644 /data/local.prop
  6. reboot

Werte für die af.tee-Eigenschaft

Der Wert von af.tee ist eine Zahl zwischen 0 und 7 und drückt die Summe mehrerer Bits aus, eines pro Merkmal. Eine kurze Erklärung der einzelnen Bits finden Sie im Code unter AudioFlinger::AudioFlinger() in AudioFlinger.cpp :

  • 1 = Eingabe
  • 2 = FastMixer-Ausgang
  • 4 = AudioRecord und AudioTrack pro Spur

Es gibt noch kein Bit für Deep Buffer oder Normal Mixer, aber mit „4“ können Sie ähnliche Ergebnisse erzielen.

Testen und erfassen Sie Daten

  1. Führen Sie Ihren Audiotest durch.
  2. adb shell dumpsys media.audio_flinger
  3. Suchen Sie in der dumpsys Ausgabe nach einer Zeile wie dieser:
    tee copied to /data/misc/audioserver/20131010101147_2.wav
    Dies ist eine PCM-WAV-Datei.
  4. Dann adb pull alle gewünschten /data/misc/audioserver/*.wav ab; Beachten Sie, dass spurspezifische Dump-Dateinamen nicht in der dumpsys Ausgabe erscheinen, aber beim Schließen des Tracks weiterhin in /data/misc/audioserver gespeichert werden.
  5. Überprüfen Sie die Dump-Dateien auf Datenschutzbedenken, bevor Sie sie mit anderen teilen.

Vorschläge

Probieren Sie diese Ideen aus, um nützlichere Ergebnisse zu erzielen:

  • Deaktivieren Sie Berührungstöne und Tastenklicks, um Unterbrechungen bei der Testausgabe zu reduzieren.
  • Maximieren Sie alle Lautstärken.
  • Deaktivieren Sie Apps, die Ton erzeugen oder über das Mikrofon aufnehmen, wenn sie für Ihren Test nicht von Interesse sind.
  • Trackspezifische Dumps werden nur gespeichert, wenn der Track geschlossen ist; Möglicherweise müssen Sie das Schließen einer App erzwingen, um deren spurspezifische Daten zu löschen
  • Führen Sie die dumpsys sofort nach dem Test durch. Der verfügbare Aufnahmeplatz ist begrenzt.
  • Um sicherzustellen, dass Sie Ihre Dump-Dateien nicht verlieren, laden Sie sie regelmäßig auf Ihren Host hoch. Es bleibt nur eine begrenzte Anzahl von Dump-Dateien erhalten; Ältere Dumps werden entfernt, nachdem dieses Limit erreicht ist.

Wiederherstellen

Wie oben erwähnt, sollte die Tee-Sink-Funktion nicht aktiviert bleiben. Stellen Sie Ihren Build und Ihr Gerät wie folgt wieder her:

  1. Machen Sie die Quellcodeänderungen in Configuration.h rückgängig.
  2. Erstellen Sie libaudioflinger.so neu.
  3. Übertragen oder synchronisieren Sie die wiederhergestellte libaudioflinger.so mit /system/lib des Geräts.
  4. adb shell
  5. rm /data/local.prop
  6. rm /data/misc/audioserver/*.wav
  7. reboot

media.log

ALOGx-Makros

Die standardmäßige Java-Sprachprotokollierungs-API im Android SDK ist android.util.Log .

Die entsprechende C-Sprach-API im Android NDK ist __android_log_print deklariert in <android/log.h> .

Im nativen Teil des Android-Frameworks bevorzugen wir Makros mit den Namen ALOGE , ALOGW , ALOGI , ALOGV usw. Sie werden in <utils/Log.h> deklariert und für die Zwecke dieses Artikels werden wir sie gemeinsam als ALOGx bezeichnen .

Alle diese APIs sind einfach zu verwenden und gut verständlich, sodass sie auf der gesamten Android-Plattform verbreitet sind. Insbesondere der mediaserver Prozess, der den AudioFlinger-Soundserver umfasst, nutzt ALOGx in großem Umfang.

Dennoch gibt es einige Einschränkungen für ALOGx und Co.:

  • Sie sind anfällig für „Protokoll-Spam“: Der Protokollpuffer ist eine gemeinsam genutzte Ressource, sodass er aufgrund nicht zusammenhängender Protokolleinträge leicht überlaufen kann, was zu fehlenden Informationen führt. Die ALOGV Variante ist zur Kompilierungszeit standardmäßig deaktiviert. Aber selbst wenn es aktiviert ist, kann es natürlich zu Protokoll-Spam kommen.
  • Die zugrunde liegenden Kernel-Systemaufrufe könnten blockieren, was möglicherweise zu einer Prioritätsumkehr und damit zu Messstörungen und Ungenauigkeiten führen könnte. Dies ist insbesondere für zeitkritische Threads wie FastMixer und FastCapture von Bedeutung.
  • Wenn ein bestimmtes Protokoll deaktiviert wird, um Protokoll-Spam zu reduzieren, gehen alle Informationen verloren, die von diesem Protokoll erfasst worden wären. Es ist nicht möglich, ein bestimmtes Protokoll nachträglich zu aktivieren, nachdem klar ist, dass das Protokoll interessant gewesen wäre.

NBLOG, media.log und MediaLogService

Die NBLOG APIs und der zugehörige media.log Prozess sowie MediaLogService Dienst bilden zusammen ein neueres Protokollierungssystem für Medien und sind speziell für die Lösung der oben genannten Probleme konzipiert. Wir werden den Begriff „media.log“ grob verwenden, um alle drei zu bezeichnen, aber streng genommen ist NBLOG die C++-Protokollierungs-API, media.log ist ein Linux-Prozessname und MediaLogService ist ein Android-Binderdienst zum Untersuchen der Protokolle.

Eine media.log „Zeitleiste“ ist eine Reihe von Protokolleinträgen, deren relative Reihenfolge beibehalten wird. Konventionell sollte jeder Thread seine eigene Zeitleiste verwenden.

Vorteile

Die Vorteile des media.log Systems bestehen darin, dass es:

  • Versendet das Hauptprotokoll nur dann mit Spam, wenn dies erforderlich ist.
  • Kann auch dann untersucht werden, wenn mediaserver abstürzt oder hängen bleibt.
  • Ist pro Zeitleiste nicht blockierend.
  • Bietet weniger Leistungsbeeinträchtigungen. (Natürlich ist keine Form der Protokollierung völlig unaufdringlich.)

Die Architektur

Das folgende Diagramm zeigt die Beziehung zwischen dem mediaserver Prozess und dem init Prozess, bevor media.log eingeführt wird:

Architektur vor media.log

Abbildung 1. Architektur vor media.log

Bemerkenswerte Punkte:

  • init forks und execs mediaserver .
  • init erkennt den Tod von mediaserver und führt bei Bedarf einen erneuten Fork durch.
  • ALOGx Protokollierung wird nicht angezeigt.

Das folgende Diagramm zeigt die neue Beziehung der Komponenten, nachdem media.log zur Architektur hinzugefügt wurde:

Architektur nach media.log

Abbildung 2. Architektur nach media.log

Wichtige Änderungen:

  • Clients verwenden NBLOG API, um Protokolleinträge zu erstellen und diese an einen Ringpuffer im gemeinsam genutzten Speicher anzuhängen.
  • MediaLogService kann den Inhalt des Ringpuffers jederzeit sichern.
  • Der Ringpuffer ist so konzipiert, dass eine Beschädigung des gemeinsam genutzten Speichers nicht zum Absturz MediaLogService führt und dennoch so viel Puffer ausgegeben werden kann, wie nicht von der Beschädigung betroffen ist.
  • Der Ringpuffer ist sowohl beim Schreiben neuer Einträge als auch beim Lesen vorhandener Einträge nicht blockierend und sperrenfrei.
  • Es sind keine Kernel-Systemaufrufe erforderlich, um in den Ringpuffer zu schreiben oder daraus zu lesen (außer optionalen Zeitstempeln).

Wo zu verwenden

Ab Android 4.4 gibt es in AudioFlinger nur noch wenige Protokollpunkte, die das media.log System verwenden. Obwohl die neuen APIs nicht so einfach zu verwenden sind wie ALOGx , sind sie auch nicht besonders schwierig. Wir empfehlen Ihnen, sich mit dem neuen Protokollierungssystem vertraut zu machen, wenn es unentbehrlich ist. Dies wird insbesondere für AudioFlinger-Threads empfohlen, die häufig, regelmäßig und ohne Blockierung ausgeführt werden müssen, wie z. B. die Threads FastMixer und FastCapture .

Wie benutzt man

Protokolle hinzufügen

Zuerst müssen Sie Ihrem Code Protokolle hinzufügen.

Verwenden Sie in FastMixer und FastCapture Threads Code wie diesen:

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

Da diese NBLog Timeline nur von den FastMixer und FastCapture Threads verwendet wird, besteht keine Notwendigkeit für einen gegenseitigen Ausschluss.

Verwenden Sie in anderen AudioFlinger-Threads mNBLogWriter :

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

Für andere Threads als FastMixer und FastCapture kann die NBLog Zeitleiste des Threads sowohl vom Thread selbst als auch von Binder-Vorgängen verwendet werden. NBLog::Writer bietet keinen impliziten gegenseitigen Ausschluss pro Zeitleiste. Stellen Sie daher sicher, dass alle Protokolle in einem Kontext erfolgen, in dem der Mutex- mLock des Threads gehalten wird.

Nachdem Sie die Protokolle hinzugefügt haben, erstellen Sie AudioFlinger neu.

Achtung: Pro Thread ist eine separate NBLog::Writer Timeline erforderlich, um die Thread-Sicherheit zu gewährleisten, da Timelines von Natur aus Mutexe auslassen. Wenn Sie möchten, dass mehr als ein Thread dieselbe Zeitleiste verwendet, können Sie dies mit einem vorhandenen Mutex schützen (wie oben für mLock beschrieben). Oder Sie können den Wrapper NBLog::LockedWriter anstelle von NBLog::Writer verwenden. Dies macht jedoch einen Hauptvorteil dieser API zunichte: ihr nicht blockierendes Verhalten.

Die vollständige NBLog API finden Sie unter frameworks/av/include/media/nbaio/NBLog.h .

Aktivieren Sie media.log

media.log ist standardmäßig deaktiviert. Es ist nur aktiv, wenn die Eigenschaft ro.test_harness 1 ist. Sie können es aktivieren, indem Sie:

adb root
adb shell
echo ro.test_harness=1 > /data/local.prop
chmod 644 /data/local.prop
reboot

Die Verbindung geht während des Neustarts verloren, also:

adb shell
Der Befehl ps media zeigt nun zwei Prozesse an:
  • media.log
  • Medienserver

Notieren Sie sich die Prozess-ID des mediaserver für später.

Zeigen Sie die Zeitleisten an

Sie können jederzeit manuell einen Protokoll-Dump anfordern. Dieser Befehl zeigt Protokolle aller aktiven und letzten Zeitleisten an und löscht sie anschließend:

dumpsys media.log

Beachten Sie, dass Zeitleisten konstruktionsbedingt unabhängig voneinander sind und es keine Möglichkeit gibt, Zeitleisten zusammenzuführen.

Stellen Sie Protokolle nach dem Tod des Medienservers wieder her

Versuchen Sie nun, den mediaserver Prozess zu beenden: kill -9 # , wobei # die Prozess-ID ist, die Sie zuvor notiert haben. Im Haupt- logcat sollte ein Dump von media.log angezeigt werden, der alle Protokolle enthält, die zum Absturz geführt haben.

dumpsys media.log