如何通过媒体源扩展 API 流式传输 WEBM 视频

问题描述 投票:0回答:1

我正在使用 MSE 开发视频流网站。

每个视频都转换为 FragmentedMP4 (h264,aac =>

avc1,mp4a
)

它工作得很好,但如果我想使用

webm
格式怎么办?就像 YouTube 或 Facebook,他们有时会使用它。

我想知道如何从

VP8
VP9
vorbis
编解码器获取索引(如 fmp4 中的 sidx 原子) 我使用 bento4ffmpeg 从视频和音频获取元数据 但bento4仅适用于MP4,并使用MP4BoxJS通过JavaScript在浏览器中解析索引。

我应该使用什么?

ffmpeg
或创建碎片
webm
或类似的东西,并获取索引流信息以将段附加到 MSE
SourceBuffer
并确保它应该是可查找流..

ffmpeg webm media-source
1个回答
0
投票

遗憾的是没有答案,但如果有一天有人需要 JavaScript 中的 WEBM 解析器(浏览器或 NodeJS)

注意1:建议将init+index(不是从index.start到index.end)传递给函数(ArrayBuffer)(在YouTube中

Range: bytes=${initRange.start}-${indexRange.end}
(Head〜Cues)

注2:MP4/Sidx 也有

parseMP4Segments

这些功能在 YouTube(WEBM 和 MP4)视频上进行了测试,运行良好

文档:https://www.matroska.org/technical/elements.html

const EBML = {
  HEAD: 0x1a45dfa3,
  Segment: 0x18538067,
  SeekHead: 0x114D9B74,
  SegmentInformation: 0x1549A966,
  Tracks: 0x1654AE6B,
  Cues: 0x1C53BB6B,
  CuePoint: 0xBB,
  CueTime: 0xB3,
  CueTrackPositions: 0xB7,
  CueTrack: 0xF7,
  CueClusterPosition: 0xF1,
  CueRelativePosition: 0xF0,
  CueDuration: 0xB2
};

/**
 * @param {ArrayBuffer} buffer
*/
export function parseWEBMSegments(buffer) {

  const view = new DataView(buffer);

  let SeekHeadPosition = 0, CuesPosition = 0;

  let offset = 0;

  if (view.getUint32() === EBML.HEAD) {

    offset += 4;

    offset = readSize(true);

    if (view.getUint32(offset) === EBML.Segment) {

      offset += 4;

      const end = readSize(true);

      for (; offset < end;) {

        if (view.getUint32(offset) === EBML.SeekHead) {

          SeekHeadPosition = offset;

          offset += 4;

          readValue(readSize(true));

        } else if (view.getUint32(offset) === EBML.SegmentInformation || view.getUint32(offset) === EBML.Tracks) {

          offset += 4;

          readValue(readSize(true));

        } else if (view.getUint32(offset) === EBML.Cues) {

          CuesPosition = offset;

          offset += 4;

          offset = readSize(true);

        }

      }

    }

  }

  offset = CuesPosition;

  function readSize(returnEndOffset = false) {

    const bytes = [view.getUint8(offset)];

    let checker = 0b1000_0000;
    let shifter = 0b0111_1111;

    for (let i = 0; i < 8; i++) {

      if (bytes[0] & checker) {

        bytes[0] &= shifter;

        break;

      }

      checker >>= 1;
      shifter >>= 1;

      bytes.push(null);

    }

    offset++;

    for (let i = 1; i < bytes.length; i++) {

      bytes[i] = view.getUint8(offset);

      offset++;

    }

    let size = 0;

    for (const byte of bytes) size = size << 8 | byte;

    return returnEndOffset ? Math.min(offset + size, buffer.byteLength) : size;

  }

  function readValue(end) {

    let value = 0;

    for (; offset < end;) {

      value = value << 8 | view.getUint8(offset);

      offset++;

    }

    return value;

  }

  function safeSkip() {

    offset++;

    const size = readSize();

    offset += size;

  }

  if (view.getUint32(offset) !== EBML.Cues) {

    throw new TypeError("This is not 'Cues' Element");

  }

  offset += 4;

  const end = readSize(true);

  const cues = [];

  for (; offset < end;) {

    if (view.getUint8(offset) === EBML.CuePoint) {

      offset++;
      const end = readSize(true);

      const cue = {};

      for (; offset < end;) {

        if (view.getUint8(offset) === EBML.CueTime) {

          if ("time" in cue) {

            throw new TypeError("Invalid layout, 'CueTime' found multiple times in a single 'CuePoint'");

          }

          offset++;

          cue.time = readValue(readSize(true)) / 1000;

        } else if (view.getUint8(offset) === EBML.CueTrackPositions) {

          offset++;
          const end = readSize(true);

          if (!("positions" in cue)) cue.positions = [];

          const position = {};

          for (; offset < end;) {

            if (view.getUint8(offset) === EBML.CueTrack) {

              offset++;

              position.track = readValue(readSize(true));

            } else if (view.getUint8(offset) === EBML.CueClusterPosition) {

              offset++;

              position.clusterPosition = SeekHeadPosition + readValue(readSize(true));

            } else safeSkip();

          }

          cue.positions.push(position);

        } else safeSkip();

      }

      if (!("time" in cue)) {

        throw new TypeError("Invalid layout, cannot find mandatory 'CueTime' Element inside 'CuePoint'");

      } else if (!("positions" in cue)) {

        throw new TypeError("Invalid layout, cannot find at least one mandatory 'CueTrackPositions' Element inside 'CuePoint'");

      }

      cues.push(cue);

    } else safeSkip();

  }

  return cues;

}

/**
 * @param {ArrayBuffer} buffer
*/
export function parseMP4Segments(buffer) {

  const view = new DataView(buffer);

  let offset = 0;

  let sidxEndPosition = 0;

  for (; offset < buffer.byteLength;) {

    const size = view.getUint32(offset);
    offset += 4;
    const type = view.getUint32(offset);
    offset += 4;

    if (type === 0x73696478) {

      sidxEndPosition = offset + size - 8;

      break;

    }

    offset += size - 8;

  }

  const version = view.getUint8(offset);

  offset += 8;

  const timescale = view.getUint32(offset);

  offset += 4;

  let time = 0, position = 0;

  if (version === 0) {

    time = view.getUint32(offset);

    offset += 4;

    position = sidxEndPosition + view.getUint32(offset);

    offset += 4;

  } else {

    time = Number(view.getBigUint64(offset));

    offset += 8;

    position = sidxEndPosition + Number(view.getBigUint64(offset));

    offset += 8;

  }

  offset += 2;

  const count = view.getUint16(offset);

  offset += 2;

  const segments = [];

  for (let i = 0; i < count; i++) {

    const size = view.getUint32(offset) & 0x7fffffff;
    const duration = view.getUint32(offset + 4) / timescale;

    const segment = {
      position: position,
      time: time
    };

    segments.push(segment);

    position += size;
    time += duration;

    offset += 12;

  }

  return segments;

}
© www.soinside.com 2019 - 2024. All rights reserved.