Multimedia-Tunneling

Durch Multimedia-Tunneling können komprimierte Videodaten durch einen Hardware-Videodecoder direkt zu einem Display getunnelt werden, ohne dass sie durch App-Code oder Android-Framework-Code verarbeitet werden. Der gerätespezifische Code unter dem Android-Stack bestimmt, welche Videobilder wann an das Display gesendet werden sollen, indem er die Zeitstempel der Videobilddarstellung mit einer der folgenden Arten von interner Uhr vergleicht:

  • Für die On-Demand-Videowiedergabe in Android 5 oder höher wird eine AudioTrack Uhr verwendet, die mit den von der App übermittelten Zeitstempeln der Audiopräsentation synchronisiert wird

  • Für die Live-Übertragungswiedergabe in Android 11 oder höher ist eine von einem Tuner gesteuerte Programmreferenzuhr (PCR) oder Systemzeituhr (STC) erforderlich

Hintergrund

Bei der herkömmlichen Videowiedergabe auf Android wird die App benachrichtigt , wenn ein komprimiertes Videobild dekodiert wurde. Die App gibt dann den dekodierten Video-Frame an die Anzeige weiter, damit er zur gleichen Systemuhrzeit wie der entsprechende Audio-Frame gerendert wird, und ruft historische AudioTimestamps Instanzen ab, um das richtige Timing zu berechnen.

Da die getunnelte Videowiedergabe den App-Code umgeht und die Anzahl der auf das Video einwirkenden Prozesse reduziert, kann je nach OEM-Implementierung eine effizientere Videowiedergabe ermöglicht werden. Es kann außerdem eine genauere Videofrequenz und Synchronisierung mit dem gewählten Takt (PRC, STC oder Audio) ermöglichen, indem Timing-Probleme vermieden werden, die durch eine mögliche Abweichung zwischen dem Timing von Android-Anfragen zum Rendern von Videos und dem Timing echter Hardware-Vsyncs entstehen. Tunneling kann jedoch auch die Unterstützung von GPU-Effekten wie Unschärfe oder abgerundeten Ecken in Bild-in-Bild-Fenstern (PiP) verringern, da die Puffer den Android-Grafikstapel umgehen.

Das folgende Diagramm zeigt, wie Tunneling den Videowiedergabeprozess vereinfacht.

Vergleich von Tradition und Tunnelmodi

Abbildung 1. Vergleich traditioneller und getunnelter Videowiedergabeprozesse

Für App-Entwickler

Da die meisten App-Entwickler eine Bibliothek für die Wiedergabeimplementierung integrieren, erfordert die Implementierung in den meisten Fällen nur eine Neukonfiguration dieser Bibliothek für die getunnelte Wiedergabe. Verwenden Sie für die Low-Level-Implementierung eines getunnelten Videoplayers die folgenden Anweisungen.

Für die On-Demand-Videowiedergabe in Android 5 oder höher:

  1. Erstellen Sie eine SurfaceView Instanz.

  2. Erstellen Sie eine audioSessionId Instanz.

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

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

Für die Live-Übertragungswiedergabe in Android 11 oder höher:

  1. Erstellen Sie eine SurfaceView Instanz.

  2. Rufen Sie eine avSyncHwId Instanz von Tuner ab.

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

Der API-Aufrufablauf wird in den folgenden Codeausschnitten 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 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 Videobild standardmäßig erst gerendert, wenn die Audiowiedergabe beginnt. Möglicherweise muss die App jedoch vor Beginn der Audiowiedergabe ein Videobild rendern, um dem Benutzer beispielsweise beim Suchen die aktuelle Videoposition anzuzeigen.

    • Um zu signalisieren, dass der erste Videoframe in der Warteschlange gerendert werden soll, sobald er dekodiert ist, legen Sie den Parameter PARAMETER_KEY_TUNNEL_PEEK auf 1 fest. Wenn komprimierte Videobilder in der Warteschlange neu angeordnet werden (z. B. wenn B-Bilder vorhanden sind), bedeutet dies, dass das erste angezeigte Videobild immer ein I-Bild sein sollte.

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

    • Wenn dieser Parameter nicht festgelegt ist, bestimmt der OEM das Verhalten des Geräts.

  • Wenn AudioTrack keine Audiodaten bereitgestellt werden und die Puffer leer sind (Audio-Unterlauf), bleibt die Videowiedergabe stehen, bis weitere Audiodaten geschrieben werden, da der Audiotakt nicht mehr vorrückt.

  • Während der Wiedergabe können in den Zeitstempeln der Audiopräsentation Unterbrechungen auftreten, die die App nicht korrigieren kann. Wenn dies geschieht, korrigiert der OEM negative Lücken, indem er den aktuellen Video-Frame anhält, und positive Lücken, indem er entweder Video-Frames löscht oder stille Audio-Frames einfügt (abhängig von der OEM-Implementierung). Die Position des AudioTimestamp Frames erhöht sich nicht für eingefügte stille Audioframes.

Für Gerätehersteller

Aufbau

OEMs sollten einen separaten Videodecoder erstellen, um die getunnelte Videowiedergabe zu unterstützen. Dieser Decoder sollte in der Datei media_codecs.xml ankündigen, dass er zur getunnelten Wiedergabe fähig ist:

<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 ordnet sie intern der Audiositzungs-ID zu:

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 es noch nicht erstellt wurde, wird die HW_AV_SYNC ID während der AudioTrack Erstellung an den Ausgabestream übergeben. Dies geschieht durch den Playback-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 Audio-Ausgabestream oder einer Tuner Konfiguration entspricht, wird an die OMX- oder Codec2-Komponente übergeben, damit der OEM-Code den Codec dem entsprechenden Audio-Ausgabestream oder Tuner-Stream zuordnen kann.

Während der Komponentenkonfiguration sollte die OMX- oder Codec2-Komponente ein Seitenband-Handle zurückgeben, das verwendet werden kann, um den Codec einer Hardware Composer-Schicht (HWC) zuzuordnen. Wenn die App eine Oberfläche mit MediaCodec verknüpft, wird dieses Seitenbandhandle über SurfaceFlinger an HWC weitergegeben, wodurch die Ebene als Seitenbandebene 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, zum richtigen Zeitpunkt neue Bildpuffer vom Codec-Ausgang zu empfangen, entweder synchronisiert mit dem zugehörigen Audio-Ausgabestream oder dem Referenztakt des Tuner-Programms, die Puffer mit den aktuellen Inhalten anderer Schichten zusammenzusetzen und das resultierende Bild anzuzeigen. Dies geschieht unabhängig vom normalen Vorbereitungs- und Einstellzyklus. Die Aufrufe „prepare“ und „set“ erfolgen nur, wenn sich andere Ebenen ändern oder wenn sich Eigenschaften der Seitenbandebene (z. B. Position oder Größe) ändern.

OMX

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

  • Festlegen des erweiterten Parameters OMX.google.android.index.configureVideoTunnelMode , der die Struktur ConfigureVideoTunnelModeParams “ verwendet, um die HW_AV_SYNC ID zu übergeben, die dem Audioausgabegerät zugeordnet ist.

  • Konfigurieren des Parameters OMX_IndexConfigAndroidTunnelPeek , der dem Codec mitteilt, den ersten decodierten Videoframe zu rendern oder nicht, unabhängig davon, ob die Audiowiedergabe gestartet wurde.

  • Senden des OMX_EventOnFirstTunnelFrameReady Ereignisses, wenn der erste getunnelte Videoframe dekodiert wurde und zum Rendern bereit ist.

Die AOSP-Implementierung konfiguriert den Tunnelmodus in ACodec über OMXNodeInstance , wie im folgenden Codeausschnitt 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 Seitenbandhandle zuweisen und es über das pSidebandWindow Mitglied 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

In Android 11 oder höher unterstützt Codec2 die getunnelte Wiedergabe. Die Decoder-Komponente sollte Folgendes unterstützen:

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

  • Abfrage von C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE , um das Seitenband-Handle für HWC zuzuweisen und abzurufen.

  • Behandelt C2_PARAMKEY_TUNNEL_HOLD_RENDER , wenn es an ein C2Work angehängt ist, das den Codec anweist, den Abschluss der Arbeit zu dekodieren und zu signalisieren, aber den Ausgabepuffer nicht zu rendern, bis entweder 1) der Codec später angewiesen wird, ihn zu rendern, oder 2) die Audiowiedergabe beginnt.

  • Behandelt 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 unkonfiguriert (empfohlen). Wenn Sie es konfigurieren, legen Sie es auf false fest.

Die AOSP-Implementierung konfiguriert den Tunnelmodus in CCodec über C2PortTunnelModeTuning , wie im folgenden Codeausschnitt 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 Seitenband-Handle zuweisen und es über C2PortTunnelHandlingTuning zurückgeben, damit der 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 in einem Header, der sich am Anfang jedes Audiodatenblocks befindet, den die App schreibt:

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 analysieren und den Präsentationszeitstempel verwenden, um den Wiedergabetakt mit der Audiowiedergabe neu zu synchronisieren. Um bei der Wiedergabe komprimierter Audiodaten eine Neusynchronisierung durchzuführen, muss die Audio-HAL möglicherweise Metadaten in den komprimierten Audiodaten analysieren, um deren Wiedergabedauer zu bestimmen.

Unterstützung pausieren

Android 5 oder niedriger bietet keine Pausenunterstützung. Sie können die getunnelte Wiedergabe nur durch A/V-Aushungerung anhalten, aber wenn der interne Puffer für Video groß ist (z. B. wenn in der OMX-Komponente eine Sekunde Daten vorhanden sind), sieht es so aus, als würde die Pause nicht reagieren.

In Android 5.1 oder höher unterstützt AudioFlinger Pause und Fortsetzen für direkte (getunnelte) Audioausgaben. Wenn die HAL Pause und Fortsetzung implementiert, wird die Pause und Fortsetzung des Tracks an die HAL weitergeleitet.

Die Pause-, Flush- und Resume-Aufrufsequenz wird durch die Ausführung der HAL-Aufrufe im Wiedergabethread eingehalten (wie bei Offload).

Umsetzungsvorschläge

Audio-HAL

Für Android 11 kann die HW-Synchronisierungs-ID von PCR oder STC für die A/V-Synchronisierung verwendet werden, sodass nur Video-Streams unterstützt werden.

Für Android 10 oder niedriger sollten Geräte, die die getunnelte Videowiedergabe unterstützen, mindestens ein Audioausgabe-Stream-Profil mit den Flags FLAG_HW_AV_SYNC und AUDIO_OUTPUT_FLAG_DIRECT in der Datei audio_policy.conf haben. Diese Flags werden verwendet, um die Systemuhr über die Audiouhr einzustellen.

OMX

Gerätehersteller sollten über eine separate OMX-Komponente für die getunnelte Videowiedergabe verfügen (Hersteller können über zusätzliche OMX-Komponenten für andere Arten der Audio- und Videowiedergabe verfügen, beispielsweise für die sichere Wiedergabe). Die getunnelte Komponente sollte:

  • Geben Sie 0 Puffer ( nBufferCountMin , nBufferCountActual ) für den Ausgabeport an.

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

  • Geben Sie seine Funktionen in der Datei media_codecs.xml an und deklarieren Sie die getunnelte Wiedergabefunktion. Außerdem sollten etwaige Einschränkungen hinsichtlich Bildgröße, Ausrichtung oder Bitrate klargestellt werden. Ein Beispiel ist unten dargestellt:

    <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 der getunnelten und nicht getunnelten Dekodierung verwendet wird, sollte die getunnelte Wiedergabefunktion als nicht erforderlich belassen werden. Sowohl für getunnelte als auch für nicht getunnelte Decoder gelten dann die gleichen Leistungseinschränkungen. Ein Beispiel ist unten dargestellt:

<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 eine getunnelte Ebene (eine Ebene mit HWC_SIDEBAND compositionType ) auf einer Anzeige vorhanden ist, ist der sidebandStream der Ebene das von der OMX-Videokomponente zugewiesene Seitenband-Handle.

Der HWC synchronisiert decodierte Videoframes (von der getunnelten OMX-Komponente) mit der zugehörigen Audiospur (mit der audio-hw-sync ID). Wenn ein neues Videobild aktuell wird, setzt das HWC es mit den aktuellen Inhalten aller Ebenen zusammen, die beim letzten Vorbereitungs- oder Set-Aufruf empfangen wurden, und zeigt das resultierende Bild an. Die Aufrufe „prepare“ oder „set“ erfolgen nur, wenn sich andere Ebenen ändern oder wenn sich Eigenschaften der Seitenbandebene (z. B. Position oder Größe) ändern.

Die folgende Abbildung stellt den HWC dar, der mit dem Hardware- (oder Kernel- oder Treiber-)Synchronisierer arbeitet, um Videobilder (7b) mit der neuesten Komposition (7a) zu kombinieren, um sie zum richtigen Zeitpunkt anzuzeigen, basierend auf Audio (7c).

HWC kombiniert Videobilder basierend auf Audio

Abbildung 2. HWC-Hardware- (oder Kernel- oder Treiber-) Synchronizer