Mit Multimedia-Tunneling können komprimierte Videodaten über einen Hardware-Videodecoder direkt an ein Display gesendet werden, ohne dass sie von App-Code oder Android-Framework-Code verarbeitet werden. Der gerätespezifische Code unter dem Android-Stack bestimmt, welche Videoframes an das Display gesendet werden und wann sie gesendet werden. Dazu werden die Zeitstempel der Videoframe-Präsentation mit einer der folgenden internen Uhrtypen verglichen:
Für die On-Demand-Videowiedergabe unter Android 5 oder höher: eine
AudioTrack
-Uhr, die mit den Zeitstempeln der Audiopräsentation synchronisiert ist, die von der App übergeben werdenFür die Wiedergabe von Liveübertragungen unter Android 11 oder höher: eine Programmreferenzzeit (Programm Reference Clock, PCR) oder Systemzeituhr (STC), die von einem Tuner gesteuert wird
Hintergrund
Bei der herkömmlichen Videowiedergabe auf Android-Geräten wird die App benachrichtigt, wenn ein komprimierter Videoframe decodiert wurde. Die App gibt den decodierten Videoframe dann an das Display frei, damit er zur selben Systemuhrzeit wie der entsprechende Audioframe gerendert wird. Dazu ruft sie bisherige AudioTimestamps
-Instanzen ab, um das richtige Timing zu berechnen.
Da die getunnelte Videowiedergabe den App-Code umgeht und die Anzahl der Prozesse reduziert, die auf das Video wirken, kann sie je nach OEM-Implementierung ein effizienteres Video-Rendering ermöglichen. Es kann auch eine genauere Videofrequenz und Synchronisierung mit der ausgewählten Uhr (PRC, STC oder Audio) bereitstellen, da Zeitprobleme vermieden werden, die durch potenzielle Verzerrungen zwischen dem Timing von Android-Anfragen zum Rendern von Videos und dem Timing echter Hardware-vSyncs entstehen. Durch Tunneling kann jedoch auch die Unterstützung für GPU-Effekte wie Unkenntlichmachung oder abgerundete Ecken in Bild-im-Bild-Fenstern (BiB) reduziert werden, da die Zwischenspeicher den Android-Grafikstapel umgehen.
Das folgende Diagramm zeigt, wie das Tunneling die Videowiedergabe vereinfacht.
Abbildung 1. Vergleich der herkömmlichen und der getunnelten Videowiedergabe
Für App-Entwickler
Da die meisten App-Entwickler eine Bibliothek für die Wiedergabeimplementierung verwenden, muss in den meisten Fällen nur diese Bibliothek für die getunnelte Wiedergabe neu konfiguriert werden. Folgen Sie der nachstehenden Anleitung, um einen getunnelten Videoplayer auf unterer Ebene zu implementieren.
So kannst du dir On-Demand-Videos unter Android 5 oder höher ansehen:
Erstellen Sie eine
SurfaceView
-Instanz.Erstellen Sie eine
audioSessionId
-Instanz.Erstellen Sie
AudioTrack
- undMediaCodec
-Instanzen mit der in Schritt 2 erstelltenaudioSessionId
-Instanz.Stellen Sie Audiodaten in der Warteschlange für
AudioTrack
mit dem Präsentationszeitstempel für den ersten Audioframe in den Audiodaten in die Warteschlange.
Wiedergabe von Livestreams unter Android 11 oder höher:
Erstellen Sie eine
SurfaceView
-Instanz.Rufen Sie eine
avSyncHwId
-Instanz vonTuner
ab.Erstellen Sie
AudioTrack
- undMediaCodec
-Instanzen mit der in Schritt 2 erstelltenavSyncHwId
-Instanz.
Der Ablauf des API-Aufrufs wird in den folgenden Code-Snippets dargestellt:
aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);
// configure for audio clock sync
aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
// or, for tuner clock sync (Android 11 or higher)
new tunerConfig = TunerConfiguration(0, avSyncId);
aab.setTunerConfiguration(tunerConfig);
if (codecName == null) {
return FAILURE;
}
// configure for audio clock sync
mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// or, for tuner clock sync (Android 11 or higher)
mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);
Verhalten der On-Demand-Videowiedergabe
Da die getunnelte On-Demand-Videowiedergabe implizit an die AudioTrack
-Wiedergabe gebunden ist, hängt das Verhalten der getunnelten Videowiedergabe möglicherweise vom Verhalten der Audiowiedergabe ab.
Auf den meisten Geräten wird ein Videoframe standardmäßig erst gerendert, wenn die Audiowiedergabe beginnt. Möglicherweise muss die App jedoch einen Videoframe rendern, bevor die Audiowiedergabe gestartet wird, z. B. um dem Nutzer die aktuelle Videoposition beim Suchen anzuzeigen.
Wenn der erste Videoframe in der Warteschlange sobald er decodiert wurde gerendert werden soll, setze den Parameter
PARAMETER_KEY_TUNNEL_PEEK
auf1
. Wenn komprimierte Videoframes in der Warteschlange neu angeordnet werden (z. B. wenn B-Frames vorhanden sind), muss der erste angezeigte Videoframe immer ein I-Frame sein.Wenn Sie nicht möchten, dass der erste Frame aus der Warteschlange bis zum Beginn der Audiowiedergabe gerendert wird, setzen Sie diesen Parameter auf
0
.Wenn dieser Parameter nicht festgelegt ist, bestimmt der OEM das Verhalten des Geräts.
Wenn
AudioTrack
keine Audiodaten zur Verfügung gestellt werden und die Puffer leer sind (Audiounterlauf), wird die Videowiedergabe angehalten, bis weitere Audiodaten geschrieben werden, da die Audiouhr nicht mehr fortschreitet.Während der Wiedergabe können in den Zeitstempeln der Audiopräsentation Unterbrechungen auftreten, die von der App nicht korrigiert werden können. In diesem Fall korrigiert der OEM negative Lücken, indem er den aktuellen Videoframe anhält, und positive Lücken, indem er entweder Videoframes weglässt oder stumme Audioframes einfügt (je nach OEM-Implementierung). Die Frame-Position von
AudioTimestamp
erhöht sich nicht durch eingefügte stumme Audioframes.
Für Gerätehersteller
Konfiguration
OEMs sollten einen separaten Videodecoder erstellen, um die getunnelte Videowiedergabe zu unterstützen.
Dieser Decoder sollte in der Datei media_codecs.xml
angeben, dass er die getunnelte Wiedergabe unterstützt:
<Feature name="tunneled-playback" required="true"/>
Wenn eine getunnelte MediaCodec
-Instanz mit einer Audiositzungs-ID konfiguriert ist, wird AudioFlinger
nach dieser HW_AV_SYNC
-ID abgefragt:
if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
int sessionId = 0;
try {
sessionId = (Integer)entry.getValue();
}
catch (Exception e) {
throw new IllegalArgumentException("Wrong Session ID Parameter!");
}
keys[i] = "audio-hw-sync";
values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
}
Bei dieser Abfrage ruft AudioFlinger
die HW_AV_SYNC
-ID vom primären Audiogerät ab und verknüpft sie intern mit der Audiositzungs-ID:
audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();
char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC);
AudioParameter param = AudioParameter(String8(reply));
int hwAVSyncId;
param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);
Wenn bereits eine AudioTrack
-Instanz erstellt wurde, wird die HW_AV_SYNC
-ID mit derselben Audiositzungs-ID an den Ausgabestream übergeben. Wenn sie noch nicht erstellt wurde, wird die HW_AV_SYNC
-ID beim Erstellen von AudioTrack
an den Ausgabestream übergeben. Dies erfolgt über den Wiedergabethread:
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);
Die HW_AV_SYNC
-ID, unabhängig davon, ob sie einem Audioausgabestream oder einer Tuner
-Konfiguration entspricht, wird an die OMX- oder Codec2-Komponente übergeben, damit der OEM-Code den Codec dem entsprechenden Audioausgabestream oder Tunerstream zuordnen kann.
Während der Komponentenkonfiguration sollte die OMX- oder Codec2-Komponente einen Sideband-Handle zurückgeben, mit dem der Codec einer Hardware Composer-Ebene (HWC) zugeordnet werden kann. Wenn die App eine Oberfläche mit MediaCodec
verknüpft, wird dieser Sideband-Handle über SurfaceFlinger
an HWC übergeben, wodurch die Schicht als Sideband-Schicht konfiguriert wird.
err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle);
if (err != OK) {
ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", sidebandHandle, err);
return err;
}
HWC ist dafür verantwortlich, neue Bild-Buffer zur richtigen Zeit von der Codec-Ausgabe zu empfangen, entweder synchronisiert mit dem zugehörigen Audio-Ausgabestream oder der Tuner-Programmreferenzuhr, die Buffer mit dem aktuellen Inhalt anderer Ebenen zu kombinieren und das resultierende Bild anzuzeigen. Das geschieht unabhängig vom normalen Zyklus zum Vorbereiten und Einstellen. Die Aufrufe „prepare“ und „set“ werden nur ausgeführt, wenn sich andere Ebenen ändern oder sich Eigenschaften der Sideband-Ebene (z. B. Position oder Größe) ändern.
OMX
Eine getunnelte Dekodierungskomponente sollte Folgendes unterstützen:
Legen Sie den erweiterten Parameter
OMX.google.android.index.configureVideoTunnelMode
fest, bei dem dieConfigureVideoTunnelModeParams
-Struktur verwendet wird, um dieHW_AV_SYNC
-ID zu übergeben, die mit dem Audioausgabegerät verknüpft ist.Konfigurieren des Parameters
OMX_IndexConfigAndroidTunnelPeek
, der den Codec anweist, den ersten decodierten Videoframe unabhängig davon zu rendern, ob die Audiowiedergabe gestartet wurdeDas Ereignis
OMX_EventOnFirstTunnelFrameReady
wird gesendet, wenn der erste getunnelte Videoframe decodiert wurde und gerendert werden kann.
In der AOSP-Implementierung wird der Tunnelmodus in ACodec
über OMXNodeInstance
konfiguriert, wie im folgenden Code-Snippet gezeigt:
OMX_INDEXTYPE index;
OMX_STRING name = const_cast<OMX_STRING>(
"OMX.google.android.index.configureVideoTunnelMode");
OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);
ConfigureVideoTunnelModeParams tunnelParams;
InitOMXParams(&tunnelParams);
tunnelParams.nPortIndex = portIndex;
tunnelParams.bTunneled = tunneled;
tunnelParams.nAudioHwSync = audioHwSync;
err = OMX_SetParameter(mHandle, index, &tunnelParams);
err = OMX_GetParameter(mHandle, index, &tunnelParams);
sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;
Wenn die Komponente diese Konfiguration unterstützt, sollte sie diesem Codec einen Sideband-Handle zuweisen und ihn über das Mitglied pSidebandWindow
zurückgeben, damit der HWC den zugehörigen Codec identifizieren kann. Wenn die Komponente diese Konfiguration nicht unterstützt, sollte sie bTunneled
auf OMX_FALSE
setzen.
Codec2
Unter Android 11 oder höher unterstützt Codec2
die getunnelte Wiedergabe. Die Dekodierungskomponente sollte Folgendes unterstützen:
Konfigurieren von
C2PortTunneledModeTuning
, wodurch der Tunnelmodus konfiguriert und dieHW_AV_SYNC
übergeben wird, die entweder vom Audioausgabegerät oder aus der Tunerkonfiguration abgerufen wurden.Abfrage von
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE
, um den Sideband-Handle für HWC zuzuweisen und abzurufen.Umgang mit
C2_PARAMKEY_TUNNEL_HOLD_RENDER
, wenn es an einenC2Work
angehängt ist, wodurch der Codec angewiesen wird, die Dekodierung abzuschließen und den Arbeitsabschluss zu signalisieren, aber den Ausgabepuffer erst zu rendern, wenn entweder 1) der Codec später zum Rendern angewiesen wird oder 2) die Audiowiedergabe beginnt.Verarbeitung von
C2_PARAMKEY_TUNNEL_START_RENDER
, wodurch der Codec angewiesen wird, den mitC2_PARAMKEY_TUNNEL_HOLD_RENDER
markierten Frame sofort zu rendern, auch wenn die Audiowiedergabe noch nicht gestartet wurdedebug.stagefright.ccodec_delayed_params
nicht konfiguriert lassen (empfohlen). Wenn Sie sie konfigurieren, legen Siefalse
fest.
Bei der AOSP-Implementierung wird der Tunnelmodus in CCodec
über C2PortTunnelModeTuning
konfiguriert, wie im folgenden Code-Snippet gezeigt:
if (msg->findInt32("audio-hw-sync", &tunneledPlayback->m.syncId[0])) {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::AUDIO_HW_SYNC;
} else if (msg->findInt32("hw-av-sync-id", &tunneledPlayback->m.syncId[0])) {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::HW_AV_SYNC;
} else {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::REALTIME;
tunneledPlayback->setFlexCount(0);
}
c2_status_t c2err = comp->config({ tunneledPlayback.get() }, C2_MAY_BLOCK,
failures);
std::vector<std::unique_ptr<C2Param>> params;
c2err = comp->query({}, {C2PortTunnelHandleTuning::output::PARAM_TYPE},
C2_DONT_BLOCK, ¶ms);
if (c2err == C2_OK && params.size() == 1u) {
C2PortTunnelHandleTuning::output *videoTunnelSideband =
C2PortTunnelHandleTuning::output::From(params[0].get());
return OK;
}
Wenn die Komponente diese Konfiguration unterstützt, sollte sie diesem Codec einen Sideband-Handle zuweisen und ihn über C2PortTunnelHandlingTuning
zurückgeben, damit die HWC den zugehörigen Codec identifizieren kann.
Audio HAL
Bei der On-Demand-Videowiedergabe empfängt die Audio-HAL die Zeitstempel der Audiopräsentation inline mit den Audiodaten im Big-Endian-Format in einer Kopfzeile, die sich am Anfang jedes Blocks von Audiodaten befindet, die von der App geschrieben werden:
struct TunnelModeSyncHeader {
// The 32-bit data to identify the sync header (0x55550002)
int32 syncWord;
// The size of the audio data following the sync header before the next sync
// header might be found.
int32 sizeInBytes;
// The presentation timestamp of the first audio sample following the sync
// header.
int64 presentationTimestamp;
// The number of bytes to skip after the beginning of the sync header to find the
// first audio sample (20 bytes for compressed audio, or larger for PCM, aligned
// to the channel count and sample size).
int32 offset;
}
Damit HWC Videoframes synchron mit den entsprechenden Audioframes rendern kann, sollte die Audio-HAL den Synchronisierungsheader parsen und den Präsentationszeitstempel verwenden, um die Wiedergabeuhr mit dem Audio-Rendering zu synchronisieren. Um bei der Wiedergabe komprimierter Audioinhalte eine synchrone Wiedergabe zu ermöglichen, muss die Audio-HAL möglicherweise Metadaten in den komprimierten Audiodaten analysieren, um die Wiedergabedauer zu ermitteln.
Support pausieren
Android 5 oder niedriger bietet keine Unterstützung für Pausen. Du kannst die getunnelte Wiedergabe nur durch A/V-Starvation pausieren. Wenn der interne Puffer für das Video jedoch groß ist (z. B. eine Sekunde Daten in der OMX-Komponente), wirkt die Pause, als würde sie nicht reagieren.
Unter Android 5.1 oder höher unterstützt AudioFlinger
die Pausierung und Wiederaufnahme für direkte (getunnelte) Audioausgaben. Wenn die HAL „Pausieren“ und „Fortsetzen“ implementiert, werden die Track-Pausierung und -Fortsetzung an die HAL weitergeleitet.
Die Aufrufsequenz zum Anhalten, Leeren und Fortsetzen wird berücksichtigt, indem die HAL-Aufrufe im Wiedergabe-Thread ausgeführt werden (entspricht der Auslagerung).
Implementierungsvorschläge
Audio-HAL
Unter Android 11 kann die HW-Synchronisierungs-ID von PCR oder STC für die A/V-Synchronisierung verwendet werden, sodass nur Videostreams unterstützt werden.
Geräte mit Android 10 oder niedriger, die die getunnelte Videowiedergabe unterstützen, sollten mindestens ein Audioausgabestreamprofil mit den Flags FLAG_HW_AV_SYNC
und AUDIO_OUTPUT_FLAG_DIRECT
in der Datei audio_policy.conf
haben. Mit diesen Flags wird die Systemuhr anhand der Audiouhr eingestellt.
OMX
Gerätehersteller sollten eine separate OMX-Komponente für die getunnelte Videowiedergabe haben. Hersteller können zusätzliche OMX-Komponenten für andere Arten der Audio- und Videowiedergabe haben, z. B. für die sichere Wiedergabe. Die getunnelte Komponente sollte:
Geben Sie auf dem Ausgabeport 0 Puffer (
nBufferCountMin
,nBufferCountActual
) an.Implementieren Sie die Erweiterung
OMX.google.android.index.prepareForAdaptivePlayback setParameter
.Geben Sie die Funktionen in der Datei
media_codecs.xml
an und deklarieren Sie die Funktion für die getunnelte Wiedergabe. Außerdem sollten darin alle Einschränkungen in Bezug auf Frame-Größe, Ausrichtung und Bitrate verdeutlicht werden. Hier ein Beispiel:<MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled" type="video/avc" > <Feature name="adaptive-playback" /> <Feature name="tunneled-playback" required=”true” /> <Limit name="size" min="32x32" max="3840x2160" /> <Limit name="alignment" value="2x2" /> <Limit name="bitrate" range="1-20000000" /> ... </MediaCodec>
Wenn zur Unterstützung der getunnelten und nicht getunnelten Decodierung dieselbe OMX-Komponente verwendet wird, sollte die getunnelte Wiedergabefunktion nicht erforderlich sein. So haben sowohl getunnelte als auch nicht getunnelte Decoder dieselben Funktionseinschränkungen. Hier ein Beispiel:
<MediaCodec name="OMX._OEM\_NAME_.VIDEO.DECODER.AVC" type="video/avc" >
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="size" min="32x32" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="bitrate" range="1-20000000" />
...
</MediaCodec>
Hardware Composer (HWC)
Wenn sich auf einem Display eine getunnelte Ebene (eine Ebene mit HWC_SIDEBAND
compositionType
) befindet, ist sidebandStream
der Sideband-Handle, der von der OMX-Videokomponente zugewiesen wird.
Die HWC synchronisiert decodierte Videoframes (aus der getunnelten OMX-Komponente) mit dem zugehörigen Audiotrack (mit der audio-hw-sync
-ID). Wenn ein neuer Videoframe aktuell wird, kombiniert die HWC ihn mit dem aktuellen Inhalt aller Ebenen, die beim letzten Aufruf von „prepare“ oder „set“ empfangen wurden, und zeigt das resultierende Bild an.
Die Aufrufe „prepare“ oder „set“ werden nur ausgeführt, wenn sich andere Ebenen ändern oder sich Eigenschaften der Sideband-Ebene (z. B. Position oder Größe) ändern.
Die folgende Abbildung zeigt die HWC, die mit dem Hardware- (oder Kernel- oder Treiber-) Synchronisierer zusammenarbeitet, um Videoframes (7b) mit der neuesten Komposition (7a) zu kombinieren, die dann anhand des Audiosignals (7c) zur richtigen Zeit angezeigt wird.
Abbildung 2. HWC-Hardware- (oder Kernel- oder Treiber-)Synchronisierer