Tunneling multimediale

Il tunneling multimediale consente ai dati video compressi di passare attraverso un decoder video hardware direttamente su un display, senza essere elaborati dal codice dell'app o dal codice del framework Android. Il codice specifico del dispositivo sotto lo stack Android determina quali fotogrammi video inviare al display e quando inviarli confrontando i timestamp di presentazione dei fotogrammi video con uno dei seguenti tipi di orologio interno:

  • Per la riproduzione di video su richiesta in Android 5 o versioni successive, un orologio AudioTrack sincronizzato con i timestamp della presentazione audio trasmessi dall'app

  • Per la riproduzione di trasmissioni dal vivo in Android 11 o versioni successive, un orologio di riferimento del programma (PCR) o un orologio di sistema (STC) gestito da un sintonizzatore

Sfondo

La riproduzione video tradizionale su Android avvisa l'app quando un fotogramma video compresso è stato decodificato. L'app rilascia quindi il fotogramma video decodificato sul display per il rendering allo stesso orario dell'orologio di sistema del fotogramma audio corrispondente, recuperando le istanze storiche AudioTimestamps per calcolare la temporizzazione corretta.

Poiché la riproduzione video con tunneling ignora il codice dell'app e riduce il numero di processi che agiscono sul video, può fornire un rendering video più efficiente a seconda dell'implementazione OEM. Può anche fornire una cadenza video e una sincronizzazione più accurate con l'orologio scelto (PRC, STC o audio) evitando problemi di temporizzazione introdotti da una potenziale distorsione tra i tempi delle richieste Android per il rendering del video e i tempi dei veri sincronismi hardware. Tuttavia, il tunneling può anche ridurre il supporto per gli effetti GPU come la sfocatura o gli angoli arrotondati nelle finestre Picture-in-Picture (PiP), perché i buffer ignorano lo stack grafico di Android.

Il diagramma seguente mostra come il tunneling semplifica il processo di riproduzione video.

confronto tra modalità tradizione e modalità tunnel

Figura 1. Confronto tra i processi di riproduzione video tradizionale e con tunnel

Per gli sviluppatori di app

Poiché la maggior parte degli sviluppatori di app si integra con una libreria per l'implementazione della riproduzione, nella maggior parte dei casi l'implementazione richiede solo la riconfigurazione di tale libreria per la riproduzione tramite tunnel. Per l'implementazione di basso livello di un lettore video con tunnel, utilizzare le seguenti istruzioni.

Per la riproduzione di video su richiesta in Android 5 o versioni successive:

  1. Crea un'istanza SurfaceView .

  2. Crea un'istanza audioSessionId .

  3. Crea istanze AudioTrack e MediaCodec con l'istanza audioSessionId creata nel passaggio 2.

  4. Accoda i dati audio ad AudioTrack con il timestamp della presentazione per il primo fotogramma audio nei dati audio.

Per la riproduzione di trasmissioni dal vivo in Android 11 o versioni successive:

  1. Crea un'istanza SurfaceView .

  2. Ottieni un'istanza avSyncHwId da Tuner .

  3. Crea istanze AudioTrack e MediaCodec con l'istanza avSyncHwId creata nel passaggio 2.

Il flusso delle chiamate API è mostrato nei seguenti frammenti di codice:

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

Comportamento della riproduzione video su richiesta

Poiché la riproduzione video su richiesta con tunneling è legata implicitamente alla riproduzione AudioTrack , il comportamento della riproduzione video con tunneling potrebbe dipendere dal comportamento della riproduzione audio.

  • Sulla maggior parte dei dispositivi, per impostazione predefinita, il rendering di un fotogramma video non viene eseguito finché non inizia la riproduzione audio. Tuttavia, l'app potrebbe dover eseguire il rendering di un fotogramma video prima di avviare la riproduzione audio, ad esempio per mostrare all'utente la posizione corrente del video durante la ricerca.

    • Per segnalare che il primo fotogramma video in coda deve essere renderizzato non appena viene decodificato, imposta il parametro PARAMETER_KEY_TUNNEL_PEEK su 1 . Quando i fotogrammi video compressi vengono riordinati nella coda (come quando sono presenti fotogrammi B ), ciò significa che il primo fotogramma video visualizzato dovrebbe sempre essere un fotogramma I.

    • Se non vuoi che venga eseguito il rendering del primo fotogramma video in coda fino all'inizio della riproduzione audio, imposta questo parametro su 0 .

    • Se questo parametro non è impostato, l'OEM determina il comportamento del dispositivo.

  • Quando i dati audio non vengono forniti ad AudioTrack e i buffer sono vuoti (audio insufficiente), la riproduzione video si blocca finché non vengono scritti altri dati audio perché il clock audio non avanza più.

  • Durante la riproduzione, nei timestamp della presentazione audio potrebbero essere visualizzate discontinuità che l'app non è in grado di correggere. Quando ciò accade, l'OEM corregge i gap negativi bloccando il fotogramma video corrente e i gap positivi eliminando fotogrammi video o inserendo fotogrammi audio silenziosi (a seconda dell'implementazione OEM). La posizione del frame AudioTimestamp non aumenta per i frame audio silenziosi inseriti.

Per i produttori di dispositivi

Configurazione

Gli OEM dovrebbero creare un decodificatore video separato per supportare la riproduzione video con tunnel. Questo decodificatore dovrebbe pubblicizzare la sua capacità di riproduzione tramite tunnel nel file media_codecs.xml :

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

Quando un'istanza MediaCodec con tunnel è configurata con un ID sessione audio, interroga AudioFlinger per questo ID HW_AV_SYNC :

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

Durante questa query, AudioFlinger recupera l'ID HW_AV_SYNC dal dispositivo audio primario e lo associa internamente all'ID della sessione audio:

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

Se è già stata creata un'istanza AudioTrack , l'ID HW_AV_SYNC viene passato al flusso di output con lo stesso ID sessione audio. Se non è stato ancora creato, l'ID HW_AV_SYNC viene passato al flusso di output durante la creazione AudioTrack . Questo viene fatto dal thread di riproduzione :

mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);

L'ID HW_AV_SYNC , indipendentemente dal fatto che corrisponda a un flusso di uscita audio o a una configurazione Tuner , viene passato al componente OMX o Codec2 in modo che il codice OEM possa associare il codec al flusso di uscita audio corrispondente o al flusso del sintonizzatore.

Durante la configurazione del componente, il componente OMX o Codec2 dovrebbe restituire un handle di banda laterale che può essere utilizzato per associare il codec a un livello Hardware Composer (HWC). Quando l'app associa una superficie a MediaCodec , questo handle di banda laterale viene passato a HWC tramite SurfaceFlinger , che configura il livello come livello di banda laterale .

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 è responsabile della ricezione di nuovi buffer di immagine dall'uscita del codec al momento opportuno, sincronizzati con il flusso di uscita audio associato o con l'orologio di riferimento del programma del sintonizzatore, componendo i buffer con il contenuto corrente di altri livelli e visualizzando l'immagine risultante. Ciò avviene indipendentemente dal normale ciclo di preparazione e impostazione. Le chiamate di preparazione e impostazione avvengono solo quando cambiano gli altri livelli o quando cambiano le proprietà del livello della banda laterale (come posizione o dimensione).

OMX

Un componente decodificatore con tunnel dovrebbe supportare quanto segue:

  • Impostazione del parametro esteso OMX.google.android.index.configureVideoTunnelMode , che utilizza la struttura ConfigureVideoTunnelModeParams per passare l'ID HW_AV_SYNC associato al dispositivo di output audio.

  • Configurazione del parametro OMX_IndexConfigAndroidTunnelPeek che indica al codec di eseguire o meno il rendering del primo fotogramma video decodificato, indipendentemente dall'avvio della riproduzione audio.

  • Invio dell'evento OMX_EventOnFirstTunnelFrameReady quando il primo fotogramma video con tunnel è stato decodificato ed è pronto per il rendering.

L'implementazione AOSP configura la modalità tunnel in ACodec tramite OMXNodeInstance come mostrato nel seguente frammento di codice:

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;

Se il componente supporta questa configurazione, deve allocare un handle di banda laterale a questo codec e ripassarlo attraverso il membro pSidebandWindow in modo che l'HWC possa identificare il codec associato. Se il componente non supporta questa configurazione, dovrebbe impostare bTunneled su OMX_FALSE .

Codec2

In Android 11 o versioni successive, Codec2 supporta la riproduzione con tunnel. Il componente decodificatore dovrebbe supportare quanto segue:

  • Configurazione di C2PortTunneledModeTuning , che configura la modalità tunnel e passa HW_AV_SYNC recuperato dal dispositivo di output audio o dalla configurazione del sintonizzatore.

  • Interrogazione di C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE per allocare e recuperare l'handle della banda laterale per HWC.

  • Gestione di C2_PARAMKEY_TUNNEL_HOLD_RENDER quando collegato a un C2Work , che indica al codec di decodificare e segnalare il completamento del lavoro, ma di non eseguire il rendering del buffer di output fino a quando 1) al codec non viene successivamente richiesto di eseguirne il rendering o 2) inizia la riproduzione audio.

  • Gestione di C2_PARAMKEY_TUNNEL_START_RENDER , che indica al codec di eseguire immediatamente il rendering del fotogramma contrassegnato con C2_PARAMKEY_TUNNEL_HOLD_RENDER , anche se la riproduzione audio non è stata avviata.

  • Lascia debug.stagefright.ccodec_delayed_params non configurato (consigliato). Se lo configuri, impostalo su false .

L'implementazione AOSP configura la modalità tunnel in CCodec tramite C2PortTunnelModeTuning , come mostrato nel seguente frammento di codice:

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

Se il componente supporta questa configurazione, deve allocare un handle di banda laterale a questo codec e ritrasmetterlo tramite C2PortTunnelHandlingTuning in modo che l'HWC possa identificare il codec associato.

AudioHAL

Per la riproduzione video su richiesta, l'HAL audio riceve i timestamp della presentazione audio in linea con i dati audio in formato big-endian all'interno di un'intestazione che si trova all'inizio di ogni blocco di dati audio scritti dall'app:

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

Affinché HWC possa eseguire il rendering dei fotogrammi video in sincronia con i fotogrammi audio corrispondenti, l'HAL audio deve analizzare l'intestazione di sincronizzazione e utilizzare il timestamp della presentazione per risincronizzare l'orologio di riproduzione con il rendering audio. Per risincronizzarsi durante la riproduzione dell'audio compresso, l'HAL audio potrebbe dover analizzare i metadati all'interno dei dati audio compressi per determinarne la durata di riproduzione.

Metti in pausa il supporto

Android 5 o versioni precedenti non includono il supporto per la pausa. Puoi mettere in pausa la riproduzione con tunnel solo tramite A/V starvation, ma se il buffer interno per il video è grande (ad esempio, c'è un secondo di dati nel componente OMX), la pausa sembra non rispondere.

In Android 5.1 o versioni successive, AudioFlinger supporta la pausa e la ripresa per le uscite audio dirette (tunnelizzate). Se l'HAL implementa la pausa e la ripresa, la pausa e la ripresa della traccia vengono inoltrate all'HAL.

La sequenza delle chiamate di pausa, svuotamento e ripresa viene rispettata eseguendo le chiamate HAL nel thread di riproduzione (come offload).

Suggerimenti per l'implementazione

AudioHAL

Per Android 11, l'ID di sincronizzazione HW di PCR o STC può essere utilizzato per la sincronizzazione A/V, quindi è supportato lo streaming solo video.

Per Android 10 o versioni precedenti, i dispositivi che supportano la riproduzione video con tunnel devono avere almeno un profilo di flusso di output audio con i flag FLAG_HW_AV_SYNC e AUDIO_OUTPUT_FLAG_DIRECT nel file audio_policy.conf . Questi flag vengono utilizzati per impostare l'orologio di sistema dall'orologio audio.

OMX

I produttori di dispositivi dovrebbero disporre di un componente OMX separato per la riproduzione video con tunneling (i produttori possono disporre di componenti OMX aggiuntivi per altri tipi di riproduzione audio e video, come la riproduzione protetta). Il componente tunnelizzato dovrebbe:

  • Specificare 0 buffer ( nBufferCountMin , nBufferCountActual ) sulla relativa porta di output.

  • Implementa l'estensione OMX.google.android.index.prepareForAdaptivePlayback setParameter .

  • Specificarne le funzionalità nel file media_codecs.xml e dichiarare la funzionalità di riproduzione con tunnel. Dovrebbe inoltre chiarire eventuali limitazioni sulla dimensione del frame, sull'allineamento o sul bitrate. Un esempio è mostrato di seguito:

    <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>
    

Se lo stesso componente OMX viene utilizzato per supportare la decodifica con e senza tunnel, dovrebbe lasciare la funzionalità di riproduzione con tunnel come non richiesta. Sia i decodificatori con tunnel che quelli senza tunnel hanno quindi le stesse limitazioni di capacità. Un esempio è mostrato di seguito:

<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>

Compositore hardware (HWC)

Quando è presente un livello con tunneling (un livello con HWC_SIDEBAND compositionType ) su un display, sidebandStream del livello è l'handle della banda laterale allocato dal componente video OMX.

L'HWC sincronizza i frame video decodificati (dal componente OMX con tunneling) alla traccia audio associata (con l'ID audio-hw-sync ). Quando un nuovo fotogramma video diventa corrente, l'HWC lo compone con il contenuto corrente di tutti i livelli ricevuti durante l'ultima chiamata di preparazione o impostazione e visualizza l'immagine risultante. Le chiamate di preparazione o impostazione avvengono solo quando cambiano gli altri livelli o quando cambiano le proprietà del livello della banda laterale (come posizione o dimensione).

La figura seguente rappresenta l'HWC che funziona con il sincronizzatore hardware (o kernel o driver), per combinare i fotogrammi video (7b) con l'ultima composizione (7a) per la visualizzazione al momento corretto, in base all'audio (7c).

HWC che combina fotogrammi video basati sull'audio

Figura 2. Sincronizzatore hardware HWC (o kernel o driver).