various plugins: improve support for complex ffmpeg inputs

This commit is contained in:
Koushik Dutta
2022-05-17 09:35:35 -07:00
parent 35d1a8e6b7
commit da8343f4e4
9 changed files with 35 additions and 75 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.0.246",
"version": "0.0.247",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.0.246",
"version": "0.0.247",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.0.246",
"version": "0.0.247",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -1,6 +1,4 @@
import { createParserRebroadcaster, ParserSession, Rebroadcaster, startParserSession } from "@scrypted/common/src/ffmpeg-rebroadcast";
import { createRawVideoParser, PIXEL_FORMAT_RGB24, StreamParser } from "@scrypted/common/src/stream-parser";
import sdk, { EventListener, EventListenerRegister, FFmpegInput, LockState, MediaStreamOptions, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceBase, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes, VideoCamera } from "@scrypted/sdk";
import sdk, { EventListener, EventListenerRegister, FFmpegInput, LockState, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceBase, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes, VideoCamera } from "@scrypted/sdk";
const { systemManager, mediaManager } = sdk;
export interface AggregateDevice extends ScryptedDeviceBase {
@@ -39,17 +37,7 @@ aggregators.set(ScryptedInterface.Lock,
function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamera {
let sessionPromise: Promise<{
session: ParserSession<"rawvideo">;
rebroadcaster: Rebroadcaster;
parsers: { rawvideo: StreamParser };
}>;
async function getVideoStreamWrapped(options: MediaStreamOptions) {
if (sessionPromise) {
console.error('session already active?');
}
async function getVideoStreamWrapped(options: RequestMediaStreamOptions) {
const args = await Promise.allSettled(devices.map(async (device) => {
const mo = await device.getVideoStream();
const buffer = await mediaManager.convertMediaObjectToBuffer(mo, ScryptedMimeTypes.FFmpegInput);
@@ -76,6 +64,8 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
const filteredInput: FFmpegInput = {
url: undefined,
container: 'rawvideo',
mediaStreamOptions: (await createVideoStreamOptions())?.[0],
inputArguments: [],
};
@@ -113,33 +103,7 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
filter.join(' '),
);
const parsers = {
rawvideo: createRawVideoParser({
size: {
width: 1920,
height: 1080,
},
pixelFormat: PIXEL_FORMAT_RGB24,
}),
};
const ret = await startParserSession(filteredInput, {
console,
parsers,
timeout: 30000,
});
let rebroadcaster = await createParserRebroadcaster(ret, 'rawvideo', {
idle: {
timeout: 30000,
callback: () => ret.kill(),
}
})
return {
session: ret,
rebroadcaster,
parsers,
};
return filteredInput;
};
const createVideoStreamOptions: () => Promise<ResponseMediaStreamOptions[]> = async () => {
@@ -148,7 +112,7 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
return [{
id: 'default',
name: 'Default',
container: 'rawvideo',
container: 'ffmpeg',
video: {},
audio: null,
}]
@@ -163,24 +127,7 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
if (devices.length === 1)
return devices[0].getVideoStream(options);
if (!sessionPromise) {
sessionPromise = getVideoStreamWrapped(options);
const ret = await sessionPromise;
ret.session.killed.finally(() => sessionPromise = undefined);
}
const ret = await sessionPromise;
const { url } = ret.rebroadcaster;
const ffmpegInput: FFmpegInput = {
url,
mediaStreamOptions: (await createVideoStreamOptions())?.[0],
container: 'rawvideo',
inputArguments: [
...(ret.parsers.rawvideo.inputArguments || []),
'-f', ret.parsers.rawvideo.container,
'-i', url,
],
}
const ffmpegInput = await getVideoStreamWrapped(options);
return mediaManager.createFFmpegMediaObject(ffmpegInput);
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.1.268",
"version": "0.1.269",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/prebuffer-mixin",
"version": "0.1.268",
"version": "0.1.269",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.1.268",
"version": "0.1.269",
"description": "Rebroadcast and Prebuffer for VideoCameras.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -108,6 +108,10 @@ class PrebufferSession {
}
}
get canPrebuffer() {
return this.advertisedMediaStreamOptions.container !== 'rawvideo' && this.advertisedMediaStreamOptions.container !== 'ffmpeg';
}
getLastH264Probe(): H264Probe {
const str = this.storage.getItem(this.lastH264ProbeKey);
if (!str) {
@@ -1189,8 +1193,8 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
let session = this.sessions.get(id);
let ffmpegInput: FFmpegInput;
if (session.advertisedMediaStreamOptions.container === 'rawvideo') {
this.console.log('Source is rawvideo. Using a direct media stream.');
if (!session.canPrebuffer) {
this.console.log('Source container can not be prebuffered. Using a direct media stream.');
session = undefined;
}
if (!session) {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/webrtc",
"version": "0.0.25",
"version": "0.0.26",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/webrtc",
"version": "0.0.25",
"version": "0.0.26",
"dependencies": {
"@koush/werift": "file:../../external/werift/packages/webrtc",
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/webrtc",
"version": "0.0.25",
"version": "0.0.26",
"scripts": {
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
"prescrypted-vscode-launch": "scrypted-webpack",

View File

@@ -223,14 +223,13 @@ export async function createRTCPeerConnectionSink(
const transcode = willTranscode
|| mediaStreamOptions?.video?.codec !== 'h264'
|| ffmpegInput.h264EncoderArguments?.length;
const width = Math.min(options?.screen?.width || 960, 1280);
if (transcode) {
const conservativeDefaultBitrate = 500000;
const bitrate = maximumCompatibilityMode ? conservativeDefaultBitrate : (ffmpegInput.destinationVideoBitrate || conservativeDefaultBitrate);
const width = Math.min(options?.screen?.width || 960, 1280);
videoArgs.push(
// this might get wonky with 4:3?
'-vf', `scale='min(${width},iw)':-2`,
// this seems to cause issues with presets i think.
// '-level:v', '4.0',
"-b:v", bitrate.toString(),
@@ -238,6 +237,16 @@ export async function createRTCPeerConnectionSink(
"-maxrate", bitrate.toString(),
'-r', '15',
);
const scaleFilter = `scale='min(${width},iw)':-2`;
let filterIndex = ffmpegInput.inputArguments.findIndex(f => f === '-vf');
if (filterIndex === -1)
filterIndex = ffmpegInput.inputArguments.findIndex(f => f === '-filter_complex');
if (filterIndex !== -1)
ffmpegInput.inputArguments[filterIndex + 1] = ffmpegInput.inputArguments[filterIndex + 1] + ` [unscaled]; [unscaled] ${scaleFilter}`;
else
videoArgs.push(scaleFilter)
if (!sessionSupportsH264High || maximumCompatibilityMode) {
// baseline profile must use libx264, not sure other encoders properly support it.
videoArgs.push(
@@ -256,7 +265,7 @@ export async function createRTCPeerConnectionSink(
}
}
else {
videoArgs.push('-vcodec', 'copy')
videoArgs.push('-vf', '-vcodec', 'copy')
}
if (ffmpegInput.h264FilterArguments)