Tunneling multimediale

Il tunneling multimediale, noto anche come modalità tunnel, consente ai dati video compressi di passare attraverso un decoder 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 dei frame video con uno dei seguenti tipi di orologio interno:

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

  • Per la riproduzione di trasmissioni live 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 in modalità non tunnel su Android notifica all'app quando un fotogramma video compresso è stato decodificato. L'app rilascia il frame video decodificato al display per il rendering alla stessa ora dell'orologio di sistema del frame audio corrispondente, recuperando le istanze AudioTimestamp storiche per calcolare la sincronizzazione corretta.

Poiché la riproduzione video in tunnel bypassa 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 e una sincronizzazione video più accurate con l'orologio scelto (PRC, STC o audio) evitando problemi di sincronizzazione introdotti da un potenziale sfasamento tra la tempistica delle richieste di rendering video di Android e la tempistica delle sincronizzazioni verticali hardware reali. Tuttavia, il tunneling può anche ridurre il supporto degli effetti della GPU, come la sfocatura o gli angoli arrotondati nelle finestre Picture in picture (PIP), perché i buffer bypassano lo stack grafico di Android.

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

confronto tra le modalità tradizionale e tunnel

Figura 1. Confronto tra i processi di riproduzione video non in tunnel e in 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 di questa libreria per la riproduzione in tunnel. Per l'implementazione di basso livello di un video player in tunnel, utilizza le seguenti istruzioni.

Per la riproduzione di video on demand in 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. Metti in coda i dati audio in AudioTrack con il timestamp della presentazione per il primo frame audio nei dati audio.

Per la riproduzione di trasmissioni live su Android 11 o versioni successive:

  1. Crea un'istanza SurfaceView.

  2. Recupera un'istanza di avSyncHwId da Tuner.

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

Il flusso di chiamate API è 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 video on demand

Poiché la riproduzione video on demand in tunnel è legata implicitamente alla riproduzione AudioTrack, il comportamento della riproduzione video in tunnel potrebbe dipendere dal comportamento 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 segnalare 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 B-frame), il primo frame video visualizzato deve sempre essere un I-frame.

    • Se non vuoi che il primo fotogramma del video in coda venga visualizzato finché non inizia la riproduzione audio, imposta questo parametro su 0.

    • Se questo parametro non è impostato, il comportamento del dispositivo viene determinato dall'OEM.

  • Quando i dati audio non vengono forniti a AudioTrack e i buffer sono vuoti (underrun audio), la riproduzione video si interrompe finché non vengono scritti altri dati audio perché l'orologio audio non avanza più.

  • Durante la riproduzione, potrebbero comparire discontinuità che l'app non riesce a correggere nei timestamp della presentazione audio. In questi casi, l'OEM corregge i gap negativi mettendo in pausa il frame video corrente e i gap positivi eliminando i frame video o inserendo frame audio silenziosi (a seconda dell'implementazione dell'OEM). La posizione del frame AudioTimestamp non aumenta per i frame audio silenziosi inseriti.

Flusso di sequenza della ricerca precisa

La ricerca precisa ti consente di trovare un punto specifico in un video. A differenza della ricerca dei fotogrammi chiave, che passa solo al fotogramma I più vicino e può discostarsi dalla posizione di destinazione di diversi secondi, la ricerca precisa esegue il rendering del video al timestamp esatto richiesto. Il rispetto di questa sequenza API specifica consente all'app di eseguire la sincronizzazione del pre-roll e della tempistica in background senza problemi. In questo modo il fotogramma di destinazione viene visualizzato immediatamente alla ripresa della riproduzione.

Per eseguire una ricerca precisa, segui l'ordine di esecuzione illustrato nella Figura 2:

seek sequence flow

Figura 2. Sequenza di flusso per ottenere una ricerca precisa.

Ecco alcuni dettagli chiave:

  • Esecuzione parallela: puoi eseguire i passaggi all'interno di una singola casella par contemporaneamente. Ad esempio, le chiamate video MediaCodec sono indipendenti da AudioTrack.

  • Dipendenze sequenziali: chiama tutte le operazioni all'interno della prima casella par prima di passare alla seconda casella par. Nello specifico, l'app deve assicurarsi che AudioTrack.write e i buffer nel video MediaCodec siano messi in coda prima di chiamare AudioTrack.play.

Flusso della sequenza di riproduzione a velocità variabile

La riproduzione a velocità variabile ti consente di riprodurre i video a una velocità superiore o inferiore a quella normale. Questa funzionalità è di uso comune nelle app per consentire agli utenti di consumare i contenuti più velocemente (ad esempio riprodurre lezioni didattiche o podcast a 1,5x o 2x per risparmiare tempo) o più lentamente (ad esempio analizzare azioni atletiche o video istruttivi a 0,5x).

Per impostare la velocità, segui l'ordine di esecuzione illustrato nella Figura 3:

flusso della sequenza di velocità

Figura 3. Flusso della sequenza per impostare la velocità.

I seguenti comportamenti e requisiti tecnici non sono acquisiti nel diagramma di sequenza della Figura 3:

  • AudioTrack.getTimestamp restituisce framePosition in base alla frequenza dell'input audio originale. Ad esempio, con una velocità di riproduzione e un input di 44100 Hz, dopo 2 secondi di riproduzione, AudioTrack.getTimestamp restituisce framePosition di 176400.

  • Se l'app chiama setSpeed(1.5) e l'operazione va a buon fine, ma poi l'app chiama setSpeed(30) e l'operazione non va a buon fine, la riproduzione rimane a 1,5x.

  • Se l'audio è disattivato (utilizzando setVolume), l'app deve comunque inviare i buffer audio perché i fotogrammi video vengono visualizzati in base alla posizione dell'audio.

  • L'intonazione dell'audio viene mantenuta quando la velocità viene modificata.

  • La velocità di riproduzione non è influenzata da altre azioni di riproduzione.

    • Esempio 1: se la velocità di riproduzione è 1,5x e AudioTrack è in pausa, la velocità rimane 1,5x dopo la riproduzione di AudioTrack.

    • Esempio 2: se la velocità di riproduzione è 1,5x e l'utente cerca un PTS diverso, seguendo la Figura 2, la riproduzione rimane a 1,5x.

  • Per garantire che tutti i frame vengano decodificati in tempo per essere visualizzati alla velocità scelta, imposta KEY_OPERATING_RATE in modo che corrisponda al prodotto della frequenza fotogrammi del video e della velocità di riproduzione. Se KEY_OPERATING_RATE non è impostato su un valore sufficientemente alto, il codec potrebbe non decodificare i frame abbastanza velocemente, causando interruzioni di frame involontarie durante la riproduzione.

    • Esempio: se la frequenza fotogrammi originale dei contenuti è 60 fps e la velocità di riproduzione è 2x, imposta KEY_OPERATING_RATE su 120.
  • L'impostazione ripetuta della velocità con diverse velocità supportate non dovrebbe attivare alcun errore e il comportamento di riproduzione dopo l'ultima chiamata dovrebbe essere lo stesso di quando la velocità viene impostata una sola volta, sull'impostazione di velocità più recente.

Per i produttori di dispositivi

Configurazione

Gli OEM devono creare un decoder video separato per supportare la riproduzione video in tunnel. Questo decoder deve pubblicizzare la sua capacità di riproduzione in tunnel nel file media_codecs.xml:

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

Quando un'istanza MediaCodec sottoposta a tunneling è configurata con un ID sessione audio, esegue una 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 trasmesso al flusso di output con lo stesso ID sessione audio. Se non è ancora stato creato, l'ID HW_AV_SYNC viene passato al flusso di output durante la creazione di AudioTrack. Questa operazione viene eseguita 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 corrispondente o allo stream del tuner.

Durante la configurazione dei componenti, il componente OMX o Codec2 deve restituire un handle sideband che può essere utilizzato per associare il codec a un livello Hardware Composer (HWC). Quando l'app associa una superficie a MediaCodec, questo handle sideband viene trasmesso a HWC tramite SurfaceFlinger, che configura il livello come un 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;
}

HWC è responsabile della ricezione di nuovi buffer di immagini dall'output del codec al momento opportuno, sincronizzati con il flusso di uscita audio associato o con l'orologio di riferimento del programma del tuner, 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 prepare e set vengono eseguite solo quando cambiano altri livelli o quando cambiano le proprietà del livello sideband (come posizione o dimensioni).

OMX

Un componente decoder in tunnel deve supportare quanto segue:

  • Impostazione del parametro OMX.google.android.index.configureVideoTunnelMode extended 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 frame video decodificato, indipendentemente dall'avvio della riproduzione audio.

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

L'implementazione AOSP configura la modalità tunnel in ACodec tramite OMXNodeInstance come mostrato nel seguente snippet 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 sideband a questo codec e passarlo di nuovo tramite il membro pSidebandWindow in modo che l'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 decoder deve supportare quanto segue:

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

  • Esegue query su C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE per allocare e recuperare l'handle sideband 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 non di eseguire il rendering del buffer di output finché 1) il codec non riceve successivamente l'istruzione di eseguirne il rendering o 2) non inizia la riproduzione audio.

  • Gestione di C2_PARAMKEY_TUNNEL_START_RENDER, che indica al codec di eseguire immediatamente il rendering 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 la configuri, impostala su false.

L'implementazione AOSP configura la modalità 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 passarlo tramite C2PortTunnelHandlingTuning in modo che HWC possa identificare il codec associato.

HAL audio

Per la riproduzione 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 che si trova 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 esegua 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 di presentazione per risincronizzare l'orologio di riproduzione con il rendering audio. Per eseguire nuovamente la sincronizzazione durante la riproduzione di 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 l'assistenza

Android 5 o versioni precedenti non includono il supporto della pausa. Puoi mettere in pausa la riproduzione in tunnel solo per esaurimento A/V, 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 riproduzione per le uscite audio dirette (tunneling). Se l'HAL implementa la pausa e la ripresa, la traccia della pausa e della ripresa viene inoltrata all'HAL.

La sequenza di chiamate di pausa, scaricamento e ripresa viene rispettata eseguendo le chiamate HAL nel thread di riproduzione (come per l'offload).

Suggerimenti per l'implementazione

HAL audio

Per Android 11, l'ID di sincronizzazione HW da 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 video in tunnel devono avere almeno un profilo di flusso di uscita 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 devono disporre di un componente OMX separato per la riproduzione video in tunnel (possono avere componenti OMX aggiuntivi per altri tipi di riproduzione audio e video, ad esempio la riproduzione protetta). Il componente sottoposto a tunneling deve:

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

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

  • Specifica le sue funzionalità nel file media_codecs.xml e dichiara la funzionalità di riproduzione in tunnel. Inoltre, deve chiarire eventuali limitazioni relative a dimensioni, allineamento o bitrate dei frame. 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 in tunnel e non in tunnel, deve lasciare la funzionalità di riproduzione in tunnel come non obbligatoria. I decoder con tunnel e senza tunnel hanno quindi 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>

Hardware Composer (HWC)

Quando è presente un livello in tunnel (un livello con HWC_SIDEBAND compositionType) su un display, il sidebandStream del livello è l'handle sideband 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, HWC lo compone con i contenuti correnti di tutti i livelli ricevuti durante l'ultima chiamata prepare o set e visualizza l'immagine risultante. Le chiamate di preparazione o impostazione vengono eseguite solo quando cambiano altri livelli o quando cambiano le proprietà del livello sideband (ad esempio posizione o dimensioni).

La figura seguente mostra 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 i frame video in base all&#39;audio

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