Open17

WebCodecsメモ202301

mganekomganeko

WebCodecsの仕様と実装状況(202212〜202301)

仕様

  • WebCodecs W3C Working Draft, 12 December 2022

  • Audio

    • AudioDecoder
    • AudioEncoder
    • EncodedAudioChunk
    • AudioData
  • Video

    • VideoDecoder
    • VideoEncoder
    • EncodedVideoChunk
    • VideoFrame
mganekomganeko

実装 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
mganekomganeko

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の場合の処理がおかしいのではないか?

mganekomganeko

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も前回のフレームの情報を保持している(ステートフルになっている)

mganekomganeko

VideoFrame と Enocoder/Decoder の組み合わせ動作

次の一連の処理を実行

  • (1) element → VideoFrame
    • フォーマット (VideoPixelFormat) は前述の通り元にしたelementによって異なる
    • Chrome / Safari TPでも異なる
  • (2) → VideoEncoder.encode() → chunk
  • (3) → VideoDecoder.decode() → VideoFrame
    • フォーマット (VideoPixelFormat) は、(1)のフォーマットによらず、常に I420になっている
      • ただし VP8 の場合のみ確認、他のCODECでは未確認
  • (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 のエンコードでクラッシュしなくなった
  • Windows 10 (x64) のChrome 111, Chrome Chanary 113でも、 VP9 profile2 のエンコードでクラッシュしなくなった
    • ただし、VP9 profile0でencodeイベントが発生しない現象は継続
    • Videoから取得したVideoFrame (NV12)の場合
mganekomganeko

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 ... 未実装
mganekomganeko

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

コード例

Generatorを準備
  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);
mganekomganeko

エンコーダー/デコーダーと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();
  }
mganekomganeko

さらに 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();
      }
    }
  }
mganekomganeko

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);
mganekomganeko

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;
  }
mganekomganeko

AudioEncoder / AudioDecoder

Chrome 110で利用可能なコーデック

  • "opus" ... OK
  • "mp3", "flac", "vorbis" ... Unsupported codec type
  • 他 ... Unknown codec

Safari TP164には、実装されていない

mganekomganeko

Audioの Encode / Decode

AudioData生成 --> Encode --> Decode --> WebAudioで再生

処理の流れ

  • AudioDataを1秒分生成
  • Encoderでエンコード
  • その結果をすぐにDecoderでデコード
  • デコードされた結果を、WebAudioで再生する

エンコーダー/デコーダーを準備

Encoder/Decoderを準備
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よりも短い時間でどんどん再生が始まり、短い時間で終わってしまう(不正確な再生になる)
mganekomganeko

WebCodecs 202306 Updates

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

実装 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
mganekomganeko

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)
mganekomganeko

2024.10 FireFox 131

FirefoxもWebCodecsサポート

  • Video
    • VideoFrame
    • VideoEncoder / VideoDecorder
  • Audio
    • AudioData
    • AudioEncoder / AudioDecorder