From ab70cce1b5971f5ec05d924970d302b70ccb8564 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 11 Oct 2023 12:53:24 -0700 Subject: [PATCH] reolink: fix bug where rtmp does not support 4k --- plugins/reolink/package-lock.json | 4 +-- plugins/reolink/package.json | 2 +- plugins/reolink/src/main.ts | 44 ++++++++++++++++++++++++++++-- plugins/reolink/src/reolink-api.ts | 37 +++++++++++++++++++++++-- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/plugins/reolink/package-lock.json b/plugins/reolink/package-lock.json index 88d7f11f5..f11b880ff 100644 --- a/plugins/reolink/package-lock.json +++ b/plugins/reolink/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/reolink", - "version": "0.0.33", + "version": "0.0.34", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/reolink", - "version": "0.0.33", + "version": "0.0.34", "license": "Apache", "dependencies": { "@koush/axios-digest-auth": "^0.8.5", diff --git a/plugins/reolink/package.json b/plugins/reolink/package.json index ed08794fa..947058a0d 100644 --- a/plugins/reolink/package.json +++ b/plugins/reolink/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/reolink", - "version": "0.0.33", + "version": "0.0.34", "description": "Reolink Plugin for Scrypted", "author": "Scrypted", "license": "Apache", diff --git a/plugins/reolink/src/main.ts b/plugins/reolink/src/main.ts index fae071dea..ceb765b19 100644 --- a/plugins/reolink/src/main.ts +++ b/plugins/reolink/src/main.ts @@ -6,12 +6,13 @@ import { Destroyable, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } fro import { OnvifCameraAPI, connectCameraAPI } from './onvif-api'; import { listenEvents } from './onvif-events'; import { OnvifIntercom } from './onvif-intercom'; -import { ReolinkCameraClient } from './reolink-api'; +import { Enc, ReolinkCameraClient } from './reolink-api'; class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom { client: ReolinkCameraClient; onvifClient: OnvifCameraAPI; onvifIntercom = new OnvifIntercom(this); + videoStreamOptions: Promise; storageSettings = new StorageSettings(this, { doorbell: { @@ -33,7 +34,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom this.updateDeviceInfo(); this.updateDevice(); } - + async startIntercom(media: MediaObject): Promise { if (!this.onvifIntercom.url) { const client = await this.getOnvifClient(); @@ -162,8 +163,26 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom } async getConstructedVideoStreamOptions(): Promise { + this.videoStreamOptions ||= this.getConstructedVideoStreamOptionsInternal().catch(e => { + this.constructedVideoStreamOptions = undefined; + throw e; + }); + + return this.videoStreamOptions; + } + + async getConstructedVideoStreamOptionsInternal(): Promise { const ret: UrlMediaStreamOptions[] = []; + let encoderConfig: Enc; + try { + const client = this.getClient(); + encoderConfig = await client.getEncoderConfiguration(); + } + catch (e) { + this.console.error("Codec query failed. Falling back to known defaults.", e); + } + const rtmpPreviews = [ `main.bcs`, `ext.bcs`, @@ -184,6 +203,8 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom } // rough guesses for rebroadcast stream selection. + const rtmpMainIndex = 0; + const rtmpMain = ret[rtmpMainIndex]; ret[0].container = 'rtmp'; ret[0].video = { width: 2560, @@ -219,6 +240,8 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom } // rough guesses for h264 + const rtspMainIndex = 3; + const rtspMain = ret[rtspMainIndex]; ret[3].container = 'rtsp'; ret[3].video = { codec: 'h264', @@ -239,6 +262,23 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom height: 672, } + if (encoderConfig) { + const { mainStream } = encoderConfig; + if (mainStream?.width && mainStream?.height) { + rtmpMain.video.width = mainStream.width; + rtmpMain.video.height = mainStream.height; + rtspMain.video.width = mainStream.width; + rtspMain.video.height = mainStream.height; + // 4k h265 rtmp is seemingly nonfunctional, but rtsp works. swap them so there is a functional stream. + if (mainStream.vType === 'h265' || mainStream.vType === 'hevc') { + this.console.warn('Detected h265. Change the camera configuration to use 2k mode to force h264. https://docs.scrypted.app/camera-preparation.html#h-264-video-codec') + rtmpMain.video.codec = 'h265'; + rtspMain.video.codec = 'h265'; + ret[rtmpMainIndex] = rtspMain; + ret[rtspMainIndex] = rtmpMain; + } + } + } return ret; } diff --git a/plugins/reolink/src/reolink-api.ts b/plugins/reolink/src/reolink-api.ts index 7c32788ac..d1b4c548e 100644 --- a/plugins/reolink/src/reolink-api.ts +++ b/plugins/reolink/src/reolink-api.ts @@ -1,8 +1,25 @@ -import axios from 'axios'; import AxiosDigestAuth from "@koush/axios-digest-auth"; -import https from 'https'; import { getMotionState, reolinkHttpsAgent } from './probe'; +export interface Enc { + audio: number; + channel: number; + mainStream: Stream; + subStream: Stream; +} + +export interface Stream { + bitRate: number; + frameRate: number; + gop: number; + height: number; + profile: string; + size: string; + vType: string; + width: number; +} + + export class ReolinkCameraClient { digestAuth: AxiosDigestAuth; @@ -77,4 +94,20 @@ export class ReolinkCameraClient { return Buffer.from(response.data); } + + async getEncoderConfiguration(): Promise { + const url = new URL(`http://${this.host}/api.cgi`); + const params = url.searchParams; + params.set('cmd', 'GetEnc'); + // is channel used on this call? + params.set('channel', this.channelId.toString()); + params.set('user', this.username); + params.set('password', this.password); + const response = await this.digestAuth.request({ + url: url.toString(), + httpsAgent: reolinkHttpsAgent, + }); + + return response.data?.[0]?.value?.Enc; + } }