Comment ça fonctionne, le streaming adaptatif ?

Temps de lecture estimé : 3 minutes

Dans le billet Qu’est-ce-que c’est le streaming à débit adaptatif ? nous avons vu en quoi l’assouplissement des contraintes de qualité était primordial pour l’universalité du streaming.

Rentrons maintenant dans la technique. Dans le streaming simple, le fichier est découpé en segments (ou chunks) de telle sorte que le fichier global n’a pas besoin d’être téléchargé entièrement pour commencer la lecture, uniquement le segment. Ce sont ces éléments qui sont placés dans le buffer.

Disons-le tout net, le streaming adaptatif (ou ABR : Adaptative BitRate streaming) fonctionne exactement de la même manière.

La production d’un ABR

Évidemment, cette adaptation ne se fait pas magiquement. On trouve deux protocoles majoritaires permettant l’ABR : le HLS et le DASH. Tous deux forgent des fichiers d’index contenant le fichier multimédia dans différentes qualités, permettant ainsi au client de basculer de l’un à l’autre à la volée. Ces différents fichiers sont eux-mêmes découpés en segments, comme tout fichier « streamable ».

Streaming adaptatif

NB: Voir comment ces fichiers sont techniquement produits n’est pas l’objectif de ce billet, nous aurons peut-être l’occasion de le voir dans un autre.

HLS

Le format index de HLS est le .m3u8 et se compose comme suit :

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/low/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/lo_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/hi_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/high/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5"
http://example.com/audio/index.m3u8

DASH

DASH quant à lui forge un .mpd et se présente sous la forme d’un XML :

<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500000S" type="static" mediaPresentationDuration="PT0H9M56.46S" profiles="urn:mpeg:dash:profile:isoff-live:2011">
    <ProgramInformation moreInformationURL="http://gpac.sourceforge.net">
        <Title>dashed/BigBuckBunny_2s_simple_2014_05_09.mpd generated by GPAC</Title>
    </ProgramInformation>
    <Period duration="PT0H9M56.46S">
        <AdaptationSet segmentAlignment="true" group="1" maxWidth="480" maxHeight="360" maxFrameRate="24" par="4:3">
            <SegmentTemplate timescale="96" media="bunny_$Bandwidth$bps/BigBuckBunny_2s$Number$.m4s" startNumber="1" duration="192" initialization="bunny_$Bandwidth$bps/BigBuckBunny_2s_init.mp4"/>
            <Representation id="854x480 595.0kbps" mimeType="video/mp4" codecs="avc1.42c01e" width="854" height="480" frameRate="24" sar="1:1" startWithSAP="1" bandwidth="595491"/>
            <Representation id="1280x720 1.5Mbps" mimeType="video/mp4" codecs="avc1.42c01f" width="1280" height="720" frameRate="24" sar="1:1" startWithSAP="1" bandwidth="1546902"/>
            <Representation id="1920x1080 2.1Mbps" mimeType="video/mp4" codecs="avc1.42c032" width="1920" height="1080" frameRate="24" sar="1:1" startWithSAP="1" bandwidth="2133691"/>
        </AdaptationSet>
    </Period>
</MPD>

Le choix du segment côté client

Bien que le serveur permette l’adaptation, le terme adaptatif est tourné vers le client car c’est lui qui connaît sa propre situation, l’intelligence doit donc se trouver de son côté. Étudions donc à titre d’exemple un client libre sur Github : hls.js.

La définition de la fonction hls.ts::nextLoadLevel() nous mène moyennant détour à src/controller/abr-controller.ts::getNextABRAutoLevel(). La partie intéressante est celle-ci :

// https://github.com/video-dev/hls.js/blob/85daa496ffd87dc0b27107a2b175ed950a450687/src/controller/abr-controller.ts#L339
let bestLevel = this.findBestLevel(
    avgbw, // moyenne pondérée et échantillonnée de la bande passante
    minAutoLevel, // rendu minimum possible
    maxAutoLevel, // rendu maximum possible, potentiellement plafonné à la taille du lecteur
    bufferStarvationDelay, // délai avant l'expiration du buffer
    config.abrBandWidthFactor, // facteur d'ajustement bas de avgbw
    config.abrBandWidthUpFactor // facteur d'ajustement haut de avgbw
);

Cette fonction trie les rendus par ordre de qualités décroissantes et retourne celui correspondant à la bande passante moyenne mais minimisant la bufferisation. Le rendu choisi est téléchargé et s’accumule au buffer, adaptant ainsi la qualité du stream aux capacités du client.

Le streaming adaptatif fonctionne de façon assez logique au final, sans être compliqué à outrance. En tant que producteur de contenus multimédias, nous avons tout à gagner à le mettre en place pour offrir davantage de possibilités à nos consommateurs.

Comme d’habitude, vous pouvez retrouver le code pour essayer par vous-même sur le dépôt d’exemple.


Sources :


    Ce billet vous a plu ? Partagez-le sur les réseaux…


    … Ou inscrivez-vous à la newsletter pour ne manquer aucun article (Si vous ne voyez pas le formulaire, désactivez temporairement uBlock).

    Voir aussi