Stoppa Din ABR-Ström Från att Studsa Mellan Bitrater

Denna artikel har översatts från engelska med hjälp av AI. Läs originalet

Hur man designar en transkodningsstege som inte driver ABR-algoritmer till vansinne

Om du har tillbringat tid med att titta på HLS-uppspelning i verkligheten har du sett det: strömmen fortsätter att studsa mellan två renditions. Upp, ner, upp, ner. Tittaren ser en konstant förändring i kvalitet, ibland var par sekunder. Det är värre än att stanna på en lägre rendition hela tiden. Åtminstone känns en stabil 720p-ström avsiktlig. En ström som pendlar mellan 720p och 1080p var tionde sekund känns trasig.

Grundorsaken är nästan alltid densamma: kodningsstegen har renditions som ligger för nära varandra i bitrate, eller kvalitetsskillnaden mellan stegen motiverar inte bandbreddsökningen. ABR-algoritmen på klientsidan kan inte bestämma sig, eftersom skillnaden mellan "jag har råd med detta" och "jag har inte råd med detta" är hårfin.

Låt oss prata om hur man åtgärdar detta ordentligt.

Bits-per-pixel rimlighetskontroll

Före allt annat behöver du förstå vad bits-per-pixel (BPP) säger dig om din stege. Det är det enklaste måttet för att bedöma om en given bitrate faktiskt ger mening för en given upplösning.

Formeln är enkel:

BPP = bitrate / (width × height × framerate)

Till exempel en 1920×1080-ström vid 4500 kbps och 30 fps:

BPP = 4,500,000 / (1920 × 1080 × 30) = 0.072

Varför spelar detta roll? Eftersom BPP visar kompressionstätheten på varje steg. Om två intilliggande steg i din stege har mycket liknande BPP-värden kommer tittaren inte att se en meningsfull kvalitetsskillnad — men ABR-algoritmen kommer ändå att försöka växla mellan dem. Det är så du hamnar med ping-pong-beteende.

En väldesignad stege bör ha en BPP-kurva som lutar nedåt när upplösningen ökar. Detta speglar en verklig egenskap hos video-codecs: de är mer effektiva vid högre upplösningar. Du behöver färre bitar per pixel vid 1080p för att uppnå samma upplevda kvalitet som vid 480p. Om din BPP är platt eller inkonsekvent över stegen är något fel.

"Regeln om 0,70" är en praktisk referens här. Tanken är att när du fördubblar antalet pixlar (till exempel från 720p till 1080p) bör du applicera ungefär 0,70× BPP:n för den lägre upplösningen. Det är en tumregel, inte en lag, men den ger dig ett snabbt sätt att upptäcka avvikelser. Om du plottar din steges BPP-värden och ett steg sticker ut — för högt eller för lågt jämfört med sina grannar — kommer det steget att orsaka problem.

Slutsatsen: välj inte bara bitrater som ser ut som fina runda tal. Beräkna BPP för varje steg och se till att kurvan ger mening. Om två intilliggande steg ligger inom 15-20% av varandra i BPP kommer tittaren inte att kunna skilja dem åt, men ABR-heuristiken kommer att slösa tid på att växla mellan dem.

Avstånd mellan bitrater: 1,5×-tumregeln

Det finns ingen universell standard, men en vanlig teknisk riktlinje är att upprätthålla minst ett 1,5×-förhållande mellan intilliggande bitratesteg. Vissa implementeringar driver detta till 2×.

Varför? Eftersom ABR-algoritmer använder bandbreddsuppskattning för att avgöra vilken rendition som ska väljas. Uppskattningen har ett konfidensintervall — den är aldrig exakt. Om två renditions ligger på 2,5 Mbps och 3,0 Mbps kan BWE enkelt pendla över och under 3,0 Mbps på en lätt variabel anslutning, vilket orsakar ständiga byten. Om hoppet istället går från 2,0 Mbps till 4,0 Mbps behöver algoritmen en mycket mer betydande bandbreddsförändring för att utlösa ett byte. Resultatet: stabilare uppspelning.

Här är ett konkret exempel. Säg att du har en ren fyrastegs-stege:

Upplösning Bitrate BPP (30fps) Förhållande till föregående
480×270400 kbps0.103
960×5402000 kbps0.1295.0×
1280×7202800 kbps0.1011.4×
1920×10804500 kbps0.0721.6×

Vid första anblick ser det rimligt ut — fyra upplösningar, stigande bitrater. Men titta på hoppet från 540p till 720p: 2000 kbps till 2800 kbps. Det är bara ett 1,4×-förhållande. På vilken anslutning som helst som svänger runt 2,5–3 Mbps (vilket är de flesta mobilanslutningar) kommer BWE konstant att korsa den tröskeln. Upp, ner, upp, ner. Tittaren ser ett upplösningsbyte var par segment.

Och här är det andra problemet: BPP sjunker faktiskt från 0,129 vid 540p till 0,101 vid 720p. Så tittaren får fler pixlar men mindre data per pixel. Beroende på innehållet kanske 720p-rendition inte ser märkbart bättre ut än 540p — du har lagt till upplösning men förlorat kompressionsmarginalen. ABR-algoritmen byter för ingenting.

En bättre version av denna stege skulle öka 720p-bitraten och justera ner 540p:

Upplösning Bitrate BPP (30fps) Förhållande till föregående
480×270400 kbps0.103
960×5401500 kbps0.0963.75×
1280×7203000 kbps0.1092.0×
1920×10805800 kbps0.0931.93×

Nu är hoppet från 540p till 720p ett rent 2×-förhållande. BWE måste fördubblas innan spelaren ens överväger att byta upp. Och BPP ökar faktiskt från 0,096 till 0,109, vilket betyder att 720p-steget levererar både fler pixlar och bättre kompressionskvalitet — tittaren ser en verklig förbättring. Hoppet från 720p till 1080p vid 1,93× är lika solitt, och BPP sjunker bara marginellt till 0,093, vilket är den förväntade effektivitetsvinsten vid högre upplösningar.

Kontrollera rendition för rendition: test av enskild uppspelning

Här är något jag sällan ser team göra, men det är avgörande: spela upp varje rendition individuellt och titta igenom hela.

Det låter uppenbart, men de flesta testar bara ABR som en komplett multivariantström. De isolerar aldrig en enskild rendition och spelar den från start till slut. När du gör detta upptäcker du problem som ABR-beteendet döljer:

  • En rendition som buffrar även vid sin egen deklarerade bitrate (eftersom den deklarerade BANDWIDTH i spellistan är för låg jämfört med den faktiska toppbitraten)
  • En rendition där kodaren hade det svårt och producerade synliga artefakter i vissa scener
  • En rendition där bildfrekvensen sjunker eller hackar eftersom kombinationen upplösning/bitrate är för krävande för målenhetens avkodare

För att testa detta kan du tvinga enskild rendition-uppspelning på flera sätt:

Med hls.js demosida: Ladda din multivariantström och välj sedan manuellt varje nivå en i taget i kvalitetsväljaren. hls.js-demon på hlsjs.video-dev.org/demo/ exponerar alla kvalitetsnivåer och låter dig åsidosätta ABR. Spela varje nivå i minst några minuter av representativt innehåll. Titta efter tappade bildrutor i fliken "Buffer & Statistics".

Med AVPlayer på Apple-plattformar: Använd preferredPeakBitRate och preferredMaximumResolutionAVPlayerItem för att begränsa uppspelningen till en enskild rendition. Eller ännu enklare: skapa en testspellista som bara inkluderar en variant.

Med ffprobe eller mediainfo: Innan du ens spelar något, kontrollera de faktiska bitratestatistiken för varje rendition. BANDWIDTH-värdet i din master-spellista måste ta hänsyn till toppvärdet, inte bara genomsnittet. Om din VBR-kodning har toppar 40% över genomsnittet måste din deklarerade BANDWIDTH återspegla det.

Om en enskild rendition inte kan spelas smidigt vid sin egen deklarerade bitrate kommer den definitivt att orsaka problem i ABR-läge. ABR-algoritmen väljer den rendition i tron att den har tillräcklig bandbredd, träffar sedan en VBR-topp, stannar, faller tillbaka, återhämtar sig, väljer den rendition igen — ping-pong.

Segmentlängd: din ABR-bytesklocka

Det finns en annan faktor som folk tenderar att förbise: segmentlängden styr direkt hur ofta ABR-algoritmen får fatta ett beslut. Varje segmentgräns är en potentiell bytespunkt. Så om du använder 2-sekunders segment kan spelaren omvärdera och byta upp till 30 gånger per minut. Med 6-sekunders segment sjunker det till 10 gånger per minut. Med 10-sekunders segment bara 6.

Detta spelar stor roll när din stege redan har tät bitrateavstånd. Korta segment kombinerat med nära bitratesteg är den sämsta kombinationen — du ger ABR-algoritmen maximala möjligheter att göra marginella byten som tittaren inte behöver se.

Omvänt fungerar längre segment som en naturlig dämpare på oscillation. Även om bandbreddsuppskattningen fluktuerar måste spelaren hålla sig till sin nuvarande rendition under hela det segment den just hämtade. När nästa beslutspunkt kommer kan BWE ha stabiliserats.

HLS-specifikationen föreskriver inte en specifik längd, men Apples riktlinjer för authoring rekommenderar ett mål på 6 sekunder. I praktiken:

  • 2-sekunders segment ger mening för live med låg latens där snabb start och snabb anpassning är avgörande. Men du behöver en välspacad stege för att undvika ständigt växlande.
  • 6-sekunders segment är en bra standard för VOD och standardlive. De ger ABR-algoritmen tillräckligt med tid att bygga en pålitlig bandbreddsuppskattning mellan beslut.
  • 10-sekunders segment är mycket stabila men tröga att anpassa sig. Om bandbredden sjunker kraftigt sitter spelaren fast med att ladda ner ett segment med hög bitrate som den kanske inte hinner slutföra i tid.

Det finns också en subtilitet med VBR-kodning: bitratevariansen inom ett segment ökar med segmentlängden. Ett 10-sekunders segment av en scenövergång — från en statisk bild till en actionsekvens — kan ha massiva bitratefluktuationer internt. Om den deklarerade BANDWIDTH i spellistan matchar det övergripande genomsnittet men inte topparna per segment blir ABR-algoritmen överraskad. Kortare segment tenderar att ha mer konsekventa bitrater per segment, vilket gör BWE mer exakt.

Slutsatsen: om du ser oscillation och din steges avstånd ser bra ut, kontrollera din segmentlängd. Att gå från 4 sekunder till 6 sekunder kan vara allt som behövs för att lugna ner saker.

Använd Network Link Conditioner för att simulera verkliga förhållanden

Att testa ABR på en stabil 100 Mbps fiberanslutning är meningslöst. Du behöver simulera verkliga förhållanden, och Apples Network Link Conditioner är det bästa verktyget för detta på macOS.

Du får det från Apples Additional Tools for Xcode-paket: i Xcode, gå till Xcode > Open Developer Tool > More Developer Tools, som tar dig till Apples utvecklarnedladdningssida. Sök efter "Additional Tools for Xcode", ladda ner DMG:n för din Xcode-version, öppna den, och i mappen Hardware hittar du Network Link Conditioner.prefPane. Dubbelklicka för att installera. Den visas sedan som en panel i Systeminställningar (eller Systeminställningar på äldre macOS). Den låter dig definiera bandbreddsprofiler med specifik genomströmning, latens, paketförlusthastighet och DNS-fördröjning.

Skapa anpassade profiler som spelar roll:

  • Medelmåttig 4G: 8 Mbps ner / 2 Mbps upp, 80ms RTT, 1% paketförlust
  • Dålig WiFi: 3 Mbps ner / 1 Mbps upp, 150ms RTT, 3% paketförlust
  • Övergångstest: Börja vid 15 Mbps, byt manuellt till 2 Mbps mitt i uppspelningen

Det sista är det avgörande testet. Om din stege är väldesignad bör spelaren smidigt sjunka till en lägre rendition inom några sekunder och stanna där. Om den börjar oscillera mellan två renditions är dina steg för nära vid bandbreddsgränsen.

På iOS-enheter är Network Link Conditioner tillgängligt via Utvecklarinställningar (aktivera det i Inställningar > Utvecklare efter att ha anslutit enheten till Xcode).

Målet med dessa tester är inte bara "buffrar den?" — det är "stabiliserar strömmen sig på en rendition och stannar där?" En bra ABR-upplevelse är en där byten är sällsynta och beslutsamma. Tittaren ser kvalitetsändringen en gång, och sedan stabiliseras det.

Använd Apples AVMetrics för att övervaka byten i produktion

Från och med iOS 18 introducerade Apple AVMetrics API i AVFoundation. Detta är en spelförändring för övervakning av ABR-beteende i fält.

Den viktigaste händelsetypen för vårt syfte är variantbyteshändelsen. Varje gång AVPlayer byter mellan HLS-varianter får du en metrisk händelse som berättar vad den bytte från, vad den bytte till, och detaljerna om medierendition. Du får också stallhändelser när spelaren ombuffrar, och en sammanfattningshändelse vid sessionens slut med övergripande KPI:er.

Här är Swift-mönstret:

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

Vad du ska leta efter i din analys:

  • Bytesfrekvens: Om den genomsnittliga sessionen har mer än 3-4 byten per minut har din stege problem. Friska strömmar byter kanske en eller två gånger per session, vanligtvis vid start.
  • Oscillationsmönster: Två renditions som fortsätter att alternera — klassiskt tecken på steg som ligger för nära.
  • Stopp korrelerade med uppbyte: Om stopp inträffar direkt efter byte till en högre rendition är dina BANDWIDTH-deklarationer i spellistan för låga.

Till WWDC 2025 utökade Apple AVMetrics till att inkludera medierenditionsinformation i variantbyteshändelser, vilket gör det enklare att se exakt vilka ljud-/video-/undertextspår som var aktiva under bytet.

Om du inte är på Apple-plattformar kan liknande data samlas in från hls.js via LEVEL_SWITCHED- och FRAG_BUFFERED-händelserna. Samma principer gäller.

Experimentera med hls.js: justera ABR-beteendet

hls.js demosida (hlsjs.video-dev.org/demo/) är det bästa gratisverktyget för att experimentera med ABR-bytesbeteende på webben. Ladda din HLS-ström och använd panelerna "Real-time metrics" och "Buffer & Statistics" för att observera vad som händer.

Viktiga hls.js konfigurationsparametrar att experimentera med:

  • abrEwmaFastVoD och abrEwmaSlowVoD: Dessa styr det exponentiellt viktade glidande medelvärdet (EWMA) för bandbreddsuppskattning. Lägre värden gör att spelaren reagerar snabbare på bandbreddsförändringar (mer aggressivt byte). Högre värden gör den mer konservativ (långsammare att byta, mer stabil). Om du ser för många byten, försök att öka dessa.
  • abrBandWidthFactor (standard: 0.95) och abrBandWidthUpFactor (standard: 0.7): Dessa är säkerhetsmarginaler. Spelaren väljer en rendition vars bitrate ligger under estimatedBandwidth × factor. Uppbytesfaktorn är avsiktligt lägre — spelaren är mer konservativ med att byta upp än ner. Om din stege har tät avstånd kan du vilja sänka abrBandWidthUpFactor till 0,6 för att minska oscillation.
  • abrMaxWithRealBitrate (standard: false): När aktiverat använder ABR-kontrollern den faktiskt uppmätta bitraten för hämtade segment istället för den deklarerade BANDWIDTH från spellistan. Detta är särskilt användbart om dina deklarerade bitrater är felaktiga.

Men här är poängen: om du behöver justera dessa parametrar kraftigt för att uppnå stabil uppspelning är din stege förmodligen fel. Dessa rattar är för finjustering. Kodningsstegen själv är grunden. En välspacad stege fungerar bra med standard ABR-inställningar på vilken spelare som helst.

Praktiska rekommendationer

Om jag var tvungen att sammanfatta detta i en checklista:

  1. Beräkna BPP för varje steg i din stege. Se till att det minskar när upplösningen ökar. Ta bort eller justera varje steg där BPP ligger inom 15% av sin granne.
  2. Upprätthåll minst 1,5× bitrateförhållande mellan intilliggande steg. Föredra 2× när det är möjligt, särskilt i den nedre halvan av stegen där bandbreddsfluktuationer har störst inverkan.
  3. Testa varje rendition isolerat. Tvinga enskild variantuppspelning och titta på representativt innehåll från start till slut. Om den inte kan spelas smidigt ensam kommer den inte att spelas smidigt i ABR.
  4. Använd Network Link Conditioner för att simulera bandbreddsfall. Strömmen bör stabiliseras på en rendition snabbt och stanna där.
  5. Kontrollera din segmentlängd. Om du är på 2–4 sekunder och ser oscillation, försök med 6 sekunder. Längre segment minskar naturligt bytesfrekvensen genom att ge BWE mer tid att stabiliseras mellan beslut.
  6. Instrumentera din spelare. Använd AVMetrics på Apple, hls.js-händelser på webben. Spåra bytesfrekvens per session. Om sessioner i genomsnitt har mer än en handfull byten utanför startfasen, undersök.
  7. Lita inte på deklarerad BANDWIDTH enbart. Använd abrMaxWithRealBitrate i hls.js eller verifiera med ffprobe att dina VBR-toppar inte överskrider det deklarerade värdet.
  8. Färre steg är ofta bättre. En 5-stegs stege med välspacade renditions kommer att överträffa en 10-stegs stege där hälften av stegen ligger för nära varandra. Tittaren behöver inte 12 kvalitetsnivåer. De behöver 4 eller 5 som var och en ser meningsfullt annorlunda ut och spelas pålitligt.

Hela poängen med ABR är att det ska vara osynligt. Tittaren ska inte märka att det fungerar. Om de gör det är något fel — och nio gånger av tio är det stegen.

Referenser:

Need Help With Your Streaming Project?

This article was written by experienced professionals available through iReplay.tv. Whether you need expertise in HLS, streaming, hls.js—our network of specialists can bring your project to life.

Hire a Professional →