Successful stream stress test

Need to add stream watchdog timer and accessory timeout timer
This commit is contained in:
watsonb8 2020-12-11 00:02:10 -05:00
parent 4e873edc97
commit 5cec734a09
8 changed files with 96 additions and 48 deletions

View File

@ -1,4 +1,4 @@
import { Rtsp } from "rtsp-stream/lib"; import { Rtsp, IStreamEventArgs } from "../src/rtsp/rtsp";
import { nets } from "@vladmandic/face-api"; import { nets } from "@vladmandic/face-api";
import * as faceapi from "@vladmandic/face-api"; import * as faceapi from "@vladmandic/face-api";
import canvas from "canvas"; import canvas from "canvas";
@ -37,10 +37,10 @@ const main = async () => {
const content = JSON.parse(raw); const content = JSON.parse(raw);
const matcher = faceapi.FaceMatcher.fromJSON(content); const matcher = faceapi.FaceMatcher.fromJSON(content);
rtsp.on("data", async (data: Buffer) => { rtsp.dataEvent.push(async (sender: Rtsp, args: IStreamEventArgs) => {
const input = ((await canvas.loadImage(data)) as unknown) as ImageData; const input = ((await canvas.loadImage(args.data)) as unknown) as ImageData;
const out = faceapi.createCanvasFromMedia(input); const out = faceapi.createCanvasFromMedia(input);
await saveFile("image.jpg", data); await saveFile(process.env.OUT_DIR as string, "image.jpg", args.data);
const resultsQuery = await faceapi const resultsQuery = await faceapi
.detectAllFaces(out, getFaceDetectorOptions(faceDetectionNet)) .detectAllFaces(out, getFaceDetectorOptions(faceDetectionNet))
.withFaceLandmarks() .withFaceLandmarks()
@ -52,10 +52,6 @@ const main = async () => {
} }
}); });
rtsp.on("error", (err) => {
// console.log(err);
});
rtsp.start(); rtsp.start();
}; };

View File

@ -15,12 +15,33 @@ export const getFaceDetectorOptions = (net: faceapi.NeuralNetwork<any>) => {
: new faceapi.TinyFaceDetectorOptions({ inputSize, scoreThreshold }); : new faceapi.TinyFaceDetectorOptions({ inputSize, scoreThreshold });
}; };
export function saveFile(basePath: string, fileName: string, buf: Buffer) { export function saveFile(
if (!fs.existsSync(basePath)) { basePath: string,
fs.mkdirSync(basePath); fileName: string,
} buf: Buffer
): Promise<void> {
fs.writeFileSync(path.resolve(basePath, fileName), buf, "base64"); const writeFile = (): Promise<void> => {
return new Promise((resolve, reject) => {
fs.writeFile(path.resolve(basePath, fileName), buf, "base64", (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
};
return new Promise(async (resolve, reject) => {
if (!fs.existsSync(basePath)) {
fs.mkdir(basePath, async (err) => {
if (err) {
return reject(err);
}
resolve(await writeFile());
});
} else {
resolve(await writeFile());
}
});
} }
export const delay = (ms: number): Promise<void> => { export const delay = (ms: number): Promise<void> => {

View File

@ -4,10 +4,12 @@ export interface IConfig extends PlatformConfig {
refImageDirectory: string; refImageDirectory: string;
trainedModelDirectory: string; trainedModelDirectory: string;
weightDirectory: string; weightDirectory: string;
outputDirectory: string;
trainOnStartup: boolean; trainOnStartup: boolean;
rooms: Array<IRoom>; rooms: Array<IRoom>;
detectionTimeout: number; detectionTimeout: number;
debug: boolean; debug: boolean;
writeOutput: boolean;
} }
export interface IRoom { export interface IRoom {
@ -27,6 +29,10 @@ export const isConfig = (object: any): object is IConfig => {
"refImageDirectory" in object && "refImageDirectory" in object &&
"trainedModelDirectory" in object && "trainedModelDirectory" in object &&
"weightDirectory" in object && "weightDirectory" in object &&
"outputDirectory" in object &&
"trainOnStartup" in object &&
"detectionTimeout" in object &&
"writeOutput" in object &&
"rooms" in object && "rooms" in object &&
roomsOkay roomsOkay
); );

View File

@ -104,7 +104,7 @@ export class HomeLocationPlatform implements DynamicPlatformPlugin {
this.config.rooms, this.config.rooms,
faceMatcher, faceMatcher,
this.log, this.log,
this.config.debug this.config
); );
locationMonitor.startStreams(); locationMonitor.startStreams();

View File

@ -1,7 +1,7 @@
import { API } from "homebridge"; import { API } from "homebridge";
import { PLATFORM_NAME } from "./settings"; import { PLATFORM_NAME } from "./settings";
import { HomeLocationPlatform } from "./platform"; import { HomeLocationPlatform } from "./homeLocationPlatform";
/** /**
* This method registers the platform with Homebridge * This method registers the platform with Homebridge

View File

@ -4,7 +4,7 @@ import {
PlatformAccessory, PlatformAccessory,
} from "homebridge"; } from "homebridge";
import { Monitor, IStateChangeEventArgs } from "./monitor"; import { Monitor, IStateChangeEventArgs } from "./monitor";
import { HomeLocationPlatform } from "./platform"; import { HomeLocationPlatform } from "./homeLocationPlatform";
import { IRoom } from "./config"; import { IRoom } from "./config";
/** /**

View File

@ -1,12 +1,19 @@
import { FaceMatcher } from "@vladmandic/face-api"; import { FaceMatcher } from "@vladmandic/face-api";
import { IRoom } from "./config"; import { IRoom } from "./config";
import { Rtsp, IStreamEventArgs, ICloseEventArgs } from "./rtsp"; import {
Rtsp,
IStreamEventArgs,
ICloseEventArgs,
IErrorEventArgs,
IMessageEventArgs,
} from "./rtsp/rtsp";
import canvas from "canvas"; import canvas from "canvas";
import * as faceapi from "@vladmandic/face-api"; import * as faceapi from "@vladmandic/face-api";
import { getFaceDetectorOptions, saveFile } from "./common"; import { getFaceDetectorOptions, saveFile } from "./common";
import { nets } from "@vladmandic/face-api"; import { nets } from "@vladmandic/face-api";
import { Logger } from "homebridge"; import { Logger } from "homebridge";
import { Event } from "./events"; import { Event } from "./events";
import { IConfig } from "./config";
const { Canvas, Image, ImageData } = canvas; const { Canvas, Image, ImageData } = canvas;
export type MonitorState = { [label: string]: string | null }; export type MonitorState = { [label: string]: string | null };
@ -26,7 +33,7 @@ export class Monitor {
private _rooms: Array<IRoom>, private _rooms: Array<IRoom>,
private _matcher: FaceMatcher, private _matcher: FaceMatcher,
private _logger: Logger, private _logger: Logger,
private _isDebug: boolean private _config: IConfig
) { ) {
this._stateChangedEvent = new Event(); this._stateChangedEvent = new Event();
@ -35,7 +42,7 @@ export class Monitor {
this._streamsByRoom[room.name] = [ this._streamsByRoom[room.name] = [
...room.rtspConnectionStrings.map((connectionString) => { ...room.rtspConnectionStrings.map((connectionString) => {
const rtsp = new Rtsp(connectionString, { const rtsp = new Rtsp(connectionString, {
rate: 1, rate: 0.7,
image: true, image: true,
}); });
rtsp.dataEvent.push((sender: Rtsp, args: IStreamEventArgs) => rtsp.dataEvent.push((sender: Rtsp, args: IStreamEventArgs) =>
@ -44,6 +51,15 @@ export class Monitor {
rtsp.closeEvent.push((sender: Rtsp, args: ICloseEventArgs) => rtsp.closeEvent.push((sender: Rtsp, args: ICloseEventArgs) =>
this.onExit(connectionString, args) this.onExit(connectionString, args)
); );
rtsp.errorEvent.push((sender: Rtsp, args: IErrorEventArgs) =>
this.onError(args, connectionString)
);
if (this._config.debug) {
rtsp.messageEvent.push((sender: Rtsp, args: IMessageEventArgs) => {
this._logger.info(`[${connectionString}] ${args.message}`);
});
}
return rtsp; return rtsp;
}), }),
]; ];
@ -116,25 +132,10 @@ export class Monitor {
.detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet)) .detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet))
.withFaceLandmarks() .withFaceLandmarks()
.withFaceDescriptors(); .withFaceDescriptors();
if (this._isDebug) {
switch (room) { //Write to output image
case "Kitchen": { if (this._config.writeOutput) {
saveFile( await saveFile(this._config.outputDirectory, room + ".jpg", args.data);
"/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out",
"Kitchen.jpg",
args.data
);
break;
}
case "LivingRoom": {
saveFile(
"/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/out",
"LivingRoom.jpg",
args.data
);
break;
}
}
} }
for (const res of resultsQuery) { for (const res of resultsQuery) {
@ -151,8 +152,8 @@ export class Monitor {
} }
}; };
private onError = (error: string, streamName: string) => { private onError = (args: IErrorEventArgs, streamName: string) => {
this._logger.info(`[${streamName}] ${error}`); this._logger.info(`[${streamName}] ${args.message}`);
}; };
private onExit = (streamName: string, args: ICloseEventArgs) => { private onExit = (streamName: string, args: ICloseEventArgs) => {
this._logger.info(`[${streamName}] Stream has exited: ${args.message}`); this._logger.info(`[${streamName}] Stream has exited: ${args.message}`);

View File

@ -18,6 +18,10 @@ export interface IErrorEventArgs {
err?: Error; err?: Error;
} }
export interface IMessageEventArgs {
message: string;
}
export class Rtsp { export class Rtsp {
private _connecteionString: string; private _connecteionString: string;
private _childProcess: ChildProcess | undefined; private _childProcess: ChildProcess | undefined;
@ -29,6 +33,7 @@ export class Rtsp {
private _dataEvent: Event<this, IStreamEventArgs>; private _dataEvent: Event<this, IStreamEventArgs>;
private _closeEvent: Event<this, ICloseEventArgs>; private _closeEvent: Event<this, ICloseEventArgs>;
private _errorEvent: Event<this, IErrorEventArgs>; private _errorEvent: Event<this, IErrorEventArgs>;
private _messageEvent: Event<this, IMessageEventArgs>;
constructor(connectionString: string, options: IOptions) { constructor(connectionString: string, options: IOptions) {
this._started = false; this._started = false;
@ -40,6 +45,7 @@ export class Rtsp {
this._dataEvent = new Event(); this._dataEvent = new Event();
this._closeEvent = new Event(); this._closeEvent = new Event();
this._errorEvent = new Event(); this._errorEvent = new Event();
this._messageEvent = new Event();
this.onData = this.onData.bind(this); this.onData = this.onData.bind(this);
} }
@ -56,6 +62,10 @@ export class Rtsp {
return this._dataEvent; return this._dataEvent;
} }
public get messageEvent(): Event<this, IMessageEventArgs> {
return this._messageEvent;
}
public get closeEvent(): Event<this, ICloseEventArgs> { public get closeEvent(): Event<this, ICloseEventArgs> {
return this._closeEvent; return this._closeEvent;
} }
@ -68,6 +78,7 @@ export class Rtsp {
const argStrings = [ const argStrings = [
`-i ${this._connecteionString}`, `-i ${this._connecteionString}`,
`-r ${this._options.rate ?? 10}`, `-r ${this._options.rate ?? 10}`,
`-vf mpdecimate,setpts=N/FRAME_RATE/TB`,
this._options.image this._options.image
? `-f image2` ? `-f image2`
: `-codec:v ${this._options.codec ?? "libx264"}`, : `-codec:v ${this._options.codec ?? "libx264"}`,
@ -81,19 +92,32 @@ export class Rtsp {
} }
this._childProcess.stdout?.on("data", this.onData); this._childProcess.stdout?.on("data", this.onData);
this._childProcess.stdout?.on("error", (err) =>
console.log("And error occurred" + err)
);
this._childProcess.stdout?.on("close", () => console.log("Stream closed"));
this._childProcess.stdout?.on("end", () => console.log("Stream ended"));
//Only register this event if there are subscribers
if (this._childProcess.stderr && this._messageEvent.length > 0) {
this._childProcess.stderr.on("data", this.onMessage);
}
this._childProcess.on("close", (code: number, signal: NodeJS.Signals) =>
this._closeEvent.fire(this, {
message: "FFmpeg exited with code: " + code + " and signal: " + signal,
})
);
this._childProcess.on("exit", (code: number, signal: NodeJS.Signals) => this._childProcess.on("exit", (code: number, signal: NodeJS.Signals) =>
this._closeEvent.fire(this, { this._closeEvent.fire(this, {
message: "FFmpeg exited with code: " + code + " and signal: " + signal, message: "FFmpeg exited with code: " + code + " and signal: " + signal,
}) })
); );
this._childProcess.on("error", (error: Error) => this._childProcess.on("error", (error: Error) =>
this._errorEvent.fire(this, { err: error }) this._errorEvent.fire(this, { err: error })
); );
//Only register this event if there are subscribers
if (this._childProcess.stderr && this._errorEvent.length > 0) {
this._childProcess.stderr.on("data", this.onStdErrorData);
}
} }
public close(): void { public close(): void {
@ -113,7 +137,7 @@ export class Rtsp {
return this._childProcess ? this._childProcess.stdin : null; return this._childProcess ? this._childProcess.stdin : null;
} }
private onStdErrorData = (data: any): void => { private onMessage = (data: any): void => {
if (!this._started) { if (!this._started) {
this._started = true; this._started = true;
} }
@ -125,7 +149,7 @@ export class Rtsp {
msg += `${line}\n`; msg += `${line}\n`;
}); });
this._errorEvent.fire(this, { message: msg }); this._messageEvent.fire(this, { message: msg });
}; };
private onData(data: Buffer): void { private onData(data: Buffer): void {