操控影片串流元件。
現代化的網路技術提供多種影片製作方式。 Media Stream API、 Media Recording API Media Source API、 WebRTC API 增加 是一項豐富的工具集,可用於錄製、傳輸及播放影片串流。 這些 API 在解決特定高階工作時,無法讓使用者 程式設計師會與影片串流的個別元件 (如影格) 合作 和非混合編碼影片或音訊片段 為了取得這些基本元件的低階存取權,開發人員一直以來都使用 WebAssembly 可將影片和音訊轉碼器匯入瀏覽器。但有了 新世代瀏覽器已經搭載許多轉碼器 ( WebAssembly 是硬體加速的 人類和電腦資源
WebCodecs API 可排除這種效率不彰的做法 讓程式設計師能夠使用 。詳細說明:
- 影片和音訊解碼器
- 影片和音訊編碼器
- 原始影片影格
- 圖片解碼器
WebCodecs API 非常適合需要完全掌控 視媒體內容的處理方式,例如視訊編輯器、視訊會議、視訊 即時串流等
影片處理工作流程
影格是影片處理過程中的核心部分。因此,在 WebCodecs 中 會消耗或產生影格影片編碼器會將影格轉換為編碼格式 分為多個片段反之,影片解碼器則相反。
此外,VideoFrame
可藉由做為 CanvasImageSource
且具有接受 CanvasImageSource
的建構函式,與其他 Web API 完美搭配運作。
因此可用於 drawImage()
和 texImage2D()
等函式。也可以利用畫布、點陣圖、影片元素和其他影片影格建構。
WebCodecs API 可與 Insertable Streams API 的類別搭配使用 可將 WebCodecs 連結至媒體串流音軌。
MediaStreamTrackProcessor
會將媒體音軌拆解成個別影格。MediaStreamTrackGenerator
會從影格串流建立媒體音軌。
WebCodecs 和網路工作站
設計 WebCodecs API 後,所有繁重工作都會以非同步方式,在主執行緒外執行。 但由於影格和區塊回呼通常可以每秒多次呼叫, 可能會讓主執行緒變得雜亂無章,進而導致網站的回應速度變慢。 因此,最好將個別影格和編碼區塊的處理方式移至 網路工作站。
為解決這個問題,ReadableStream
可自動傳輸所有來自媒體的影格
向 worker 提交追蹤舉例來說,MediaStreamTrackProcessor
可用來取得
為來自網路攝影機的媒體串流音軌 ReadableStream
。之後
串流會傳輸到網路工作站,其中會逐一讀取影格並排入佇列
放入 VideoEncoder
中。
使用 HTMLCanvasElement.transferControlToOffscreen
甚至可以透過主執行緒完成轉譯。但如果所有高階工具都採用
操作起來比較不方便,VideoFrame
本身可以轉讓,
並在工作站之間移動。
WebCodecs 實際操作
編碼
一切都以 VideoFrame
開頭。
建構影片影格的方式有三種。
圖片來源包含圖片來源,例如畫布、圖片點陣圖或影片元素。
const canvas = document.createElement("canvas"); // Draw something on the canvas... const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
使用
MediaStreamTrackProcessor
從MediaStreamTrack
提取影格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
中根據二進位像素表示法建立框架const 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);
無論影格來源為何,都可以將影格編碼為
具有 VideoEncoder
的 EncodedVideoChunk
物件。
在編碼之前,需要提供兩個 JavaScript 物件 VideoEncoder
:
- 包含兩個函式,用於處理已編碼區塊和
發生錯誤。這些函式是由開發人員定義,之後即無法變更
會傳遞至
VideoEncoder
建構函式。 - 編碼器設定物件,含有輸出的參數
影片串流。您之後可以呼叫
configure()
來變更這些參數。
如果未設定,configure()
方法會擲回 NotSupportedError
瀏覽器的檔案。建議您呼叫
使用 VideoEncoder.isConfigSupported()
搭配設定,事先檢查是否
該設定受到支援,並等待其承諾。
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.
}
編碼器設定完成後,即可透過 encode()
方法接受影格。
configure()
和 encode()
都會立即傳回,不會等待
實際完成的工作此函式允許多個頁框在
encodeQueueSize
會顯示佇列中正在等待的要求數
才能完成先前的編碼
如果發生引數,系統會立即擲回例外狀況,來回報錯誤
或方法呼叫的順序違反 API 合約,或呼叫 error()
轉碼器實作問題的回呼。
如果編碼成功完成,output()
系統會使用新的編碼區塊做為引數來呼叫回呼。
這裡的另一個重點是,在沒有頁框時,需要告知頁框
需要 close()
。
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();
}
}
最後是時候請編寫一個函式,用來完成編碼 從編碼器傳出的編碼影片片段 這個函式通常會透過網路傳送資料區塊,或是將資料區塊多工成媒體 儲存空間
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,
});
}
如果需要確認所有待處理的編碼請求,
,您可以呼叫 flush()
並等待結果。
await encoder.flush();
解碼中
設定 VideoDecoder
與
VideoEncoder
:解碼器建立時會傳送兩個函式,轉碼器
參數傳遞至 configure()
每組轉碼器參數都有不同的轉碼器。例如 H.264 轉碼器
可能需要二進位 blob
的 AVCC 格式。除非編碼為 Annex B 格式 (encoderConfig.avc = { format: "annexb" }
)。
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.
}
初始化解碼器後,即可開始使用 EncodedVideoChunk
物件動態饋給。
如要建立區塊,您需要:
- 已編碼影片資料的
BufferSource
- 區塊的開始時間戳記,以微秒為單位 (區塊中第一個編碼影格的媒體時間)
- 區塊的類型,下列其中一種:
- 如果區塊可從先前的區塊獨立解碼,則為
key
- 如果前一個區塊只能在前一個區塊解碼後才能解碼,則為
delta
- 如果區塊可從先前的區塊獨立解碼,則為
此外,編碼器傳出的所有區塊也可供解碼器使用, 關於上述錯誤報表和非同步特性的敘述 對解碼器來說,編碼器方法的結果也是一樣
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();
現在要展示如何在網頁上顯示新解碼的頁框。是
請務必確保解碼器輸出回呼 (handleFrame()
)
可快速傳回值在以下範例中,它只會將影格新增至
準備轉譯的幾個影格
轉譯作業會獨立進行,包含兩個步驟:
- 等待適當時機顯示影格。
- 在畫布上繪製影格。
不再需要影格後,請呼叫 close()
釋出基礎記憶體
這樣就會減少平均
記憶體用量。
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);
}
開發人員秘訣
使用媒體面板 查看媒體記錄並對 WebCodecs 偵錯。
示範
以下示範展示畫布動畫影格的樣貌:
MediaStreamTrackProcessor
以 25 FPS 擷取到ReadableStream
- 移轉到網路工作站
- 編碼為 H.264 影片格式
- 再次解碼成一系列影片
- 並使用
transferControlToOffscreen()
在第二個畫布上顯示
其他示範
另請觀賞其他示範:
使用 WebCodecs API
特徵偵測
如何檢查 WebCodecs 支援:
if ('VideoEncoder' in window) {
// WebCodecs API is supported.
}
請注意,WebCodecs API 僅適用於安全內容。
因此如果 self.isSecureContext
為 false,偵測作業就會失敗。
意見回饋
Chrome 小組想瞭解您的 WebCodecs API 使用體驗。
請與我們分享 API 設計
您覺得這個 API 有什麼不如預期的運作方式?或是 沒有你想實現創意的方法或屬性嗎?擁有 對安全性模型有任何疑問或意見嗎?在 對應的 GitHub 存放區 對現有問題的看法
回報導入問題
您發現 Chrome 實作錯誤嗎?另一種是實作
該怎麼辦?前往 new.crbug.com 回報錯誤。
請盡可能提供最詳細的細節和簡單的說明
請在「Components」(元件) 方塊中輸入 Blink>Media>WebCodecs
。
Glitch 有便捷的報復工具,
顯示對 API 的支援
您是否打算使用 WebCodecs API?你的公開支援讓 優先考慮各項功能,並向其他瀏覽器供應商說明其重要性 就必須支持他們
將電子郵件傳送到 media-dev@chromium.org 或傳送推文
使用主題標記套用至 @ChromiumDev
#WebCodecs
,並說明你使用這項服務的位置和方式。