Video akışı bileşenlerini değiştirme
Modern web teknolojileri, videolarla çalışmanın birçok yolunu sunar. Media Stream API, Media Recording API, Media Source API ve WebRTC API, video akışlarını kaydetmek, aktarmak ve oynatmak için zengin bir araç seti oluşturur. Bu API'ler, belirli üst düzey görevleri çözerken web programcılarının video akışının tek tek bileşenleriyle (ör. kareler ve kodlanmış video veya sesin birleştirilmemiş parçaları) çalışmasını engellemez. Geliştiriciler, bu temel bileşenlere düşük düzeyde erişim elde etmek için video ve ses codec'lerini tarayıcıya getirmek üzere WebAssembly'i kullanıyordu. Ancak modern tarayıcıların zaten çeşitli codec'lerle (genellikle donanım tarafından hızlandırılır) birlikte gönderildiği göz önüne alındığında, bunları WebAssembly olarak yeniden paketlemek insan ve bilgisayar kaynaklarının israf edilmesi gibi görünüyor.
WebCodecs API, programcılara tarayıcıda zaten mevcut olan medya bileşenlerini kullanmanın bir yolunu sunarak bu verimsizliği ortadan kaldırır. Özellikle:
- Video ve ses kod çözücüler
- Video ve ses kodlayıcılar
- İşlenmemiş video kareleri
- Resim kod çözücüler
WebCodecs API, medya içeriğinin işlenme şekli üzerinde tam kontrol gerektiren web uygulamaları (ör. video düzenleyiciler, video konferansı, video aktarımı vb.) için kullanışlıdır.
Video işleme iş akışı
Çerçeveler, video işleme sürecinin merkezinde yer alır. Bu nedenle, WebCodecs'teki çoğu sınıf kare tüketir veya üretir. Video kodlayıcılar, kareleri kodlanmış parçalara dönüştürür. Video kod çözücüler ise tam tersini yapar.
Ayrıca VideoFrame
, CanvasImageSource
türüne sahip ve CanvasImageSource
kabul eden bir yapıcı içererek diğer Web API'leriyle iyi çalışır.
Bu nedenle drawImage()
vetexImage2D()
gibi işlevlerde kullanılabilir. Ayrıca kanvaslar, bitmap'ler, video öğeleri ve diğer video çerçevelerinden de oluşturulabilir.
WebCodecs API, WebCodecs'i medya akış kanallarına bağlayan Insertable Streams API sınıflarıyla birlikte iyi çalışır.
MediaStreamTrackProcessor
, medya parçalarını ayrı karelere ayırır.MediaStreamTrackGenerator
, bir kare akışından medya parçası oluşturur.
WebCodecs ve web işçileri
WebCodecs API, tüm ağır işleri tasarım gereği asenkron olarak ve ana iş parçacığından bağımsız olarak yapar. Ancak çerçeve ve parça geri çağırma işlevleri genellikle saniyede birden çok kez çağrılabildiğinden ana iş parçacığı tıkanabilir ve bu da web sitesinin daha az duyarlı olmasına neden olabilir. Bu nedenle, tek tek karelerin ve kodlanmış parçaların işlenmesi bir web çalışanına taşınmalıdır.
Bu konuda yardımcı olmak için ReadableStream, bir medya kanalından gelen tüm kareleri işleyiciye otomatik olarak aktarmanın uygun bir yolunu sağlar. Örneğin, web kamerasından gelen bir medya akışı parçası için MediaStreamTrackProcessor
, ReadableStream
elde etmek amacıyla kullanılabilir. Ardından akış, karelerin tek tek okunup VideoEncoder
olarak sıraya alındığı bir web işleyicisine aktarılır.
HTMLCanvasElement.transferControlToOffscreen
ile oluşturma işlemi bile ana iş parçacığının dışında yapılabilir. Ancak tüm üst düzey araçlar uygun değilse VideoFrame
'ün kendisi aktarılabilir ve çalışanlar arasında taşınabilir.
WebCodecs'in kullanımı
Kodlama
Her şey bir VideoFrame
ile başlar.
Video kareleri oluşturmanın üç yolu vardır.
Kanvas, resim bitmap'i veya video öğesi gibi bir resim kaynağından.
const canvas = document.createElement("canvas"); // Draw something on the canvas... const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
MediaStreamTrack
kaynağından kare almak içinMediaStreamTrackProcessor
'ü kullanın.const stream = await navigator.mediaDevices.getUserMedia({…}); const track = stream.getTracks()[0]; const trackProcessor = new MediaStreamTrackProcessor(track); const reader = trackProcessor.readable.getReader(); while (true) { const result = await reader.read(); if (result.done) break; const frameFromCamera = result.value; }
BufferSource
içinde ikili piksel temsilinden çerçeve oluşturmaconst pixelSize = 4; const init = { timestamp: 0, codedWidth: 320, codedHeight: 200, format: "RGBA", }; const data = new Uint8Array(init.codedWidth * init.codedHeight * pixelSize); for (let x = 0; x < init.codedWidth; x++) { for (let y = 0; y < init.codedHeight; y++) { const offset = (y * init.codedWidth + x) * pixelSize; data[offset] = 0x7f; // Red data[offset + 1] = 0xff; // Green data[offset + 2] = 0xd4; // Blue data[offset + 3] = 0x0ff; // Alpha } } const frame = new VideoFrame(data, init);
Kaynakları ne olursa olsun kareler, VideoEncoder
ile EncodedVideoChunk
nesnelerine kodlanabilir.
Kodlamadan önce VideoEncoder
'e iki JavaScript nesnesi verilmelidir:
- Kodlanmış parçaları ve hataları işlemek için sözlüğü iki işlevle başlatın. Bu işlevler geliştirici tarafından tanımlanır ve
VideoEncoder
yapıcısına iletildikten sonra değiştirilemez. - Çıkış video akışıyla ilgili parametreleri içeren kodlayıcı yapılandırma nesnesi. Bu parametreleri daha sonra
configure()
'yi çağırarak değiştirebilirsiniz.
Yapılandırma tarayıcı tarafından desteklenmiyorsa configure()
yöntemi NotSupportedError
hatası atar. Yapılandırmanın desteklenip desteklenmediğini önceden kontrol etmek ve promise'ini beklemek için yapılandırmayı içeren statik VideoEncoder.isConfigSupported()
yöntemini çağırmanız önerilir.
const init = {
output: handleChunk,
error: (e) => {
console.log(e.message);
},
};
const config = {
codec: "vp8",
width: 640,
height: 480,
bitrate: 2_000_000, // 2 Mbps
framerate: 30,
};
const { supported } = await VideoEncoder.isConfigSupported(config);
if (supported) {
const encoder = new VideoEncoder(init);
encoder.configure(config);
} else {
// Try another config.
}
Kodlayıcı ayarlandıktan sonra encode()
yöntemi aracılığıyla kareleri kabul etmeye hazırdır.
Hem configure()
hem de encode()
, gerçek çalışmanın tamamlanmasını beklemeden hemen döndürülür. Bu sayede, aynı anda birden fazla karenin kodlama için sıraya alınmasına izin verilir. encodeQueueSize
ise önceki kodlamaların tamamlanması için sırada bekleyen isteklerin sayısını gösterir.
Hatalar, bağımsız değişkenler veya yöntem çağrılarının sırası API sözleşmesini ihlal ettiğinde hemen bir istisna atılarak ya da codec uygulamasında karşılaşılan sorunlar için error()
geri çağırma işlevi çağrılarak bildirilir.
Kodlama işlemi başarıyla tamamlanırsa output()
geri çağırma işlevi, bağımsız değişken olarak yeni kodlanmış bir parçayla çağrılır.
Buradaki bir diğer önemli ayrıntı da, artık ihtiyaç duyulmayan çerçevelerin close()
çağrılarak kaldırılması gerektiğidir.
let frameCounter = 0;
const track = stream.getVideoTracks()[0];
const trackProcessor = new MediaStreamTrackProcessor(track);
const reader = trackProcessor.readable.getReader();
while (true) {
const result = await reader.read();
if (result.done) break;
const frame = result.value;
if (encoder.encodeQueueSize > 2) {
// Too many frames in flight, encoder is overwhelmed
// let's drop this frame.
frame.close();
} else {
frameCounter++;
const keyFrame = frameCounter % 150 == 0;
encoder.encode(frame, { keyFrame });
frame.close();
}
}
Son olarak, kodlayıcıdan çıktıkları sırada kodlanmış video parçalarını işleyen bir işlev yazarak kodlama kodunu tamamlamanın zamanı geldi. Bu işlev genellikle veri parçalarını ağ üzerinden gönderir veya depolama için bir medya kapsayıcısında birleştirir.
function handleChunk(chunk, metadata) {
if (metadata.decoderConfig) {
// Decoder needs to be configured (or reconfigured) with new parameters
// when metadata has a new decoderConfig.
// Usually it happens in the beginning or when the encoder has a new
// codec specific binary configuration. (VideoDecoderConfig.description).
fetch("/upload_extra_data", {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: metadata.decoderConfig.description,
});
}
// actual bytes of encoded data
const chunkData = new Uint8Array(chunk.byteLength);
chunk.copyTo(chunkData);
fetch(`/upload_chunk?timestamp=${chunk.timestamp}&type=${chunk.type}`, {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: chunkData,
});
}
Bir noktada bekleyen tüm kodlama isteklerinin tamamlandığından emin olmanız gerekirse flush()
'ü arayabilir ve sözünü bekleyebilirsiniz.
await encoder.flush();
Kod çözme
VideoDecoder
oluşturma işlemi, VideoEncoder
için yapılana benzer: Kod çözücü oluşturulurken iki işlev iletilir ve codec parametreleri configure()
'ye verilir.
Codec parametreleri grubu codec'e göre değişir. Örneğin, H.264 codec'i, ek B biçiminde (encoderConfig.avc = { format: "annexb" }
) kodlanmadığı sürece AVCC ikili blob'una ihtiyaç duyabilir.
const init = {
output: handleFrame,
error: (e) => {
console.log(e.message);
},
};
const config = {
codec: "vp8",
codedWidth: 640,
codedHeight: 480,
};
const { supported } = await VideoDecoder.isConfigSupported(config);
if (supported) {
const decoder = new VideoDecoder(init);
decoder.configure(config);
} else {
// Try another config.
}
Kod çözücü başlatıldıktan sonra EncodedVideoChunk
nesneleriyle beslemeye başlayabilirsiniz.
Bir parça oluşturmak için şunlara ihtiyacınız vardır:
- Kodlanmış video verilerinin
BufferSource
- Parçanın başlangıç zaman damgası (mikrosaniye cinsinden) (parçadaki ilk kodlanmış karenin medya zamanı)
- Parçanın türü. Şunlardan biri olabilir:
key
(parçanın önceki parçalardan bağımsız olarak kodu çözülebiliyorsa)delta
(parçanın kodu yalnızca bir veya daha fazla önceki parçanın kodu çözüldükten sonra çözülebiliyorsa)
Ayrıca kodlayıcı tarafından yayınlanan tüm parçalar olduğu gibi kod çözücüye hazırdır. Hata raporlama ve kodlayıcı yöntemlerinin asenkron yapısı hakkında yukarıda söylenenlerin tümü kod çözücüler için de geçerlidir.
const responses = await downloadVideoChunksFromServer(timestamp);
for (let i = 0; i < responses.length; i++) {
const chunk = new EncodedVideoChunk({
timestamp: responses[i].timestamp,
type: responses[i].key ? "key" : "delta",
data: new Uint8Array(responses[i].body),
});
decoder.decode(chunk);
}
await decoder.flush();
Şimdi, yeni kod çözülmüş bir çerçevenin sayfada nasıl gösterilebileceğini göstermenin zamanı geldi. Kod çözücü çıkışı geri çağırma işlevinin (handleFrame()
) hızlı bir şekilde döndürüldüğünden emin olmak daha iyidir. Aşağıdaki örnekte, yalnızca oluşturmaya hazır karelerin sırasına bir kare eklenmektedir.
Oluşturma işlemi ayrı olarak gerçekleşir ve iki adımdan oluşur:
- Çerçeveyi göstermek için doğru zamanı bekleme.
- Çerçeveyi tuvale çizme.
Bir çerçeveye artık ihtiyaç duyulmadığında, temel belleği çöp toplayıcıdan önce serbest bırakmak için close()
işlevini çağırın. Bu, web uygulaması tarafından kullanılan ortalama bellek miktarını azaltır.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let pendingFrames = [];
let underflow = true;
let baseTime = 0;
function handleFrame(frame) {
pendingFrames.push(frame);
if (underflow) setTimeout(renderFrame, 0);
}
function calculateTimeUntilNextFrame(timestamp) {
if (baseTime == 0) baseTime = performance.now();
let mediaTime = performance.now() - baseTime;
return Math.max(0, timestamp / 1000 - mediaTime);
}
async function renderFrame() {
underflow = pendingFrames.length == 0;
if (underflow) return;
const frame = pendingFrames.shift();
// Based on the frame's timestamp calculate how much of real time waiting
// is needed before showing the next frame.
const timeUntilNextFrame = calculateTimeUntilNextFrame(frame.timestamp);
await new Promise((r) => {
setTimeout(r, timeUntilNextFrame);
});
ctx.drawImage(frame, 0, 0);
frame.close();
// Immediately schedule rendering of the next frame
setTimeout(renderFrame, 0);
}
Geliştirici İpuçları
Medya günlüklerini görüntülemek ve WebCodec'lerde hata ayıklama yapmak için Chrome Geliştirici Araçları'ndaki Medya Paneli'ni kullanın.
Demo
Aşağıdaki demoda, kanvastaki animasyon karelerinin nasıl olduğu gösterilmektedir:
MediaStreamTrackProcessor
tarafından 25 fps'deReadableStream
cihazına çekilen- bir web işleyicisine aktarılır.
- H.264 video biçiminde kodlanmış olmalıdır.
- yeniden kodlanarak bir video karesi dizisi haline getirilir.
- ve
transferControlToOffscreen()
kullanılarak ikinci tuvalde oluşturulur.
Diğer demolar
Diğer demolarımıza da göz atın:
WebCodecs API'yi kullanma
Özellik algılama
WebCodecs desteğini kontrol etmek için:
if ('VideoEncoder' in window) {
// WebCodecs API is supported.
}
WebCodecs API'nin yalnızca güvenli bağlamlarda kullanılabileceğini unutmayın. Bu nedenle, self.isSecureContext
yanlışsa algılama başarısız olur.
Geri bildirim
Chrome ekibi, WebCodecs API ile ilgili deneyimlerinizi öğrenmek istiyor.
API tasarımı hakkında bilgi verin
API ile ilgili olarak beklediğiniz gibi çalışmayan bir şey var mı? Yoksa fikrinizi uygulamak için ihtiyaç duyduğunuz yöntemler veya özellikler eksik mi? Güvenlik modeliyle ilgili bir sorunuz veya yorumunuz var mı? İlgili GitHub deposunda özellik sorunu oluşturun veya mevcut bir soruna düşüncelerinizi ekleyin.
Uygulamayla ilgili sorunları bildirme
Chrome'un uygulamasında bir hata mı buldunuz? Yoksa uygulama, spesifikasyondan farklı mı? new.crbug.com adresinden hata kaydı oluşturun. Mümkün olduğunca fazla ayrıntı ekleyin, sorunu yeniden oluşturmayla ilgili basit talimatlar verin ve Bileşenler kutusuna Blink>Media>WebCodecs
yazın.
Glitch, hızlı ve kolay yeniden oluşturma işlemlerini paylaşmak için idealdir.
API'yi destekleme
WebCodecs API'yi kullanmayı planlıyor musunuz? Herkese açık desteğiniz, Chrome ekibinin özelliklere öncelik vermesine yardımcı olur ve diğer tarayıcı tedarikçi firmalarına bu özellikleri desteklemenin ne kadar önemli olduğunu gösterir.
media-dev@chromium.org adresine e-posta gönderin veya #WebCodecs
hashtag'ini kullanarak @ChromiumDev adresine tweet gönderin ve bu özelliği nerede ve nasıl kullandığınızı bize bildirin.
Unsplash'taki Denise Jans tarafından oluşturulan lokomotif resim.