Tunnel multimediale

Il tunneling multimediale consente ai dati video compressi di passare attraverso un decodificatore video hardware direttamente a 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 frame video inviare al display e quando inviarli confrontando i timestamp di presentazione del frame video con uno dei seguenti tipi di orologio interno:

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

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

Background

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

Poiché la riproduzione video in tunnel aggira 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 dell'OEM. Inoltre, può fornire una cadenza e una sincronizzazione video più precise con l'orologio scelto (PRC, STC o audio) evitando i problemi di temporizzazione introdotti da potenziali scostamenti tra i tempi delle richieste di Android per il rendering del video e i tempi delle vsync hardware reali. Tuttavia, il tunneling può anche ridurre il supporto degli effetti GPU come la sfocatura o gli angoli arrotondati nelle finestre Picture in Picture (PiP), perché i buffer aggirano la pila grafica di Android.

Il seguente diagramma mostra come il tunneling semplifica la procedura di riproduzione dei video.

confronto tra modalità tradizionale e tunnel

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

Per gli sviluppatori di app

Poiché la maggior parte degli sviluppatori di app esegue l'integrazione con una libreria per l'implementazione della riproduzione, nella maggior parte dei casi l'implementazione richiede solo la riconfigurazione della libreria per la riproduzione in tunnel. Per l'implementazione di basso livello di un video player con tunnel, segui le istruzioni riportate di seguito.

Per la riproduzione di video on demand su Android 5 o versioni successive:

  1. Crea un'istanza SurfaceView.

  2. Crea un'istanza audioSessionId.

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

  4. Inserisci in coda i dati audio in AudioTrack con il timestamp della presentazione per il primo frame audio nei dati audio.

Per la riproduzione delle trasmissioni in diretta su Android 11 o versioni successive:

  1. Crea un'istanza SurfaceView.

  2. Ottieni un'istanza avSyncHwId da Tuner.

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

Il flusso di chiamata API viene mostrato nei seguenti snippet 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 dei video on demand

Poiché la riproduzione di video on demand con tunnel è legata implicitamente alla riproduzione di AudioTrack, il comportamento della riproduzione di video con tunneling potrebbe dipendere da quello della riproduzione audio.

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

    • Per indicare che il primo fotogramma video in coda deve essere visualizzato non appena viene decodificato, imposta il parametro PARAMETER_KEY_TUNNEL_PEEK su 1. Quando i frame video compressi vengono riordinati nella coda (ad esempio quando sono presenti frame B), il primo frame video visualizzato deve sempre essere un frame 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 a AudioTrack e i buffer sono vuoti (underrun audio), la riproduzione video si blocca finché non vengono scritti altri dati audio perché l'orologio audio non avanza più.

  • Durante la riproduzione, le discontinuità che l'app non possono correggere potrebbero apparire nei timestamp della presentazione audio. In questo caso, l'OEM corregge gli intervalli negativi bloccando il fotogramma video corrente e quelli positivi eliminando fotogrammi video o inserendo fotogrammi audio silenziosi (a seconda dell'implementazione dell'OEM). La posizione del frame AudioTimestamp non aumenta per i frame audio silenziosi inseriti.

Per i produttori di dispositivi

Configurazione

Gli OEM devono creare un decodificatore video separato per supportare la riproduzione video con tunnel. Questo decodificatore deve dichiarare di essere in grado di riprodurre in streaming nel filemedia_codecs.xml:

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

Quando un'istanza MediaCodec sottoposta a tunnelling è configurata con un ID sessione audio, esegue query su 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 principale e lo associa internamente all'ID 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 allo stream di output con lo stesso ID sessione audio. Se non è stato ancora creato, l'ID HW_AV_SYNC viene passato allo stream di output durante la creazione di AudioTrack. Ciò 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, che corrisponde a uno stream di output audio o a una configurazione Tuner, viene passato al componente OMX o Codec2 in modo che il codice OEM possa associare il codec allo stream di output audio o allo stream del sintonizzatore corrispondente.

Durante la configurazione del componente, il componente OMX o Codec2 deve restituire un handle sideband che può essere utilizzato per associare il codec a un livello di compositore hardware (HWC). Quando l'app associa una superficie a MediaCodec, questo handle sideband viene trasmesso all'HWC tramite SurfaceFlinger, che configura il livello come livello sideband.

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

L'HWC è responsabile della ricezione di nuovi buffer di immagini dall'uscita del codec al momento opportuno, sincronizzati con lo stream di output audio associato o con l'orologio di riferimento del programma del sintonizzatore, della composizione dei buffer con i contenuti correnti di altri livelli e della visualizzazione dell'immagine risultante. Ciò avviene indipendentemente dal normale ciclo di preparazione e impostazione. Le chiamate di preparazione e impostazione si verificano solo quando cambiano altri strati o quando cambiano le proprietà del livello della banda laterale (come la posizione o le dimensioni).

OMX

Un componente decodificatore in tunnel deve supportare quanto segue:

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

  • Configurazione del parametro OMX_IndexConfigAndroidTunnelPeek che indica al codec di eseguire o meno il rendering del primo frame video decodificato, a prescindere dall'inizio della riproduzione audio.

  • Invio dell'evento OMX_EventOnFirstTunnelFrameReady quando il primo frame video sottoposto a tunneling è stato decodificato ed è pronto per essere visualizzato.

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

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, dovrebbe allocare un handle di banda laterale al codec e ritrasmetterlo tramite il membro pSidebandWindow in modo che HWC possa identificare il codec associato. Se il componente non supporta questa configurazione, deve impostare bTunneled su OMX_FALSE.

Codec2

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

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

  • Esecuzione di una query su C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE per allocare e recuperare l'handle della banda laterale per HWC.

  • Gestione di C2_PARAMKEY_TUNNEL_HOLD_RENDER quando viene collegato a un C2Work, il che indica al codec di decodificare e segnalare il completamento del lavoro, ma di non eseguire il rendering del buffer di output finché non viene indicato in seguito 1) al codec di eseguire il rendering o 2) l'avvio della riproduzione audio.

  • Gestione di C2_PARAMKEY_TUNNEL_START_RENDER, che indica al codec di eseguire il rendering immediato del frame contrassegnato con C2_PARAMKEY_TUNNEL_HOLD_RENDER, anche se la riproduzione audio non è iniziata.

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

L'implementazione AOSP configura la modalità di tunnel in CCodec tramite C2PortTunnelModeTuning, come mostrato nel seguente snippet 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 sideband a questo codec e ritrasmetterlo tramite C2PortTunnelHandlingTuning in modo che l'HWC possa identificare il codec associato.

HAL audio

Per la riproduzione di video on demand, l'HAL Audio riceve i timestamp della presentazione audio in linea con i dati audio in formato big endian all'interno di un'intestazione trovata all'inizio di ogni blocco di dati audio scritto 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 risicronizzare il clock 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.

Mettere in pausa l'assistenza

Android 5 o versioni precedenti non include il supporto per la messa in pausa. Puoi mettere in pausa la riproduzione in tunnel solo per esaurimento A/V, ma se il buffer interno per il video è elevato (ad esempio, è presente un secondo di dati nel componente OMX), la messa in pausa sembra non essere reattiva.

In Android 5.1 o versioni successive, AudioFlinger supporta la messa in pausa e la ripresa per le uscite audio dirette (in tunnel). Se l'HAL implementa la messa in pausa e la ripresa, la messa in pausa e la ripresa del monitoraggio vengono inoltrate all'HAL.

La sequenza di chiamate di messa in pausa, svuotamento e ripresa viene rispettata mediante l'esecuzione delle chiamate HAL nel thread di riproduzione (come per il trasferimento).

Suggerimenti per l'implementazione

HAL audio

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

Per Android 10 o versioni precedenti, i dispositivi che supportano la riproduzione di video con tunnel devono avere almeno un profilo di stream 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 da quello audio.

OMX

I produttori di dispositivi devono avere un componente OMX separato per la riproduzione video con tunnel (i produttori possono avere componenti OMX aggiuntivi per altri tipi di riproduzione audio e video, ad esempio la riproduzione sicura). Il componente sottoposto a tunneling deve:

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

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

  • Specifica le relative funzionalità nel file media_codecs.xml e dichiara la funzionalità di riproduzione in tunnel. Deve anche chiarire eventuali limitazioni relative a dimensioni, allineamento o velocità in bit del fotogramma. Di seguito è riportato un esempio:

    <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 tunnel e senza tunnel, la funzionalità di riproduzione con tunnel deve essere lasciata come non obbligatoria. Pertanto, i decoder con e senza tunnel hanno le stesse limitazioni di funzionalità. Di seguito è riportato un esempio:

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

HWC (Hardware Composer)

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

L'HWC sincronizza i frame video decodificati (dal componente OMX in tunnel) con la traccia audio associata (con l'ID audio-hw-sync). Quando un nuovo frame video diventa corrente, l'HWC lo compone con i contenuti correnti di tutti i livelli ricevuti durante l'ultima chiamata prepare o set e mostra l'immagine risultante. Le chiamate di preparazione o impostazione vengono effettuate solo quando cambiano gli altri livelli o quando cambiano le proprietà del livello della banda laterale (come la posizione o le dimensioni).

La figura seguente mostra l'HWC che funziona con il sincronizzatore hardware (o kernel o driver) per combinare i frame video (7b) con la composizione più recente (7a) per la visualizzazione al momento giusto, in base all'audio (7c).

HWC che combina i fotogrammi video in base all&#39;audio

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