WebCodecsメモ202301

WebCodecsの仕様と実装状況(202212〜202301)
仕様
-
WebCodecs W3C Working Draft, 12 December 2022
-
Audio
- AudioDecoder
- AudioEncoder
- EncodedAudioChunk
- AudioData
-
Video
- VideoDecoder
- VideoEncoder
- EncodedVideoChunk
- VideoFrame

実装 Chrome 111 (Canary), Chrome 108
VideoFrame
const frame = new VideoFrame(element, {
timestamp: timestamp_in_micro_second, // タイムスタンプをμ秒で指定
duration: duration_in_micro_second, // フレームの長さをμ秒で指定
});
// do something with frame
// release
frame.close();
元にできる要素
- img ... <img>
- canvas ... <canvas>
- video ... <video>
- VideoFrame
- ImageBitmap
- OffscreenCanvas
元にした要素で、フォーマット (VideoPixelFormat) が異なる
元の要素 | Chrome 108/111 | Safari TP 160 |
---|---|---|
img | BGRX | RGBA |
canvas | RGBA | RGBA |
video | NV12 | I420 |

VideoFrame をキャンバスに描画
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, width, height);
ctx.drawImage(frame, 0, 0);
元の要素 | Chrome 108/111 | Safari TP 160 | Safari TP 171 |
---|---|---|---|
img | OK | OK | OK |
canvas | OK | OK | OK |
video | OK | NG(真っ白) ※ | OK |
※Safar TP でvideoから取得したVideoFrameを描画すると真っ白になるのは、フォーマットが I420の場合の処理がおかしいのではないか?

VideoEncoder / VideoDecoder
const encoder = new VideoEncoder({
output: (chunk) => {
// エンコード成功時の処理
// chunkはエンコード済みのデータ
},
error: (err) => {
// エラー時の処理
}
});
await encoder.configure({
codec: CODEC, // コーデックの指定
width: WIDTH, // 幅の指定
height: HEIGHT, // 高さの指定
framerate: 10 // フレームレイトの指定
});
CODEC として指定できるもの(仕様上、実装状況はVP8と、VP9の一部、H.264の一部のみ確認)
- 'vp8' ... VP8
- 'vp09.*' ... VP9
- 'vp09.00.10.08' ... encoding vp9 profile0 : 動作OK (Chrome 108, 111 Mac)
- 'vp09.02.10.10' ... encoding vp9 profile2 : NG (Chrome 108, 111でタブがクラッシュ Mac)
- 'avc1.*' ... H264
- codec: 'avc1.42001E', avc: { format: "annexb" } // Chrome 108, 111, Safari TP 161 OK
- codec: 'avc1.64001E', avc: { format: "annexb" } // Chrome 108, 111, Safari TP 161 OK
- 'av01.*' ... AV1
- 'av01.0.01M.08' ... AV1: 動作OK (Chrome 108, 111 Mac)
*の部分はパラメータを指定した文字列が入る。CODECに合わせて適切に指定しないとエラーになる。詳細確認中
// フレーム取得
const frame = new VideoFrame(element, {
timestamp: timestamp_in_micro_second, // タイムスタンプをμ秒で指定
duration: duration_in_micro_second, // フレームの長さをμ秒で指定
});
// エンコード (成功すると、output に指定した関数が呼ばれる)
encoder.encode(frame, { keyFrame : true});
frame.close(); // フレームを解放
encorder.encode()でのkeyFrameの指定
- keyフレーム(完全なコマ画像)なら true を指定
- 差分(delta)フレームなら false を指定
※ 差分が取れるということは、VideoEncoderは内部で前回のフレームの情報を保持している(ステートフルになっている)
const decoder = new VideoDecoder({
output: (frame) => {
// デコード成功時の処理
frame.close(); // 使い終わったフレームを解放
},
error: (err) => {
// エラー時の処理
}
});
await decoder.configure({ codec: CODEC });
// デコード (成功すると、output に指定した関数が呼ばれる)
decoder.decode(chunk); // chunkはエンコード済みのデータ
※ chunkがキーフレームのエンコード結果でも、deltaフレームのエンコード結果でもデコードできる。つまりVideoDecoderも前回のフレームの情報を保持している(ステートフルになっている)

VideoFrame と Enocoder/Decoder の組み合わせ動作
次の一連の処理を実行
- (1) element → VideoFrame
- フォーマット (VideoPixelFormat) は前述の通り元にしたelementによって異なる
- Chrome / Safari TPでも異なる
- (2) → VideoEncoder.encode() → chunk
- (3) → VideoDecoder.decode() → VideoFrame
- フォーマット (VideoPixelFormat) は、(1)のフォーマットによらず、常に I420になっている
- ただし VP8 の場合のみ確認、他のCODECでは未確認
- フォーマット (VideoPixelFormat) は、(1)のフォーマットによらず、常に I420になっている
- (4) → canvasのctx.drawImage()
- Chrome 108/111では常にOK
- Safari TP 160では、元がVideo要素の場合には映像の種別によってOK/NGが発生
Chrome 108/111の場合
元の要素 | 映像の種類 | 元のVideoFrameのフォーマット | エンコード→デコード後のフォーマット | drawImage()結果 |
---|---|---|---|---|
img | - | RGBX | I420 | OK |
canvas | - | RGBA | I420 | OK |
video | mp4ファイル | NV12 | I420 | OK |
video | getUserMedia()で取得したカメラ映像 | NV12 | I420 | OK |
Safari TP 160の場合
※ なぜ元の映像の種類で結果が異なるのかは不明
元の要素 | 映像の種類 | 元のVideoFrameのフォーマット | エンコード→デコード後のフォーマット | drawImage()結果 |
---|---|---|---|---|
img | - | RGBA | I420 | OK |
canvas | - | RGBA | I420 | OK |
video | mp4ファイル | I420 | I420 | NG(真っ白) |
video | getUserMedia()で取得したカメラ映像 | I420 | I420 | OK |
CODECの実装状況確認(2023.01.29追記)
encoder/decoderの初期化
CODEC | encoder.cofigure()のオプション | decoder.configure()のオプション |
---|---|---|
VP8 | codec : "vp8" | codec : "vp8" |
VP9 profile0 | codec: "vp09.00.10.08" | codec: "vp09.00.10.08" |
VP9 profile2 | codec: "vp09.02.10.10" | codec: "vp09.02.10.10" |
AV1 | codec: "av01.0.01M.08" | codec: "av01.0.01M.08" |
H.264 | codec: "avc1.42001E", avc: { format: "annexb" } |
codec: "avc1.42001E" |
encode() → decode() 実行結果
CODEC | VideoFrameのソース | Chrome 109 (Mac) | Chrome Canary 112 (Mac) | Safari TP 162 | Safari TP 171 | Chrome 109 (Win10) | Chrome Canary 112 (Win10) |
---|---|---|---|---|---|---|---|
VP8 | image(jpeg) | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
VP8 | video(mp4) | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
✕ 緑色や崩れた描画 |
? | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
VP8 | video(カメラ映像) | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
VP9 profile0 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | ✕ encode output イベントが発生しない |
✕ encode output イベントが発生しない |
VP9 profile0 | video(mp4) | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | ✕ encode output イベントが発生しない videoの再生も止まる |
✕ encode output イベントが発生しない videoの再生も止まる |
VP9 profile0 | video(カメラ映像) | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | ✕ encode output イベントが発生しない videoの再生も止まる |
✕ encode output イベントが発生しない videoの再生も止まる |
VP9 profile2 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
✕ エラー: Not supported |
? | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
VP9 profile2 | video(mp4) | ✕ encode()でタブがクラッシュ |
✕ encode()でタブがクラッシュ |
✕ エラー: Not supported |
? | ✕ encode()でタブがクラッシュ |
✕ encode()でタブがクラッシュ |
VP9 profile2 | video(カメラ映像) | ✕ encode()でタブがクラッシュ |
✕ encode()でタブがクラッシュ |
✕ エラー: Not supported |
? | ✕ encode()でタブがクラッシュ |
✕ encode()でタブがクラッシュ |
AV1 | image(jpeg) | 〇 4フレーム目のデコードで表示 |
〇 4フレーム目のデコードで表示 |
✕ configure()でエラー |
? | 〇 4フレーム目のデコードで表示 |
〇 4フレーム目のデコードで表示 |
AV1 | video(mp4) | 〇 4フレーム目のデコードで表示 |
〇 4フレーム目のデコードで表示 |
✕ configure()でエラー |
? | 〇 4フレーム目のデコードで表示 |
〇 4フレーム目のデコードで表示 |
AV1 | video(カメラ映像) | 〇 4フレーム目のデコードで表示 |
〇 4フレーム目のデコードで表示 |
✕ configure()でエラー |
? | 〇 4フレーム目のデコードで表示 |
〇 4フレーム目のデコードで表示 |
H.264 | image(jpeg) | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
H.264 | video(mp4) | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
H.264 | video(カメラ映像) | 〇 2フレーム目のデコードで表示 |
〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
? | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
VP9のエンコードでタブクラッシュの回避策(2023.02.06追記)
twitter経由で、次のように教えていただいた。
you should be able to work around it by using a I420 input instead of NV12.
video要素から取得したVideoFrameではフォーマットが NV12 になっている。これをI420に変換してから vp9 profile0/profile2 のエンコードを行えばOKということ。
- VP8のencode() → decode()を行うとデコード結果のVideoFrameのフォーマットが NV12 → I420になる
- これをさらに VP9で enocde() → decode()すれば、正常に動作することを確認
※もっとシンプルにI420変換を行いたいが、方法は今の所不明 → こちらに変換ロジックを追記 https://zenn.dev/link/comments/ad2038330e0e88
Chrome 111, Canary 113 ではクラッシュしない(2023.03.09追記)
- Mac (ARM64) のChrome 111, Chrome Chanary 113では、 VP9 profile2 のエンコードでクラッシュしなくなった
- Videoから取得したVideoFrame (NV12)の場合
- https://chromium.googlesource.com/chromium/src/+/2a98a1c69f6df6c93bddfeba6f1ea887c8e23d8a
- Windows 10 (x64) のChrome 111, Chrome Chanary 113でも、 VP9 profile2 のエンコードでクラッシュしなくなった
- ただし、VP9 profile0でencodeイベントが発生しない現象は継続
- Videoから取得したVideoFrame (NV12)の場合

MediaStreamTrack Insertable Media Processing using Streams
仕様
映像(MediaStreamのVideoTrack)とVideoFrameの変換が行える 仕様
- MediaStreamTrack Insertable Media Processing using Streams Editor’s Draft, 20 October 2022
- MediaStreamTrackProcessor ... MediaStreamTrackからVideoFram/AudioDataを取り出す
- VideoTrackGenerator ... VideoFrameからMediaStreamTrack (VideoTrack)を作り出す
- ※こちらにはAudio系のGeneratorが記載されていない。どうなるのか??
以前の仕様
- MediaStreamTrack Insertable Media Processing using Streams, Unofficial Proposal Draft, 26 November 2021
- MediaStreamTrackProcessor ... MediaStreamTrackからVideoFram/AudioDataを取り出す
- MediaStreamTrackGenerator ... VideoFrame/AudioDataからMediaStreamTrack (VideoTrack/AudioTrack)を作り出す
実装状況
- Chrome 108/111 ... 後者の以前の仕様( Unofficial Proposal Draft, 26 November 2021)で実装
- Safari TP ... 未実装

MediaStreamTrackGenerator で映像を生成した動作
VideoFrame → MediaStreamTrackGenerator → MediaStream → Video
元の要素 | 画像/映像の種類 | VideoFrameのフォーマット | GeneratorでVideoTrack生成、video要素で再生結果 |
---|---|---|---|
img | JPEG | RGBX | Chrome 108...NG(映像が正しくない) Chrome 111... OK |
canvas | 画像を描画(drawImage) | RGBA | OK |
canvas | テキストを描画(fillText) | RGBA | OK |
video | mp4ファイル | NV12 | OK |
video | getUserMedia()で取得したカメラ映像 | NV12 | OK |
video | canvas.captureStream()で取得した映像 | RGBA | ※OK |
※ canvas.captureStream()で取得した映像のケースの補足
- テキスト描画の場合
- Chrome 108
- 背景色やテキスト色が未指定(透明)の場合、キャプチャーされる映像が真っ黒になる
- 結果、Genaratorで生成した映像も真っ黒になる
- 明示的にfillStyle()を指定し、fillRect(), fillText()で描写すればOK
- Chrome 111 では未指定でもOK
- Chrome 108
コード例
let trackGenerator = null;
let writer = null;
let mediaStream = null;
async function prepareGenerator() {
if (! trackGenerator) {
trackGenerator = new MediaStreamTrackGenerator({ kind: 'video' });
writer = trackGenerator.writable.getWriter();
await writer.ready;
mediaStream = new MediaStream();
mediaStream.addTrack(trackGenerator);
}
}
let frameCounter = 0;
function frameHandlerFunc(element) {
const frame = getFrame(element, frameCounter);
frameCounter++;
writer.write(frame); // GeneratorのWritableStreamに書き込む
frame.close();
}
// VideoFrameの取得
function getFrame(element, frameCount) {
const frame = new VideoFrame(element, {
timestamp: frameCount * FRAME_DURATION, // マイクロ秒で指定
duration: FRAME_DURATION, // マイクロ秒で指定
});
return frame;
}
const miliSec = FRAME_DURATION / 1000; // m秒で指定
let timerId = setInterval(frameHandlerFunc, miliSec, element);

エンコーダー/デコーダーとGeneratorの組み合わせ
VideoFrame → Encoder → Decoder → VideoFrame → MediaStreamTrackGenerator → MediaStream → VideoElement
※ 実際の利用ケースでは、Enocoder と Decoder の間に通信が挟まり圧縮されたデータが送受信される想定
動作結果
元の要素 | 画像/映像の種類 | エンコード前の VideoFrameのフォーマット |
デコード後の VideoFrameのフォーマット |
GeneratorでVideoTrack生成、video要素で再生結果 |
---|---|---|---|---|
img | JPEG | RGBX | I420 | Chrome 108... OK Chrome 111... OK |
canvas | 画像を描画(drawImage) | RGBA | I420 | OK |
canvas | テキストを描画(fillText) | RGBA | I420 | OK |
video | mp4ファイル | NV12 | I420 | OK |
video | getUserMedia()で取得したカメラ映像 | I420 | NV12 | OK |
video | canvas.captureStream()で取得した映像 | RGBA | I420 | ※OK |
※ テキスト描画の場合Chrome 108では明示的にfillStyleを指定しないと、映像が真っ黒になる。Chrome 111ではOK
コード例
// --- エンコーダーの準備 ---
let encoder = new VideoEncoder({
output: (chunk) => {
if (decoder) {
decoder.decode(chunk); // デコーダーに渡す
}
},
error: (err) => {
// エラー時の処理
}
});
// --- エンコーダーの初期化 ---
await encoder.configure({
codec: CODEC,
width: SRC_WIDTH,
height: SRC_HEIGHT,
framerate: 10
});
// --- デコーダーの準備 ---
let decoder = new VideoDecoder({
output: async (frame) => {
writer.write(frame); // GeneratorのWritableStreamに書き込む
frame.close();
},
error: (err) => {
// エラー時の処理
}
})
// デコーダーの初期化
await decoder.configure({ codec: CODEC });
let frameCounter = 0;
function frameEncDecHandlerFunc(element) {
const asKyeFrame = ((frameCounter % 20) === 0); // 定期的にキーフレームを入れる
const frame = getFrame(element, frameCounter);
frameCounter++;
encoder.encode(frame, { keyFrame : asKyeFrame}); // エンコーダーに渡す
// エンコーダー → デコーダー → Generator と処理がつながっている
frame.close();
}

さらに MediaStreamTrackProcessor を組み合わせる
MediaStreamTrackProcessorを使ってMediaStreamからVideoFrameを取得する
コード例
let trackProcessor = new MediaStreamTrackProcessor(videoTrack);
const reader = trackProcessor.readable.getReader();
let frameCounter = 0;
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);
console.log('src Frame', frame);
encoder.encode(frame, { keyFrame });
frame.close();
}
}
}

VideoFrameのフォーマットの変換 (NV12 → I420)
// convert VideoFrame format: NV12 --> I420
async function convertNV12toI420(srcFrame) {
if (srcFrame.format !== 'NV12') {
debugger;
}
const size = srcFrame.allocationSize();
const buf = new ArrayBuffer(size);
await srcFrame.copyTo(buf);
// === convert ===
const buf2 = new ArrayBuffer(size);
const view1 = new Int8Array(buf);
const view2 = new Int8Array(buf2);
// --- Y ----
const sizeY = srcFrame.codedWidth * srcFrame.codedHeight;
for(let i = 0; i < sizeY; i++) {
view2[i] = view1[i];
}
// --- UV --- (UVUVUV --> UUUVVV)
const UNDERSAMPLING = 4;
const sizeU = sizeY / UNDERSAMPLING;
const offsetU = sizeY;
const offsetV = sizeY + sizeU;
for(let i = 0; i < sizeU; i++) {
// U
view2[offsetU + i] = view1[sizeY + i*2];
// V
view2[offsetV + i] = view1[sizeY + i*2 + 1];
}
// ===== convert ====
const format = 'I420';
const destFrame = new VideoFrame(buf2, {
format: format,
codedWidth: srcFrame.codedWidth,
codedHeight: srcFrame.codedHeight,
timestamp: srcFrame.timestamp,
duration: srcFrame.duration
});
return destFrame;
}
const frameI420 = await convertNV12toI420(frameNV12);

AudioData の利用
AudioDataの仕様 https://www.w3.org/TR/webcodecs/#audiodata-interface
仕様上可能なフォーマット
- "u8",
- "s16",
- "s32",
- "f32",
- "u8-planar",
- "s16-planar",
- "s32-planar",
- "f32-planar",
WebAudioでは f32 をサポートしているので、ここでは f32(planar)を利用。
三角波のデータを生成
const SAMPLE_RATE = 8000; // 8000サンプル/秒
const SAMPLE_PER_CYCLE = SAMPLE_RATE / 500; // 500 Hz の音の一周分のサンプル数
function makeAudioData() {
const format = 'f32-planar'; // 実数の -1.0〜1.0の値をとる、チャネルごとにデータをまとめるplanar
// sampleRate need between 3000 and 768000,
const sampleRate = SAMPLE_RATE;
const frames = sampleRate * 1; // 総フレーム数。1 秒分
const channels = 1; // モノラル
const timestamp = 0;
// -- サンプルの生成 ---
const step = SAMPLE_PER_CYCLE;
const part = step/4;
const waveHeight = 0.64;
const waveStep = waveHeight / part;
const data = new Float32Array(frames);
for (let i = 0; i < frames; i += step) {
for (let j = 0; j < part; j++) { // 0 → waveHeight
data[i + j] = waveStep * j;
}
for (let j = 0; j < part; j++) { // waveHeight → 0
data[i + part + j] = waveHeight - waveStep * j;
}
for (let j = 0; j < part; j++) { // 0 → -waveHeight
data[i + part*2 + j] = -(waveStep * j);
}
for (let j = 0; j < part; j++) { // -waveHeight → 0
data[i + part*3 + j] = -(waveHeight - waveStep * j);
}
}
// --- AudioData のインスタンスを生成 ---
const audioData = new AudioData({
format: format,
sampleRate: sampleRate,
numberOfFrames: frames,
numberOfChannels: channels,
timestamp: timestamp,
data: data
});
return audioData;
}
AudioDataをWebAudioで再生
let ctx = null;
let startTime = 0;
function playAudioData(audioData) {
// get F32FloatArray
const len = audioData.allocationSize({ planeIndex: 0}) / 4;
const data = new Float32Array(len);
audioData.copyTo(data, {planeIndex: 0});
if (! ctx) {
ctx = new AudioContext();
}
if (startTime <= 0) {
startTime = ctx.currentTime; // 初めての再生時に、基準時間を覚える
}
const buffer = new AudioBuffer({
sampleRate: audioData.sampleRate,
length: audioData.allocationSize({ planeIndex: 0}) / 4, // バイト数 → 要素数
numberOfChannels: audioData.numberOfChannels,
});
buffer.copyToChannel(data, 0);
const offsetSec = audioData.timestamp / (1000*1000);
// --- 鳴らす ---
// AudioBufferSourceNode を使う
const source = ctx.createBufferSource();
source.buffer = buffer;
source.connect(ctx.destination);
// 適切なタイミングで再生
source.start(startTime + offsetSec);
}
// 基準時間をリセット
function resetStartTime() {
startTime = 0;
}

AudioEncoder / AudioDecoder
- AudioEncoder https://www.w3.org/TR/webcodecs/#audioencoder-interface
- Audio Codecs https://w3c.github.io/webcodecs/codec_registry.html#audio-codec-registry
- "flac" ... Flac
- "mp3" ... MP3
- "mp4a.*" ... AAC
- "opus" ... OPUS
- "vorbis" ... Vorbis
- "ulaw" ... u-law PCM
- "alaw" ... A-law PCM
- "pcm-*" ... Linear PCM
Chrome 110で利用可能なコーデック
- "opus" ... OK
- "mp3", "flac", "vorbis" ... Unsupported codec type
- 他 ... Unknown codec
Safari TP164には、実装されていない

Audioの Encode / Decode
AudioData生成 --> Encode --> Decode --> WebAudioで再生
処理の流れ
- AudioDataを1秒分生成
- Encoderでエンコード
- その結果をすぐにDecoderでデコード
- デコードされた結果を、WebAudioで再生する
エンコーダー/デコーダーを準備
let encoder = null;
let decoder = null;
const CODEC = 'opus'; // OK, decoded 48k (chrome)
async function prepareEncoder() {
if (!encoder) {
const selectedCodec = CODEC;
encoder = new AudioEncoder({
output: (chunk) => {
if (decoder) {
decoder.decode(chunk);
}
},
error: (err) => {
console.error(err);
}
});
await encoder.configure({
codec: selectedCodec,
numberOfChannels: 1,
sampleRate: SAMPLE_RATE,
});
}
if (!decoder) {
const selectedCodec = CODEC;
decoder = new AudioDecoder({
output: async (audioData) => {
playAudioData(audioData);
audioData.close();
},
error: (err) => {
console.error('Decode Error:', err);
}
})
await decoder.configure({
codec: selectedCodec,
numberOfChannels: 1,
sampleRate: SAMPLE_RATE,
});
}
}
エンコード&デコード、再生を実行
async function encodeAudio() {
// エンコーダー、デコーダーを準備
await prepareEncoder();
// 1秒間のオーディオデータを生成
const audioData = makeAudioData();
// 基準時間をクリア
resetStartTime();
// エンコード → デコード → 再生を開始
if (encoder) {
encoder.encode(audioData);
}
}
- 8kサンプル/秒、1秒分のAudioDataを一気にエンコード
- デコードでは20msごとに分割されたAudioDataが得られる
- その際、48kサンプル/秒(Macの場合)
- ここのブロックは20msの間はおかず、もっと短い間隔で得られる
- WebAudioで再生する場合には、きちんと再生タイミングを指定する必要がある
- 指定しないと20msよりも短い時間でどんどん再生が始まり、短い時間で終わってしまう(不正確な再生になる)

WebCodecs 202306 Updates
- 仕様: https://www.w3.org/TR/webcodecs/
- 11 May 2023
- 実装状況
- Chrome
- 114, 116 (Canary)
- AudioData ... ⚪︎
- AudioEncoder ... ⚪︎
- AudioDecoder ... ⚪︎
- EncodedAudioChunk ... ⚪︎
- VideoFrame ... ⚪︎
- VideoEncoder ... ⚪︎
- VideoDecoder ... ⚪︎
- EncodedVideoChunk ... ⚪︎
- 114, 116 (Canary)
- Safari
- 16.5 + Development Setting
- AudioData ... ×
- AudioEncoder ... ×
- AudioDecoder ... ×
- EncodedAudioChunk ... ⚪︎
- VideoFrame ... ⚪︎
- VideoEncoder ... ⚪︎
- VideoDecoder ... ⚪︎
- EncodedVideoChunk ... ⚪︎
- TP 171 + Develop - Experimental Feature
- 同上
- 16.5 + Development Setting
- Chrome

実装 Chrome 114
VideoFrame
const frame = new VideoFrame(element, {
timestamp: timestamp_in_micro_second, // タイムスタンプをμ秒で指定. required (仕様上はoptional)
duration: duration_in_micro_second, // フレームの長さをμ秒で指定. optional
});
// do something with frame
// release
frame.close();
元にできる要素
- img ... <img>
- canvas ... <canvas>
- video ... <video>
- VideoFrame
- ImageBitmap
- OffscreenCanvas(仕様上、未確認)
- HTMLOrSVGImageElement (仕様上、未確認)
元にした要素で、フォーマット (VideoPixelFormat) が異なる
元の要素 | Chrome 108/111 | Safari TP 160 |
---|---|---|
img | BGRX | RGBA |
canvas | RGBA | RGBA |
video | NV12 | I420 |

VideoEncoder / Decoder update 202306
M1 Mac (2023.06.18)
encode() → decode() 実行結果
CODEC | VideoFrameのソース | Chrome 114 / Canary 116 | Safari TP 171 |
---|---|---|---|
VP8 | image(jpeg) | 〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
VP8 | video(mp4) | 〇 2フレーム目のデコードで表示 |
✕ 緑色や崩れた描画 |
VP8 | video(カメラ映像) | 〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
VP9 profile0 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
VP9 profile0 | video(mp4) | 〇 1フレーム目のデコードで表示 |
✕ 緑色や崩れた描画 |
VP9 profile0 | video(カメラ映像) | 〇 1フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
VP9 profile2 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
✕ エラー: Not supported |
VP9 profile2 | video(mp4) | 〇 1フレーム目のデコードで表示 |
✕ エラー: Not supported |
VP9 profile2 | video(カメラ映像) | 〇 1フレーム目のデコードで表示 |
✕ エラー: Not supported |
AV1 | image(jpeg) | 〇 4フレーム目のデコードで表示 |
✕ NotSupportedError: VPx encoding initialization failed with error -1 |
AV1 | video(mp4) | 〇 4フレーム目のデコードで表示 |
✕ NotSupportedError: VPx encoding initialization failed with error -1 |
AV1 | video(カメラ映像) | 〇 4フレーム目のデコードで表示 |
✕ NotSupportedError: VPx encoding initialization failed with error -1 |
H.264 | image(jpeg) | 〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
H.264 | video(mp4) | 〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
H.264 | video(カメラ映像) | 〇 2フレーム目のデコードで表示 |
〇 1フレーム目のデコードで表示 |
- VP8 Decoded ... I420(Mac Chrome 114), I420(Mac Safari TP171)
- VP9 profile0 Decoded ... NV12(Mac Chrome 114), I420(Mac Safari TP171)
- VP9 profile2 Decoded ... null(Mac Chrome 114)
- AV1 Decoded ... I420(Mac Chrome 114)
- H.264 Decoded ... NV12(Mac Chrome 114), I420(Mac Safari TP171)
Windows 10 pro (2023.06.19)
encode() → decode() 実行結果
CODEC | VideoFrameのソース | Chrome 114 / Canary 116 |
---|---|---|
VP8 | image(jpeg) | 〇 2フレーム目のデコードで表示 |
VP8 | video(mp4) | 〇 2フレーム目のデコードで表示 |
VP8 | video(カメラ映像) | 〇 2フレーム目のデコードで表示 |
VP9 profile0 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
VP9 profile0 | video(mp4) | 〇 1フレーム目のデコードで表示 |
VP9 profile0 | video(カメラ映像) | 〇 1フレーム目のデコードで表示 |
VP9 profile2 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
VP9 profile2 | video(mp4) | 〇 1フレーム目のデコードで表示 |
VP9 profile2 | video(カメラ映像) | 〇 1フレーム目のデコードで表示 |
AV1 | image(jpeg) | 〇 4フレーム目のデコードで表示 |
AV1 | video(mp4) | 〇 4フレーム目のデコードで表示 |
AV1 | video(カメラ映像) | 〇 4フレーム目のデコードで表示 |
H.264 | image(jpeg) | 〇 1フレーム目のデコードで表示 |
H.264 | video(mp4) | 〇 1フレーム目のデコードで表示 |
H.264 | video(カメラ映像) | 〇 1フレーム目のデコードで表示 |
- VP8 Decoded ... I420(Win10 Chrome 114)
- VP9 profile0 Decoded ... NV12(Win10 Chrome 114)
- VP9 profile2 Decoded ... null(Win10 Chrome 114)
- AV1 Decoded ... I420(Win10 Chrome 114)
- H.264 Decoded ... NV12(Win10 Chrome 114)

2024.10 FireFox 131
FirefoxもWebCodecsサポート
- Video
- VideoFrame
- VideoEncoder / VideoDecorder
- Audio
- AudioData
- AudioEncoder / AudioDecorder