chromium/ash/webui/camera_app_ui/resources/js/device/camera3_device_info.ts

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

import {DeviceOperator} from '../mojo/device_operator.js';
import {
  Facing,
  FpsRangeList,
  MaxFpsInfo,
  Resolution,
  ResolutionList,
  VideoConfig,
} from '../type.js';

/**
 * Groups resolutions with same ratio into same list.
 *
 * @param resolutions Resolutions to be grouped.
 * @return Ratio as key, all resolutions with that ratio as value.
 */
function groupResolutionRatio(resolutions: Resolution[]):
    Map<number, Resolution[]> {
  const result = new Map<number, ResolutionList>();
  for (const r of resolutions) {
    const ratio = r.aspectRatio;
    const ratios = result.get(ratio) ?? [];
    ratios.push(r);
    result.set(ratio, ratios);
  }
  return result;
}

export type CapturePreviewPairs =
    Array<{captureResolutions: Resolution[], previewResolutions: Resolution[]}>;

/**
 * Video device information queried from HALv3 mojo private API.
 */
export class Camera3DeviceInfo {
  readonly deviceId: string;

  readonly videoResolutions: ResolutionList = [];

  readonly videoMaxFps: MaxFpsInfo = {};

  readonly photoPreviewPairs: CapturePreviewPairs;

  readonly videoPreviewPairs: CapturePreviewPairs;

  /**
   * @param deviceInfo Information of the video device.
   * @param facing Camera facing of the video device.
   * @param photoResolutions Supported available photo resolutions
   *     of the video device.
   * @param videoResolutionFpses Supported available video
   *     resolutions and maximal capture fps of the video device.
   * @param fpsRanges Supported fps ranges of the video device.
   * @param builtinPTZSupport Is PTZ controls supported by the camera.
   */
  constructor(
      deviceInfo: MediaDeviceInfo,
      readonly facing: Facing,
      readonly photoResolutions: ResolutionList,
      videoResolutionFpses: VideoConfig[],
      readonly fpsRanges: FpsRangeList,
      readonly builtinPTZSupport: boolean,
  ) {
    this.deviceId = deviceInfo.deviceId;
    // If all fps supported by the camera is lower than 24, use the maximum
    // supported fps.
    const maxSupportedFps =
        Math.max(...videoResolutionFpses.map(({maxFps}) => maxFps));
    for (const {width, height, maxFps} of videoResolutionFpses) {
      if (maxFps < 24 && maxFps !== maxSupportedFps) {
        continue;
      }
      const r = new Resolution(width, height);
      this.videoResolutions.push(r);
      this.videoMaxFps[r.toString()] = maxFps;
    }
    this.photoPreviewPairs =
        this.pairedPreviewCaptureResolutions(this.photoResolutions);
    this.videoPreviewPairs =
        this.pairedPreviewCaptureResolutions(this.videoResolutions);
  }

  /**
   * @return The constant fps supported by this video resolution.
   */
  getConstFpses(videoResolution: Resolution): number[] {
    const deviceConstFpses =
        this.fpsRanges.filter(({minFps, maxFps}) => minFps === maxFps)
            .map(({minFps}) => minFps);
    const resolutionMaxFps = this.videoMaxFps[videoResolution.toString()];
    return deviceConstFpses.filter((constFps) => constFps <= resolutionMaxFps);
  }

  private pairedPreviewCaptureResolutions(captureRs: Resolution[]):
      CapturePreviewPairs {
    // Filters out preview resolution greater than 1920x1080 and 1600x1200 for
    // preventing performance issue.
    const previewRs = this.videoResolutions.filter(
        ({width, height}) => width <= 1920 && height <= 1200);
    const previewRatios = groupResolutionRatio(previewRs);
    const captureRatios = groupResolutionRatio(captureRs);
    // Pairing preview and capture resolution with same aspect ratio.
    const pairedResolutions: CapturePreviewPairs = [];
    for (const [ratio, captureResolutions] of captureRatios) {
      const previewResolutions = previewRatios.get(ratio);
      if (previewResolutions === undefined) {
        continue;
      }
      pairedResolutions.push({captureResolutions, previewResolutions});
    }
    return pairedResolutions;
  }

  /**
   * Creates a Camera3DeviceInfo by the given device info and the mojo device
   *     operator.
   *
   * @param deviceInfo Given device info.
   * @param videoConfigFilter Filters the available video capability exposed by
   *     device.
   * @throws Thrown when the device operation is not supported.
   */
  static async create(
      deviceInfo: MediaDeviceInfo,
      videoConfigFilter: (videoConfig: VideoConfig) => boolean):
      Promise<Camera3DeviceInfo> {
    const deviceId = deviceInfo.deviceId;

    const deviceOperator = DeviceOperator.getInstance();
    if (deviceOperator === null) {
      throw new Error('Device operation is not supported');
    }
    const facing = await deviceOperator.getCameraFacing(deviceId);
    // Check if the camera has PTZ support, not the PTZ support through stream
    // manipulators, by checking with USB HAL vendor tags.
    const builtinPTZSupport =
        !(await deviceOperator.isDigitalZoomSupported(deviceId)) &&
        ((await deviceOperator.getPanDefault(deviceId)) !== undefined ||
         (await deviceOperator.getTiltDefault(deviceId)) !== undefined ||
         (await deviceOperator.getZoomDefault(deviceId)) !== undefined);
    const photoResolution = await deviceOperator.getPhotoResolutions(deviceId);
    const videoConfigs = await deviceOperator.getVideoConfigs(deviceId);
    const filteredVideoConfigs = videoConfigs.filter(videoConfigFilter);
    const supportedFpsRanges =
        await deviceOperator.getSupportedFpsRanges(deviceId);

    return new Camera3DeviceInfo(
        deviceInfo, facing, photoResolution, filteredVideoConfigs,
        supportedFpsRanges, builtinPTZSupport);
  }
}