chromium/ash/webui/camera_app_ui/resources/js/h264.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @file
 * H264 related utility functions referenced from
 * media/video/h264_level_limits.cc.
 */

import {assert, assertNotReached} from './assert.js';
import {Resolution} from './type.js';
import {getNumberEnumMapping} from './util.js';

export enum Profile {
  BASELINE = 66,
  MAIN = 77,
  HIGH = 100,
}

export const profileValues =
    new Set(Object.values(getNumberEnumMapping(Profile)));

/**
 * Asserts that a number is one of the value of possible h264 profile.
 */
export function assertProfile(v: number): Profile {
  assert(profileValues.has(v));
  return v;
}

const profileNames: Record<Profile, string> = {
  [Profile.BASELINE]: 'baseline',
  [Profile.MAIN]: 'main',
  [Profile.HIGH]: 'high',
};

/**
 * Gets the name of a h264 profile.
 */
export function getProfileName(profile: Profile): string {
  return profileNames[profile];
}

export enum Level {
  // Ignore unsupported lower levels.
  LV30 = 30,
  LV31 = 31,
  LV32 = 32,
  LV40 = 40,
  LV41 = 41,
  LV42 = 42,
  LV50 = 50,
  LV51 = 51,
  LV52 = 52,
  LV60 = 60,
  LV61 = 61,
  LV62 = 62,
}

export const LEVELS =
    Object.values(getNumberEnumMapping(Level)).sort((a, b) => a - b);

export interface EncoderParameters {
  profile: Profile;
  level: Level;
  bitrate: number;
}

const levelLimits = (() => {
  function limit(processRate: number, frameSize: number, mainBitrate: number) {
    return {processRate, frameSize, mainBitrate};
  }
  return {
    [Level.LV30]: limit(40500, 1620, 10000),
    [Level.LV31]: limit(108000, 3600, 14000),
    [Level.LV32]: limit(216000, 5120, 20000),
    [Level.LV40]: limit(245760, 8192, 20000),
    [Level.LV41]: limit(245760, 8192, 50000),
    [Level.LV42]: limit(522240, 8704, 50000),
    [Level.LV50]: limit(589824, 22080, 135000),
    [Level.LV51]: limit(983040, 36864, 240000),
    [Level.LV52]: limit(2073600, 36864, 240000),
    [Level.LV60]: limit(4177920, 139264, 240000),
    [Level.LV61]: limit(8355840, 139264, 480000),
    [Level.LV62]: limit(16711680, 139264, 800000),
  };
})();

/**
 * @return Returns the maximal available bitrate supported by target
 *     profile and level.
 */
export function getMaxBitrate(profile: Profile, level: Level): number {
  const {mainBitrate} = levelLimits[level];
  const kbs = (() => {
    switch (profile) {
      case Profile.BASELINE:
      case Profile.MAIN:
        return mainBitrate;
      case Profile.HIGH:
        return Math.floor(mainBitrate * 5 / 4);
      default:
        assertNotReached();
    }
  })();
  return kbs * 1000;
}

/**
 * H264 macroblock size in pixels.
 */
const MACROBLOCK_SIZE = 16;

/**
 * @return Returns whether the recording |fps| and |resolution| fit in the h264
 *     level limitation.
 */
export function checkLevelLimits(
    level: Level, fps: number, {width, height}: Resolution): boolean {
  const frameSize =
      Math.ceil(width / MACROBLOCK_SIZE) * Math.ceil(height / MACROBLOCK_SIZE);
  const processRate = frameSize * fps;
  const limit = levelLimits[level];
  return frameSize <= limit.frameSize && processRate <= limit.processRate;
}

/**
 * Gets minimal available level with respect to given profile, bitrate,
 * resolution, fps.
 */
export function getMinimalLevel(
    profile: Profile, bitrate: number, fps: number,
    resolution: Resolution): Level|null {
  for (const level of LEVELS) {
    if (checkLevelLimits(level, fps, resolution) &&
        getMaxBitrate(profile, level) >= bitrate) {
      return level;
    }
  }
  return null;
}