hakkında en iyi şeylerden biri Web Montajı tarayıcı bu özellikleri yerel olarak göndermeden önce (eğer varsa) yeni yeteneklerle deney yapma ve yeni fikirleri uygulama yeteneğidir. WebAssembly’ı bu şekilde kullanmayı, özelliğinizi JavaScript yerine C/C++ veya Rust’ta yazdığınız yüksek performanslı bir çoklu doldurma mekanizması olarak düşünebilirsiniz.
Taşıma için çok sayıda mevcut kod mevcut olduğundan, WebAssembly gelene kadar mümkün olmayan şeyleri tarayıcıda yapmak mümkündür.
Bu makale, mevcut AV1 video codec kaynak kodunun nasıl alınacağına, bunun için bir sarmalayıcının nasıl oluşturulacağına ve tarayıcınızda nasıl deneneceğine dair bir örnek ve paketleyicide hata ayıklamak için bir test donanımı oluşturmaya yardımcı olacak ipuçlarını ele alacaktır. Buradaki örnek için tam kaynak kodu şu adreste mevcuttur: github.com/GoogleChromeLabs/wasm-av1 referans için.
Bu iki 24 fps testinden birini indirin video Dosyalar ve onları inşa ettiğimiz üzerinde deneyin gösteri.
İlginç bir kod tabanı seçme #
Birkaç yıldır, web üzerindeki trafiğin büyük bir yüzdesinin video verilerinden oluştuğunu gördük, Cisco tahmin ediyor aslında% 80 kadar! Tabii ki, tarayıcı satıcıları ve video siteleri, tüm bu video içeriği tarafından tüketilen verileri azaltma arzusunun büyük ölçüde farkındadır. Bunun anahtarı, elbette, daha iyi sıkıştırma ve beklediğiniz gibi, videoyu internet üzerinden göndermenin veri yükünü azaltmayı amaçlayan yeni nesil video sıkıştırma konusunda çok sayıda araştırma var.
Olduğu gibi, Açık Medya İttifakı adlı yeni nesil bir video sıkıştırma şeması üzerinde çalışmaktadır. AV1 video veri boyutunu önemli ölçüde küçültmeyi vaat ediyor. Gelecekte, tarayıcıların AV1 için yerel destek sunmasını bekliyoruz, ancak neyse ki sıkıştırıcı ve sıkıştırıcı açıcı için kaynak kodu açık kaynakbu da onu tarayıcıda deneyebilmemiz için WebAssembly’de derlemeye çalışmak için ideal bir aday yapar.
Tarayıcıda kullanım için uyarlama #
Bu kodu tarayıcıya sokmak için yapmamız gereken ilk şeylerden biri, API’nin nasıl bir şey olduğunu anlamak için mevcut kodu tanımaktır. Bu koda ilk bakıldığında iki şey göze çarpıyor:
- Kaynak ağaç, adlı bir araç kullanılarak oluşturulur.
cmake
; Ve - Hepsinin bir tür dosya tabanlı arayüz varsaydığı çok sayıda örnek vardır.
Varsayılan olarak oluşturulan tüm örnekler, komut satırında çalıştırılabilir ve bu, toplulukta bulunan diğer birçok kod tabanında muhtemelen doğru olacaktır. Bu nedenle, tarayıcıda çalışmasını sağlamak için oluşturacağımız arayüz, diğer birçok komut satırı aracı için yararlı olabilir.
kullanma cmake
kaynak kodunu oluşturmak için #
Neyse ki, AV1 yazarları yazmak, WebAssembly sürümümüzü oluşturmak için kullanacağımız SDK. kökünde AV1 deposudosya CMakeLists.txt
şu derleme kurallarını içerir:
if(EMSCRIPTEN)
add_preproc_definition(_POSIX_SOURCE)
append_link_flag_to_target("inspect" "-s TOTAL_MEMORY=402653184")
append_link_flag_to_target("inspect" "-s MODULARIZE=1")
append_link_flag_to_target("inspect"
"-s EXPORT_NAME=\"\'DecoderModule\'\"")
append_link_flag_to_target("inspect" "--memory-init-file 0")if("${CMAKE_BUILD_TYPE}" STREQUAL "")
# Default to -O3 when no build type is specified.
append_compiler_flag("-O3")
endif()
em_link_post_js(inspect "${AOM_ROOT}/tools/inspect-post.js")
endif()
Emscripten araç zinciri iki formatta çıktı üretebilir, biri denir asm.js
diğeri ise WebAssembly’dir. Daha küçük çıktılar ürettiği ve daha hızlı çalışabildiği için WebAssembly’ı hedefleyeceğiz. Bu mevcut derleme kuralları, bir asm.js
bir video dosyasının içeriğine bakmak için kullanılan bir denetçi uygulamasında kullanım için kitaplığın sürümü. Kullanımımız için WebAssembly çıktısına ihtiyacımız var bu yüzden bu satırları kapanıştan hemen önce ekliyoruz. endif()
Yukarıdaki kurallarda ifade.
# Force generation of Wasm instead of asm.js
append_link_flag_to_target("inspect" "-s WASM=1")
append_compiler_flag("-s WASM=1")
ile bina cmake
önce biraz üretmek anlamına gelir Makefiles
koşarak cmake
kendisi, ardından komutu çalıştırarak make
derleme adımını gerçekleştirecek olan. Emscripten kullandığımız için, varsayılan ana bilgisayar derleyicisi yerine Emscripten derleyici araç zincirini kullanmamız gerektiğini unutmayın. Bu kullanılarak elde edilir Emscripten.cmake
parçası olan Emscripten SDK’sı ve yolunu bir parametre olarak geçirmek cmake
kendisi. Aşağıdaki komut satırı, Makefile’leri oluşturmak için kullandığımız şeydir:
cmake path/to/aom \
-DENABLE_CCACHE=1 -DAOM_TARGET_CPU=generic -DENABLE_DOCS=0 \
-DCONFIG_ACCOUNTING=1 -DCONFIG_INSPECTION=1 -DCONFIG_MULTITHREAD=0 \
-DCONFIG_RUNTIME_CPU_DETECT=0 -DCONFIG_UNIT_TESTS=0
-DCONFIG_WEBM_IO=0 \
-DCMAKE_TOOLCHAIN_FILE=path/to/emsdk-portable/.../Emscripten.cmake
parametre path/to/aom
AV1 kitaplığı kaynak dosyalarının konumunun tam yoluna ayarlanmalıdır. bu path/to/emsdk-portable/…/Emscripten.cmake
parametresinin Emscripten.cmake araç zinciri açıklama dosyasının yoluna ayarlanması gerekir.
Kolaylık sağlamak için, bu dosyayı bulmak için bir kabuk betiği kullanıyoruz:
#!/bin/sh
EMCC_LOC=`which emcc`
EMSDK_LOC=`echo $EMCC_LOC | sed 's?/emscripten/[0-9.]*/emcc??'`
EMCMAKE_LOC=`find $EMSDK_LOC -name Emscripten.cmake -print`
echo $EMCMAKE_LOC
En üst seviyeye bakarsanız Makefile
bu proje için, bu komut dosyasının yapıyı yapılandırmak için nasıl kullanıldığını görebilirsiniz.
Artık tüm kurulumlar yapıldığına göre, basitçe çağırıyoruz make
örnekler de dahil olmak üzere tüm kaynak ağacını oluşturacak, ancak en önemlisi libaom.a
derlenmiş ve projemize dahil etmemiz için hazır olan video kod çözücüyü içerir.
Kitaplığa arayüz oluşturmak için bir API tasarlama #
Kitaplığımızı oluşturduktan sonra, ona sıkıştırılmış video verileri göndermek için onunla nasıl arayüz oluşturacağımızı bulmamız ve ardından videonun tarayıcıda görüntüleyebileceğimiz arka plan karelerini okumamız gerekir.
AV1 kod ağacının içine bakıldığında, dosyada bulunabilen örnek bir video kod çözücü iyi bir başlangıç noktasıdır. [simple_decoder.c](https://aomedia.googlesource.com/aom/+/master/examples/simple_decoder.c)
. Bu kod çözücü bir IVF dosyasını açar ve videodaki kareleri temsil eden bir dizi görüntüye dönüştürür.
Arayüzümüzü kaynak dosyada uyguluyoruz [decode-av1.c](https://github.com/GoogleChromeLabs/wasm-av1/blob/master/decode-av1.c)
.
Tarayıcımız dosya sisteminden dosyaları okuyamadığından, AV1 kitaplığımıza veri almak için örnek kod çözücüye benzer bir şey oluşturabilmemiz için G/Ç’mizi soyutlamamıza izin veren bir tür arabirim tasarlamamız gerekir.
Komut satırında, dosya G/Ç, akış arabirimi olarak bilinen şeydir, bu nedenle akış G/Ç’ye benzeyen kendi arabirimimizi tanımlayabilir ve temel uygulamada istediğimizi oluşturabiliriz.
Arayüzümüzü şu şekilde tanımlıyoruz:
DATA_Source *DS_open(const char *what);
size_t DS_read(DATA_Source *ds,
unsigned char *buf, size_t bytes);
int DS_empty(DATA_Source *ds);
void DS_close(DATA_Source *ds);
// Helper function for blob support
void DS_set_blob(DATA_Source *ds, void *buf, size_t len);
bu open/read/empty/close
işlevler, onları bir komut satırı uygulaması için dosya G/Ç’ye kolayca eşlememize veya bir tarayıcı içinde çalıştırıldığında başka bir şekilde uygulamamıza izin veren normal dosya G/Ç işlemlerine çok benzer. bu DATA_Source
type, JavaScript açısından opaktır ve yalnızca arayüzü kapsüllemeye yarar. Dosya semantiğini yakından takip eden bir API oluşturmanın, bir komut satırından kullanılması amaçlanan diğer birçok kod tabanında (örn. diff, sed, vb.) yeniden kullanımı kolaylaştırdığını unutmayın.
Ayrıca, adında bir yardımcı işlev tanımlamamız gerekiyor. DS_set_blob
ham ikili verileri akış G/Ç işlevlerimize bağlayan. Bu, blobun bir akışmış gibi ‘okunmasını’ sağlar (yani sıralı olarak okunan bir dosya gibi görünür).
Örnek uygulamamız, aktarılan blob’un sıralı olarak okunan bir veri kaynağıymış gibi okunmasını sağlar. Referans kodu dosyada bulunabilir. blob-api.c
ve tüm uygulama sadece şudur:
struct DATA_Source {
void *ds_Buf;
size_t ds_Len;
size_t ds_Pos;
};DATA_Source *
DS_open(const char *what) {
DATA_Source *ds;
ds = malloc(sizeof *ds);
if (ds != NULL) {
memset(ds, 0, sizeof *ds);
}
return ds;
}
size_t
DS_read(DATA_Source *ds, unsigned char *buf, size_t bytes) {
if (DS_empty(ds) || buf == NULL) {
return 0;
}
if (bytes > (ds->ds_Len - ds->ds_Pos)) {
bytes = ds->ds_Len - ds->ds_Pos;
}
memcpy(buf, &ds->ds_Buf[ds->ds_Pos], bytes);
ds->ds_Pos += bytes;
return bytes;
}
int
DS_empty(DATA_Source *ds) {
return ds->ds_Pos >= ds->ds_Len;
}
void
DS_close(DATA_Source *ds) {
free(ds);
}
void
DS_set_blob(DATA_Source *ds, void *buf, size_t len) {
ds->ds_Buf = buf;
ds->ds_Len = len;
ds->ds_Pos = 0;
}
Tarayıcının dışında test etmek için bir test donanımı oluşturma #
Yazılım mühendisliğindeki en iyi uygulamalardan biri, entegrasyon testleriyle birlikte kod için birim testleri oluşturmaktır.
Tarayıcıda WebAssembly ile oluştururken, üzerinde çalıştığımız kodun arabirimi için bir tür birim testi oluşturmak mantıklıdır, böylece tarayıcının dışında hata ayıklayabilir ve ayrıca oluşturduğumuz arabirimi test edebiliriz. .
Bu örnekte, AV1 kitaplığına arabirim olarak akış tabanlı bir API’yi öykündük. Bu nedenle, mantıksal olarak, API’mizin komut satırında çalışan bir sürümünü oluşturmak için kullanabileceğimiz bir test donanımı oluşturmak mantıklıdır ve G/Ç dosyasının kendisini bizim dosyamızın altına uygulayarak kaputun altında gerçek dosya G/Ç’si gerçekleştirir. DATA_Source
API.
Test donanımımızın akış G/Ç kodu basittir ve şöyle görünür:
DATA_Source *
DS_open(const char *what) {
return (DATA_Source *)fopen(what, "rb");
}size_t
DS_read(DATA_Source *ds, unsigned char *buf, size_t bytes) {
return fread(buf, 1, bytes, (FILE *)ds);
}
int
DS_empty(DATA_Source *ds) {
return feof((FILE *)ds);
}
void
DS_close(DATA_Source *ds) {
fclose((FILE *)ds);
}
Akış arabirimini soyutlayarak, WebAssembly modülümüzü, tarayıcıdayken ikili veri damlalarını kullanacak şekilde ve komut satırından test etmek için kodu oluşturduğumuzda gerçek dosyalara arabirim oluşturabiliriz. Test koşum kodumuz örnek kaynak dosyasında bulunabilir test.c
.
Birden çok video karesi için ara belleğe alma mekanizması uygulama #
Videoyu oynatırken, daha akıcı oynatmaya yardımcı olması için birkaç kareyi arabelleğe almak yaygın bir uygulamadır. Amaçlarımız doğrultusunda, sadece 10 karelik bir video tamponu uygulayacağız, dolayısıyla oynatmaya başlamadan önce 10 kareyi tampon belleğe alacağız. Ardından, her çerçeve görüntülendiğinde, tamponu dolu tutmak için başka bir çerçevenin kodunu çözmeye çalışacağız. Bu yaklaşım, videodaki takılmanın durdurulmasına yardımcı olmak için karelerin önceden kullanılabilir olmasını sağlar.
Basit örneğimizde, sıkıştırılmış videonun tamamı okunabilir, bu nedenle arabelleğe almaya gerçekten gerek yoktur. Bununla birlikte, kaynak veri arayüzünü bir sunucudan akış girişini destekleyecek şekilde genişleteceksek, o zaman arabelleğe alma mekanizmasına sahip olmamız gerekir.
içindeki kod decode-av1.c
AV1 kitaplığından video verilerinin çerçevelerini okumak ve arabelleğe şu şekilde depolamak için:
void
AVX_Decoder_run(AVX_Decoder *ad) {
...
// Try to decode an image from the compressed stream, and buffer
while (ad->ad_NumBuffered NUM_FRAMES_BUFFERED) {
ad->ad_Image = aom_codec_get_frame(&ad->ad_Codec,
&ad->ad_Iterator);
if (ad->ad_Image == NULL) {
break;
}
else {
buffer_frame(ad);
}
}
Tamponun 10 kare video içermesini seçtik, bu sadece keyfi bir seçimdir. Daha fazla kareyi arabelleğe almak, videonun oynatmaya başlaması için daha fazla bekleme süresi anlamına gelirken, çok az kareyi arabelleğe almak oynatma sırasında duraklamaya neden olabilir. Yerel bir tarayıcı uygulamasında, çerçevelerin arabelleğe alınması bu uygulamadan çok daha karmaşıktır.
Video karelerini WebGL ile sayfaya alma #
Arabelleğe aldığımız video karelerinin sayfamızda görüntülenmesi gerekiyor. Bu dinamik video içeriği olduğundan, bunu olabildiğince hızlı yapabilmek istiyoruz. Bunun için dönüyoruz WebGL.
WebGL, video karesi gibi bir görüntü almamıza ve onu bir geometri üzerine boyanan bir doku olarak kullanmamıza izin verir. WebGL dünyasında her şey üçgenlerden oluşur. Bu nedenle, bizim durumumuz için WebGL’nin gl.TRIANGLE_FAN adlı kullanışlı bir yerleşik özelliğini kullanabiliriz.
Ancak küçük bir sorun var. WebGL dokularının, renk kanalı başına bir bayt olacak şekilde RGB görüntüleri olması gerekir. AV1 kod çözücümüzün çıktısı, varsayılan çıkışın kanal başına 16 bit olduğu ve ayrıca her U veya V değerinin gerçek çıktı görüntüsünde 4 piksele karşılık geldiği YUV formatındaki görüntülerdir. Tüm bunlar, görüntüleme için WebGL’ye aktarmadan önce görüntüyü renklendirmemiz gerektiği anlamına gelir.
Bunu yapmak için bir işlev uyguluyoruz AVX_YUV_to_RGB()
kaynak dosyada bulabileceğiniz yuv-to-rgb.c
. Bu işlev, AV1 kod çözücüsünden gelen çıktıyı WebGL’ye iletebileceğimiz bir şeye dönüştürür. Bu işlevi JavaScript’ten çağırdığımızda, dönüştürülen görüntüyü yazdığımız belleğin WebAssembly modülünün belleği içinde tahsis edildiğinden emin olmamız gerektiğini unutmayın – aksi takdirde modül buna erişemez. WebAssembly modülünden bir görüntü alıp ekrana boyama işlevi şudur:
function show_frame(af) {
if (rgb_image != 0) {
// Convert The 16-bit YUV to 8-bit RGB
let buf = Module._AVX_Video_Frame_get_buffer(af);
Module._AVX_YUV_to_RGB(rgb_image, buf, WIDTH, HEIGHT);
// Paint the image onto the canvas
drawImageToCanvas(new Uint8Array(Module.HEAPU8.buffer,
rgb_image, 3 * WIDTH * HEIGHT), WIDTH, HEIGHT);
}
}
bu drawImageToCanvas()
WebGL resmini uygulayan işlev kaynak dosyada bulunabilir draw-image.js
referans için.
Gelecekteki çalışmalar ve çıkarımlar #
bizim denemek gösteri iki testte çıktı video Dosyalar (24 fps video olarak kaydedilmiş) bize birkaç şey öğretiyor:
- WebAssembly kullanarak tarayıcıda yüksek performansla çalışacak karmaşık bir kod tabanı oluşturmak tamamen mümkündür; Ve
- WebAssembly aracılığıyla gelişmiş video kod çözme gibi CPU yoğun bir şey mümkündür.
Yine de bazı sınırlamalar var: uygulamanın tamamı ana iş parçacığında çalışıyor ve boyama ve video kod çözme işlemlerini bu tek iş parçacığına serpiştiriyoruz. Kod çözmeyi bir web çalışanına boşaltmak, karelerin kodunu çözme süresi büyük ölçüde o karenin içeriğine bağlı olduğundan ve bazen bütçelendirdiğimizden daha uzun sürebildiğinden, bize daha sorunsuz oynatma sağlayabilir.
WebAssembly derlemesi, genel bir CPU türü için AV1 yapılandırmasını kullanır. Genel bir CPU için yerel olarak komut satırında derlersek, videonun kodunu çözmek için WebAssembly sürümünde olduğu gibi benzer CPU yükü görürüz, ancak AV1 kod çözücü kitaplığı şunları da içerir: simd 5 kata kadar daha hızlı çalışan uygulamalar. WebAssembly Topluluk Grubu şu anda standardı aşağıdakileri içerecek şekilde genişletmek için çalışmaktadır: SIMD ilkellerive bu ortaya çıktığında, kod çözmeyi önemli ölçüde hızlandırmayı vaat ediyor. Bu olduğunda, 4k HD videonun kodunu bir WebAssembly video kod çözücüsünden gerçek zamanlı olarak çözmek tamamen mümkün olacaktır.
Her durumda, örnek kod, mevcut herhangi bir komut satırı yardımcı programının bir WebAssembly modülü olarak çalıştırılmasına yardımcı olacak bir kılavuz olarak kullanışlıdır ve web’de bugünden nelerin mümkün olduğunu gösterir.
Kredi #
Değerli inceleme ve geri bildirim sağladıkları için Jeff Posnick, Eric Bidelman ve Thomas Steiner’a teşekkürler.