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 kareler ve kodlanmamış video veya ses parçaları gibi bağımsız video akışı bileşenleriyle çalışmasına izin vermez. 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 bulunan medya bileşenlerini kullanma olanağı sunarak bu verimsizliği ortadan kaldırır. Özellikle:
- Video ve ses kod çözücüleri
- 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. Dolayısıyla, WebCodecs'te çoğu sınıf çerçeveleri tüketir veya üretir. Video kodlayıcılar, kareleri kodlanmış parçalara dönüştürür. Video kod çözücüler tam tersini yapar.
Ayrıca VideoFrame
, CanvasImageSource
türüne sahip ve CanvasImageSource
kabul eden bir constructor içererek diğer Web API'leriyle iyi çalışır.
Bu nedenle, drawImage()
ve texImage2D()
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ığında yapmaz. Ancak çerçeve ve parça geri çağırmaları genellikle saniyede birden çok kez çağrılabildiğinden, ana iş parçacığını karmaşık hale getirebilir ve web sitesini daha az duyarlı hale getirebilir. 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 kullanışlı 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.
Tuval, resim bit eşlemi 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
öğesine iki JavaScript nesnesi verilmesi gerekir:
- Kodlanmış parçaları ve hataları işlemek için iki işleve sahip ilk sözlük. 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
değerini bildirir. Yapılandırmanın desteklenip desteklenmediğini önceden kontrol etmek ve sözünü beklemek için yapılandırmayı içeren VideoEncoder.isConfigSupported()
statik 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öntemiyle 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()
yöntemini çağırabilir ve isteğin gönderilmesini bekleyebilirsiniz.
await encoder.flush();
Kod çözme
VideoDecoder
kurulumu VideoEncoder
için yapılanlara benzer: Kod çözücü oluşturulduğunda iki işlev iletilir ve codec parametreleri configure()
öğesine verilir.
Codec parametre 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ı)
- aşağıdakilerden biri olabilir:
- Parçanın kodu önceki parçalardan bağımsız olarak çözülebiliyorsa
key
delta
(parçanın kodu yalnızca bir veya daha fazla önceki parçanın kodu çözüldükten sonra çözülebiliyorsa)
- Parçanın kodu önceki parçalardan bağımsız olarak çö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 sıra kodu çözülmüş yeni bir karenin sayfada nasıl gösterilebileceğini göstermeye geldi. Kod çözücü çıkışı geri çağırmasının (handleFrame()
) hızlı bir şekilde dönmesini sağlamak 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çeve tuvale çiziliyor.
Bir çerçeveye artık ihtiyaç duyulmadığında, çöp toplayıcı ona ulaşmadan temel belleği serbest bırakmak için close()
çağrısı yapı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, tuvaldeki animasyon karelerinin nasıl olduğu gösterilmektedir:
MediaStreamTrackProcessor
tarafından 25 fps'deReadableStream
içine ç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 mu var? İ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.
Denise Jans'dan Unsplash'teki hero resim.