Avage kvaliteetne videostriimimine brauseris. Õppige WebCodecs API ja VideoFrame'i manipuleerimise abil rakendama täiustatud ajalist filtreerimist müra vähendamiseks.
WebCodecs'i valdamine: videokvaliteedi parandamine ajalise müra vähendamisega
Veebipõhise videosuhtluse, voogedastuse ja reaalajas rakenduste maailmas on kvaliteet esmatähtis. Kasutajad üle kogu maailma ootavad teravat ja selget videot, olgu nad siis ärikohtumisel, otseülekannet vaatamas või kaugeteenusega suhtlemas. Videovood on aga sageli vaevatud püsiva ja häiriva artefaktiga: müraga. See digitaalne müra, mis on sageli nähtav teralise või staatilise tekstuurina, võib halvendada vaatamiskogemust ja üllatuslikult suurendada ka ribalaiuse tarbimist. Õnneks annab võimas brauseri API, WebCodecs, arendajatele enneolematu madala taseme kontrolli selle probleemi lahendamiseks.
See põhjalik juhend viib teid sügavuti WebCodecs'i kasutamisse spetsiifilise ja suure mõjuga videotöötlustehnika jaoks: ajaline müra vähendamine. Uurime, mis on videomüra, miks see on kahjulik ja kuidas saate VideoFrame
objekti abil luua filtreerimistoru otse brauseris. Käsitleme kõike alates põhiteooriast kuni praktilise JavaScripti implementeerimiseni, jõudluskaalutlusi WebAssembly'ga ja täiustatud kontseptsioone professionaalse taseme tulemuste saavutamiseks.
Mis on videomĂĽra ja miks see on oluline?
Enne probleemi lahendamist peame seda kõigepealt mõistma. Digitaalvideos viitab müra juhuslikele heleduse või värviinfo variatsioonidele videosignaalis. See on pildistamis- ja edastusprotsessi soovimatu kõrvalsaadus.
MĂĽra allikad ja tĂĽĂĽbid
- Sensori müra: Peamine süüdlane. Madalates valgustingimustes võimendavad kaamerasensorid sissetulevat signaali, et luua piisavalt ere pilt. See võimendusprotsess võimendab ka juhuslikke elektroonilisi kõikumisi, mille tulemuseks on nähtav teralisus.
- Termiline müra: Kaamera elektroonika tekitatud soojus võib põhjustada elektronide juhuslikku liikumist, luues müra, mis ei sõltu valguse tasemest.
- Kvantimismüra: Tekib analoog-digitaalmuundamise ja tihendamise protsesside käigus, kus pidevad väärtused kaardistatakse piiratud hulgale diskreetsetele tasemetele.
See müra avaldub tavaliselt Gaussi mürana, kus iga piksli intensiivsus varieerub juhuslikult ümber oma tegeliku väärtuse, luues peene, virvendava teralisuse üle kogu kaadri.
Müra kahekordne mõju
Videomüra on enamat kui lihtsalt kosmeetiline probleem; sellel on olulised tehnilised ja tajutavad tagajärjed:
- Halvenenud kasutajakogemus: Kõige ilmsem mõju on visuaalsele kvaliteedile. Mürarikas video näeb välja ebaprofessionaalne, on häiriv ja võib raskendada oluliste detailide eristamist. Rakendustes nagu telekonverentsid võib see muuta osalejad teraliseks ja ebaselgeks, vähendades kohaloleku tunnet.
- Vähenenud tihendamise tõhusus: See on vähem intuitiivne, kuid sama kriitiline probleem. Kaasaegsed videokoodekid (nagu H.264, VP9, AV1) saavutavad kõrge tihendussuhte, kasutades ära liiasust. Nad otsivad sarnasusi kaadrite vahel (ajaline liiasus) ja ühe kaadri sees (ruumiline liiasus). Müra on oma olemuselt juhuslik ja ettearvamatu. See rikub neid liiasuse mustreid. Kodeerija näeb juhuslikku müra kõrgsagedusliku detailina, mida tuleb säilitada, sundides seda eraldama rohkem bitte müra kodeerimiseks tegeliku sisu asemel. Selle tulemuseks on kas suurem failimaht sama tajutava kvaliteedi juures või madalam kvaliteet sama bitikiiruse juures.
Eemaldades müra enne kodeerimist, saame muuta videosignaali ennustatavamaks, võimaldades kodeerijal tõhusamalt töötada. See viib parema visuaalse kvaliteedi, väiksema ribalaiuse kasutuse ja sujuvama voogedastuskogemuseni kasutajatele kõikjal.
Siseneb WebCodecs: madala taseme videokontrolli jõud
Aastaid oli otsene videomanipulatsioon brauseris piiratud. Arendajad olid suures osas piiratud <video>
elemendi ja Canvas API võimalustega, mis sageli hõlmasid jõudlust tapvaid tagasilugemisi GPU-lt. WebCodecs muudab mängu täielikult.
WebCodecs on madala taseme API, mis pakub otsejuurdepääsu brauseri sisseehitatud meediakodeerijatele ja -dekooderitele. See on mõeldud rakendustele, mis nõuavad täpset kontrolli meediumitöötluse üle, nagu videotöötlusprogrammid, pilvemänguplatvormid ja täiustatud reaalajas suhtluskliendid.
Põhikomponent, millele keskendume, on VideoFrame
objekt. VideoFrame
esindab ühte videokaadrit pildina, kuid see on palju enamat kui lihtsalt bitmap. See on ülitõhus, ülekantav objekt, mis võib hoida videoandmeid erinevates pikslivormingutes (nagu RGBA, I420, NV12) ja kannab olulisi metaandmeid, näiteks:
timestamp
: Kaadri esitusaeg mikrosekundites.duration
: Kaadri kestus mikrosekundites.codedWidth
jacodedHeight
: Kaadri mõõtmed pikslites.format
: Andmete pikslivorming (nt 'I420', 'RGBA').
Oluline on, et VideoFrame
pakub meetodit nimega copyTo()
, mis võimaldab meil kopeerida toored, tihendamata piksliandmed ArrayBuffer
isse. See on meie sisenemispunkt analüüsiks ja manipuleerimiseks. Kui meil on toorbaidid, saame rakendada oma müra vähendamise algoritmi ja seejärel konstrueerida muudetud andmetest uue VideoFrame
'i, et see edasi töötlustorusse saata (nt videokodeerijale või lõuendile).
Ajalise filtreerimise mõistmine
Müra vähendamise tehnikaid saab laias laastus jagada kahte tüüpi: ruumilised ja ajalised.
- Ruumiline filtreerimine: See tehnika töötab ühe kaadriga eraldi. See analüüsib naaberpikslite vahelisi seoseid, et tuvastada ja siluda müra. Lihtne näide on hägususfilter. Kuigi ruumilised filtrid on müra vähendamisel tõhusad, võivad need pehmendada ka olulisi detaile ja servi, mille tulemuseks on vähem terav pilt.
- Ajaline filtreerimine: See on keerukam lähenemine, millele me keskendume. See töötab mitme kaadri peal aja jooksul. Põhiprintsiip on see, et tegelik stseeni sisu on tõenäoliselt korrelatsioonis ühelt kaadrilt teisele, samas kui müra on juhuslik ja korreleerimata. Võrreldes piksli väärtust konkreetses asukohas mitme kaadri lõikes, saame eristada püsivat signaali (tegelik pilt) juhuslikest kõikumistest (müra).
Ajalise filtreerimise lihtsaim vorm on ajaline keskmistamine. Kujutage ette, et teil on praegune kaader ja eelmine kaader. Iga antud piksli puhul on selle 'tõeline' väärtus tõenäoliselt kuskil selle väärtuse vahel praeguses ja eelmises kaadris. Neid segades saame juhusliku müra välja keskmistada. Uue piksli väärtuse saab arvutada lihtsa kaalutud keskmisega:
new_pixel = (alpha * current_pixel) + ((1 - alpha) * previous_pixel)
Siin on alpha
segamistegur vahemikus 0 kuni 1. Kõrgem alpha
tähendab, et usaldame rohkem praegust kaadrit, mis toob kaasa vähem müra vähendamist, kuid ka vähem liikumisartefakte. Madalam alpha
pakub tugevamat müra vähendamist, kuid võib põhjustada 'kummitamist' või jälgi liikuvates piirkondades. Õige tasakaalu leidmine on võtmetähtsusega.
Lihtsa ajalise keskmistamise filtri implementeerimine
Ehitame selle kontseptsiooni praktilise implementatsiooni, kasutades WebCodecs'i. Meie toru koosneb kolmest peamisest sammust:
- Hankige
VideoFrame
objektide voog (nt veebikaamerast). - Iga kaadri jaoks rakendage meie ajaline filter, kasutades eelmise kaadri andmeid.
- Looge uus, puhastatud
VideoFrame
.
Samm 1: kaadrivoo seadistamine
Lihtsaim viis VideoFrame
objektide otsevoo saamiseks on kasutada MediaStreamTrackProcessor
'it, mis tarbib MediaStreamTrack
'i (nagu see, mis saadakse getUserMedia
'st) ja paljastab selle kaadrid loetava voona.
Kontseptuaalne JavaScripti seadistus:
async function setupVideoStream() {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const track = stream.getVideoTracks()[0];
const trackProcessor = new MediaStreamTrackProcessor({ track });
const reader = trackProcessor.readable.getReader();
let previousFrameBuffer = null;
let previousFrameTimestamp = -1;
while (true) {
const { value: frame, done } = await reader.read();
if (done) break;
// Here is where we will process each 'frame'
const processedFrame = await applyTemporalFilter(frame, previousFrameBuffer);
// For the next iteration, we need to store the data of the *original* current frame
// You would copy the original frame's data to 'previousFrameBuffer' here before closing it.
// Don't forget to close frames to release memory!
frame.close();
// Do something with processedFrame (e.g., render to canvas, encode)
// ... and then close it too!
processedFrame.close();
}
}
Samm 2: filtreerimisalgoritm – piksliandmetega töötamine
See on meie töö tuum. Meie applyTemporalFilter
funktsiooni sees peame pääsema juurde sissetuleva kaadri piksliandmetele. Lihtsuse huvides oletame, et meie kaadrid on 'RGBA' formaadis. Iga piksel on esindatud 4 baidiga: punane, roheline, sinine ja alfa (läbipaistvus).
async function applyTemporalFilter(currentFrame, previousFrameBuffer) {
// Define our blending factor. 0.8 means 80% of the new frame and 20% of the old.
const alpha = 0.8;
// Get the dimensions
const width = currentFrame.codedWidth;
const height = currentFrame.codedHeight;
// Allocate an ArrayBuffer to hold the pixel data of the current frame.
const currentFrameSize = width * height * 4; // 4 bytes per pixel for RGBA
const currentFrameBuffer = new Uint8Array(currentFrameSize);
await currentFrame.copyTo(currentFrameBuffer);
// If this is the first frame, there's no previous frame to blend with.
// Just return it as is, but store its buffer for the next iteration.
if (!previousFrameBuffer) {
const newFrameBuffer = new Uint8Array(currentFrameBuffer);
// We'll update our global 'previousFrameBuffer' with this one outside this function.
return { buffer: newFrameBuffer, frame: currentFrame };
}
// Create a new buffer for our output frame.
const outputFrameBuffer = new Uint8Array(currentFrameSize);
// The main processing loop.
for (let i = 0; i < currentFrameSize; i++) {
const currentPixelValue = currentFrameBuffer[i];
const previousPixelValue = previousFrameBuffer[i];
// Apply the temporal averaging formula for each color channel.
// We skip the alpha channel (every 4th byte).
if ((i + 1) % 4 !== 0) {
outputFrameBuffer[i] = Math.round(alpha * currentPixelValue + (1 - alpha) * previousPixelValue);
} else {
// Keep the alpha channel as is.
outputFrameBuffer[i] = currentPixelValue;
}
}
return { buffer: outputFrameBuffer, frame: currentFrame };
}
Märkus YUV-vormingute (I420, NV12) kohta: Kuigi RGBA-d on lihtne mõista, töödeldakse enamikku videotest tõhususe huvides algselt YUV-värviruumides. YUV-i käsitlemine on keerulisem, kuna värvi- (U, V) ja heleduse (Y) teave salvestatakse eraldi ('tasanditel'). Filtreerimisloogika jääb samaks, kuid peate iga tasandi (Y, U ja V) eraldi läbima, pidades silmas nende vastavaid mõõtmeid (värvitasandid on sageli madalama eraldusvõimega, tehnikat nimetatakse värvi aladiskreetimiseks).
Samm 3: uue filtreeritud VideoFrame
'i loomine
Pärast meie tsükli lõppu sisaldab outputFrameBuffer
meie uue, puhtama kaadri piksliandmeid. NĂĽĂĽd peame selle pakkima uude VideoFrame
objekti, veendudes, et kopeerime metaandmed algsest kaadrist.
// Inside your main loop after calling applyTemporalFilter...
const { buffer: processedBuffer, frame: originalFrame } = await applyTemporalFilter(frame, previousFrameBuffer);
// Create a new VideoFrame from our processed buffer.
const newFrame = new VideoFrame(processedBuffer, {
format: 'RGBA',
codedWidth: originalFrame.codedWidth,
codedHeight: originalFrame.codedHeight,
timestamp: originalFrame.timestamp,
duration: originalFrame.duration
});
// IMPORTANT: Update the previous frame buffer for the next iteration.
// We need to copy the *original* frame's data, not the filtered data.
// A separate copy should be made before filtering.
previousFrameBuffer = new Uint8Array(originalFrameData);
// Now you can use 'newFrame'. Render it, encode it, etc.
// renderer.draw(newFrame);
// And critically, close it when you are done to prevent memory leaks.
newFrame.close();
Mäluhaldus on kriitilise tähtsusega: VideoFrame
objektid võivad sisaldada suuri koguseid tihendamata videoandmeid ja võivad olla toetatud mäluga väljaspool JavaScripti mäluvahemikku. Te peate kutsuma frame.close()
igal kaadril, millega olete lõpetanud. Selle tegemata jätmine viib kiiresti mälu ammendumiseni ja vahekaardi kokkujooksmiseni.
Jõudluskaalutlused: JavaScript vs. WebAssembly
Ülaltoodud puhas JavaScripti implementatsioon on suurepärane õppimiseks ja demonstreerimiseks. Kuid 30 FPS, 1080p (1920x1080) video puhul peab meie tsükkel tegema üle 248 miljoni arvutuse sekundis! (1920 * 1080 * 4 baiti * 30 fps). Kuigi kaasaegsed JavaScripti mootorid on uskumatult kiired, on see pikslipõhine töötlemine ideaalne kasutusjuhtum jõudlusele orienteeritud tehnoloogia jaoks: WebAssembly (Wasm).
WebAssembly lähenemine
WebAssembly võimaldab teil käitada brauseris peaaegu natiivse kiirusega koodi, mis on kirjutatud keeltes nagu C++, Rust või Go. Meie ajalise filtri loogikat on nendes keeltes lihtne implementeerida. Kirjutaksite funktsiooni, mis võtab vastu osutid sisend- ja väljundpuhvritele ning teostab sama iteratiivse segamisoperatsiooni.
Kontseptuaalne C++ funktsioon Wasm'i jaoks:
extern "C" {
void apply_temporal_filter(unsigned char* current_frame, unsigned char* previous_frame, unsigned char* output_frame, int buffer_size, float alpha) {
for (int i = 0; i < buffer_size; ++i) {
if ((i + 1) % 4 != 0) { // Skip alpha channel
output_frame[i] = (unsigned char)(alpha * current_frame[i] + (1.0 - alpha) * previous_frame[i]);
} else {
output_frame[i] = current_frame[i];
}
}
}
}
JavaScripti poolelt laadiksite selle kompileeritud Wasm mooduli. Peamine jõudluseelis tuleneb mälu jagamisest. Saate luua JavaScriptis ArrayBuffer
eid, mis on toetatud Wasm mooduli lineaarse mäluga. See võimaldab teil edastada kaadri andmed Wasm'ile ilma kalli kopeerimiseta. Kogu pikslitöötlustsükkel jookseb siis ühe, kõrgelt optimeeritud Wasm funktsioonikutsena, mis on oluliselt kiirem kui JavaScripti `for` tsükkel.
Täiustatud ajalise filtreerimise tehnikad
Lihtne ajaline keskmistamine on suurepärane lähtepunkt, kuid sellel on oluline puudus: see tekitab liikumisest tingitud hägusust ehk 'kummitamist'. Kui objekt liigub, segatakse selle pikslid praeguses kaadris eelmise kaadri taustapikslitega, luues jälje. Tõeliselt professionaalse tasemega filtri ehitamiseks peame arvestama liikumisega.
Liikumiskompenseeritud ajaline filtreerimine (MCTF)
Ajalise müra vähendamise kuldstandard on liikumiskompenseeritud ajaline filtreerimine. Selle asemel, et pimesi segada pikslit sama (x, y) koordinaadiga piksliga eelmises kaadris, üritab MCTF kõigepealt välja selgitada, kust see piksel pärit on.
Protsess hõlmab:
- Liikumise hindamine: Algoritm jagab praeguse kaadri plokkideks (nt 16x16 pikslit). Iga ploki jaoks otsib see eelmisest kaadrist kõige sarnasema ploki (nt selle, millel on madalaim absoluutsete erinevuste summa). Nende kahe ploki vahelist nihet nimetatakse 'liikumisvektoriks'.
- Liikumise kompenseerimine: Seejärel ehitab see 'liikumiskompenseeritud' versiooni eelmisest kaadrist, nihutades plokke vastavalt nende liikumisvektoritele.
- Filtreerimine: Lõpuks teostab see ajalise keskmistamise praeguse kaadri ja selle uue, liikumiskompenseeritud eelmise kaadri vahel.
Sel viisil segatakse liikuv objekt iseendaga eelmisest kaadrist, mitte taustaga, mille see just paljastas. See vähendab drastiliselt kummitusartefakte. Liikumise hindamise implementeerimine on arvutusmahukas ja keeruline, nõudes sageli täiustatud algoritme, ning on peaaegu eranditult ülesanne WebAssembly või isegi WebGPU arvutusvarjutajate jaoks.
Adaptiivne filtreerimine
Teine täiustus on muuta filter adaptiivseks. Selle asemel, et kasutada fikseeritud alpha
väärtust kogu kaadri jaoks, saate seda varieerida vastavalt kohalikele tingimustele.
- Liikumisega kohanemine: Suure tuvastatud liikumisega piirkondades saate suurendada
alpha
't (nt 0,95-ni või 1,0-ni), et toetuda peaaegu täielikult praegusele kaadrile, vältides liikumisest tingitud hägusust. Staatilistes piirkondades (nagu sein taustal) saate vähendadaalpha
't (nt 0,5-ni) palju tugevama müra vähendamiseks. - Heledusega kohanemine: Müra on sageli paremini nähtav pildi tumedamates osades. Filtrit võiks muuta agressiivsemaks varjudes ja vähem agressiivseks heledates piirkondades detailide säilitamiseks.
Praktilised kasutusjuhud ja rakendused
Võime teostada brauseris kvaliteetset müra vähendamist avab arvukalt võimalusi:
- Reaalajas suhtlus (WebRTC): Töödelge kasutaja veebikaamera voogu enne selle saatmist videokodeerijale. See on suur võit videokõnede jaoks madala valgustusega keskkondades, parandades visuaalset kvaliteeti ja vähendades vajalikku ribalaiust.
- Veebipõhine videotöötlus: Pakkuge 'Müra eemaldamise' filtrit funktsioonina brauserisiseses videotöötlusprogrammis, võimaldades kasutajatel oma üleslaaditud materjali puhastada ilma serveripoolse töötluseta.
- Pilvemängud ja kaugtöölaud: Puhastage sissetulevaid videovooge, et vähendada tihendusartefakte ja pakkuda selgemat, stabiilsemat pilti.
- Arvutinägemise eeltöötlus: Veebipõhiste tehisintellekti/masinõppe rakenduste (nagu objektide jälgimine või näotuvastus) puhul võib sisendvideo müra eemaldamine stabiliseerida andmeid ja viia täpsemate ja usaldusväärsemate tulemusteni.
Väljakutsed ja tulevikusuunad
Kuigi see lähenemine on võimas, ei ole see ilma väljakutseteta. Arendajad peavad olema teadlikud:
- Jõudlus: Reaalajas töötlemine HD- või 4K-video jaoks on nõudlik. Tõhus implementeerimine, tavaliselt WebAssembly'ga, on hädavajalik.
- Mälu: Ühe või mitme eelmise kaadri salvestamine tihendamata puhvritena tarbib märkimisväärse hulga RAM-i. Hoolikas haldamine on oluline.
- Latentsus: Iga töötlusetapp lisab latentsust. Reaalajas suhtluse jaoks peab see toru olema kõrgelt optimeeritud, et vältida märgatavaid viivitusi.
- Tulevik WebGPU-ga: Arenev WebGPU API pakub uut piiri selliseks tööks. See võimaldab neid pikslipõhiseid algoritme käitada kõrgelt paralleelsete arvutusvarjutajatena süsteemi GPU-l, pakkudes veel üht tohutut jõudluse hüpet isegi CPU-l töötava WebAssembly üle.
Kokkuvõte
WebCodecs API tähistab uut ajastut täiustatud meediumitöötluses veebis. See lammutab traditsioonilise musta kasti <video>
elemendi barjäärid ja annab arendajatele peeneteralise kontrolli, mis on vajalik tõeliselt professionaalsete videorakenduste ehitamiseks. Ajaline müra vähendamine on selle võimsuse ideaalne näide: keerukas tehnika, mis tegeleb otse nii kasutaja tajutava kvaliteedi kui ka aluseks oleva tehnilise tõhususega.
Oleme näinud, et individuaalsete VideoFrame
objektide pealtkuulamisega saame implementeerida võimsa filtreerimisloogika, et vähendada müra, parandada tihendatavust ja pakkuda paremat videokogemust. Kuigi lihtne JavaScripti implementatsioon on suurepärane lähtepunkt, viib tee tootmisvalmis reaalajas lahenduseni läbi WebAssembly jõudluse ja tulevikus WebGPU paralleeltöötlusvõimsuse.
Järgmine kord, kui näete veebirakenduses teralist videot, pidage meeles, et selle parandamise tööriistad on nüüd esmakordselt otse veebiarendajate kätes. On põnev aeg veebis videotega tegelemiseks.