codec-helpers.js 2.6 KB
import {padStart, toHexString, toBinaryString} from './byte-helpers.js';

// https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#AV1
export const getAv1Codec = function(bytes) {
  let codec = '';
  const profile = bytes[1] >>> 3;
  const level = bytes[1] & 0x1F;
  const tier = bytes[2] >>> 7;
  const highBitDepth = (bytes[2] & 0x40) >> 6;
  const twelveBit = (bytes[2] & 0x20) >> 5;
  const monochrome = (bytes[2] & 0x10) >> 4;
  const chromaSubsamplingX = (bytes[2] & 0x08) >> 3;
  const chromaSubsamplingY = (bytes[2] & 0x04) >> 2;
  const chromaSamplePosition = bytes[2] & 0x03;

  codec += `${profile}.${padStart(level, 2, '0')}`;

  if (tier === 0) {
    codec += 'M';
  } else if (tier === 1) {
    codec += 'H';
  }

  let bitDepth;

  if (profile === 2 && highBitDepth) {
    bitDepth = twelveBit ? 12 : 10;
  } else {
    bitDepth = highBitDepth ? 10 : 8;
  }

  codec += `.${padStart(bitDepth, 2, '0')}`;

  // TODO: can we parse color range??
  codec += `.${monochrome}`;
  codec += `.${chromaSubsamplingX}${chromaSubsamplingY}${chromaSamplePosition}`;

  return codec;
};

export const getAvcCodec = function(bytes) {
  const profileId = toHexString(bytes[1]);
  const constraintFlags = toHexString(bytes[2] & 0xFC);
  const levelId = toHexString(bytes[3]);

  return `${profileId}${constraintFlags}${levelId}`;
};

export const getHvcCodec = function(bytes) {
  let codec = '';
  const profileSpace = bytes[1] >> 6;
  const profileId = bytes[1] & 0x1F;
  const tierFlag = (bytes[1] & 0x20) >> 5;

  const profileCompat = bytes.subarray(2, 6);
  const constraintIds = bytes.subarray(6, 12);
  const levelId = bytes[12];

  if (profileSpace === 1) {
    codec += 'A';
  } else if (profileSpace === 2) {
    codec += 'B';
  } else if (profileSpace === 3) {
    codec += 'C';
  }

  codec += `${profileId}.`;

  // ffmpeg does this in big endian
  let profileCompatVal = parseInt(toBinaryString(profileCompat).split('').reverse().join(''), 2);

  // apple does this in little endian...
  if (profileCompatVal > 255) {
    profileCompatVal = parseInt(toBinaryString(profileCompat), 2);
  }

  codec += `${profileCompatVal.toString(16)}.`;

  if (tierFlag === 0) {
    codec += 'L';
  } else {
    codec += 'H';
  }

  codec += levelId;

  let constraints = '';

  for (let i = 0; i < constraintIds.length; i++) {
    const v = constraintIds[i];

    if (v) {
      if (constraints) {
        constraints += '.';
      }
      constraints += v.toString(16);
    }
  }

  if (constraints) {
    codec += `.${constraints}`;
  }

  return codec;
};