如何从 VideoFrame 创建视频 srcObject?

How to create video srcObject from VideoFrame?

我现在正在学习webcodecs,看到的东西如下:

所以我想知道它是否可以在包含多张图片的视频元素上播放视频。我尝试了很多次,但仍然无法正常工作。 我从图片创建 videoFrame,然后使用 MediaStreamTrackGenerator 创建媒体轨道。但是调用play()时视频显示为黑色。

这是我的代码:

 const test = async () => {
    const imgSrcList = [
      'https://gw.alicdn.com/imgextra/i4/O1CN01CeTlwJ1Pji9Pu6KW6_!!6000000001877-2-tps-62-66.png',
      'https://gw.alicdn.com/imgextra/i3/O1CN01h7tWZr1ZiTEk1K02I_!!6000000003228-2-tps-62-66.png',
      'https://gw.alicdn.com/imgextra/i4/O1CN01CSwWiA1xflg5TnI9b_!!6000000006471-2-tps-62-66.png',
    ];
    const imgEleList: HTMLImageElement[] = [];

    await Promise.all(
      imgSrcList.map((src, index) => {
        return new Promise((resolve) => {
          let img = new Image();
          img.src = src;
          img.crossOrigin = 'anonymous';
          img.onload = () => {
            imgEleList[index] = img;
            resolve(true);
          };
        });
      }),
    );

    const trackGenerator = new MediaStreamTrackGenerator({ kind: 'video' });
    const writer = trackGenerator.writable.getWriter();
    await writer.ready;

    for (let i = 0; i < imgEleList.length; i++) {
      const frame = new VideoFrame(imgEleList[i], {
        duration: 500,
        timestamp: i * 500,
        alpha: 'keep',
      });
      await writer.write(frame);
      frame.close();
    }

    // Call ready again to ensure that all chunks are written before closing the writer.
    await writer.ready.then(() => {
      writer.close();
    });

    const stream = new MediaStream();
    stream.addTrack(trackGenerator);

    const videoEle = document.getElementById('video') as HTMLVideoElement;
    videoEle.onloadedmetadata = () => {
      videoEle.play();
    };
    videoEle.srcObject = stream;
  };

谢谢!

免责声明:
我不是该领域的专家,这是我第一次以这种方式使用 API。规范和当前的实现似乎不匹配,并且很可能在不久的将来事情会发生变化。因此,请尽可能多地接受这个答案,它只有经过试验才能支持。



您的实施中有几处似乎是错误的:

  • durationtimestamp 设置为 micro 秒,即 1/1,000,000 秒。你的 500 持续时间只有半毫秒,大约是 2000FPS,你的三张图片将在 1.5 毫秒内全部显示。你会想要改变它。
  • 在当前 Chrome 的实现中,您需要指定 VideoFrameInit 字典的 displayWidthdisplayHeight 成员(尽管如果我正确阅读规范应该默认为源图像的宽度和高度)。

然后有些事情我不太确定,但你似乎不能 batch-write 很多帧。 timestamp 字段在这种情况下似乎有点无用(即使它需要存在,即使具有无意义的值)。再一次,规格已经改变,所以很难知道它是否是一个实现错误,或者它是否应该像那样工作,但无论如何它是这样的(除非我也错过了什么)。

因此,要解决该限制,您需要定期写入流并在您希望它们出现时附加帧。

这是一个这样的例子,当我们希望它被呈现时,通过向 WritableStream 写入一个新的框架,试图让它接近您自己的实现。

const test = async() => {
  const imgSrcList = [
    'https://gw.alicdn.com/imgextra/i4/O1CN01CeTlwJ1Pji9Pu6KW6_!!6000000001877-2-tps-62-66.png',
    'https://gw.alicdn.com/imgextra/i3/O1CN01h7tWZr1ZiTEk1K02I_!!6000000003228-2-tps-62-66.png',
    'https://gw.alicdn.com/imgextra/i4/O1CN01CSwWiA1xflg5TnI9b_!!6000000006471-2-tps-62-66.png',
  ];
  // rewrote this part to use ImageBitmaps,
  // using HTMLImageElement works too
  // but it's less efficient
  const imgEleList = await Promise.all(
    imgSrcList.map((src) => fetch(src)
      .then(resp => resp.ok && resp.blob())
      .then(createImageBitmap)
    )
  );
  const trackGenerator = new MediaStreamTrackGenerator({
    kind: 'video'
  });

  const duration = 1000 * 1000; // in µs (1/1,000,000s)
  let i = 0;
  const presentFrame = async() => {
    i++;
    const writer = trackGenerator.writable.getWriter();
    const img = imgEleList[i % imgEleList.length];
    await writer.ready;
    const frame = new VideoFrame(img, {
      duration, // value doesn't mean much, but required
      timestamp: i * duration, // ditto
      alpha: 'keep',
      displayWidth: img.width * 2, // required
      displayHeight: img.height * 2, // required
    });
    await writer.write(frame);
    frame.close();
    await writer.ready;
    // unlock our Writable so we can write again at next frame
    writer.releaseLock();
    setTimeout(presentFrame, duration / 1000);
  }
  presentFrame();
  const stream = new MediaStream();
  stream.addTrack(trackGenerator);

  const videoEle = document.getElementById('video');
  videoEle.srcObject = stream;
};
test().catch(console.error)
<video id=video controls autoplay muted></video>