Hoe je een transcoderingsladder ontwerpt die ABR-algoritmen niet gek maakt
Als je ooit HLS-weergave in de praktijk hebt bekeken, heb je het gezien: de stream springt voortdurend heen en weer tussen twee renditions. Omhoog, omlaag, omhoog, omlaag. De kijker ziet een constante verschuiving in kwaliteit, soms om de paar seconden. Het is erger dan de hele tijd op een lagere rendition blijven. Tenminste een stabiele 720p-stream voelt bewust aan. Een stream die elke 10 seconden schakelt tussen 720p en 1080p voelt kapot aan.
De oorzaak is bijna altijd dezelfde: de encoderingsladder heeft renditions die te dicht bij elkaar liggen in bitrate, of het kwaliteitsverschil tussen de treden rechtvaardigt de bandbreedtesprong niet. Het ABR-algoritme aan de clientzijde kan niet beslissen, omdat het verschil tussen "dit kan ik me veroorloven" en "dit kan ik me niet veroorloven" flinterdun is.
Laten we bespreken hoe je dit goed oplost.
De bits-per-pixel-plausibiliteitscheck
Voordat je iets anders doet, moet je begrijpen wat bits-per-pixel (BPP) je vertelt over je ladder. Het is de eenvoudigste metriek om te beoordelen of een bepaalde bitrate daadwerkelijk zinvol is voor een bepaalde resolutie.
De formule is eenvoudig:
BPP = bitrate / (width × height × framerate)
Bijvoorbeeld een 1920×1080-stream op 4500 kbps en 30 fps:
BPP = 4,500,000 / (1920 × 1080 × 30) = 0.072
Waarom is dit belangrijk? Omdat BPP je de compressiedichtheid op elke trede laat zien. Als twee aangrenzende treden in je ladder zeer vergelijkbare BPP-waarden hebben, zal de kijker geen betekenisvol kwaliteitsverschil zien — maar het ABR-algoritme zal toch proberen om ertussen te schakelen. Zo krijg je ping-ponggedrag.
Een goed ontworpen ladder zou een BPP-curve moeten hebben die naar beneden loopt naarmate de resolutie toeneemt. Dit weerspiegelt een echte eigenschap van videocodecs: ze zijn efficiënter bij hogere resoluties. Je hebt bij 1080p minder bits per pixel nodig om dezelfde waargenomen kwaliteit te bereiken als bij 480p. Als je BPP vlak of inconsistent is over de treden, klopt er iets niet.
De "0,70-regel" is hier een praktische referentie. Het idee is dat wanneer je het aantal pixels verdubbelt (bijvoorbeeld bij de overgang van 720p naar 1080p), je ongeveer 0,70× de BPP van de lagere resolutie moet toepassen. Het is een vuistregel, geen wet, maar het geeft je een snelle manier om uitschieters te herkennen. Als je de BPP-waarden van je ladder uitzet en één trede er als een vreemde eend in de bijt uitspringt — te hoog of te laag vergeleken met zijn buren — zal die trede problemen veroorzaken.
De conclusie: kies niet zomaar bitrates die er als mooie ronde getallen uitzien. Bereken de BPP voor elke trede en zorg ervoor dat de curve logisch is. Als twee aangrenzende treden binnen 15-20% van elkaar in BPP liggen, zal de kijker ze niet uit elkaar kunnen houden, maar de ABR-heuristiek zal tijd verspillen met schakelen ertussen.
Bitrateafstanden: de 1,5×-vuistregel
Er is geen universele standaard, maar een gangbare technische richtlijn is om minimaal een 1,5× verhouding te handhaven tussen aangrenzende bitratetreden. Sommige implementaties gaan tot 2×.
Waarom? Omdat ABR-algoritmen bandbreedteschatting gebruiken om te bepalen welke rendition ze kiezen. De schatting heeft een betrouwbaarheidsinterval — het is nooit exact. Als twee renditions op 2,5 Mbps en 3,0 Mbps liggen, kan de BWE op een licht variabele verbinding gemakkelijk boven en onder 3,0 Mbps schommelen, wat constant schakelen veroorzaakt. Als de sprong in plaats daarvan van 2,0 Mbps naar 4,0 Mbps gaat, heeft het algoritme een veel significantere bandbreedtewijziging nodig om een schakelmoment te triggeren. Het resultaat: stabielere weergave.
Hier is een concreet voorbeeld. Stel dat je een nette ladder met vier treden hebt:
| Resolutie | Bitrate | BPP (30fps) | Verhouding tot vorige |
|---|---|---|---|
| 480×270 | 400 kbps | 0.103 | — |
| 960×540 | 2000 kbps | 0.129 | 5.0× |
| 1280×720 | 2800 kbps | 0.101 | 1.4× |
| 1920×1080 | 4500 kbps | 0.072 | 1.6× |
Op het eerste gezicht ziet het er redelijk uit — vier resoluties, stijgende bitrates. Maar kijk naar de sprong van 540p naar 720p: 2000 kbps naar 2800 kbps. Dat is slechts een 1,4× verhouding. Op elke verbinding die rond 2,5–3 Mbps schommelt (wat de meeste mobiele verbindingen zijn), zal de BWE die drempel voortdurend overschrijden. Omhoog, omlaag, omhoog, omlaag. De kijker ziet om de paar segmenten een resolutiewisseling.
En hier is het andere probleem: de BPP daalt feitelijk van 0,129 bij 540p naar 0,101 bij 720p. Dus de kijker krijgt meer pixels maar minder data per pixel. Afhankelijk van de inhoud ziet de 720p-rendition er mogelijk niet merkbaar beter uit dan 540p — je hebt resolutie toegevoegd maar compressieruimte verloren. Het ABR-algoritme schakelt voor niets.
Een betere versie van deze ladder zou de 720p-bitrate omhoog duwen en 540p omlaag bijstellen:
| Resolutie | Bitrate | BPP (30fps) | Verhouding tot vorige |
|---|---|---|---|
| 480×270 | 400 kbps | 0.103 | — |
| 960×540 | 1500 kbps | 0.096 | 3.75× |
| 1280×720 | 3000 kbps | 0.109 | 2.0× |
| 1920×1080 | 5800 kbps | 0.093 | 1.93× |
Nu is de sprong van 540p naar 720p een nette 2× verhouding. De BWE moet verdubbelen voordat de speler zelfs maar overweegt om op te schakelen. En de BPP stijgt feitelijk van 0,096 naar 0,109, wat betekent dat de 720p-trede zowel meer pixels als betere compressiekwaliteit levert — de kijker ziet een echte verbetering. De sprong van 720p naar 1080p op 1,93× is even solide, en de BPP daalt slechts licht naar 0,093, wat de verwachte efficiëntiewinst bij hogere resoluties weerspiegelt.
Trede voor trede controleren: weergavetests met enkele rendition
Hier is iets wat ik zelden bij teams zie, maar het is cruciaal: speel elke rendition afzonderlijk af en bekijk het hele stuk.
Het klinkt vanzelfsprekend, maar de meeste mensen testen ABR alleen als een complete multi-variant-stream. Ze isoleren nooit een enkele rendition en spelen die van begin tot eind af. Wanneer je dat doet, ontdek je problemen die ABR-gedrag verbergt:
- Een rendition die buffert zelfs bij zijn eigen gedeclareerde bitrate (omdat de gedeclareerde BANDWIDTH in de playlist te laag is vergeleken met de werkelijke piekbitrate)
- Een rendition waarbij de encoder moeite had en zichtbare artefacten produceerde bij bepaalde scènes
- Een rendition waarbij de framerate daalt of hapert omdat de combinatie van resolutie en bitrate te veeleisend is voor de decoder van het doelapparaat
Om dit te testen, kun je weergave met een enkele rendition op verschillende manieren afdwingen:
Met de hls.js-demopagina: Laad je multi-variant-stream, selecteer vervolgens in het kwaliteitskeuzemenu handmatig elk niveau één voor één. De hls.js-demo op hlsjs.video-dev.org/demo/ toont alle kwaliteitsniveaus en laat je ABR overschrijven. Speel elk niveau minstens een paar minuten af met representatieve inhoud. Let op verloren frames in het tabblad "Buffer & Statistics".
Met AVPlayer op Apple-platformen: Gebruik preferredPeakBitRate en preferredMaximumResolution op AVPlayerItem om de weergave tot een enkele rendition te beperken. Of nog eenvoudiger: maak een testplaylist die slechts één variant bevat.
Met ffprobe of mediainfo: Voordat je ook maar iets afspeelt, controleer de werkelijke bitratestatistieken van elke rendition. De BANDWIDTH-waarde in je masterplaylist moet rekening houden met de piekwaarden, niet alleen het gemiddelde. Als je VBR-codering pieken heeft die 40% boven het gemiddelde liggen, moet je gedeclareerde BANDWIDTH-waarde dat weerspiegelen.
Als een enkele rendition niet soepel kan worden afgespeeld bij zijn eigen gedeclareerde bitrate, zal het absoluut problemen veroorzaken in ABR-modus. Het ABR-algoritme selecteert die rendition in de veronderstelling dat het genoeg bandbreedte heeft, raakt dan een VBR-piek, stopt, valt terug, herstelt, selecteert die rendition opnieuw — ping-pong.
Segmentduur: je ABR-schakelklok
Er is nog een factor die mensen vaak over het hoofd zien: segmentduur bepaalt rechtstreeks hoe vaak het ABR-algoritme een beslissing kan nemen. Elke segmentgrens is een potentieel schakelpunt. Dus als je 2-secondensegmenten gebruikt, kan de speler tot 30 keer per minuut opnieuw evalueren en schakelen. Met 6-secondensegmenten daalt dat naar 10 keer per minuut. Met 10-secondensegmenten slechts 6 keer.
Dit is vooral belangrijk wanneer je ladder al krappe bitrateafstanden heeft. Korte segmenten gecombineerd met dicht bij elkaar liggende bitratetreden is de slechtste combinatie — je geeft het ABR-algoritme maximale kansen om marginale schakelingen te maken die de kijker niet hoeft te zien.
Omgekeerd werken langere segmenten als een natuurlijke demper op oscillatie. Zelfs als de bandbreedteschatting fluctueert, moet de speler bij zijn huidige rendition blijven voor de duur van het segment dat hij net heeft opgehaald. Tegen de tijd dat het volgende beslissingsmoment aanbreekt, kan de BWE gestabiliseerd zijn.
De HLS-specificatie schrijft geen specifieke duur voor, maar Apples auteursrichtlijnen bevelen een doel van 6 seconden aan. In de praktijk:
- 2-secondensegmenten zijn zinvol voor low-latency live waarbij snelle start en snelle aanpassing cruciaal zijn. Maar je hebt een goed gespreide ladder nodig om constant heen en weer schakelen te voorkomen.
- 6-secondensegmenten zijn een goede standaard voor VOD en standaard livestreaming. Ze geven het ABR-algoritme voldoende tijd om tussen beslissingen een betrouwbare bandbreedteschatting op te bouwen.
- 10-secondensegmenten zijn zeer stabiel maar traag in aanpassing. Als de bandbreedte scherp daalt, zit de speler vast met het downloaden van een segment met hoge bitrate dat hij mogelijk niet op tijd kan voltooien.
Er is ook een subtiliteit bij VBR-codering: de bitratevariantie binnen een segment neemt toe met de segmentduur. Een 10-secondensegment van een scèneovergang — van een statisch shot naar een actiescène — kan intern enorme bitratefluctuaties hebben. Als de gedeclareerde BANDWIDTH in de playlist overeenkomt met het totale gemiddelde maar niet met de pieken per segment, wordt het ABR-algoritme verrast. Kortere segmenten hebben doorgaans consistentere bitrates per segment, wat de BWE nauwkeuriger maakt.
De conclusie: als je oscillatie ziet en de afstanden van je ladder er goed uitzien, controleer dan je segmentduur. Overgaan van 4 naar 6 seconden kan alles zijn wat nodig is om de situatie te kalmeren.
Gebruik Network Link Conditioner om echte omstandigheden te simuleren
ABR testen op een stabiele 100 Mbps glasvezelverbinding is nutteloos. Je moet echte omstandigheden simuleren, en Apples Network Link Conditioner is hiervoor het beste hulpmiddel op macOS.
Je krijgt het via Apples Additional Tools for Xcode-pakket: ga in Xcode naar Xcode > Open Developer Tool > More Developer Tools, wat je naar de Apple-ontwikkelaarsdownloadpagina brengt. Zoek naar "Additional Tools for Xcode", download de DMG voor je Xcode-versie, open deze, en in de map Hardware vind je Network Link Conditioner.prefPane. Dubbelklik om te installeren. Het verschijnt vervolgens als een paneel in Systeeminstellingen. Hiermee kun je bandbreedteprofielen definiëren met specifieke doorvoer, latentie, pakketverliespercentage en DNS-vertraging.
Maak relevante aangepaste profielen:
- Matig 4G: 8 Mbps download / 2 Mbps upload, 80 ms RTT, 1% pakketverlies
- Slecht wifi: 3 Mbps download / 1 Mbps upload, 150 ms RTT, 3% pakketverlies
- Overgang: Start op 15 Mbps, schakel halverwege het afspelen handmatig naar 2 Mbps
Die laatste test is de cruciale. Als je ladder goed ontworpen is, zou de speler binnen een paar seconden soepel naar een lagere rendition moeten schakelen en daar moeten blijven. Als hij begint te oscilleren tussen twee renditions, liggen je treden te dicht bij elkaar op de bandbreedtegrens.
Op iOS-apparaten is Network Link Conditioner beschikbaar via de Ontwikkelaarsinstellingen (schakel het in via Instellingen > Ontwikkelaar nadat je het apparaat met Xcode hebt verbonden).
Het doel van deze tests is niet alleen "buffert het?" — het is "pendelt de stream in op een rendition en blijft hij daar?" Een goede ABR-ervaring is er een waarbij schakelingen zeldzaam en beslissend zijn. De kijker ziet de kwaliteitsverandering één keer, en daarna stabiliseert het.
Gebruik Apples AVMetrics om schakelgedrag in productie te monitoren
Vanaf iOS 18 introduceerde Apple de AVMetrics-API in AVFoundation. Dit is een doorbraak voor het monitoren van ABR-gedrag in het veld.
Het belangrijkste eventtype voor ons doel is het variant switch event. Elke keer dat AVPlayer schakelt tussen HLS-varianten, ontvang je een metriek-event dat je vertelt waarvan werd geschakeld, waarnaar werd geschakeld en de details van de mediarendition. Je ontvangt ook stall-events wanneer de speler opnieuw buffert, en een summary-event aan het einde van de sessie met de belangrijkste KPI's.
Hier is het Swift-patroon:
let playerItem: AVPlayerItem = // your configured item
let switchMetrics = playerItem.metrics(
forType: AVMetricPlayerItemVariantSwitchEvent.self
)
let stallMetrics = playerItem.metrics(
forType: AVMetricPlayerItemStallEvent.self
)
for await (event, _) in switchMetrics.chronologicalMerge(with: stallMetrics) {
switch event {
case let switchEvent as AVMetricPlayerItemVariantSwitchEvent:
// Log: from variant, to variant, timestamp
await analytics.logSwitch(switchEvent)
case let stallEvent as AVMetricPlayerItemStallEvent:
// Log: stall duration, variant at time of stall
await analytics.logStall(stallEvent)
default:
break
}
}
Waar je op moet letten in je analyses:
- Schakelfrequentie: Als de gemiddelde sessie meer dan 3-4 schakelingen per minuut heeft, heeft je ladder problemen. Gezonde streams schakelen misschien een of twee keer per sessie, doorgaans bij het opstarten.
- Oscillatiepatronen: Twee renditions die blijven alterneren — klassiek teken van treden die te dicht bij elkaar liggen.
- Stalls gecorreleerd met opwaarts schakelen: Als stalls optreden direct na het schakelen naar een hogere rendition, zijn je BANDWIDTH-declaraties in de playlist te laag.
Voor WWDC 2025 heeft Apple AVMetrics uitgebreid met mediarendition-informatie in variant switch events, waardoor het makkelijker wordt om precies te zien welke audio-/video-/ondertitelingstracks actief waren tijdens het schakelen.
Als je niet op Apple-platformen werkt, kunnen vergelijkbare gegevens worden verzameld via hls.js met de events LEVEL_SWITCHED en FRAG_BUFFERED. Dezelfde principes zijn van toepassing.
Experimenteer met hls.js: het ABR-gedrag afstemmen
De hls.js-demopagina (hlsjs.video-dev.org/demo/) is het beste gratis hulpmiddel om te experimenteren met ABR-schakelgedrag op het web. Laad je HLS-stream en gebruik de panelen "Real-time metrics" en "Buffer & Statistics" om te observeren wat er gebeurt.
Belangrijke hls.js-configuratieparameters om mee te experimenteren:
abrEwmaFastVoDenabrEwmaSlowVoD: Deze bepalen het exponentieel gewogen voortschrijdend gemiddelde (EWMA) voor bandbreedteschatting. Lagere waarden laten de speler sneller reageren op bandbreedtewijzigingen (agressiever schakelen). Hogere waarden maken hem conservatiever (langzamer schakelen, stabieler). Als je te veel schakelgedrag ziet, probeer dan deze waarden te verhogen.abrBandWidthFactor(standaard: 0,95) enabrBandWidthUpFactor(standaard: 0,7): Dit zijn veiligheidsmarges. De speler selecteert een rendition waarvan de bitrate onderestimatedBandwidth × factorligt. De opwaartse schakelfactor is bewust lager — de speler is conservatiever bij opschakelen dan bij terugschakelen. Als je ladder krappe afstanden heeft, kun jeabrBandWidthUpFactorverlagen naar 0,6 om oscillatie te verminderen.abrMaxWithRealBitrate(standaard: false): Wanneer ingeschakeld, gebruikt de ABR-controller de werkelijk gemeten bitrate van opgehaalde segmenten in plaats van de gedeclareerde BANDWIDTH uit de playlist. Dit is vooral nuttig als je gedeclareerde bitrates onnauwkeurig zijn.
Maar hier is het punt: als je deze parameters zwaar moet afstemmen om stabiele weergave te krijgen, is je ladder waarschijnlijk verkeerd. Deze knoppen zijn voor fijnafstemming. De encoderingsladder zelf is het fundament. Een goed gespreide ladder werkt goed met standaard ABR-instellingen op elke speler.
Praktische aanbevelingen
Als ik dit in een checklist moest samenvatten:
- Bereken de BPP voor elke trede van je ladder. Zorg ervoor dat deze afneemt naarmate de resolutie toeneemt. Verwijder of pas elke trede aan waarvan de BPP binnen 15% van zijn buur ligt.
- Handhaaf minimaal een 1,5× bitrateverhouding tussen aangrenzende treden. Geef de voorkeur aan 2× waar mogelijk, vooral in de onderste helft van de ladder waar bandbreedtefluctuatie de meeste impact heeft.
- Test elke rendition afzonderlijk. Dwing weergave met een enkele variant af en bekijk representatieve inhoud van begin tot eind. Als het alleen al niet soepel loopt, zal het in ABR-modus ook niet soepel lopen.
- Gebruik Network Link Conditioner om bandbreedtedalingen te simuleren. De stream zou snel op een rendition moeten stabiliseren en daar moeten blijven.
- Controleer je segmentduur. Als je op 2-4 seconden zit en oscillatie ziet, probeer dan 6 seconden. Langere segmenten verminderen de schakelfrequentie op natuurlijke wijze door de BWE meer tijd te geven om te stabiliseren tussen beslissingen.
- Instrumenteer je speler. Gebruik AVMetrics op Apple, hls.js-events op het web. Volg de schakelfrequentie per sessie. Als sessies gemiddeld meer dan een handvol schakelingen buiten de opstartfase hebben, onderzoek dan de oorzaak.
- Vertrouw niet alleen op gedeclareerde BANDWIDTH. Gebruik
abrMaxWithRealBitratein hls.js of verifieer met ffprobe dat je VBR-pieken de gedeclareerde waarde niet overschrijden. - Minder treden is vaak beter. Een ladder met 5 treden en goed gespreide renditions zal beter presteren dan een ladder met 10 treden waarvan de helft te dicht bij elkaar ligt. De kijker heeft geen 12 kwaliteitsniveaus nodig. Ze hebben er 4 of 5 nodig die er elk merkbaar anders uitzien en betrouwbaar afspelen.
Het hele punt van ABR is dat het onzichtbaar zou moeten zijn. De kijker zou niet moeten merken dat het werkt. Als dat wel zo is, is er iets mis — en negen van de tien keer ligt het aan de ladder.
Referenties: