Le tunneling multimédia, également appelé mode tunnel, permet aux données vidéo compressées de transiter par un décodeur vidéo matériel directement vers un écran, sans être traitées par le code de l'application ni par le code du framework Android. Le code spécifique à l'appareil situé sous la pile Android détermine les frames vidéo à envoyer à l'écran et le moment où les envoyer en comparant les codes temporels de présentation des frames vidéo à l'un des types d'horloge interne suivants :
Pour la lecture de vidéos à la demande dans Android 5 ou version ultérieure, une horloge
AudioTracksynchronisée avec les codes temporels de présentation audio transmis par l'applicationPour la lecture de diffusions en direct sur Android 11 ou version ultérieure, une horloge de référence de programme (PCR) ou une horloge de temps système (STC) pilotée par un tuner
Arrière-plan
La lecture vidéo en mode non tunnel sur Android avertit l'application lorsqu'une image vidéo compressée a été décodée. L'application libère ensuite la trame vidéo décodée pour qu'elle soit affichée au même moment que la trame audio correspondante, en récupérant les instances AudioTimestamp historiques pour calculer le timing correct.
Étant donné que la lecture de vidéos tunnelisées 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 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 éventuel décalage entre le timing des requêtes Android pour afficher la vidéo et le timing des véritables VSync matérielles. Toutefois, le tunneling peut également réduire la prise en charge des effets GPU tels que le flou ou les angles 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.

Figure 1. Comparaison des processus de lecture vidéo non tunnelés et tunnelés.
Pour les développeurs d'applications
Étant donné que la plupart des développeurs d'applications s'intègrent à une bibliothèque pour l'implémentation de la lecture, l'implémentation ne nécessite, dans la plupart des cas, que la reconfiguration de cette bibliothèque pour la lecture tunnelée. Pour une implémentation de bas niveau d'un lecteur vidéo tunnelé, suivez les instructions ci-dessous.
Pour la lecture de vidéos à la demande sous Android 5 ou version ultérieure :
Créer une instance
SurfaceView.Créez une instance
audioSessionId.Créez des instances
AudioTracketMediaCodecavec l'instanceaudioSessionIdcréée à l'étape 2.Mettez en file d'attente les données audio dans
AudioTrackavec le code temporel de présentation de la première trame audio des données audio.
Pour lire une diffusion en direct sur Android 11 ou version ultérieure :
Créer une instance
SurfaceView.Obtenez une instance
avSyncHwIdà partir deTuner.Créez des instances
AudioTracketMediaCodecavec l'instanceavSyncHwIdcréé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 de la lecture de vidéos à la demande
Étant donné que la lecture de vidéos à la demande en mode tunnel est implicitement liée à la lecture AudioTrack, le comportement de la lecture de vidéos en mode tunnel peut dépendre du comportement de la lecture audio.
Sur la plupart des appareils, par défaut, un frame vidéo n'est rendu que lorsque la lecture audio commence. Toutefois, l'application peut avoir besoin d'afficher une image vidéo avant de commencer la lecture audio, par exemple pour montrer à l'utilisateur la position actuelle de la vidéo lors de la recherche.
Pour indiquer que la première image vidéo mise en file d'attente doit être affichée dès qu'elle est décodée, définissez le paramètre
PARAMETER_KEY_TUNNEL_PEEKsur1. Lorsque les images vidéo compressées sont réorganisées dans la file d'attente (par exemple, lorsque des B-frames sont présentes), cela signifie que la première image vidéo affichée doit toujours être une I-frame.Si vous ne souhaitez pas que la première image vidéo mise en file d'attente soit affichée 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 à
AudioTracket que les mémoires tampons sont vides (sous-utilisation audio), la lecture vidéo s'interrompt 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 codes temporels de la présentation audio. Dans ce cas, l'OEM corrige les écarts négatifs en mettant en pause l'image vidéo actuelle et les écarts positifs en supprimant des images vidéo ou en insérant des images audio silencieuses (selon l'implémentation de l'OEM). La position de la trame
AudioTimestampn'augmente pas pour les trames audio silencieuses insérées.
Flux de séquence de la recherche précise
La recherche précise vous permet de trouver un moment spécifique dans une vidéo. Contrairement à la recherche par images clés, qui ne saute qu'à la trame I la plus proche et peut s'écarter de la position cible de plusieurs secondes, la recherche précise affiche la vidéo à l'heure exacte demandée. Le respect de cette séquence d'API spécifique permet à l'application d'effectuer le pré-roll en arrière-plan et la synchronisation du timing de manière fluide. Cela garantit que le frame cible s'affiche instantanément lorsque la lecture reprend.
Pour effectuer une recherche précise, suivez l'ordre d'exécution illustré à la figure 2 :
Figure 2. Séquence de flux pour une recherche précise.
Voici quelques informations clés :
Exécution parallèle : vous pouvez exécuter des étapes dans une même boîte
parsimultanément. Par exemple, les appels vidéoMediaCodecsont indépendants deAudioTrack.Dépendances séquentielles : appelez toutes les opérations de la première zone
paravant de passer à la deuxième zonepar. Plus précisément, l'application doit s'assurer queAudioTrack.writeet les tampons dansMediaCodecsont mis en file d'attente avant d'appelerAudioTrack.play.
Séquence de lecture à vitesse variable
La lecture à vitesse variable vous permet de lire une vidéo à une vitesse plus rapide ou plus lente que la vitesse normale. Cette fonctionnalité est couramment utilisée par les applications pour permettre aux utilisateurs de consommer du contenu plus rapidement (par exemple, en lisant des conférences éducatives ou des podcasts à 1,5x ou 2x pour gagner du temps) ou plus lentement (par exemple, en analysant des séquences sportives ou des vidéos pédagogiques à 0,5x).
Pour définir la vitesse, suivez l'ordre d'exécution illustré à la figure 3 :
Figure 3. Séquence de flux pour définir la vitesse.
Les comportements et exigences techniques suivants ne sont pas repris dans le diagramme de séquence de la figure 3 :
AudioTrack.getTimestamprenvoieframePositionen fonction de la fréquence d'entrée audio d'origine. Par exemple, avec une entrée de 44 100 Hz et une vitesse de lecture de 2,0x, après 2 secondes de lecture,AudioTrack.getTimestamprenvoieframePositionde 176 400.Si l'application appelle
setSpeed(1.5)et que cela réussit, puis que l'application appellesetSpeed(30)et que cela échoue, la lecture reste à 1,5x.Si le son est coupé (à l'aide de
setVolume), l'application est toujours tenue d'envoyer des tampons audio, car les images vidéo sont rendues en fonction de la position audio.La hauteur tonale de l'audio est conservée lorsque la vitesse est modifiée.
La vitesse de lecture n'est pas affectée par les autres actions de lecture.
Exemple 1 : Si la vitesse de lecture est définie sur 1,5x et que
AudioTrackest mis en pause, la vitesse reste à 1,5x après la reprise deAudioTrack.Exemple 2 : Si la vitesse de lecture est de 1,5x et que l'utilisateur recherche un autre PTS, la lecture reste à 1,5x, comme indiqué dans la figure 2.
Pour vous assurer que toutes les images sont décodées à temps pour être affichées à la vitesse choisie, définissez
KEY_OPERATING_RATEpour qu'il corresponde au produit de la fréquence d'images vidéo et de la vitesse de lecture. SiKEY_OPERATING_RATEn'est pas défini sur une valeur suffisamment élevée, il est possible que le codec ne décode pas les frames assez rapidement, ce qui peut entraîner des pertes de frames involontaires pendant la lecture.- Exemple : Si la fréquence d'images d'origine du contenu est de 60 fps et que la vitesse de lecture est de 2x, définissez
KEY_OPERATING_RATEsur120.
- Exemple : Si la fréquence d'images d'origine du contenu est de 60 fps et que la vitesse de lecture est de 2x, définissez
La définition répétée de la vitesse avec différentes vitesses prises en charge ne doit déclencher aucune erreur, et le comportement de lecture après le dernier appel doit être le même que si la vitesse n'était définie qu'une seule fois, sur le paramètre de vitesse le plus récent.
Pour les fabricants d'appareils
Configuration
Les OEM doivent créer un décodeur vidéo distinct pour prendre en charge la lecture de vidéos tunnelées.
Ce décodeur doit indiquer qu'il est capable de lire des flux tunnelés dans le fichier media_codecs.xml :
<Feature name="tunneled-playback" required="true"/>
Lorsqu'une instance MediaCodec tunnelé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);
}
Lors 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. S'il n'a pas encore été créé, l'ID HW_AV_SYNC est transmis au flux de sortie lors de la création de AudioTrack. Pour ce faire, utilisez le thread 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 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 handle de bande latérale qui peut être utilisé pour associer le codec à un calque Hardware Composer (HWC). Lorsque l'application associe une surface à MediaCodec, ce handle de bande latérale est transmis à HWC via SurfaceFlinger, qui configure le calque en tant que calque 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 les nouveaux tampons d'image à partir 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 calques et d'afficher l'image résultante. Cela se produit indépendamment du cycle normal de préparation et de définition. Les appels de préparation et de définition ne se produisent que lorsque d'autres calques changent ou lorsque les propriétés du calque de bande latérale (telles que la position ou la taille) changent.
OMX
Un composant de décodeur tunnelisé doit être compatible avec les éléments suivants :
Définissez le paramètre étendu
OMX.google.android.index.configureVideoTunnelMode, qui utilise la structureConfigureVideoTunnelModeParamspour transmettre l'IDHW_AV_SYNCassocié au périphérique de sortie audio.Configuration du paramètre
OMX_IndexConfigAndroidTunnelPeekqui indique au codec s'il doit afficher ou non la première image vidéo décodée, que la lecture audio ait commencé ou non.Envoi de l'événement
OMX_EventOnFirstTunnelFrameReadylorsque le premier frame vidéo mis en tunnel a été décodé et est prêt à être rendu.
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
Dans Android 11 ou version ultérieure, Codec2 est compatible avec la lecture en mode tunnel. Le composant du décodeur doit prendre en charge les éléments suivants :
Configurer
C2PortTunneledModeTuning, qui configure le mode tunnel et transmet leHW_AV_SYNCrécupéré à partir de l'appareil de sortie audio ou de la configuration du tuner.Interrogation de
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLEpour allouer et récupérer le handle de bande latérale pour HWC.Gestion de
C2_PARAMKEY_TUNNEL_HOLD_RENDERlorsqu'il est associé à unC2Work, qui indique au codec de décoder et de signaler la fin du travail, mais pas d'effectuer le rendu du tampon de sortie tant que 1) le codec n'a pas reçu l'instruction d'effectuer le rendu ou 2) la lecture audio n'a pas commencé.Gestion de
C2_PARAMKEY_TUNNEL_START_RENDER, qui indique au codec d'afficher immédiatement le frame marqué avecC2_PARAMKEY_TUNNEL_HOLD_RENDER, même si la lecture audio n'a pas commencé.Ne configurez pas
debug.stagefright.ccodec_delayed_params(recommandé). Si vous le configurez, définissez-le surfalse.
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, ¶ms);
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 handle 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 de vidéos à la demande, l'Audio HAL 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 é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 affiche les images vidéo en synchronisation avec les images audio correspondantes, le HAL audio doit analyser l'en-tête de synchronisation et utiliser le code temporel de présentation pour resynchroniser l'horloge de lecture avec le rendu audio. Pour resynchroniser la lecture d'un contenu audio compressé, il est possible que l'Audio HAL doive analyser les métadonnées contenues dans les données audio compressées afin de 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 ne pouvez mettre en pause la lecture en tunnel que par manque de données A/V. Toutefois, si le tampon interne de la vidéo est volumineux (par exemple, s'il contient une seconde de données dans le composant OMX), la mise en pause semble ne pas répondre.
Dans Android 5.1 ou version ultérieure, AudioFlinger est compatible avec la pause et la reprise pour les sorties audio directes (tunnelées). Si la HAL implémente la pause et la reprise, le suivi de la pause et de la reprise est transmis à la HAL.
La séquence d'appel de pause, de vidage et de reprise est respectée en exécutant les appels HAL dans le thread de lecture (comme pour le déchargement).
Suggestions d'implémentation
HAL audio
Pour Android 11, l'ID de synchronisation matérielle du PCR ou du STC peut être utilisé pour la synchronisation audio/vidéo. Le flux vidéo uniquement est donc compatible.
Pour Android 10 ou version antérieure, les appareils compatibles avec la lecture vidéo tunnelisée 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 leur 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 tunnelée (ils peuvent disposer de composants OMX supplémentaires pour d'autres types de lecture audio et vidéo, comme la lecture sécurisée). Le composant tunnelisé 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.xmlet déclarez la fonctionnalité de lecture tunnelée. Il doit également préciser les éventuelles limites concernant la taille, l'alignement ou le débit binaire des frames. 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 mode tunnel et non tunnel, il doit laisser la fonctionnalité de lecture en mode tunnel comme non requise. Les décodeurs tunnelés et non tunnelés présentent alors les mêmes limites de fonctionnalités. Voici un exemple :
<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'un calque tunnelisé (calque avec HWC_SIDEBAND compositionType) est affiché, le sidebandStream du calque est le handle de bande latérale alloué par le composant vidéo OMX.
Le HWC synchronise les images vidéo décodées (à partir du composant OMX tunnelé) avec la piste audio associée (avec l'ID audio-hw-sync). Lorsqu'une nouvelle image vidéo devient l'image 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, puis affiche l'image résultante.
Les appels de préparation ou de définition ne se produisent que lorsque d'autres calques changent ou lorsque les propriétés du calque de bande latérale (telles que la position ou la taille) changent.
La figure suivante représente le HWC fonctionnant avec le synchroniseur matériel (ou du noyau ou du pilote) pour combiner les images vidéo (7b) avec la dernière composition (7a) à afficher au bon moment, en fonction de l'audio (7c).

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