Tunnellisation multimédia

Le tunneling multimédia permet aux données vidéo compressées de transiter via un décodeur vidéo matériel directement vers un écran, 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 quelles images vidéo envoyer à l'écran et quand les envoyer 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 horodatages de présentation audio transmis par l'application

  • Pour la lecture de diffusions 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 diffuse ensuite l'image vidéo décodée sur l'écran pour qu'elle soit restituée à la même heure d'horloge système que l'image audio correspondante, récupérant les instances AudioTimestamps historiques pour calculer le timing correct.

Étant donné que la lecture vidéo tunnelisée contourne le code de l'application et réduit le nombre de processus agissant sur la vidéo, elle peut fournir un rendu vidéo plus efficace en fonction de l'implémentation OEM. Il peut également fournir une cadence vidéo et une synchronisation 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 de rendu vidéo et le timing des véritables vsyncs matérielles. Cependant, le tunneling peut également réduire la prise en charge des effets GPU tels que le flou ou les coins arrondis dans les fenêtres d'image dans l'image (PiP), car les tampons contournent la pile graphique Android.

Le diagramme suivant montre comment le tunneling simplifie le processus de lecture vidéo.

comparaison des modes tradition et tunnel

Figure 1. Comparaison des processus de lecture vidéo traditionnels et tunnelés

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 nécessite uniquement la reconfiguration de cette bibliothèque pour la lecture tunnelisée. Pour l’implémentation de bas niveau d’un lecteur vidéo tunnelé, utilisez les instructions suivantes.

Pour la lecture vidéo à 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. Mettez en file d'attente les données audio sur AudioTrack avec l'horodatage de présentation de la première image audio des données audio.

Pour la lecture de diffusions en direct sous Android 11 ou version ultérieure :

  1. Créez une instance SurfaceView .

  2. Obtenez une instance avSyncHwId auprès de Tuner .

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

Le flux d'appels de l'API est présenté 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 de la lecture vidéo à la demande

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

  • Sur la plupart des appareils, par défaut, une image vidéo n'est rendue qu'après le début de la lecture audio. Cependant, l'application peut avoir besoin de restituer une image vidéo avant de démarrer la lecture audio, par exemple pour montrer à l'utilisateur la position actuelle de la vidéo lors de la recherche.

    • Pour signaler que la première image vidéo en file d'attente doit être restituée dès qu'elle est 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 lorsque des images B sont présentes), cela signifie que la première image vidéo affichée doit toujours être une image I.

    • Si vous ne souhaitez pas que la première image vidéo en file d'attente soit rendue 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 du périphérique.

  • Lorsque les données audio ne sont pas fournies à AudioTrack et que les tampons sont vides (sous-débit audio), la lecture vidéo s'arrête jusqu'à ce que davantage de 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 horodatages des présentations audio. Lorsque cela se produit, l'OEM corrige les écarts négatifs en bloquant l'image vidéo actuelle, et les écarts positifs en supprimant les images vidéo ou en insérant des images audio silencieuses (en fonction de l'implémentation de l'OEM). La position de l’image AudioTimestamp n’augmente pas pour les images audio silencieuses insérées.

Pour les fabricants d'appareils

Configuration

Les OEM doivent créer un décodeur vidéo distinct pour prendre en charge la lecture vidéo tunnelisée. Ce décodeur devrait annoncer qu'il est capable d'effectuer une lecture tunnelisée dans le fichier media_codecs.xml :

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

Lorsqu'une instance MediaCodec tunnelisée 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 du périphérique 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. S'il n'a pas encore été créé, l'ID HW_AV_SYNC est transmis au flux de sortie lors de la création AudioTrack . Ceci est fait par le fil de lecture :

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 passé dans le composant OMX ou Codec2 afin que le code OEM puisse associer le codec au flux de sortie audio correspondant ou au flux tuner.

Lors de la configuration du composant, le composant OMX ou Codec2 doit renvoyer un handle de bande latérale qui peut être utilisé pour associer le codec à une couche Hardware Composer (HWC). Lorsque l'application associe une surface à MediaCodec , ce handle de bande latérale est transmis à HWC via SurfaceFlinger , qui configure la couche en tant que couche de bande latérale .

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 est chargé de recevoir de nouveaux tampons d'image de la sortie du codec au moment approprié, soit synchronisés 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 couches et d'afficher l'image résultante. Cela se produit indépendamment du cycle normal de préparation et de réglage. Les appels de préparation et de définition se produisent uniquement 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 de décodeur tunnelé doit prendre en charge les éléments suivants :

  • Définition du paramètre étendu OMX.google.android.index.configureVideoTunnelMode , qui utilise la structure ConfigureVideoTunnelModeParams pour transmettre l'ID HW_AV_SYNC associé au périphérique de sortie audio.

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

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

L'implémentation 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 prend en charge cette configuration, il doit allouer un handle de bande latérale à ce codec et le renvoyer via le membre pSidebandWindow afin que le HWC puisse identifier le codec associé. Si le composant ne prend pas en charge cette configuration, il doit définir bTunneled sur OMX_FALSE .

Codec2

Sous Android 11 ou version ultérieure, Codec2 prend en charge la lecture tunnelisée. Le composant décodeur doit prendre en charge les éléments suivants :

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

  • Interrogation C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE , pour allouer et récupérer le handle de bande latérale pour HWC.

  • Gestion de C2_PARAMKEY_TUNNEL_HOLD_RENDER lorsqu'il est attaché à un C2Work , qui demande au codec de décoder et de signaler la fin du travail, mais de ne pas restituer le tampon de sortie jusqu'à ce que 1) le codec reçoive ultérieurement l'instruction de le restituer ou 2) que la lecture audio commence.

  • Gestion de C2_PARAMKEY_TUNNEL_START_RENDER , qui demande au codec de restituer immédiatement l'image marquée avec C2_PARAMKEY_TUNNEL_HOLD_RENDER , même si la lecture audio n'a pas démarré.

  • Laissez debug.stagefright.ccodec_delayed_params non configuré (recommandé). Si vous le configurez, définissez-le 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 prend en charge cette configuration, il doit allouer un handle de bande latérale à ce codec et le renvoyer via C2PortTunnelHandlingTuning afin que le HWC puisse identifier le codec associé.

AudioHAL

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

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 restitue les images vidéo en synchronisation avec les images audio correspondantes, l'Audio HAL 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 fichier audio compressé, l'Audio HAL peut avoir besoin d'analyser les métadonnées contenues dans les données audio compressées pour déterminer leur durée de lecture.

Suspendre l'assistance

Android 5 ou version antérieure n'inclut pas la prise en charge de la pause. Vous pouvez suspendre la lecture tunnelisée uniquement en cas de manque d'A/V, mais si le tampon interne pour la vidéo est volumineux (par exemple, il y a une seconde de données dans le composant OMX), la pause semble ne pas répondre.

Sous Android 5.1 ou version ultérieure, AudioFlinger prend en charge la pause et la reprise pour les sorties audio directes (tunnelées). Si le HAL implémente une pause et une reprise, la pause et la reprise de la piste sont transmises au HAL.

La séquence d'appel pause, vidage, reprise est respectée en exécutant les appels HAL dans le thread de lecture (identique au déchargement).

Suggestions de mise en œuvre

AudioHAL

Pour Android 11, l'ID de synchronisation matérielle de PCR ou STC peut être utilisé pour la synchronisation A/V, de sorte que le flux vidéo uniquement est pris en charge.

Pour Android 10 ou version antérieure, les appareils prenant en charge la lecture vidéo tunnelisée doivent avoir 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 sont utilisés pour régler 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 tunnelisée (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 tunnelé doit :

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

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

  • Spécifiez ses capacités dans le fichier media_codecs.xml et déclarez la fonctionnalité de lecture tunnelisée. Il doit également clarifier toute limitation concernant la taille de trame, l'alignement ou le débit binaire. Un exemple est présenté ci-dessous :

    <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 tunnelisé et non tunnelisé, il doit laisser la fonctionnalité de lecture tunnelisée comme non requise. Les décodeurs tunnelisés et non tunnelisés ont alors les mêmes limitations de capacités. Un exemple est présenté 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>

Compositeur de matériel (HWC)

Lorsqu'il y a une couche tunnelisée (une couche avec HWC_SIDEBAND compositionType ) sur un écran, le sidebandStream de la couche est le handle de bande latérale alloué par le composant vidéo OMX.

Le HWC synchronise les images vidéo décodées (du composant OMX tunnelisé) avec la piste audio associée (avec l'ID audio-hw-sync ). Lorsqu'une nouvelle image vidéo devient actuelle, 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 et affiche l'image résultante. Les appels de préparation ou de définition se produisent uniquement 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) pour un affichage au bon moment, en fonction de l'audio (7c).

HWC combinant des images vidéo basées sur l'audio

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