cameras: auto detect two way audio

This commit is contained in:
Koushik Dutta
2023-02-27 13:19:32 -08:00
parent ab42ccd889
commit e630589489
13 changed files with 89 additions and 19 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.117",
"version": "0.0.118",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.117",
"version": "0.0.118",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.117",
"version": "0.0.118",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -32,6 +32,16 @@ export class AmcrestCameraClient {
});
}
async checkTwoWayAudio() {
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'text',
url: `http://${this.ip}/cgi-bin/devAudioOutput.cgi?action=getCollect`,
});
return (response.data as string).includes('result=1');
}
// appAutoStart=true
// deviceType=IP4M-1041B
// hardwareVersion=1.00

View File

@@ -545,9 +545,10 @@ class AmcrestProvider extends RtspProvider {
const username = settings.username?.toString();
const password = settings.password?.toString();
const skipValidate = settings.skipValidate === 'true';
let twoWayAudio: string;
if (!skipValidate) {
const api = new AmcrestCameraClient(httpAddress, username, password, this.console);
try {
const api = new AmcrestCameraClient(httpAddress, username, password, this.console);
const deviceInfo = await api.getDeviceInfo();
settings.newCamera = deviceInfo.deviceType;
@@ -558,6 +559,16 @@ class AmcrestProvider extends RtspProvider {
this.console.error('Error adding Amcrest camera', e);
throw e;
}
try {
if (await api.checkTwoWayAudio()) {
// onvif seems to work better than Amcrest, except for AD110.
twoWayAudio = 'ONVIF';
}
}
catch (e) {
this.console.warn('Error probing two way audio', e);
}
}
settings.newCamera ||= 'Hikvision Camera';
@@ -569,6 +580,8 @@ class AmcrestProvider extends RtspProvider {
device.putSetting('password', password);
device.setIPAddress(settings.ip?.toString());
device.setHttpPortOverride(settings.httpPort?.toString());
if (twoWayAudio)
device.putSetting('twoWayAudio', twoWayAudio);
return nativeId;
}

View File

@@ -208,6 +208,7 @@ export abstract class CameraProviderBase<T extends ResponseMediaStreamOptions> e
name,
interfaces,
type: type || ScryptedDeviceType.Camera,
info: deviceManager.getNativeIds().includes(nativeId) ? deviceManager.getDeviceState(nativeId)?.info : undefined,
});
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/hikvision",
"version": "0.0.123",
"version": "0.0.124",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/hikvision",
"version": "0.0.123",
"version": "0.0.124",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/hikvision",
"version": "0.0.123",
"version": "0.0.124",
"description": "Hikvision Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -43,6 +43,17 @@ export class HikvisionCameraAPI {
return getDeviceInfo(this.digestAuth, this.ip);
}
async checkTwoWayAudio() {
const response = await this.digestAuth.request({
httpsAgent: hikvisionHttpsAgent,
method: "GET",
responseType: 'text',
url: `http://${this.ip}/ISAPI/System/TwoWayAudio/channels`,
});
return (response.data as string).includes('Speaker');
}
async checkDeviceModel(): Promise<string> {
if (!this.deviceModel) {
this.deviceModel = this.getDeviceInfo().then(d => d.deviceModel).catch(e => {

View File

@@ -1,13 +1,12 @@
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
import { readLength } from '@scrypted/common/src/read-stream';
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoStreamOptions } from "@scrypted/sdk";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting } from "@scrypted/sdk";
import child_process, { ChildProcess } from 'child_process';
import { PassThrough, Readable } from "stream";
import { sleep } from "../../../common/src/sleep";
import xml2js from 'xml2js';
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
import { RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { getChannel, HikvisionCameraAPI, HikvisionCameraEvent } from "./hikvision-camera-api";
import xml2js from 'xml2js';
import { HikvisionCameraAPI, HikvisionCameraEvent } from "./hikvision-camera-api";
import { hikvisionHttpsAgent } from './probe';
const { mediaManager } = sdk;
@@ -537,9 +536,10 @@ class HikvisionProvider extends RtspProvider {
const username = settings.username?.toString();
const password = settings.password?.toString();
const skipValidate = settings.skipValidate === 'true';
let twoWayAudio: string;
if (!skipValidate) {
const api = new HikvisionCameraAPI(httpAddress, username, password, this.console);
try {
const api = new HikvisionCameraAPI(httpAddress, username, password, this.console);
const deviceInfo = await api.getDeviceInfo();
settings.newCamera = deviceInfo.deviceName;
@@ -553,6 +553,15 @@ class HikvisionProvider extends RtspProvider {
this.console.error('Error adding Hikvision camera', e);
throw e;
}
try {
if (await api.checkTwoWayAudio()) {
twoWayAudio = 'Hikvision';
}
}
catch (e) {
this.console.warn('Error probing two way audio', e);
}
}
settings.newCamera ||= 'Hikvision Camera';
@@ -564,6 +573,8 @@ class HikvisionProvider extends RtspProvider {
device.putSetting('password', password);
device.setIPAddress(settings.ip?.toString());
device.setHttpPortOverride(settings.httpPort?.toString());
if (twoWayAudio)
device.putSetting('twoWayAudio', twoWayAudio);
return nativeId;
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/onvif",
"version": "0.0.113",
"version": "0.0.114",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/onvif",
"version": "0.0.113",
"version": "0.0.114",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/onvif",
"version": "0.0.113",
"version": "0.0.114",
"description": "ONVIF Camera Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -561,6 +561,21 @@ class OnvifProvider extends RtspProvider implements DeviceDiscovery {
device.putSetting('password', password);
device.setIPAddress(settings.ip?.toString());
device.setHttpPortOverride(settings.httpPort?.toString());
const intercom = new OnvifIntercom(device);
try {
intercom.url = (await device.getConstructedVideoStreamOptions())[0].url;
if (await intercom.checkIntercom()) {
device.putSetting('onvifTwoWay', 'true');
}
}
catch (e) {
this.console.warn("error while probing intercom", e);
}
finally {
intercom.intercomClient?.client.destroy();
}
return nativeId;
}

View File

@@ -65,6 +65,8 @@ function* parseCodecs(audioSection: string): Generator<CodecMatch> {
}
}
const Require = 'www.onvif.org/ver20/backchannel';
export class OnvifIntercom implements Intercom {
intercomClient: RtspClient;
url: string;
@@ -72,9 +74,7 @@ export class OnvifIntercom implements Intercom {
constructor(public camera: RtspSmartCamera) {
}
async startIntercom(media: MediaObject) {
await this.stopIntercom();
async checkIntercom() {
const username = this.camera.storage.getItem("username");
const password = this.camera.storage.getItem("password");
const url = new URL(this.url);
@@ -83,7 +83,6 @@ export class OnvifIntercom implements Intercom {
this.intercomClient = new RtspClient(url.toString());
this.intercomClient.console = this.camera.console;
await this.intercomClient.options();
const Require = 'www.onvif.org/ver20/backchannel';
const describe = await this.intercomClient.describe({
Require,
@@ -95,6 +94,16 @@ export class OnvifIntercom implements Intercom {
if (!audioBackchannel)
throw new Error('ONVIF audio backchannel not found');
return audioBackchannel;
}
async startIntercom(media: MediaObject) {
await this.stopIntercom();
const audioBackchannel = await this.checkIntercom();
if (!audioBackchannel)
throw new Error('ONVIF audio backchannel not found');
const rtp = await reserveUdpPort();
const rtcp = rtp + 1;