Tunnelisation multimédia

Le tunnel multimédia permet de tunneliser des données vidéo compressées à un écran via un décodeur vidéo matériel, sans être traitées par le code d'application ou le code du framework Android. Le code spécifique à l'appareil situé sous la pile Android détermine les images vidéo à envoyer à l'écran et à quel moment en comparant les horodatages de présentation des images vidéo avec l'un des types d'horloge interne suivants:

  • Pour la lecture vidéo à la demande sous Android 5 ou version ultérieure, une horloge AudioTrack synchronisée avec les codes temporels de la présentation audio transmis par l'application

  • Pour une diffusion en direct sous Android 11 ou version ultérieure, une horloge de référence de programme (PCR) ou une horloge système (STC) pilotée par un tuner

Arrière-plan

La lecture vidéo traditionnelle sur Android informe l'application lorsqu'une image vidéo compressée a été décodée. L'application libère ensuite l'image vidéo décodée sur l'écran pour être affichée à la même heure système que l'image audio correspondante, en récupérant des instances historiques AudioTimestamps pour calculer la durée correcte.

Étant donné que la lecture vidéo en tunnel contourne le code de l'application et réduit le nombre de processus qui agissent sur la vidéo, elle peut fournir un rendu vidéo plus efficace en fonction de l'implémentation de l'OEM. Il peut également fournir une cadence et une synchronisation vidéo plus précises avec l'horloge choisie (PRC, STC ou audio) en évitant les problèmes de synchronisation introduits par un décalage potentiel entre le timing des requêtes Android pour l'affichage de la vidéo et le timing des véritables synchronisations matérielles. Toutefois, le tunnelling peut également réduire la prise en charge des effets GPU tels que le flou ou les coins arrondis dans les fenêtres Picture-in-Picture (PiP), car les tampons contournent la pile graphique Android.

Le schéma suivant montre comment la tunnelisation simplifie le processus de lecture vidéo.

Comparaison des modes traditionnels et tunnel

Figure 1 : Comparaison entre les processus de lecture vidéo traditionnels et les processus de lecture par tunnel

Pour les développeurs d'applications

Étant donné que la plupart des développeurs d'applications intègrent une bibliothèque pour l'implémentation de la lecture, dans la plupart des cas, l'implémentation ne nécessite que de reconfigurer cette bibliothèque pour la lecture en tunnel. Pour l'implémentation de bas niveau d'un lecteur vidéo par tunnel, suivez les instructions ci-dessous.

Pour lire des vidéos à la demande sous Android 5 ou version ultérieure:

  1. Créez une instance SurfaceView.

  2. Créez une instance audioSessionId.

  3. Créez des instances AudioTrack et MediaCodec avec l'instance audioSessionId créée à l'étape 2.

  4. Mettre en file d'attente des données audio dans AudioTrack avec le code temporel de présentation du premier frame audio dans les données audio.

Pour une diffusion en direct sous Android 11 ou version ultérieure:

  1. Créez une instance SurfaceView.

  2. Obtenez une instance avSyncHwId à partir de Tuner.

  3. Créez les instances AudioTrack et MediaCodec avec l'instance avSyncHwId créée à l'étape 2.

Le flux d'appel d'API est illustré dans les extraits de code suivants :

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

Comportement lors de la lecture de vidéos à la demande

Étant donné que la lecture vidéo à la demande en tunnel est implicitement liée à la lecture AudioTrack, le comportement de la lecture vidéo en tunnel peut dépendre du comportement de la lecture audio.

  • Sur la plupart des appareils, par défaut, une image vidéo n'est pas affichée tant que la lecture audio n'a pas commencé. Toutefois, l'application peut être amenée à afficher un frame vidéo avant de lancer la lecture audio, par exemple pour montrer à l'utilisateur la position actuelle de la vidéo lors d'une recherche.

    • Pour signaler que la première image vidéo en file d'attente doit être affichée dès qu'elle a été décodée, définissez le paramètre PARAMETER_KEY_TUNNEL_PEEK sur 1. Lorsque les images vidéo compressées sont réorganisées dans la file d'attente (par exemple, en présence d'images B), cela signifie que la première image vidéo affichée doit toujours être une image iFrame.

    • Si vous ne souhaitez pas que le premier frame vidéo mis en file d'attente soit affiché avant le début de la lecture audio, définissez ce paramètre sur 0.

    • Si ce paramètre n'est pas défini, l'OEM détermine le comportement de l'appareil.

  • Lorsque des données audio ne sont pas fournies à AudioTrack et que les tampons sont vides (sous-utilisation audio), la lecture vidéo s'arrête jusqu'à ce que d'autres données audio soient écrites, car l'horloge audio n'avance plus.

  • Pendant la lecture, des discontinuités que l'application ne peut pas corriger peuvent apparaître dans les codes temporels de la présentation audio. Dans ce cas, l'OEM corrige les intervalles négatifs en bloquant l'image vidéo actuelle, et les intervalles positifs en supprimant des images vidéo ou en insérant des images audio silencieuses (selon l'implémentation de l'OEM). La position des trames AudioTimestamp n'augmente pas pour les trames audio silencieuses insérées.

Pour les fabricants d'appareils

Configuration

Les OEM doivent créer un décodeur vidéo distinct pour permettre la lecture vidéo par tunnel. Ce décodeur doit annoncer qu'il est capable de tunneliser la lecture dans le fichier media_codecs.xml:

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

Lorsqu'une instance MediaCodec en tunnel est configurée avec un ID de session audio, elle interroge AudioFlinger pour cet 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);
}

Au cours de cette requête, AudioFlinger récupère l'ID HW_AV_SYNC de l'appareil audio principal et l'associe en interne à l'ID de session 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);

Si une instance AudioTrack a déjà été créée, l'ID HW_AV_SYNC est transmis au flux de sortie avec le même ID de session audio. Si elle n'a pas encore été créée, l'ID HW_AV_SYNC est transmis au flux de sortie lors de la création de AudioTrack. C'est le thread de lecture qui s'en charge :

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

L'ID HW_AV_SYNC, qu'il corresponde à un flux de sortie audio ou à une configuration Tuner, est transmis au composant OMX ou Codec2 afin que le code OEM puisse associer le codec au flux de sortie audio ou au flux du tuner correspondant.

Lors de la configuration du composant, le composant OMX ou Codec2 doit renvoyer un gestionnaire de bande latérale pouvant être utilisé pour associer le codec à une couche de composition matérielle (HWC). Lorsque l'application associe une surface à MediaCodec, cette poignée de bande latérale est transmise à HWC via SurfaceFlinger, qui configure la couche en tant que couche 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;
}

Le HWC est chargé de recevoir de nouveaux tampons d'image à partir de la sortie du codec au moment opportun, soit synchronisé avec le flux de sortie audio associé, soit avec l'horloge de référence du programme du tuner, de composer les tampons avec le contenu actuel des autres calques et d'afficher l'image obtenue. Cela se produit indépendamment du cycle de préparation et de configuration normal. Les appels de préparation et de définition ne se produisent que lorsque d'autres couches changent ou lorsque les propriétés de la couche de bande latérale (telles que la position ou la taille) changent.

OMX

Un composant décodeur en tunnel doit prendre en charge les éléments suivants :

  • Définir le paramètre étendu OMX.google.android.index.configureVideoTunnelMode, qui utilise la structure ConfigureVideoTunnelModeParams pour transmettre l'ID HW_AV_SYNC associé à l'appareil de sortie audio.

  • Configuration du paramètre OMX_IndexConfigAndroidTunnelPeek qui indique au codec d'effectuer ou non le rendu de la première image vidéo décodée, que la lecture audio ait commencé ou non.

  • Envoi de l'événement OMX_EventOnFirstTunnelFrameReady lorsque la première image vidéo acheminée par tunnel a été décodée et est prête à être affichée.

L'implémentation d'AOSP configure le mode tunnel dans ACodec via OMXNodeInstance, comme indiqué dans l'extrait de code suivant:

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;

Si le composant est compatible avec cette configuration, il doit attribuer un handle de bande latérale à ce codec et le transmettre via le membre pSidebandWindow afin que le matériel puisse identifier le codec associé. Si le composant n'est pas compatible avec cette configuration, il doit définir bTunneled sur OMX_FALSE.

Codec2

Sous Android 11 ou version ultérieure, Codec2 est compatible avec la lecture par tunnel. Le composant du décodeur doit être compatible avec les éléments suivants:

  • Configuration de C2PortTunneledModeTuning, qui configure le mode tunnel et transmet le HW_AV_SYNC récupéré à partir de l'appareil de sortie audio ou de la configuration du tuner.

  • En interrogeant C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE pour allouer et récupérer le handle de bande latérale pour le matériel

  • Gestion de C2_PARAMKEY_TUNNEL_HOLD_RENDER lorsqu'il est associé à un C2Work, ce qui indique au codec de décoder et de signaler la fin du travail, mais pas de restituer le tampon de sortie tant que 1) le codec n'est pas invité à le restituer plus tard ou 2) la lecture audio ne commence pas.

  • Gestion de C2_PARAMKEY_TUNNEL_START_RENDER, qui indique au codec de rendre immédiatement le frame marqué avec C2_PARAMKEY_TUNNEL_HOLD_RENDER, même si la lecture audio n'a pas commencé.

  • Laissez debug.stagefright.ccodec_delayed_params non configuré (recommandé). Si vous le configurez, définissez cette option sur false.

L'implémentation AOSP configure le mode tunnel dans CCodec via C2PortTunnelModeTuning, comme indiqué dans l'extrait de code suivant :

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

Si le composant est compatible avec cette configuration, il doit allouer un gestionnaire de bande latérale à ce codec et le renvoyer via C2PortTunnelHandlingTuning afin que le HWC puisse identifier le codec associé.

HAL audio

Pour la lecture vidéo à la demande, le HAL audio reçoit les codes temporels de présentation audio en ligne avec les données audio au format big-endian dans un en-tête situé au début de chaque bloc de données audio que l'application écrit :

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

Pour que HWC effectue le rendu des images vidéo synchronisées avec les images audio correspondantes, la HAL audio doit analyser l'en-tête de synchronisation et utiliser l'horodatage de présentation pour resynchroniser l'horloge de lecture avec le rendu audio. Pour se resynchroniser lors de la lecture d'un contenu audio compressé, le HAL audio peut avoir besoin d'analyser les métadonnées à l'intérieur des données audio compressées afin de déterminer sa durée de lecture.

Suspendre l'assistance

Android 5 ou version antérieure n'est pas compatible avec la mise en pause. Vous ne pouvez mettre en pause la lecture en tunnel que par manque de ressources A/V, mais si la mémoire tampon interne pour la vidéo est importante (par exemple, une seconde de données dans le composant OMX), la mise en pause semble ne pas répondre.

Sous Android 5.1 ou version ultérieure, AudioFlinger prend en charge la mise en pause et la reprise pour les sorties audio directes (en tunnel). Si le HAL implémente la pause et la reprise, le suivi de la pause et de la reprise est transmis au HAL.

La séquence d'appels de mise en pause, de vidage et de reprise est respectée par l'exécution des appels HAL dans le thread de lecture (comme pour le déchargement).

Suggestions d'implémentation

Audio HAL

Pour Android 11, l'ID de synchronisation matérielle de PCR ou STC peut être utilisé pour la synchronisation A/V. Le flux vidéo uniquement est donc compatible.

Pour Android 10 ou version antérieure, les appareils compatibles avec la lecture de vidéos par tunnel doivent disposer d'au moins un profil de flux de sortie audio avec les indicateurs FLAG_HW_AV_SYNC et AUDIO_OUTPUT_FLAG_DIRECT dans son fichier audio_policy.conf. Ces indicateurs permettent de définir l'horloge système à partir de l'horloge audio.

OMX

Les fabricants d'appareils doivent disposer d'un composant OMX distinct pour la lecture vidéo par tunnel. Les fabricants peuvent disposer de composants OMX supplémentaires pour d'autres types de lecture audio et vidéo, tels que la lecture sécurisée. Le composant acheminé par tunnel doit:

  • Spécifiez 0 buffers (nBufferCountMin, nBufferCountActual) sur son port de sortie.

  • Implémentez l'extension OMX.google.android.index.prepareForAdaptivePlayback setParameter.

  • Spécifiez ses fonctionnalités dans le fichier media_codecs.xml et déclarez la fonctionnalité de lecture en tunnel. Il doit également clarifier les limites de taille, d'alignement ou de débit du frame. Voici un exemple :

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

Si le même composant OMX est utilisé pour prendre en charge le décodage en tunnel et hors tunnel, la fonctionnalité de lecture en tunnel doit être définie comme non requise. Les décodeurs en tunnel et non en tunnel ont alors les mêmes limites de fonctionnalités. Vous trouverez un exemple ci-dessous:

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

Lorsqu'une couche tunnel (c'est-à-dire une couche avec HWC_SIDEBAND compositionType) est présente sur un écran, l'élément sidebandStream de la couche correspond à la poignée de bande latérale allouée par le composant vidéo OMX.

Le matériel synchronise les images vidéo décodées (à partir du composant OMX acheminé par tunnel) avec la piste audio associée (avec l'ID audio-hw-sync). Lorsqu'une nouvelle image vidéo devient active, le HWC la compose avec le contenu actuel de toutes les couches reçues lors du dernier appel de préparation ou de définition, puis affiche l'image obtenue. Les appels de préparation ou de définition ne se produisent que lorsque d'autres couches changent ou lorsque les propriétés de la couche de bande latérale (telles que la position ou la taille) changent.

La figure suivante représente le HWC travaillant avec le synchroniseur matériel (ou noyau ou pilote) pour combiner les images vidéo (7b) avec la dernière composition (7a) à afficher au bon moment, en fonction de l'audio (7c).

HWC combinant des trames vidéo en fonction de l&#39;audio

Figure 2. Synchronisateur matériel (ou noyau ou pilote) HWC