mirror of
https://github.com/koush/scrypted.git
synced 2026-09-17 17:20:39 +01:00
missing files
This commit is contained in:
109
plugins/homekit/src/intercom.ts
Normal file
109
plugins/homekit/src/intercom.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import sdk from "@scrypted/sdk";
|
||||
import { listenZeroCluster } from "@scrypted/common/src/listen-cluster";
|
||||
import { FFMpegInput, Intercom, ScryptedDevice } from "@scrypted/sdk";
|
||||
import { createSocket, Socket, SocketType } from "dgram";
|
||||
import { createServer, Server } from "net";
|
||||
import child_process from "child_process";
|
||||
import { ffmpegLogInitialOutput } from "@scrypted/common/src/ffmpeg-helper";
|
||||
import { FFMpegRebroadcastSession, startRebroadcastSession } from "@scrypted/common/src/ffmpeg-rebroadcast";
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
async function pickPort(socketType: SocketType) {
|
||||
// const socket = createSocket(socketType);
|
||||
// return await new Promise(resolve => socket.bind(0, () => {
|
||||
// const { port } = socket.address();
|
||||
// socket.close(() => resolve(port));
|
||||
// }));
|
||||
return Math.round(Math.abs(Math.random()) * 40000 + 10000);
|
||||
}
|
||||
|
||||
export class IntercomSession {
|
||||
sdpReturnAudio: string;
|
||||
sdpServer: Server;
|
||||
session: FFMpegRebroadcastSession;
|
||||
port: number;
|
||||
heartbeatTimer: NodeJS.Timeout;
|
||||
|
||||
constructor(public device: ScryptedDevice & Intercom, public socketType: SocketType, public address: string, public srtp: Buffer) {
|
||||
|
||||
}
|
||||
|
||||
async start(): Promise<FFMpegRebroadcastSession> {
|
||||
const sdpIpVersion = this.socketType === "udp6" ? "IP6 " : "IP4";
|
||||
this.port = await pickPort(this.socketType);
|
||||
|
||||
// Session description protocol message that FFmpeg will share with HomeKit.
|
||||
// SDP messages tell the other side of the connection what we're expecting to receive.
|
||||
//
|
||||
// Parameters are:
|
||||
// v protocol version - always 0.
|
||||
// o originator and session identifier.
|
||||
// s session description.
|
||||
// c connection information.
|
||||
// t timestamps for the start and end of the session.
|
||||
// m media type - audio, adhering to RTP/AVP, payload type 110.
|
||||
// b bandwidth information - application specific, 24k.
|
||||
// a=rtpmap payload type 110 corresponds to an MP4 stream.
|
||||
// a=fmtp for payload type 110, use these format parameters.
|
||||
// a=crypto crypto suite to use for this session.
|
||||
this.sdpReturnAudio = [
|
||||
"v=0",
|
||||
"o=- 0 0 IN " + sdpIpVersion + " 127.0.0.1",
|
||||
"s=" + this.device.name + " Audio Talkback",
|
||||
"c=IN " + sdpIpVersion + " " + this.address,
|
||||
"t=0 0",
|
||||
"m=audio " + this.port + " RTP/AVP 110",
|
||||
"b=AS:24",
|
||||
"a=rtpmap:110 MPEG4-GENERIC/16000/1",
|
||||
"a=fmtp:110 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=F8F0212C00BC00",
|
||||
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:" + this.srtp.toString("base64")
|
||||
].join("\n");
|
||||
|
||||
this.sdpServer = createServer(socket => {
|
||||
this.sdpServer.close();
|
||||
socket.write(this.sdpReturnAudio);
|
||||
socket.end();
|
||||
});
|
||||
const sdpPort = await listenZeroCluster(this.sdpServer);
|
||||
console.log('sdp port', sdpPort);
|
||||
|
||||
const ffmpegInput: FFMpegInput = {
|
||||
inputArguments: [
|
||||
"-f", "sdp",
|
||||
"-acodec", "libfdk_aac",
|
||||
"-ac", '1',
|
||||
"-i", `tcp://127.0.0.1:${sdpPort}`,
|
||||
]
|
||||
};
|
||||
|
||||
this.session = await startRebroadcastSession(ffmpegInput, {
|
||||
vcodec: ['-vn'],
|
||||
acodec: ['-acodec', 'libfdk_aac', '-ac', '1'],
|
||||
outputFormat: 'adts',
|
||||
});
|
||||
|
||||
return this.session;
|
||||
}
|
||||
|
||||
// Send a regular heartbeat to FFmpeg to ensure the pipe remains open and the process alive.
|
||||
heartbeat(socket: Socket, heartbeat: Buffer): void {
|
||||
|
||||
// Clear the old heartbeat timer.
|
||||
clearTimeout(this.heartbeatTimer);
|
||||
|
||||
// Send a heartbeat to FFmpeg every few seconds to keep things open. FFmpeg has a five-second timeout
|
||||
// in reading input, and we want to be comfortably within the margin for error to ensure the process
|
||||
// continues to run.
|
||||
this.heartbeatTimer = setTimeout(() => {
|
||||
socket.send(heartbeat, this.port);
|
||||
this.heartbeat(socket, heartbeat);
|
||||
|
||||
}, 3.5 * 1000);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.sdpServer?.close();
|
||||
this.session?.kill();
|
||||
}
|
||||
}
|
||||
72
plugins/homekit/src/rtp-demuxer.ts
Normal file
72
plugins/homekit/src/rtp-demuxer.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/* Copyright(C) 2017-2021, HJD (https://github.com/hjdhjd). All rights reserved.
|
||||
*
|
||||
* protect-rtp.ts: RTP-related utilities to slice and dice RTP streams.
|
||||
*
|
||||
* This module is heavily inspired by the homebridge and homebridge-camera-ffmpeg source code and
|
||||
* borrows heavily from both. Thank you for your contributions to the HomeKit world.
|
||||
*/
|
||||
import { Socket } from "dgram";
|
||||
import { EventEmitter } from "stream";
|
||||
|
||||
// How often, in seconds, should we heartbeat FFmpeg in two-way audio sessions. This should be less than 5 seconds, which is
|
||||
// FFmpeg's input timeout interval.
|
||||
export const PROTECT_TWOWAY_HEARTBEAT_INTERVAL = 3.5;
|
||||
|
||||
/*
|
||||
* Here's the problem this class solves: FFmpeg doesn't support multiplexing RTP and RTCP data on a single UDP port (RFC 5761).
|
||||
* If it did, we wouldn't need this workaround for HomeKit compatibility, which does multiplex RTP and RTCP over a single UDP port.
|
||||
*
|
||||
* This class inspects all packets coming in from inputPort and demultiplexes RTP and RTCP traffic to rtpPort and rtcpPort, respectively.
|
||||
*
|
||||
* Credit to @dgreif and @brandawg93 who graciously shared their code as a starting point, and their collaboration
|
||||
* in answering the questions needed to bring all this together. A special thank you to @Sunoo for the many hours of
|
||||
* discussion and brainstorming on this and other topics.
|
||||
*/
|
||||
export class RtpDemuxer extends EventEmitter {
|
||||
private heartbeatTimer!: NodeJS.Timeout;
|
||||
private heartbeatMsg!: Buffer;
|
||||
|
||||
// Create an instance of RtpDemuxer.
|
||||
constructor(public deviceName: string, public console: Console, public socket: Socket) {
|
||||
super();
|
||||
// Catch errors when they happen on our demuxer.
|
||||
this.socket.on("error", (error) => {
|
||||
this.console.error("%s: RtpDemuxer Error: %s", this.deviceName, error);
|
||||
this.socket.close();
|
||||
});
|
||||
|
||||
// Split the message into RTP and RTCP packets.
|
||||
this.socket.on("message", (msg) => {
|
||||
|
||||
// Send RTP packets to the RTP port.
|
||||
if (this.isRtpMessage(msg)) {
|
||||
this.emit('rtp', msg);
|
||||
} else {
|
||||
this.emit('rtcp', msg);
|
||||
}
|
||||
});
|
||||
|
||||
this.console.log("%s: Creating an RtpDemuxer instance - inbound port: %s, RTCP port: %s, RTP port: %s.",
|
||||
this.deviceName);
|
||||
}
|
||||
|
||||
// Close the socket and cleanup.
|
||||
public close(): void {
|
||||
this.console.log("%s: Closing the RtpDemuxer instance on port %s.", this.deviceName);
|
||||
|
||||
clearTimeout(this.heartbeatTimer);
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
// Retrieve the payload information from a packet to discern what the packet payload is.
|
||||
private getPayloadType(message: Buffer): number {
|
||||
return message.readUInt8(1) & 0x7f;
|
||||
}
|
||||
|
||||
// Return whether or not a packet is RTP (or not).
|
||||
private isRtpMessage(message: Buffer): boolean {
|
||||
const payloadType = this.getPayloadType(message);
|
||||
|
||||
return (payloadType > 90) || (payloadType === 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user