@camera.ui/common 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cameraUtils/ffmpeg-process.d.ts +23 -0
- package/dist/cameraUtils/ffmpeg-process.js +72 -0
- package/dist/cameraUtils/ffmpeg-process.js.map +1 -0
- package/dist/cameraUtils/ffmpeg.d.ts +11 -0
- package/dist/{utils → cameraUtils}/ffmpeg.js +18 -2
- package/dist/cameraUtils/ffmpeg.js.map +1 -0
- package/dist/cameraUtils/index.d.ts +10 -0
- package/dist/cameraUtils/index.js +11 -0
- package/dist/cameraUtils/index.js.map +1 -0
- package/dist/cameraUtils/mp4.d.ts +10 -0
- package/dist/cameraUtils/mp4.js +53 -0
- package/dist/cameraUtils/mp4.js.map +1 -0
- package/dist/cameraUtils/ports.d.ts +7 -0
- package/dist/cameraUtils/ports.js +43 -0
- package/dist/cameraUtils/ports.js.map +1 -0
- package/dist/cameraUtils/processor.d.ts +49 -0
- package/dist/cameraUtils/processor.js +154 -0
- package/dist/cameraUtils/processor.js.map +1 -0
- package/dist/cameraUtils/return-audio-transcoder.d.ts +40 -0
- package/dist/cameraUtils/return-audio-transcoder.js +84 -0
- package/dist/cameraUtils/return-audio-transcoder.js.map +1 -0
- package/dist/cameraUtils/rtp-splitter.d.ts +35 -0
- package/dist/cameraUtils/rtp-splitter.js +86 -0
- package/dist/cameraUtils/rtp-splitter.js.map +1 -0
- package/dist/cameraUtils/rtp.d.ts +3 -0
- package/dist/cameraUtils/rtp.js +16 -0
- package/dist/cameraUtils/rtp.js.map +1 -0
- package/dist/cameraUtils/srtp.d.ts +8 -0
- package/dist/cameraUtils/srtp.js +22 -0
- package/dist/cameraUtils/srtp.js.map +1 -0
- package/dist/cameraUtils/utils.d.ts +1 -0
- package/dist/cameraUtils/utils.js +15 -0
- package/dist/cameraUtils/utils.js.map +1 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/{utils/reader.d.ts → net/index.d.ts} +2 -1
- package/dist/{utils/reader.js → net/index.js} +32 -1
- package/dist/net/index.js.map +1 -0
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/utils.d.ts +9 -9
- package/dist/utils/utils.js +19 -17
- package/dist/utils/utils.js.map +1 -1
- package/package.json +15 -3
- package/dist/utils/ffmpeg.d.ts +0 -14
- package/dist/utils/ffmpeg.js.map +0 -1
- package/dist/utils/reader.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface FfmpegProcessOptions {
|
|
2
|
+
ffmpegPath: string;
|
|
3
|
+
ffmpegArgs: (string | number)[];
|
|
4
|
+
logger?: {
|
|
5
|
+
error: (log: string) => unknown;
|
|
6
|
+
info: (log: string) => unknown;
|
|
7
|
+
};
|
|
8
|
+
logLabel?: string;
|
|
9
|
+
exitCallback?: (code: number | null, signal: string | null) => unknown;
|
|
10
|
+
stdoutCallback?: (data: any) => unknown;
|
|
11
|
+
startedCallback?: () => unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare class FfmpegProcess {
|
|
14
|
+
readonly options: FfmpegProcessOptions;
|
|
15
|
+
private ff;
|
|
16
|
+
private processSubscription;
|
|
17
|
+
private started;
|
|
18
|
+
private stopped;
|
|
19
|
+
private exited;
|
|
20
|
+
constructor(options: FfmpegProcessOptions);
|
|
21
|
+
stop(): void;
|
|
22
|
+
writeStdin(input: string): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
const noop = () => null;
|
|
4
|
+
const onGlobalProcessStopped = new Subject();
|
|
5
|
+
// register a single event listener, rather than listener per ffmpeg process
|
|
6
|
+
// this helps avoid a warning for hitting too many listeners
|
|
7
|
+
process.on('exit', () => onGlobalProcessStopped.next(null));
|
|
8
|
+
export class FfmpegProcess {
|
|
9
|
+
options;
|
|
10
|
+
ff;
|
|
11
|
+
processSubscription = onGlobalProcessStopped.subscribe(() => this.stop());
|
|
12
|
+
started = false;
|
|
13
|
+
stopped = false;
|
|
14
|
+
exited = false;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.ff = spawn(options.ffmpegPath, options.ffmpegArgs.map((x) => x.toString()));
|
|
18
|
+
const { logger, logLabel } = options;
|
|
19
|
+
const logError = logger?.error || noop;
|
|
20
|
+
const logInfo = logger?.info || noop;
|
|
21
|
+
const logPrefix = logLabel ? `${logLabel}: ` : '';
|
|
22
|
+
if (options.stdoutCallback) {
|
|
23
|
+
const { stdoutCallback } = options;
|
|
24
|
+
this.ff.stdout.on('data', (data) => {
|
|
25
|
+
stdoutCallback(data);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
this.ff.stderr.on('data', (data) => {
|
|
29
|
+
if (!this.started) {
|
|
30
|
+
this.started = true;
|
|
31
|
+
options.startedCallback?.();
|
|
32
|
+
}
|
|
33
|
+
logInfo(logPrefix + data);
|
|
34
|
+
});
|
|
35
|
+
this.ff.stdin.on('error', (error) => {
|
|
36
|
+
if (!error.message.includes('EPIPE')) {
|
|
37
|
+
logError(logPrefix + error.message);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
this.ff.on('exit', (code, signal) => {
|
|
41
|
+
this.exited = true;
|
|
42
|
+
this.options.exitCallback?.(code, signal);
|
|
43
|
+
if (!code || code === 255) {
|
|
44
|
+
logInfo(logPrefix + 'stopped gracefully');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
logError(logPrefix + `exited with code ${code} and signal ${signal}`);
|
|
48
|
+
}
|
|
49
|
+
this.stop();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
stop() {
|
|
53
|
+
if (this.stopped) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.stopped = true;
|
|
57
|
+
this.processSubscription.unsubscribe();
|
|
58
|
+
this.ff.stderr.pause();
|
|
59
|
+
this.ff.stdout.pause();
|
|
60
|
+
if (!this.exited) {
|
|
61
|
+
this.ff.kill();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
writeStdin(input) {
|
|
65
|
+
if (this.stopped) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.ff.stdin.write(input);
|
|
69
|
+
this.ff.stdin.end();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=ffmpeg-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg-process.js","sourceRoot":"","sources":["../../src/cameraUtils/ffmpeg-process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FfmpegArgs } from '@camera.ui/types';
|
|
2
|
+
export interface HardwareSource {
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HardwareResponse {
|
|
7
|
+
sources: HardwareSource[];
|
|
8
|
+
}
|
|
9
|
+
export declare function doesFfmpegSupportCodec(codec: string, ffmpegPath: string): Promise<boolean>;
|
|
10
|
+
export declare function isFfmpegInstalled(ffmpegPath: string): Promise<boolean>;
|
|
11
|
+
export declare function getHwaccelInfo(go2rtcEndpoint: string): Promise<FfmpegArgs>;
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execAwait = promisify(exec);
|
|
4
|
+
export async function doesFfmpegSupportCodec(codec, ffmpegPath) {
|
|
5
|
+
const output = await execAwait(`${ffmpegPath} -codecs`);
|
|
6
|
+
return output.stdout.includes(codec);
|
|
7
|
+
}
|
|
8
|
+
export async function isFfmpegInstalled(ffmpegPath) {
|
|
9
|
+
try {
|
|
10
|
+
await execAwait(`${ffmpegPath} -codecs`);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function getHwaccelInfo(go2rtcEndpoint) {
|
|
2
18
|
let hwaccel = 'auto';
|
|
3
19
|
let hwaccelArgs = [];
|
|
4
20
|
let hwaccelFilter = 'scale=640:-1';
|
|
@@ -57,5 +73,5 @@ export const getHwaccelInfo = async (go2rtcEndpoint) => {
|
|
|
57
73
|
// Do nothing
|
|
58
74
|
}
|
|
59
75
|
return { hwaccel, hwaccelArgs, hwaccelFilter, threads };
|
|
60
|
-
}
|
|
76
|
+
}
|
|
61
77
|
//# sourceMappingURL=ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/cameraUtils/ffmpeg.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './ffmpeg-process.js';
|
|
2
|
+
export * from './ffmpeg.js';
|
|
3
|
+
export * from './mp4.js';
|
|
4
|
+
export * from './ports.js';
|
|
5
|
+
export * from './processor.js';
|
|
6
|
+
export * from './return-audio-transcoder.js';
|
|
7
|
+
export * from './rtp-splitter.js';
|
|
8
|
+
export * from './rtp.js';
|
|
9
|
+
export * from './srtp.js';
|
|
10
|
+
export * from './utils.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './ffmpeg-process.js';
|
|
2
|
+
export * from './ffmpeg.js';
|
|
3
|
+
export * from './mp4.js';
|
|
4
|
+
export * from './ports.js';
|
|
5
|
+
export * from './processor.js';
|
|
6
|
+
export * from './return-audio-transcoder.js';
|
|
7
|
+
export * from './rtp-splitter.js';
|
|
8
|
+
export * from './rtp.js';
|
|
9
|
+
export * from './srtp.js';
|
|
10
|
+
export * from './utils.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cameraUtils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const NAL_TYPE_STAP_A = 24;
|
|
2
|
+
export declare const NAL_TYPE_FU_A = 28;
|
|
3
|
+
export declare const NAL_TYPE_NON_IDR = 1;
|
|
4
|
+
export declare const NAL_TYPE_IDR = 5;
|
|
5
|
+
export declare const NAL_TYPE_SEI = 6;
|
|
6
|
+
export declare const NAL_TYPE_SPS = 7;
|
|
7
|
+
export declare const NAL_TYPE_PPS = 8;
|
|
8
|
+
export declare const NAL_TYPE_DELIMITER = 9;
|
|
9
|
+
export declare const allowedNaluTypes: number[];
|
|
10
|
+
export declare function checkMp4StartsWithKeyFrame(mp4: Buffer, ffmpegPath: string): Promise<boolean>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { timeoutPromise } from './utils.js';
|
|
3
|
+
// https://yumichan.net/video-processing/video-compression/introduction-to-h264-nal-unit/
|
|
4
|
+
export const NAL_TYPE_STAP_A = 24;
|
|
5
|
+
export const NAL_TYPE_FU_A = 28;
|
|
6
|
+
export const NAL_TYPE_NON_IDR = 1;
|
|
7
|
+
export const NAL_TYPE_IDR = 5;
|
|
8
|
+
export const NAL_TYPE_SEI = 6;
|
|
9
|
+
export const NAL_TYPE_SPS = 7;
|
|
10
|
+
export const NAL_TYPE_PPS = 8;
|
|
11
|
+
export const NAL_TYPE_DELIMITER = 9;
|
|
12
|
+
export const allowedNaluTypes = [NAL_TYPE_STAP_A, NAL_TYPE_SPS, NAL_TYPE_PPS, NAL_TYPE_SEI, NAL_TYPE_DELIMITER];
|
|
13
|
+
export async function checkMp4StartsWithKeyFrame(mp4, ffmpegPath) {
|
|
14
|
+
const cp = spawn(ffmpegPath, ['-hide_banner', '-f', 'mp4', '-i', 'pipe:3', '-vcodec', 'copy', '-f', 'h264', 'pipe:4'], {
|
|
15
|
+
stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
|
|
16
|
+
});
|
|
17
|
+
const input = cp.stdio[3];
|
|
18
|
+
const output = cp.stdio[4];
|
|
19
|
+
const buffers = [];
|
|
20
|
+
input.write(mp4);
|
|
21
|
+
input.end();
|
|
22
|
+
output.on('data', (data) => buffers.push(data));
|
|
23
|
+
try {
|
|
24
|
+
await timeoutPromise(1000, new Promise((resolve) => cp.on('exit', resolve)));
|
|
25
|
+
const h264 = Buffer.concat(buffers);
|
|
26
|
+
let offset = 0;
|
|
27
|
+
while (offset < h264.length - 6) {
|
|
28
|
+
if (h264.readInt32BE(offset) !== 1) {
|
|
29
|
+
offset++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
offset += 4;
|
|
33
|
+
let naluType = h264.readUInt8(offset) & 0x1f;
|
|
34
|
+
if (naluType === NAL_TYPE_FU_A) {
|
|
35
|
+
offset++;
|
|
36
|
+
naluType = h264.readUInt8(offset) & 0x1f;
|
|
37
|
+
}
|
|
38
|
+
if (naluType === NAL_TYPE_IDR) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (allowedNaluTypes.includes(naluType)) {
|
|
42
|
+
offset++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=mp4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mp4.js","sourceRoot":"","sources":["../../src/cameraUtils/mp4.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAI5C,yFAAyF;AACzF,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;AAEhH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,GAAW,EAAE,UAAkB;IAC9E,MAAM,EAAE,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE;QACrH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;KAChD,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;IACtC,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;IACvC,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjB,KAAK,CAAC,GAAG,EAAE,CAAC;IACZ,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhD,IAAI,CAAC;QACH,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,OAAO,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnC,MAAM,EAAE,CAAC;gBACT,SAAS;YACX,CAAC;YAED,MAAM,IAAI,CAAC,CAAC;YAEZ,IAAI,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;YAC7C,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;gBAC/B,MAAM,EAAE,CAAC;gBACT,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;YAC3C,CAAC;YAED,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC9B,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxC,MAAM,EAAE,CAAC;gBACT,SAAS;YACX,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Socket } from 'node:dgram';
|
|
2
|
+
export declare function reservePorts({ count, type, attemptNumber, }?: {
|
|
3
|
+
count?: number;
|
|
4
|
+
type?: 'udp' | 'tcp';
|
|
5
|
+
attemptNumber?: number;
|
|
6
|
+
}): Promise<number[]>;
|
|
7
|
+
export declare function bindToPort(socket: Socket, address?: string): Promise<number>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { pickPort } from 'pick-port';
|
|
2
|
+
// Need to reserve ports in sequence because ffmpeg uses the next port up by default. If it's taken, ffmpeg will error
|
|
3
|
+
export async function reservePorts({ count = 1, type = 'udp', attemptNumber = 0, } = {}) {
|
|
4
|
+
if (attemptNumber > 100) {
|
|
5
|
+
throw new Error('Failed to reserve ports after 100 tries');
|
|
6
|
+
}
|
|
7
|
+
const pickPortOptions = {
|
|
8
|
+
type,
|
|
9
|
+
reserveTimeout: 15, // 15 seconds is max setup time for HomeKit streams, so the port should be in use by then
|
|
10
|
+
}, port = await pickPort(pickPortOptions), ports = [port], tryAgain = () => {
|
|
11
|
+
return reservePorts({
|
|
12
|
+
count,
|
|
13
|
+
type,
|
|
14
|
+
attemptNumber: attemptNumber + 1,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
for (let i = 1; i < count; i++) {
|
|
18
|
+
try {
|
|
19
|
+
const targetConsecutivePort = port + i, openPort = await pickPort({
|
|
20
|
+
...pickPortOptions,
|
|
21
|
+
minPort: targetConsecutivePort,
|
|
22
|
+
maxPort: targetConsecutivePort,
|
|
23
|
+
});
|
|
24
|
+
ports.push(openPort);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// can't reserve next port, bail and get another set
|
|
28
|
+
return tryAgain();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return ports;
|
|
32
|
+
}
|
|
33
|
+
export function bindToPort(socket, address = '127.0.0.1') {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
socket.on('error', reject);
|
|
36
|
+
// 0 means select a random open port
|
|
37
|
+
socket.bind(0, address, () => {
|
|
38
|
+
const { port } = socket.address();
|
|
39
|
+
resolve(port);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/cameraUtils/ports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAKrC,uHAAuH;AACvH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EACjC,KAAK,GAAG,CAAC,EACT,IAAI,GAAG,KAAK,EACZ,aAAa,GAAG,CAAC,MAKf,EAAE;IACJ,IAAI,aAAa,GAAG,GAAG,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,eAAe,GAAG;QACpB,IAAI;QACJ,cAAc,EAAE,EAAE,EAAE,yFAAyF;KAC9G,EACD,IAAI,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,EACtC,KAAK,GAAG,CAAC,IAAI,CAAC,EACd,QAAQ,GAAG,GAAG,EAAE;QACd,OAAO,YAAY,CAAC;YAClB,KAAK;YACL,IAAI;YACJ,aAAa,EAAE,aAAa,GAAG,CAAC;SACjC,CAAC,CAAC;IACL,CAAC,CAAC;IAEJ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,EACpC,QAAQ,GAAG,MAAM,QAAQ,CAAC;gBACxB,GAAG,eAAe;gBAClB,OAAO,EAAE,qBAAqB;gBAC9B,OAAO,EAAE,qBAAqB;aAC/B,CAAC,CAAC;YAEL,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;YACpD,OAAO,QAAQ,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,OAAO,GAAG,WAAW;IAC9D,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAE3B,oCAAoC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE;YAC3B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,EAAiB,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RtpPacket } from 'werift';
|
|
2
|
+
import type { Logger } from '../logger/index.js';
|
|
3
|
+
import type { RtpSplitter } from './rtp-splitter.js';
|
|
4
|
+
import type { SrtpOptions } from './srtp.js';
|
|
5
|
+
export declare function getSessionConfig(srtpOptions: SrtpOptions): {
|
|
6
|
+
keys: {
|
|
7
|
+
localMasterKey: Buffer;
|
|
8
|
+
localMasterSalt: Buffer;
|
|
9
|
+
remoteMasterKey: Buffer;
|
|
10
|
+
remoteMasterSalt: Buffer;
|
|
11
|
+
};
|
|
12
|
+
profile: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function getDurationSeconds(start: number): number;
|
|
15
|
+
export declare class AudioProcessor {
|
|
16
|
+
private audioSsrc;
|
|
17
|
+
private audioSrtp;
|
|
18
|
+
private audioSplitter;
|
|
19
|
+
private targetAddress;
|
|
20
|
+
private targetPort;
|
|
21
|
+
private payloadType;
|
|
22
|
+
private isOpus;
|
|
23
|
+
private packetTime;
|
|
24
|
+
private logger;
|
|
25
|
+
private audioSrtpSession;
|
|
26
|
+
private sequenceNumber;
|
|
27
|
+
private timestamp;
|
|
28
|
+
private buffer;
|
|
29
|
+
constructor(audioSsrc: number, audioSrtp: SrtpOptions, audioSplitter: RtpSplitter, targetAddress: string, targetPort: number, payloadType: number, isOpus: boolean, packetTime: number, logger: Logger);
|
|
30
|
+
processPacket(data: Buffer): void;
|
|
31
|
+
private process20ms;
|
|
32
|
+
private process60ms;
|
|
33
|
+
private sendPacket;
|
|
34
|
+
}
|
|
35
|
+
export declare class VideoProcessor {
|
|
36
|
+
private videoSsrc;
|
|
37
|
+
private videoSrtp;
|
|
38
|
+
private videoSplitter;
|
|
39
|
+
private targetAddress;
|
|
40
|
+
private targetPort;
|
|
41
|
+
private mtu;
|
|
42
|
+
private payloadType;
|
|
43
|
+
private logger;
|
|
44
|
+
private videoSrtpSession;
|
|
45
|
+
private sequenceNumber;
|
|
46
|
+
constructor(videoSsrc: number, videoSrtp: SrtpOptions, videoSplitter: RtpSplitter, targetAddress: string, targetPort: number, mtu: number, payloadType: number, logger: Logger);
|
|
47
|
+
processPacket(rtp: RtpPacket): void;
|
|
48
|
+
private fragmentPacket;
|
|
49
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { RtpHeader, RtpPacket, SrtpSession } from 'werift';
|
|
2
|
+
export function getSessionConfig(srtpOptions) {
|
|
3
|
+
return {
|
|
4
|
+
keys: {
|
|
5
|
+
localMasterKey: srtpOptions.srtpKey,
|
|
6
|
+
localMasterSalt: srtpOptions.srtpSalt,
|
|
7
|
+
remoteMasterKey: srtpOptions.srtpKey,
|
|
8
|
+
remoteMasterSalt: srtpOptions.srtpSalt,
|
|
9
|
+
},
|
|
10
|
+
profile: 1,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function getDurationSeconds(start) {
|
|
14
|
+
return (Date.now() - start) / 1000;
|
|
15
|
+
}
|
|
16
|
+
export class AudioProcessor {
|
|
17
|
+
audioSsrc;
|
|
18
|
+
audioSrtp;
|
|
19
|
+
audioSplitter;
|
|
20
|
+
targetAddress;
|
|
21
|
+
targetPort;
|
|
22
|
+
payloadType;
|
|
23
|
+
isOpus;
|
|
24
|
+
packetTime;
|
|
25
|
+
logger;
|
|
26
|
+
audioSrtpSession;
|
|
27
|
+
sequenceNumber = Math.floor(Math.random() * 65536);
|
|
28
|
+
timestamp = 0;
|
|
29
|
+
buffer = [];
|
|
30
|
+
constructor(audioSsrc, audioSrtp, audioSplitter, targetAddress, targetPort, payloadType, isOpus, packetTime, logger) {
|
|
31
|
+
this.audioSsrc = audioSsrc;
|
|
32
|
+
this.audioSrtp = audioSrtp;
|
|
33
|
+
this.audioSplitter = audioSplitter;
|
|
34
|
+
this.targetAddress = targetAddress;
|
|
35
|
+
this.targetPort = targetPort;
|
|
36
|
+
this.payloadType = payloadType;
|
|
37
|
+
this.isOpus = isOpus;
|
|
38
|
+
this.packetTime = packetTime;
|
|
39
|
+
this.logger = logger;
|
|
40
|
+
this.audioSrtpSession = new SrtpSession(getSessionConfig(this.audioSrtp));
|
|
41
|
+
if (this.isOpus) {
|
|
42
|
+
this.logger.log(`AudioProcessor created with OPUS codec, audio will be split into ${this.packetTime}ms frames`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.logger.log('AudioProcessor created with AAC codec, audio will be sent as is');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
processPacket(data) {
|
|
49
|
+
const rtp = RtpPacket.deSerialize(data);
|
|
50
|
+
if (this.isOpus) {
|
|
51
|
+
if (this.packetTime === 20) {
|
|
52
|
+
this.process20ms(rtp);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.process60ms(rtp);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.sendPacket(rtp);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
process20ms(rtp) {
|
|
63
|
+
rtp.header.sequenceNumber = this.sequenceNumber++;
|
|
64
|
+
rtp.header.timestamp = this.timestamp;
|
|
65
|
+
rtp.header.ssrc = this.audioSsrc;
|
|
66
|
+
rtp.header.payloadType = this.payloadType;
|
|
67
|
+
this.sendPacket(rtp);
|
|
68
|
+
this.timestamp += 16000 * 0.02; // 16kHz * 20ms
|
|
69
|
+
}
|
|
70
|
+
process60ms(rtp) {
|
|
71
|
+
this.buffer.push(rtp.payload.subarray(1)); // Remove TOC byte
|
|
72
|
+
if (this.buffer.length === 3) {
|
|
73
|
+
const toc = rtp.payload[0] & 0xfc; // Keep config bits
|
|
74
|
+
const frameCountByte = 0x83; // VBR flag set, 3 frames
|
|
75
|
+
const header = Buffer.from([toc | 0x03, frameCountByte]);
|
|
76
|
+
const payload = Buffer.concat([header, ...this.buffer]);
|
|
77
|
+
rtp.header.sequenceNumber = this.sequenceNumber++;
|
|
78
|
+
rtp.header.timestamp = this.timestamp;
|
|
79
|
+
rtp.header.ssrc = this.audioSsrc;
|
|
80
|
+
rtp.payload = payload;
|
|
81
|
+
this.sendPacket(rtp);
|
|
82
|
+
this.timestamp += 16000 * 0.06; // 16kHz * 60ms
|
|
83
|
+
this.buffer = [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
sendPacket(rtp) {
|
|
87
|
+
const encryptedPacket = this.audioSrtpSession.encrypt(rtp.payload, rtp.header);
|
|
88
|
+
this.audioSplitter.send(encryptedPacket, {
|
|
89
|
+
port: this.targetPort,
|
|
90
|
+
address: this.targetAddress,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export class VideoProcessor {
|
|
95
|
+
videoSsrc;
|
|
96
|
+
videoSrtp;
|
|
97
|
+
videoSplitter;
|
|
98
|
+
targetAddress;
|
|
99
|
+
targetPort;
|
|
100
|
+
mtu;
|
|
101
|
+
payloadType;
|
|
102
|
+
logger;
|
|
103
|
+
videoSrtpSession;
|
|
104
|
+
sequenceNumber = Math.floor(Math.random() * 65536);
|
|
105
|
+
constructor(videoSsrc, videoSrtp, videoSplitter, targetAddress, targetPort, mtu, payloadType, logger) {
|
|
106
|
+
this.videoSsrc = videoSsrc;
|
|
107
|
+
this.videoSrtp = videoSrtp;
|
|
108
|
+
this.videoSplitter = videoSplitter;
|
|
109
|
+
this.targetAddress = targetAddress;
|
|
110
|
+
this.targetPort = targetPort;
|
|
111
|
+
this.mtu = mtu;
|
|
112
|
+
this.payloadType = payloadType;
|
|
113
|
+
this.logger = logger;
|
|
114
|
+
this.videoSrtpSession = new SrtpSession(getSessionConfig(this.videoSrtp));
|
|
115
|
+
}
|
|
116
|
+
processPacket(rtp) {
|
|
117
|
+
if (!rtp || !rtp.payload) {
|
|
118
|
+
this.logger.warn('Received invalid RTP packet, ignore...');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const fragments = this.fragmentPacket(rtp.payload, this.mtu - 12); // Subtract RTP header size
|
|
122
|
+
for (let i = 0; i < fragments.length; i++) {
|
|
123
|
+
const newRtp = new RtpPacket(new RtpHeader({
|
|
124
|
+
version: 2,
|
|
125
|
+
padding: false,
|
|
126
|
+
extension: false,
|
|
127
|
+
marker: i === fragments.length - 1 && rtp.header.marker,
|
|
128
|
+
payloadType: this.payloadType,
|
|
129
|
+
sequenceNumber: this.sequenceNumber++,
|
|
130
|
+
timestamp: rtp.header.timestamp,
|
|
131
|
+
ssrc: this.videoSsrc,
|
|
132
|
+
}), fragments[i]);
|
|
133
|
+
const encryptedPacket = this.videoSrtpSession.encrypt(newRtp.payload, newRtp.header);
|
|
134
|
+
this.videoSplitter.send(encryptedPacket, {
|
|
135
|
+
port: this.targetPort,
|
|
136
|
+
address: this.targetAddress,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
fragmentPacket(payload, maxFragmentSize) {
|
|
141
|
+
if (payload.length <= maxFragmentSize) {
|
|
142
|
+
return [payload];
|
|
143
|
+
}
|
|
144
|
+
const fragments = [];
|
|
145
|
+
let startIndex = 0;
|
|
146
|
+
while (startIndex < payload.length) {
|
|
147
|
+
const endIndex = Math.min(startIndex + maxFragmentSize, payload.length);
|
|
148
|
+
fragments.push(payload.subarray(startIndex, endIndex));
|
|
149
|
+
startIndex = endIndex;
|
|
150
|
+
}
|
|
151
|
+
return fragments;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.js","sourceRoot":"","sources":["../../src/cameraUtils/processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FfmpegProcess } from './ffmpeg-process.js';
|
|
2
|
+
import type { FfmpegProcessOptions } from './ffmpeg-process.js';
|
|
3
|
+
import type { RtpSplitter } from './rtp-splitter.js';
|
|
4
|
+
interface Source {
|
|
5
|
+
srtp_key: Buffer;
|
|
6
|
+
srtp_salt: Buffer;
|
|
7
|
+
}
|
|
8
|
+
interface PrepareStreamRequest {
|
|
9
|
+
targetAddress: string;
|
|
10
|
+
addressVersion: 'ipv4' | 'ipv6';
|
|
11
|
+
audio: Source;
|
|
12
|
+
}
|
|
13
|
+
interface StartStreamRequest {
|
|
14
|
+
audio: {
|
|
15
|
+
codec: 'OPUS' | 'AAC_eld' | string;
|
|
16
|
+
channel: number;
|
|
17
|
+
sample_rate: number;
|
|
18
|
+
pt: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare class ReturnAudioTranscoder {
|
|
22
|
+
private options;
|
|
23
|
+
readonly returnRtpSplitter: RtpSplitter;
|
|
24
|
+
private startStreamRequest;
|
|
25
|
+
readonly ffmpegProcess: FfmpegProcess;
|
|
26
|
+
readonly reservedPortsPromise: Promise<number[]>;
|
|
27
|
+
constructor(options: {
|
|
28
|
+
outputArgs: (string | number)[];
|
|
29
|
+
prepareStreamRequest: PrepareStreamRequest;
|
|
30
|
+
incomingAudioOptions: {
|
|
31
|
+
ssrc: number;
|
|
32
|
+
rtcpPort: number;
|
|
33
|
+
};
|
|
34
|
+
returnAudioSplitter: RtpSplitter;
|
|
35
|
+
startStreamRequest?: StartStreamRequest;
|
|
36
|
+
} & Omit<FfmpegProcessOptions, 'ffmpegArgs'>);
|
|
37
|
+
start(): Promise<void>;
|
|
38
|
+
stop(): void;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { FfmpegProcess } from './ffmpeg-process.js';
|
|
2
|
+
import { reservePorts } from './ports.js';
|
|
3
|
+
import { getSsrc } from './rtp.js';
|
|
4
|
+
import { createCryptoLine } from './srtp.js';
|
|
5
|
+
const defaultStartStreamReqeuest = {
|
|
6
|
+
audio: {
|
|
7
|
+
codec: 'AAC_eld',
|
|
8
|
+
channel: 1,
|
|
9
|
+
sample_rate: 16,
|
|
10
|
+
pt: 110,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export class ReturnAudioTranscoder {
|
|
14
|
+
options;
|
|
15
|
+
returnRtpSplitter;
|
|
16
|
+
startStreamRequest;
|
|
17
|
+
ffmpegProcess;
|
|
18
|
+
reservedPortsPromise = reservePorts({ count: 2 });
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.startStreamRequest = this.options.startStreamRequest || defaultStartStreamReqeuest;
|
|
22
|
+
this.ffmpegProcess = new FfmpegProcess({
|
|
23
|
+
ffmpegArgs: [
|
|
24
|
+
'-hide_banner',
|
|
25
|
+
'-protocol_whitelist',
|
|
26
|
+
'pipe,udp,rtp,file,crypto',
|
|
27
|
+
'-f',
|
|
28
|
+
'sdp',
|
|
29
|
+
'-acodec',
|
|
30
|
+
this.startStreamRequest.audio.codec === 'OPUS' ? 'libopus' : 'libfdk_aac',
|
|
31
|
+
'-i',
|
|
32
|
+
'pipe:',
|
|
33
|
+
'-map',
|
|
34
|
+
'0:0',
|
|
35
|
+
...this.options.outputArgs,
|
|
36
|
+
],
|
|
37
|
+
...this.options,
|
|
38
|
+
});
|
|
39
|
+
// allow return audio splitter to be passed in if you want to create one in the prepare stream phase, and create the transcoder in the stream request phase
|
|
40
|
+
this.returnRtpSplitter = options.returnAudioSplitter;
|
|
41
|
+
}
|
|
42
|
+
async start() {
|
|
43
|
+
const [rtpPort, rtcpPort] = await this.reservedPortsPromise, { targetAddress, addressVersion, audio: { srtp_key: srtpKey, srtp_salt: srtpSalt }, } = this.options.prepareStreamRequest, { ssrc: incomingAudioSsrc, rtcpPort: incomingAudioRtcpPort } = this.options.incomingAudioOptions, { codec, sample_rate, channel, pt: packetType } = this.startStreamRequest.audio;
|
|
44
|
+
this.ffmpegProcess.writeStdin(
|
|
45
|
+
// This SDP was generated using ffmpeg, and describes the type of packets we expect to receive from HomeKit.
|
|
46
|
+
[
|
|
47
|
+
'v=0',
|
|
48
|
+
'o=- 0 0 IN IP4 127.0.0.1',
|
|
49
|
+
's=Talk',
|
|
50
|
+
`c=IN ${addressVersion.replace('v', '').toUpperCase()} ${targetAddress}`,
|
|
51
|
+
't=0 0',
|
|
52
|
+
'a=tool:libavformat 58.38.100',
|
|
53
|
+
`m=audio ${rtpPort} RTP/AVP ${packetType}`,
|
|
54
|
+
'b=AS:24',
|
|
55
|
+
...(codec === 'OPUS'
|
|
56
|
+
? [`a=rtpmap:${packetType} opus/${sample_rate}000/${channel}`, `a=fmtp:${packetType} minptime=10;useinbandfec=1`]
|
|
57
|
+
: [
|
|
58
|
+
`a=rtpmap:${packetType} MPEG4-GENERIC/${sample_rate}000/${channel}`,
|
|
59
|
+
`a=fmtp:${packetType} profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=F8F0212C00BC00`,
|
|
60
|
+
]),
|
|
61
|
+
createCryptoLine({
|
|
62
|
+
srtpKey,
|
|
63
|
+
srtpSalt,
|
|
64
|
+
}),
|
|
65
|
+
].join('\n'));
|
|
66
|
+
this.returnRtpSplitter.addMessageHandler(({ isRtpMessage, message }) => {
|
|
67
|
+
// This splitter will receive all audio-related packets from HomeKit.
|
|
68
|
+
// This includes RTP + RTCP for return audio, as well as RTCP for incoming audio
|
|
69
|
+
if (!isRtpMessage && getSsrc(message) === incomingAudioSsrc) {
|
|
70
|
+
return {
|
|
71
|
+
port: incomingAudioRtcpPort,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
port: isRtpMessage ? rtpPort : rtcpPort,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
stop() {
|
|
80
|
+
this.ffmpegProcess.stop();
|
|
81
|
+
this.returnRtpSplitter.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=return-audio-transcoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-audio-transcoder.js","sourceRoot":"","sources":["../../src/cameraUtils/return-audio-transcoder.ts"],"names":[],"mappings":"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"}
|