Multimedia-Tunneling

Beim Multimedia-Tunneling werden komprimierte Videodaten über einen Hardware-Videodecoder direkt an ein Display gesendet, 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 Präsentationszeitstempel der Videoframes mit einem der folgenden Typen von internen Uhren verglichen:

  • Für die On-Demand-Videowiedergabe in Android 5 oder höher ist eine AudioTrack erforderlich, die mit den Zeitstempeln der Audiopräsentation synchronisiert ist und von der App übergeben wird.

  • Für die Wiedergabe von Live-Übertragungen unter Android 11 oder höher ist eine Program Reference Clock (PCR) oder System Time Clock (STC) erforderlich, die von einem Tuner gesteuert wird.

Hintergrund

Bei der herkömmlichen Videowiedergabe unter Android wird die App benachrichtigt, wenn ein komprimierter Videoframes decodiert wurde. Die App gibt dann den decodierten Videoframes an das Display aus, damit er zur selben Systemuhrzeit wie der entsprechende Audioframe gerendert wird. Dazu werden historische AudioTimestamps-Instanzen abgerufen, um das richtige Timing zu berechnen.

Da die getunnelte Videowiedergabe den App-Code umgeht und die Anzahl der Prozesse, die auf das Video wirken, reduziert, kann sie je nach OEM-Implementierung eine effizientere Videowiedergabe ermöglichen. Außerdem kann die Videocadence und Synchronisierung mit dem ausgewählten Takt (PRC, STC oder Audio) genauer sein, da Timing-Probleme vermieden werden, die durch eine mögliche Abweichung zwischen dem Timing von Android-Anfragen zum Rendern von Videos und dem Timing von echten Hardware-Vsyncs entstehen. Durch das Tunneling kann jedoch auch die Unterstützung für GPU-Effekte wie Unschärfe oder abgerundete Ecken in Bild-im-Bild-Fenstern (BiB) eingeschränkt werden, da die Puffer den Android-Grafikstack umgehen.

Das folgende Diagramm zeigt, wie das Tunneling den Videowiedergabeprozess vereinfacht.

Vergleich von traditionellen und Tunnelmodi

Abbildung 1: Vergleich von herkömmlichen und getunnelten Videowiedergabeprozessen

Für App-Entwickler

Da die meisten App-Entwickler eine Bibliothek für die Implementierung der Wiedergabe verwenden, ist in den meisten Fällen nur eine Neukonfiguration dieser Bibliothek für die Wiedergabe über Tunneling erforderlich. Verwenden Sie die folgende Anleitung für die Low-Level-Implementierung eines getunnelten Videoplayers.

Für die On-Demand-Videowiedergabe unter Android 5 oder höher gilt Folgendes:

  1. Erstellen Sie eine SurfaceView-Instanz.

  2. Erstellen Sie eine audioSessionId-Instanz.

  3. Erstellen Sie AudioTrack- und MediaCodec-Instanzen mit der audioSessionId-Instanz, die Sie in Schritt 2 erstellt haben.

  4. Stellen Sie Audiodaten mit dem Präsentationszeitstempel für den ersten Audio-Frame in den Audiodaten in die Warteschlange von AudioTrack.

Für die Wiedergabe von Live-Übertragungen unter Android 11 oder höher gilt Folgendes:

  1. Erstellen Sie eine SurfaceView-Instanz.

  2. avSyncHwId-Instanz von Tuner abrufen

  3. Erstellen Sie AudioTrack- und MediaCodec-Instanzen mit der avSyncHwId-Instanz, die Sie in Schritt 2 erstellt haben.

Der API-Aufrufablauf 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);

Wiedergabe von On-Demand-Videos

Da die Wiedergabe von On-Demand-Videos im Tunnelmodus implizit an die AudioTrack-Wiedergabe gebunden ist, kann das Verhalten der Videowiedergabe im Tunnelmodus vom Verhalten der Audiowiedergabe abhängen.

  • Bei den meisten Geräten wird standardmäßig kein Videobild gerendert, bis die Audiowiedergabe beginnt. Die App muss jedoch möglicherweise einen Videoframes rendern, bevor die Audiowiedergabe gestartet wird, z. B. um dem Nutzer die aktuelle Videoposition beim Suchen anzuzeigen.

    • Wenn Sie signalisieren möchten, dass der erste in die Warteschlange gestellte Videoframes gerendert werden soll, sobald er decodiert wurde, setzen Sie den Parameter PARAMETER_KEY_TUNNEL_PEEK auf 1. Wenn komprimierte Videoframes in der Warteschlange neu angeordnet werden (z. B. wenn B-Frames vorhanden sind), sollte der erste angezeigte Videoframes immer ein I-Frame sein.

    • Wenn Sie nicht möchten, dass der erste in der Warteschlange befindliche Videoframes gerendert wird, bevor die Audiowiedergabe beginnt, legen Sie diesen Parameter auf 0 fest.

    • Wenn dieser Parameter nicht festgelegt ist, bestimmt der OEM das Verhalten für das Gerät.

  • Wenn AudioTrack keine Audiodaten erhält und die Puffer leer sind (Audio-Underrun), wird die Videowiedergabe angehalten, bis weitere Audiodaten geschrieben werden, da die Audio-Clock nicht mehr weiterläuft.

  • Während der Wiedergabe können in den Zeitstempeln der Audio-Präsentation Unregelmäßigkeiten auftreten, die die App nicht korrigieren kann. In diesem Fall korrigiert der OEM negative Lücken, indem er den aktuellen Videoframes anhält, und positive Lücken, indem er entweder Videoframes entfernt oder stumme Audioframes einfügt (je nach OEM-Implementierung). Die AudioTimestamp-Frame-Position erhöht sich nicht für eingefügte stumme Audioframes.

Für Gerätehersteller

Konfiguration

OEMs sollten einen separaten Videodecoder erstellen, um die Wiedergabe von Videos im Tunnelmodus zu unterstützen. Dieser Decoder sollte in der Datei media_codecs.xml angeben, dass er die Wiedergabe im Tunnelmodus unterstützt:

<Feature name="tunneled-playback" required="true"/>

Wenn eine getunnelte MediaCodec-Instanz mit einer Audio-Sitzungs-ID konfiguriert ist, wird AudioFlinger nach dieser HW_AV_SYNC-ID gefragt:

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 Anfrage ruft AudioFlinger die HW_AV_SYNC-ID vom primären Audiogerät ab und verknüpft sie intern mit der Audio-Sitzungs-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 Audio-Sitzungs-ID an den Ausgabestream übergeben. Wenn sie noch nicht erstellt wurde, wird die HW_AV_SYNC-ID während der AudioTrack-Erstellung an den Ausgabestream übergeben. Dies geschieht ü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 dem Tunerstream zuordnen kann.

Bei der Konfiguration der OMX- oder Codec2-Komponente sollte ein Sideband-Handle zurückgegeben werden, mit dem der Codec einer Hardware Composer-Ebene (HWC) zugeordnet werden kann. Wenn die App eine Oberfläche mit MediaCodec verknüpft, wird dieses Sideband-Handle über SurfaceFlinger an HWC übergeben, wodurch der Layer als Sideband-Layer 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 Bildpuffer rechtzeitig aus der Codec-Ausgabe zu empfangen, entweder synchronisiert mit dem zugehörigen Audioausgabestream oder der Referenzuhr des Tunerprogramms, die Puffer mit den aktuellen Inhalten anderer Ebenen zusammenzufügen und das resultierende Bild anzuzeigen. Das geschieht unabhängig vom normalen Zyklus aus Vorbereitung und Festlegung. Die prepare- und set-Aufrufe erfolgen nur, wenn sich andere Ebenen ändern oder wenn sich Eigenschaften der Sideband-Ebene ändern, z. B. Position oder Größe.

OMX

Eine getunnelte Decoderkomponente sollte Folgendes unterstützen:

  • Der erweiterte Parameter OMX.google.android.index.configureVideoTunnelMode wird festgelegt. Dabei wird die HW_AV_SYNC-ID, die dem Audioausgabegerät zugeordnet ist, über die ConfigureVideoTunnelModeParams-Struktur übergeben.

  • Konfigurieren des Parameters OMX_IndexConfigAndroidTunnelPeek, der dem Codec mitteilt, ob der erste decodierte Videoframes gerendert werden soll oder nicht, unabhängig davon, ob die Audiowiedergabe gestartet wurde.

  • Das OMX_EventOnFirstTunnelFrameReady-Ereignis wird gesendet, wenn der erste getunnelte Videoframes 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 ein Sideband-Handle zuweisen und es über das pSidebandWindow-Element 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 festlegen.

Codec2

Unter Android 11 oder höher unterstützt Codec2 die Wiedergabe über Tunneling. Die Decoderkomponente sollte Folgendes unterstützen:

  • Konfiguration von C2PortTunneledModeTuning, wodurch der Tunnelmodus konfiguriert und die HW_AV_SYNC übergeben werden, die entweder vom Audioausgabegerät oder von der Tunerkonfiguration abgerufen werden.

  • C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE abfragen, um das Sideband-Handle für HWC zuzuweisen und abzurufen.

  • Verarbeitung von C2_PARAMKEY_TUNNEL_HOLD_RENDER, wenn es an ein C2Work angehängt ist. Dadurch wird der Codec angewiesen, den Vorgang zu decodieren und abzuschließen, den Ausgabepuffer aber erst zu rendern, wenn 1) der Codec später angewiesen wird, ihn zu rendern, oder 2) die Audiowiedergabe beginnt.

  • Verarbeitung von C2_PARAMKEY_TUNNEL_START_RENDER, wodurch der Codec angewiesen wird, den mit C2_PARAMKEY_TUNNEL_HOLD_RENDER markierten Frame sofort zu rendern, auch wenn die Audiowiedergabe noch nicht begonnen hat.

  • Lassen Sie debug.stagefright.ccodec_delayed_params unkonfiguriert (empfohlen). Wenn Sie sie konfigurieren, legen Sie sie auf false 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, &params);
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 ein Sideband-Handle zuweisen und es über C2PortTunnelHandlingTuning zurückgeben, damit der HWC den zugehörigen Codec identifizieren kann.

Audio-HAL

Bei der On-Demand-Videowiedergabe empfängt das Audio-HAL die Zeitstempel für die Audiopräsentation inline mit den Audiodaten im Big-Endian-Format in einem Header, der 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 das Audio-HAL den Synchronisationsheader parsen und den Präsentationszeitstempel verwenden, um die Wiedergabeuhr mit dem Audio-Rendering zu synchronisieren. Um bei der Wiedergabe von komprimiertem Audio neu zu synchronisieren, muss der Audio-HAL möglicherweise Metadaten in den komprimierten Audiodaten parsen, um die Wiedergabedauer zu ermitteln.

Support pausieren

Android 5 oder niedriger unterstützt die Pausenfunktion nicht. Die Wiedergabe im Tunnel kann nur durch A/V-Starvation pausiert werden. Wenn der interne Puffer für Video groß ist (z. B. eine Sekunde Daten in der OMX-Komponente), sieht die Pause nicht reaktionsschnell aus.

Unter Android 5.1 oder höher unterstützt AudioFlinger das Pausieren und Fortsetzen von direkten (getunnelten) Audioausgaben. Wenn das HAL die Funktionen „Pause“ und „Fortsetzen“ implementiert, werden „Pause“ und „Fortsetzen“ an das HAL weitergeleitet.

Die Aufruffolge „pause“, „flush“, „resume“ wird eingehalten, indem die HAL-Aufrufe im Wiedergabethread ausgeführt werden (wie beim Offload).

Vorschläge zur Implementierung

Audio-HAL

Bei Android 11 kann die HW-Synchronisierungs-ID aus PCR oder STC für die A/V-Synchronisierung verwendet werden. Daher wird ein reiner Videostream unterstützt.

Bei Geräten mit Android 10 oder älter, die die Wiedergabe von Videos über Tunneling unterstützen, sollte in der audio_policy.conf-Datei mindestens ein Audioausgabestreamprofil mit den Flags FLAG_HW_AV_SYNC und AUDIO_OUTPUT_FLAG_DIRECT vorhanden sein. Mit diesen Flags wird die Systemuhr anhand der Audio-Uhr eingestellt.

OMX

Gerätehersteller sollten eine separate OMX-Komponente für die Wiedergabe von Video im Tunnelmodus haben. Sie 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:

  • Gibt 0 Puffer (nBufferCountMin, nBufferCountActual) für den Ausgabeprotokollport an.

  • Implementieren Sie die OMX.google.android.index.prepareForAdaptivePlayback setParameter-Erweiterung.

  • Geben Sie die Funktionen in der Datei media_codecs.xml an und deklarieren Sie die Funktion für die getunnelte Wiedergabe. Außerdem sollten alle Einschränkungen in Bezug auf Bildgröße, Ausrichtung oder Bitrate angegeben werden. Ein Beispiel ist unten aufgeführt:

    <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 dieselbe OMX-Komponente zur Unterstützung von getunnelter und nicht getunnelter Decodierung verwendet wird, sollte die Funktion für die getunnelte Wiedergabe als nicht erforderlich gekennzeichnet werden. Sowohl getunnelte als auch nicht getunnelte Decoder haben dann dieselben Einschrä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 auf einem Display eine getunnelte Ebene (eine Ebene mit HWC_SIDEBAND compositionType) vorhanden ist, ist das sidebandStream der Ebene das vom OMX-Videomodul zugewiesene Sideband-Handle.

Die HWC synchronisiert decodierte Videoframes (von der getunnelten OMX-Komponente) mit dem zugehörigen Audiotrack (mit der ID audio-hw-sync). Wenn ein neuer Videoframes aktuell wird, kombiniert HWC ihn mit dem aktuellen Inhalt aller Ebenen, die beim letzten „prepare“- oder „set“-Aufruf empfangen wurden, und zeigt das resultierende Bild an. Die prepare- oder set-Aufrufe erfolgen nur, wenn sich andere Ebenen ändern oder wenn sich Eigenschaften der Sideband-Ebene (z. B. Position oder Größe) ändern.

Die folgende Abbildung zeigt, wie der HWC mit dem Hardware- (oder Kernel- oder Treiber-)Synchronizer zusammenarbeitet, um Videoframes (7b) mit der neuesten Komposition (7a) zu kombinieren und sie basierend auf dem Audio (7c) zum richtigen Zeitpunkt anzuzeigen.

HWC kombiniert Videoframes basierend auf Audio

Abbildung 2: HWC-Hardwaresynchronisierer (oder Kernel- oder Treibersynchronisierer)