From a64cb494a29772cf4357de17a2db187cb12ed823 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 14 Oct 2021 23:26:18 -0700 Subject: [PATCH] homekit: ffmpeg encoder suggestions --- common/src/ffmpeg-hardware-acceleration.ts | 124 +++++++++++++++++++++ plugins/homekit/package-lock.json | 4 +- plugins/homekit/package.json | 2 +- plugins/homekit/src/camera-mixin.ts | 21 +++- 4 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 common/src/ffmpeg-hardware-acceleration.ts diff --git a/common/src/ffmpeg-hardware-acceleration.ts b/common/src/ffmpeg-hardware-acceleration.ts new file mode 100644 index 000000000..518fcb31b --- /dev/null +++ b/common/src/ffmpeg-hardware-acceleration.ts @@ -0,0 +1,124 @@ +import os from 'os'; +import fs from 'fs'; +import { CodeActionCommand } from 'typescript'; + +const PI_MODEL_NO = [ + // https://www.raspberrypi.org/documentation/hardware/raspberrypi/ + 'BCM2708', + 'BCM2709', + 'BCM2710', + 'BCM2835', // Raspberry Pi 1 and Zero + 'BCM2836', // Raspberry Pi 2 + 'BCM2837', // Raspberry Pi 3 (and later Raspberry Pi 2) + 'BCM2837B0', // Raspberry Pi 3B+ and 3A+ + 'BCM2711' // Raspberry Pi 4B +]; + +function isPi(model: string) { + return PI_MODEL_NO.indexOf(model) > -1; +} + +export function isRaspberryPi() { + let cpuInfo: string; + try { + cpuInfo = require('realfs').readFileSync('/proc/cpuinfo', { encoding: 'utf8' }); + } + catch (e) { + // if this fails, this is probably not a pi + return false; + } + + const model = cpuInfo + .split('\n') + .map(line => line.replace(/\t/g, '')) + .filter(line => line.length > 0) + .map(line => line.split(':')) + .map(pair => pair.map(entry => entry.trim())) + .filter(pair => pair[0] === 'Hardware') + + if (!model || model.length == 0) { + return false; + } + + const number = model[0][1]; + return isPi(number); +} + +export type CodecArgs = { [type: string]: string[] }; + +export function getH264DecoderArgs(): CodecArgs { + if (isRaspberryPi()) { + return { + 'Raspberry Pi': ['-c:v', 'h264_mmal'], + 'Video4Linux': ['-c:v', 'h264_v4l2m2m'], + } + } + else if (os.platform() === 'darwin') { + return { + 'VideoToolbox': ['-hwaccel', 'videotoolbox'] + } + } + + const ret: CodecArgs = { + 'Nvidia CUDA': [ + '-vsync', '0', '–hwaccel', 'cuda', '-hwaccel_output_format', 'cuda', + ], + 'Nvidia CUVID:': [ + '-vsync', '0', '–hwaccel', 'cuvid', '-c:v', 'h264_cuvid', + ], + }; + + if (os.platform() === 'linux') { + ret['Video4Linux'] = [ + '-c:v', 'h264_v4l2m2m', + ]; + ret[''] + } + else if (os.platform() === 'win32') { + ret['Intel QuickSync'] = [ + '-c:v', 'h264_qsv', + ]; + } + else { + return {}; + } + + return ret; +} + +export function getH264EncoderArgs() { + const encoders: { [type: string]: string } = {}; + if (isRaspberryPi()) { + encoders['Raspberry Pi'] = 'h264_omx'; + encoders['Video4Linux'] = 'h264_v4l2m2m'; + } + else if (os.platform() === 'darwin') { + encoders['VideoToolbox'] = 'h264_videotoolbox'; + } + else if (os.platform() === 'win32') { + // h264_amf h264_nvenc h264_qsv + encoders['Intel QuickSync'] = 'h264_qsv'; + encoders['AMD'] = 'h264_amf'; + encoders['Nvidia'] = 'h264_nvenc'; + } + else if (os.platform() === 'linux') { + // h264_v4l2m2m h264_vaapi nvenc_h264 + encoders['Video4Linux'] = 'h264_v4l2m2m'; + encoders['VAAPI'] = 'h264_vaapi'; + encoders['Nvidia'] = 'nvenc_h264'; + } + else { + return {}; + } + + const encoderArgs: CodecArgs = {}; + for (const [name, encoder] of Object.entries(encoders)) { + encoderArgs[name] = [ + '-c:v', + encoder, + '-b:v', + '${request.video.max_bit_rate * 10}k', + ] + } + return encoderArgs; +} diff --git a/plugins/homekit/package-lock.json b/plugins/homekit/package-lock.json index 1a20d4ada..34e987cc8 100644 --- a/plugins/homekit/package-lock.json +++ b/plugins/homekit/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/homekit", - "version": "0.0.83", + "version": "0.0.84", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/homekit", - "version": "0.0.83", + "version": "0.0.84", "dependencies": { "hap-nodejs": "file:../HAP-NodeJS", "lodash": "^4.17.21", diff --git a/plugins/homekit/package.json b/plugins/homekit/package.json index c1665fb53..0b42d71f5 100644 --- a/plugins/homekit/package.json +++ b/plugins/homekit/package.json @@ -40,5 +40,5 @@ "@types/qrcode": "^1.4.1", "@types/url-parse": "^1.4.3" }, - "version": "0.0.83" + "version": "0.0.84" } diff --git a/plugins/homekit/src/camera-mixin.ts b/plugins/homekit/src/camera-mixin.ts index 101517f61..2e5409bbc 100644 --- a/plugins/homekit/src/camera-mixin.ts +++ b/plugins/homekit/src/camera-mixin.ts @@ -1,6 +1,6 @@ import sdk, { VideoCamera, Settings, Setting, ScryptedInterface, ObjectDetector, SettingValue, MediaStreamOptions } from "@scrypted/sdk"; import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin"; -import { ContactSensor } from "../HAP-NodeJS/src/lib/definitions"; +import { getH264DecoderArgs, getH264EncoderArgs } from "../../../common/src/ffmpeg-hardware-acceleration"; const { log, systemManager, deviceManager } = sdk; @@ -84,12 +84,17 @@ export class CameraMixin extends SettingsMixinDeviceBase implements Setting } if (showTranscodeArgs) { + const encoderArgs = getH264EncoderArgs(); + const decoderArgs = getH264DecoderArgs(); + settings.push({ title: 'Video Decoder Arguments', key: "videoDecoderArguments", value: this.storage.getItem('videoDecoderArguments'), description: 'FFmpeg arguments used to decode input video.', placeholder: '-hwaccel auto', + choices: Object.keys(decoderArgs), + combobox: true, }); settings.push({ title: 'H264 Encoder Arguments', @@ -97,6 +102,8 @@ export class CameraMixin extends SettingsMixinDeviceBase implements Setting value: this.storage.getItem('h264EncoderArguments'), description: 'FFmpeg arguments used to encode h264 video.', placeholder: '-vcodec h264_omx', + choices: Object.keys(encoderArgs), + combobox: true, }); } @@ -153,12 +160,24 @@ export class CameraMixin extends SettingsMixinDeviceBase implements Setting } async putMixinSetting(key: string, value: SettingValue) { + if (key === 'videoDecoderArguments') { + const decoderArgs = getH264DecoderArgs(); + value = decoderArgs[value.toString()]?.join(' ') || value; + } + + if (key === 'h264EncoderArguments') { + const encoderArgs = getH264EncoderArgs(); + const substitute = encoderArgs[value.toString()]?.join(' '); + value = substitute ? `\`${substitute}\`` : value; + } + if (key === 'objectDetectionContactSensors') { this.storage.setItem(key, JSON.stringify(value)); } else { this.storage.setItem(key, value?.toString()); } + if (key === 'detectAudio' || key === 'linkedMotionSensor' || key === 'objectDetectionContactSensors') { log.a(`You must reload the HomeKit plugin for the changes to ${this.name} to take effect.`); }