Multimedia-Tunneling

Multimedia-Tunneling ermöglicht es komprimierten Videodaten, einen Hardware-Videodecoder direkt zu einem Bildschirm zu führen, ohne dass sie durch 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 werden

  • Fü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 veröffentlicht dann den decodierten Videoframe auf dem Bildschirm, damit er zur selben Systemuhrzeit wie der entsprechende Audioframe gerendert wird. Dabei werden historische AudioTimestamps-Instanzen abgerufen, um das richtige Timing zu berechnen.

Da die getunnelte Videowiedergabe den App-Code umgeht und die Anzahl der Prozesse reduziert, die auf das Video reagieren, kann sie je nach OEM-Implementierung ein effizienteres Video-Rendering ermöglichen. Außerdem kann es eine genauere Video-Taktfrequenz und ‑synchronisierung mit der ausgewählten Uhr (PRC, STC oder Audio) bieten, da Timing-Probleme vermieden werden, die durch eine mögliche Abweichung zwischen dem Timing von Android-Anfragen zum Rendern von Video und dem Timing von echten 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.

Vergleich zwischen traditionellem und Tunnelmodus

Abbildung 1. Vergleich zwischen herkömmlicher und getunnelter Videowiedergabe

Für App-Entwickler

Da die meisten App-Entwickler eine Bibliothek für die Implementierung der Wiedergabe integrieren, muss diese Bibliothek in den meisten Fällen nur für die getunnelte Wiedergabe neu konfiguriert werden. Folgen Sie der nachstehenden Anleitung, um einen getunnelten Videoplayer auf unterer Ebene zu implementieren.

On-Demand-Videowiedergabe unter Android 5 oder höher:

  1. Erstellen Sie eine SurfaceView-Instanz.

  2. Erstellen Sie eine audioSessionId-Instanz.

  3. Erstellen Sie die Instanzen AudioTrack und MediaCodec mit der in Schritt 2 erstellten Instanz audioSessionId.

  4. Stellen Sie Audiodaten in der Warteschlange für AudioTrack mit dem Präsentationszeitstempel für den ersten Audioframe in den Audiodaten in die Warteschlange.

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

  1. Erstellen Sie eine SurfaceView-Instanz.

  2. Rufen Sie eine avSyncHwId-Instanz von Tuner ab.

  3. Erstellen Sie die Instanzen AudioTrack und MediaCodec mit der in Schritt 2 erstellten Instanz avSyncHwId.

Der API-Aufruffluss wird in den folgenden Code-Snippets gezeigt:

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-Wiedergabe implizit mit der AudioTrack-Wiedergabe verbunden ist, kann das Verhalten der getunnelten Videowiedergabe vom Verhalten der Audiowiedergabe abhängen.

  • Auf den meisten Geräten wird ein Videoframe standardmäßig erst gerendert, wenn die Audiowiedergabe gestartet wird. Unter Umständen muss die App jedoch vor dem Starten der Audiowiedergabe einen Videoframe rendern, um dem Nutzer beispielsweise bei der Suche die aktuelle Videoposition zu zeigen.

    • Um zu signalisieren, dass der erste Videoframe in der Warteschlange sofort nach seiner Decodierung gerendert werden soll, 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 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 für das Gerät.

  • 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 durch die App nicht korrigiert werden können. In diesem Fall korrigiert der OEM negative Lücken, indem er den aktuellen Videoframe bremst, und positive Lücken, indem er entweder Videoframes löscht oder stille Audioframes einfügt (je nach OEM-Implementierung). Die Frameposition AudioTimestamp erhöht sich bei eingefügten stillen Audioframes nicht.

Für Gerätehersteller

Konfiguration

OEMs sollten einen separaten Videodecoder erstellen, um eine 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, fragt sie AudioFlinger nach dieser HW_AV_SYNC-ID ab:

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);
}

Während 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. Das geschieht über den Wiedergabe-Thread:

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 mit dem entsprechenden Audioausgabestream oder Tuner-Stream verknüpfen kann.

Während der Komponentenkonfiguration sollte die OMX- oder Codec2-Komponente einen Seitenband-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 Seitenband-Handle über SurfaceFlinger an HWC übergeben, wodurch die Schicht als Seitenbandschicht 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.

Logo: OMX

Eine Tunneled-Decoder-Komponente sollte Folgendes unterstützen:

  • Sie legen den erweiterten Parameter OMX.google.android.index.configureVideoTunnelMode fest, der die ConfigureVideoTunnelModeParams-Struktur verwendet, um die dem Audioausgabegerät zugeordnete HW_AV_SYNC-ID zu übergeben.

  • Konfigurieren des Parameters OMX_IndexConfigAndroidTunnelPeek, der den Codec anweist, den ersten decodierten Videoframe unabhängig davon zu rendern, ob die Audiowiedergabe gestartet wurde

  • Das Ereignis OMX_EventOnFirstTunnelFrameReady wird gesendet, wenn der erste getunnelte Videoframe decodiert wurde und zum Rendern bereit ist.

Die AOSP-Implementierung konfiguriert den Tunnelmodus in ACodec bis OMXNodeInstance, 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 Seitenband-Handle zuweisen und ihn über das pSidebandWindow-Mitglied zurücksenden, damit die HWC den zugehörigen Codec identifizieren kann. Wenn die Komponente diese Konfiguration nicht unterstützt, sollte bTunneled auf OMX_FALSE gesetzt werden.

Codec2

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

  • Konfigurieren von C2PortTunneledModeTuning, wodurch der Tunnelmodus konfiguriert und HW_AV_SYNC übergeben wird, das entweder vom Audioausgabegerät oder von der Tunerkonfiguration abgerufen wird.

  • 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 einen C2Work 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 mit C2_PARAMKEY_TUNNEL_HOLD_RENDER markierten Frame sofort zu rendern, auch wenn die Audiowiedergabe noch nicht gestartet wurde

  • Lassen Sie debug.stagefright.ccodec_delayed_params nicht konfiguriert (empfohlen). Wenn Sie es konfigurieren, legen Sie 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 einen Seitenband-Handle zuweisen und ihn über C2PortTunnelHandlingTuning zurücksenden, damit die HWC den zugehörigen Codec identifizieren kann.

Audio HAL

Für die On-Demand-Videowiedergabe empfängt der Audio-HAL die Zeitstempel der Audiopräsentation inline mit den Audiodaten im Big-Endian-Format innerhalb eines Headers, der sich am Anfang jedes Audioblocks befindet, der von der Anwendung geschrieben wird:

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 Audiodaten neu zu synchronisieren, muss der Audio-HAL möglicherweise Metadaten in den komprimierten Audiodaten parsen, um die Wiedergabedauer zu bestimmen.

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 Aufrufssequenz „pausieren“, „leerstellen“ und „fortsetzen“ wird eingehalten, indem die HAL-Aufrufe im Wiedergabe-Thread ausgeführt werden (wie beim Offload).

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 reine Videostreams unterstützt werden.

Bei Android 10 oder niedriger sollten Geräte, die die getunnelte Videowiedergabe unterstützen, mindestens ein Audioausgabestream-Profil mit den Flags FLAG_HW_AV_SYNC und AUDIO_OUTPUT_FLAG_DIRECT in der audio_policy.conf-Datei haben. Diese Flags werden verwendet, um die Systemuhr aus der Audiouhr festzulegen.

Logo: 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 alle Einschränkungen für Framegröße, Ausrichtung oder Bitrate klargestellt 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 dieselbe OMX-Komponente für die Unterstützung der getunnelten und nicht getunnelten Dekodierung verwendet wird, sollte die Funktion für die getunnelte Wiedergabe nicht erforderlich sein. Sowohl getunnelte als auch nicht getunnelte Decoder haben dann 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 (von der getunnelten OMX-Komponente) mit dem zugehörigen Audiotrack (mit der ID audio-hw-sync). Wenn ein neuer Videoframe aktuell wird, setzt der HWC ihn mit den aktuellen Inhalten aller Ebenen zusammen, die während des letzten Vorbereiten- oder Festlegen-Aufrufs 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.

HWC-Kombination von Videoframes basierend auf Audio

Abbildung 2: HWC-Hardware- (oder Kernel- oder Treiber-) Synchronisierer