@camera.ui/camera-ui-homekit 0.0.26 → 0.0.28
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/camera/accessory.d.ts +1 -1
- package/dist/camera/accessory.js +42 -32
- package/dist/camera/accessory.js.map +1 -1
- package/dist/camera/recordingDelegate.d.ts +3 -3
- package/dist/camera/recordingDelegate.js +20 -20
- package/dist/camera/recordingDelegate.js.map +1 -1
- package/dist/camera/services.d.ts +3 -3
- package/dist/camera/services.js +19 -19
- package/dist/camera/services.js.map +1 -1
- package/dist/camera/sessionWrapper.d.ts +6 -4
- package/dist/camera/sessionWrapper.js +120 -80
- package/dist/camera/sessionWrapper.js.map +1 -1
- package/dist/camera/streamingDelegate.d.ts +4 -3
- package/dist/camera/streamingDelegate.js +19 -14
- package/dist/camera/streamingDelegate.js.map +1 -1
- package/dist/camera/streamingServer.d.ts +3 -3
- package/dist/camera/streamingServer.js +7 -7
- package/dist/camera/streamingServer.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/utils/processor.d.ts +20 -8
- package/dist/utils/processor.js +129 -64
- package/dist/utils/processor.js.map +1 -1
- package/package.json +8 -8
package/dist/camera/services.js
CHANGED
|
@@ -4,7 +4,7 @@ export class CameraServices {
|
|
|
4
4
|
accessory;
|
|
5
5
|
cameraAccessory;
|
|
6
6
|
cameraDevice;
|
|
7
|
-
|
|
7
|
+
cameraLogger;
|
|
8
8
|
onMotionDetected;
|
|
9
9
|
onDoorbellPressed;
|
|
10
10
|
onLightSwitched;
|
|
@@ -13,11 +13,11 @@ export class CameraServices {
|
|
|
13
13
|
maxMotionTimeout;
|
|
14
14
|
maxMotionTime = 2 * 60 * 1000;
|
|
15
15
|
motionTimeoutDuration = 30000;
|
|
16
|
-
constructor(accessory, cameraAccessory, cameraDevice
|
|
16
|
+
constructor(accessory, cameraAccessory, cameraDevice) {
|
|
17
17
|
this.accessory = accessory;
|
|
18
18
|
this.cameraAccessory = cameraAccessory;
|
|
19
19
|
this.cameraDevice = cameraDevice;
|
|
20
|
-
this.
|
|
20
|
+
this.cameraLogger = cameraDevice.logger;
|
|
21
21
|
// this.cameraDevice.onPropertyChange('hasMotionDetector').subscribe(({ newData }) => {
|
|
22
22
|
// if (newData) {
|
|
23
23
|
// this.addMotionService();
|
|
@@ -52,7 +52,7 @@ export class CameraServices {
|
|
|
52
52
|
this.addServices();
|
|
53
53
|
}
|
|
54
54
|
addServices() {
|
|
55
|
-
this.
|
|
55
|
+
this.cameraLogger.debug('Adding services');
|
|
56
56
|
this.addMotionService();
|
|
57
57
|
this.addDoorbellService();
|
|
58
58
|
this.addLightService();
|
|
@@ -61,7 +61,7 @@ export class CameraServices {
|
|
|
61
61
|
addBatteryService() {
|
|
62
62
|
let batteryService = this.accessory.getService(Service.Battery);
|
|
63
63
|
if (!batteryService && this.cameraDevice.hasBattery) {
|
|
64
|
-
this.
|
|
64
|
+
this.cameraLogger.debug('Adding battery service');
|
|
65
65
|
batteryService = this.accessory.addService(Service.Battery, 'Battery');
|
|
66
66
|
if (!batteryService.testCharacteristic(Characteristic.BatteryLevel)) {
|
|
67
67
|
batteryService.addCharacteristic(Characteristic.BatteryLevel);
|
|
@@ -71,17 +71,17 @@ export class CameraServices {
|
|
|
71
71
|
}
|
|
72
72
|
batteryService.getCharacteristic(Characteristic.BatteryLevel).on('change', (change) => {
|
|
73
73
|
if (change.oldValue !== change.newValue) {
|
|
74
|
-
this.
|
|
74
|
+
this.cameraLogger.debug('Battery level changed to', change.newValue);
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
77
|
batteryService.getCharacteristic(Characteristic.StatusLowBattery).on('change', (change) => {
|
|
78
78
|
if (change.oldValue !== change.newValue) {
|
|
79
|
-
this.
|
|
79
|
+
this.cameraLogger.debug('Battery state changed to', change.newValue);
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
batteryService.getCharacteristic(Characteristic.ChargingState).on('change', (change) => {
|
|
83
83
|
if (change.oldValue !== change.newValue) {
|
|
84
|
-
this.
|
|
84
|
+
this.cameraLogger.debug('Battery charging state changed to', change.newValue);
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
this.onBatteryChanged = this.cameraDevice.onBatteryChanged.subscribe(({ level, lowBattery, charging }) => {
|
|
@@ -97,7 +97,7 @@ export class CameraServices {
|
|
|
97
97
|
removeBatteryService() {
|
|
98
98
|
const batteryService = this.accessory.getService(Service.Battery);
|
|
99
99
|
if (batteryService) {
|
|
100
|
-
this.
|
|
100
|
+
this.cameraLogger.debug('Removing battery service');
|
|
101
101
|
this.onBatteryChanged?.unsubscribe();
|
|
102
102
|
this.accessory.removeService(batteryService);
|
|
103
103
|
this.services = this.services.filter((service) => service.UUID !== batteryService.UUID);
|
|
@@ -106,16 +106,16 @@ export class CameraServices {
|
|
|
106
106
|
addMotionService() {
|
|
107
107
|
let motionService = this.accessory.getService(Service.MotionSensor);
|
|
108
108
|
if (!motionService) {
|
|
109
|
-
this.
|
|
109
|
+
this.cameraLogger.debug('Adding motion service');
|
|
110
110
|
motionService = this.accessory.addService(Service.MotionSensor, 'Motion Sensor');
|
|
111
111
|
motionService.getCharacteristic(Characteristic.MotionDetected).on('change', (change) => {
|
|
112
112
|
if (change.oldValue !== change.newValue) {
|
|
113
113
|
clearTimeout(this.maxMotionTimeout);
|
|
114
|
-
this.
|
|
114
|
+
this.cameraLogger.debug('Motion state changed to', change.newValue);
|
|
115
115
|
if (change.newValue) {
|
|
116
116
|
this.maxMotionTimeout = setTimeout(() => {
|
|
117
117
|
clearTimeout(this.motionTimeout);
|
|
118
|
-
this.
|
|
118
|
+
this.cameraLogger.debug('Resetting motion state');
|
|
119
119
|
motionService?.getCharacteristic(Characteristic.MotionDetected).updateValue(false);
|
|
120
120
|
}, this.maxMotionTime);
|
|
121
121
|
}
|
|
@@ -140,7 +140,7 @@ export class CameraServices {
|
|
|
140
140
|
// private removeMotionService(): void {
|
|
141
141
|
// const motionService = this.accessory.getService(Service.MotionSensor);
|
|
142
142
|
// if (motionService) {
|
|
143
|
-
// this.
|
|
143
|
+
// this.cameraLogger.debug('Removing motion service');
|
|
144
144
|
// this.onMotionDetected?.unsubscribe();
|
|
145
145
|
// this.accessory.removeService(motionService);
|
|
146
146
|
// this.services = this.services.filter((service) => service.UUID !== motionService.UUID);
|
|
@@ -149,11 +149,11 @@ export class CameraServices {
|
|
|
149
149
|
addDoorbellService() {
|
|
150
150
|
let doorbellService = this.accessory.getService(Service.Doorbell);
|
|
151
151
|
if (!doorbellService && this.cameraDevice.hasBinarySensor) {
|
|
152
|
-
this.
|
|
152
|
+
this.cameraLogger.debug('Adding doorbell service');
|
|
153
153
|
doorbellService = this.accessory.addService(Service.Doorbell, 'Doorbell');
|
|
154
154
|
doorbellService.getCharacteristic(Characteristic.ProgrammableSwitchEvent).on('change', (change) => {
|
|
155
155
|
if (change.oldValue !== change.newValue) {
|
|
156
|
-
this.
|
|
156
|
+
this.cameraLogger.debug('Doorbell state changed to', change.newValue);
|
|
157
157
|
}
|
|
158
158
|
});
|
|
159
159
|
this.onDoorbellPressed = this.cameraDevice.onDoorbellPressed.subscribe(({ state }) => {
|
|
@@ -167,7 +167,7 @@ export class CameraServices {
|
|
|
167
167
|
removeDoorbellService() {
|
|
168
168
|
const doorbellService = this.accessory.getService(Service.Doorbell);
|
|
169
169
|
if (doorbellService) {
|
|
170
|
-
this.
|
|
170
|
+
this.cameraLogger.debug('Removing doorbell service');
|
|
171
171
|
this.onDoorbellPressed?.unsubscribe();
|
|
172
172
|
this.accessory.removeService(doorbellService);
|
|
173
173
|
this.services = this.services.filter((service) => service.UUID !== doorbellService.UUID);
|
|
@@ -176,11 +176,11 @@ export class CameraServices {
|
|
|
176
176
|
addLightService() {
|
|
177
177
|
let lightService = this.accessory.getService(Service.Lightbulb);
|
|
178
178
|
if (!lightService && this.cameraDevice.hasLight) {
|
|
179
|
-
this.
|
|
179
|
+
this.cameraLogger.debug('Adding light service');
|
|
180
180
|
lightService = this.accessory.addService(Service.Lightbulb, 'Light');
|
|
181
181
|
lightService.getCharacteristic(Characteristic.On).on('change', (change) => {
|
|
182
182
|
if (change.oldValue !== change.newValue) {
|
|
183
|
-
this.
|
|
183
|
+
this.cameraLogger.debug('Light state changed to', change.newValue);
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
186
|
this.onLightSwitched = this.cameraDevice.onLightSwitched.subscribe(({ state }) => {
|
|
@@ -192,7 +192,7 @@ export class CameraServices {
|
|
|
192
192
|
removeLightService() {
|
|
193
193
|
const lightService = this.accessory.getService(Service.Lightbulb);
|
|
194
194
|
if (lightService) {
|
|
195
|
-
this.
|
|
195
|
+
this.cameraLogger.debug('Removing light service');
|
|
196
196
|
this.onLightSwitched?.unsubscribe();
|
|
197
197
|
this.accessory.removeService(lightService);
|
|
198
198
|
this.services = this.services.filter((service) => service.UUID !== lightService.UUID);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/camera/services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAOrD,MAAM,OAAO,cAAc;IAClB,QAAQ,GAAc,EAAE,CAAC;IAExB,SAAS,CAAY;IACrB,eAAe,CAAkB;IACjC,YAAY,CAAe;IAC3B,
|
|
1
|
+
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/camera/services.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RtpSplitter } from '@camera.ui/common/cameraUtils';
|
|
2
|
-
import type { CameraDevice
|
|
1
|
+
import { FFMpegHardware, RtpSplitter } from '@camera.ui/common/cameraUtils';
|
|
2
|
+
import type { CameraDevice } from '@camera.ui/types';
|
|
3
3
|
import type { PrepareStreamRequest, StartStreamRequest } from 'hap-nodejs';
|
|
4
4
|
import type { CameraAccessory } from './accessory.js';
|
|
5
5
|
export declare class StreamingSessionWrapper {
|
|
@@ -15,13 +15,15 @@ export declare class StreamingSessionWrapper {
|
|
|
15
15
|
private streamingSession;
|
|
16
16
|
private prepareStreamRequest;
|
|
17
17
|
private ffmpegProcess?;
|
|
18
|
-
private
|
|
18
|
+
private cameraLogger;
|
|
19
19
|
private repacketizeAudioSplitter;
|
|
20
20
|
private packetReceivedSubject;
|
|
21
|
-
|
|
21
|
+
private ffmpegHardware;
|
|
22
|
+
constructor(cameraAccessory: CameraAccessory, cameraDevice: CameraDevice, prepareStreamRequest: PrepareStreamRequest, start: number, ffmpegHardware: FFMpegHardware);
|
|
22
23
|
prepare(): Promise<void>;
|
|
23
24
|
activate(startStreamRequest: StartStreamRequest): Promise<void>;
|
|
24
25
|
stop(): void;
|
|
26
|
+
private preCheck;
|
|
25
27
|
private run;
|
|
26
28
|
private startAudioSession;
|
|
27
29
|
private startRtspSession;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { timeoutPromise } from '@camera.ui/common';
|
|
2
|
-
import { RtpSplitter } from '@camera.ui/common/cameraUtils';
|
|
2
|
+
import { FFMpegHardware, RtpSplitter } from '@camera.ui/common/cameraUtils';
|
|
3
3
|
import { CameraController } from 'hap-nodejs';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { isIPv6 } from 'node:net';
|
|
6
6
|
import { networkInterfaces } from 'node:os';
|
|
7
|
-
import { interval, merge, of, Subject } from 'rxjs';
|
|
7
|
+
import { firstValueFrom, interval, merge, of, Subject } from 'rxjs';
|
|
8
8
|
import { debounceTime, delay, take } from 'rxjs/operators';
|
|
9
9
|
import { RtcpSenderInfo, RtcpSrPacket, RtpPacket, SrtcpSession, SrtpSession } from 'werift';
|
|
10
10
|
import { AudioProcessor, getSessionConfig, VideoProcessor } from '../utils/processor.js';
|
|
@@ -23,30 +23,34 @@ export class StreamingSessionWrapper {
|
|
|
23
23
|
streamingSession;
|
|
24
24
|
prepareStreamRequest;
|
|
25
25
|
ffmpegProcess;
|
|
26
|
-
|
|
26
|
+
cameraLogger;
|
|
27
27
|
repacketizeAudioSplitter = new RtpSplitter();
|
|
28
28
|
packetReceivedSubject = new Subject();
|
|
29
|
-
|
|
29
|
+
ffmpegHardware;
|
|
30
|
+
constructor(cameraAccessory, cameraDevice, prepareStreamRequest, start, ffmpegHardware) {
|
|
30
31
|
this.cameraAccessory = cameraAccessory;
|
|
31
32
|
this.cameraDevice = cameraDevice;
|
|
32
33
|
this.streamingSession = this.cameraDevice.streamSource.createSession();
|
|
33
34
|
this.prepareStreamRequest = prepareStreamRequest;
|
|
34
35
|
this.start = start;
|
|
35
|
-
this.
|
|
36
|
+
this.ffmpegHardware = ffmpegHardware;
|
|
37
|
+
this.cameraLogger = cameraDevice.logger;
|
|
36
38
|
}
|
|
37
39
|
async prepare() {
|
|
38
40
|
const { socketType, sessionID, sourceAddress, targetAddress, addressVersion } = await this.setupAddress();
|
|
39
|
-
this.
|
|
40
|
-
await
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
this.cameraLogger.debug('Preparing stream:', { sessionID, sourceAddress, targetAddress, addressVersion });
|
|
42
|
+
await Promise.all([
|
|
43
|
+
this.audioSplitter.prepare(socketType, sourceAddress),
|
|
44
|
+
this.videoSplitter.prepare(socketType, sourceAddress),
|
|
45
|
+
this.repacketizeAudioSplitter.prepare(socketType, sourceAddress),
|
|
46
|
+
]);
|
|
43
47
|
if (!this.videoSplitter.port || !this.audioSplitter.port || !this.repacketizeAudioSplitter.port) {
|
|
44
48
|
throw new Error('Failed to prepare stream');
|
|
45
49
|
}
|
|
46
50
|
const videoSrtcpSession = new SrtcpSession(getSessionConfig(this.videoSrtp));
|
|
47
51
|
let firstRtcp = false;
|
|
48
52
|
const logFirstRtcp = () => {
|
|
49
|
-
this.
|
|
53
|
+
this.cameraLogger.debug('Received RTCP packet from HomeKit');
|
|
50
54
|
};
|
|
51
55
|
this.videoSplitter.addMessageHandler(() => {
|
|
52
56
|
if (!firstRtcp) {
|
|
@@ -67,7 +71,7 @@ export class StreamingSessionWrapper {
|
|
|
67
71
|
this.streamingSession.addSubscriptions(merge(of(null).pipe(delay(15000)), this.packetReceivedSubject.asObservable())
|
|
68
72
|
.pipe(debounceTime(5000))
|
|
69
73
|
.subscribe(() => {
|
|
70
|
-
this.
|
|
74
|
+
this.cameraLogger.log(`Live stream appears to be inactive. (${this.getDurationSeconds()}s)`);
|
|
71
75
|
this.streamingSession.stop();
|
|
72
76
|
}));
|
|
73
77
|
this.streamingSession.addSubscriptions(interval(500).subscribe(() => {
|
|
@@ -89,23 +93,44 @@ export class StreamingSessionWrapper {
|
|
|
89
93
|
}));
|
|
90
94
|
}
|
|
91
95
|
async activate(startStreamRequest) {
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
this.cameraLogger.debug('Starting stream:', startStreamRequest);
|
|
97
|
+
const precheckInfo = await this.preCheck();
|
|
98
|
+
const { audioStream, videoStream, talkbackStream, isH264, prebufferingUrl } = precheckInfo;
|
|
99
|
+
this.cameraLogger.debug('Prechecked stream info:', {
|
|
100
|
+
videoStream: videoStream ?? false,
|
|
101
|
+
audioStream: audioStream ?? false,
|
|
102
|
+
talkbackStream: talkbackStream ?? false,
|
|
103
|
+
prebufferingUrl: prebufferingUrl ?? false,
|
|
104
|
+
});
|
|
105
|
+
let waitForRtcp = false;
|
|
106
|
+
if (this.isLowBandwidth(startStreamRequest)) {
|
|
107
|
+
this.cameraLogger.debug('Low bandwidth detected, waiting for initial RTCP');
|
|
108
|
+
waitForRtcp = true;
|
|
96
109
|
}
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
await this.run(startStreamRequest);
|
|
110
|
+
else if (!isH264) {
|
|
111
|
+
this.cameraLogger.debug('Stream is not H264, streaming will be transcoded, waiting for initial RTCP');
|
|
112
|
+
waitForRtcp = true;
|
|
101
113
|
}
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
|
|
114
|
+
else if (this.cameraAccessory.cameraStorage.values.transcodeStreaming) {
|
|
115
|
+
this.cameraLogger.debug('Transcode streaming enabled, waiting for initial RTCP');
|
|
116
|
+
waitForRtcp = true;
|
|
117
|
+
}
|
|
118
|
+
if (waitForRtcp) {
|
|
119
|
+
try {
|
|
120
|
+
await timeoutPromise(5000, firstValueFrom(this.packetReceivedSubject.asObservable().pipe(take(20))));
|
|
121
|
+
await this.run(startStreamRequest, precheckInfo);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
this.cameraLogger.error('Failed to receive initial RTCP packet');
|
|
125
|
+
this.streamingSession.stop();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
await this.run(startStreamRequest, precheckInfo);
|
|
105
130
|
}
|
|
106
131
|
}
|
|
107
132
|
stop() {
|
|
108
|
-
this.
|
|
133
|
+
this.cameraLogger.debug('Stopping stream');
|
|
109
134
|
this.ffmpegProcess?.kill('SIGKILL');
|
|
110
135
|
this.ffmpegProcess = undefined;
|
|
111
136
|
this.audioSplitter.close();
|
|
@@ -113,35 +138,38 @@ export class StreamingSessionWrapper {
|
|
|
113
138
|
this.videoSplitter.close();
|
|
114
139
|
this.streamingSession.stop();
|
|
115
140
|
}
|
|
116
|
-
async
|
|
117
|
-
const probeStream = await this.cameraDevice.streamSource.probeStream();
|
|
141
|
+
async preCheck() {
|
|
142
|
+
const probeStream = await this.cameraDevice.streamSource.probeStream(true);
|
|
118
143
|
const audioStream = probeStream?.audio.find((a) => a.direction === 'sendonly');
|
|
119
144
|
const videoStream = probeStream?.video;
|
|
120
145
|
const talkbackStream = probeStream?.audio.find((a) => a.direction === 'recvonly');
|
|
146
|
+
const isH264 = videoStream?.codec === 'H264';
|
|
121
147
|
const mpegtsPrebufferingState = await this.cameraDevice.streamSource.getPrebufferingState('mpegts');
|
|
122
|
-
const
|
|
123
|
-
|
|
148
|
+
const prebufferingUrl = mpegtsPrebufferingState?.url;
|
|
149
|
+
return { audioStream, videoStream, talkbackStream, isH264, prebufferingUrl };
|
|
150
|
+
}
|
|
151
|
+
async run(startStreamRequest, precheckInfo) {
|
|
152
|
+
const useRtsp = Boolean(this.cameraAccessory.cameraStorage.values.transcodeStreaming || precheckInfo.prebufferingUrl || !precheckInfo.isH264);
|
|
124
153
|
this.listenForAudioPackets(startStreamRequest);
|
|
125
154
|
this.listenForVideoPackets(startStreamRequest, useRtsp);
|
|
126
155
|
if (useRtsp) {
|
|
127
|
-
await this.startRtspSession(startStreamRequest);
|
|
156
|
+
await this.startRtspSession(startStreamRequest, precheckInfo);
|
|
128
157
|
}
|
|
129
|
-
else if (audioStream) {
|
|
158
|
+
else if (precheckInfo.audioStream) {
|
|
130
159
|
await this.startAudioSession(startStreamRequest);
|
|
131
160
|
}
|
|
132
161
|
else {
|
|
133
|
-
this.
|
|
162
|
+
this.cameraLogger.debug('No audio stream detected, skipping audio transcoding and talkback');
|
|
134
163
|
}
|
|
135
|
-
if (talkbackStream) {
|
|
136
|
-
await this.createTwoWayAudioTranscoder(startStreamRequest, talkbackStream);
|
|
164
|
+
if (precheckInfo.talkbackStream) {
|
|
165
|
+
await this.createTwoWayAudioTranscoder(startStreamRequest, precheckInfo.talkbackStream);
|
|
137
166
|
}
|
|
138
167
|
else {
|
|
139
|
-
this.
|
|
168
|
+
this.cameraLogger.debug('No talkback stream detected, skipping two-way audio');
|
|
140
169
|
}
|
|
141
170
|
}
|
|
142
171
|
async startAudioSession(startStreamRequest) {
|
|
143
172
|
const audioMtu = 400;
|
|
144
|
-
const ffmpegPath = await this.cameraAccessory.api.coreManager.getFFmpegPath();
|
|
145
173
|
const audioArgs = [];
|
|
146
174
|
if (startStreamRequest.audio.codec === "OPUS" /* AudioStreamingCodecType.OPUS */) {
|
|
147
175
|
audioArgs.push(...this.opusTranscodeArgs(startStreamRequest));
|
|
@@ -150,54 +178,48 @@ export class StreamingSessionWrapper {
|
|
|
150
178
|
audioArgs.push(...this.aacTranscodeArgs(startStreamRequest));
|
|
151
179
|
}
|
|
152
180
|
await this.streamingSession.startTranscoding({
|
|
153
|
-
ffmpegPath,
|
|
181
|
+
ffmpegPath: this.ffmpegHardware.ffmpegPath,
|
|
154
182
|
input: ['-vn', '-sn', '-dn'],
|
|
155
183
|
audio: [...audioArgs, '-f', 'rtp', `rtp://${this.repacketizeAudioSplitter.address}:${this.repacketizeAudioSplitter.port}?pkt_size=${audioMtu}`],
|
|
156
184
|
output: [],
|
|
157
185
|
logger: {
|
|
158
|
-
info: (...args) => this.
|
|
159
|
-
error: (...args) => this.
|
|
186
|
+
info: (...args) => this.cameraLogger.trace('Audio:', ...args),
|
|
187
|
+
error: (...args) => this.cameraLogger.error('Audio:', ...args),
|
|
160
188
|
},
|
|
161
189
|
});
|
|
162
190
|
}
|
|
163
|
-
async startRtspSession(startStreamRequest) {
|
|
191
|
+
async startRtspSession(startStreamRequest, precheckInfo) {
|
|
164
192
|
const audioMtu = startStreamRequest.audio.codec === "OPUS" /* AudioStreamingCodecType.OPUS */ ? 400 : 3840 / startStreamRequest.audio.sample_rate;
|
|
165
193
|
const videoMtu = startStreamRequest.video.mtu;
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const ffmpegPath = await this.cameraAccessory.api.coreManager.getFFmpegPath();
|
|
170
|
-
const mpegtsPrebufferingState = await this.cameraDevice.streamSource.getPrebufferingState('mpegts');
|
|
171
|
-
const isPrebufferingEnabled = mpegtsPrebufferingState?.url;
|
|
172
|
-
const isH264 = videoStream?.codec === 'H264';
|
|
173
|
-
const transcodeVideoStream = this.cameraAccessory.cameraStorage.values.transcodeStreaming || !isH264;
|
|
174
|
-
if (!isH264) {
|
|
175
|
-
this.logger.warn(this.cameraAccessory.logPrefix, 'Stream is not H264, streaming will be transcoded');
|
|
194
|
+
const transcodeVideoStream = this.cameraAccessory.cameraStorage.values.transcodeStreaming || !precheckInfo.isH264;
|
|
195
|
+
if (!precheckInfo.isH264) {
|
|
196
|
+
this.cameraLogger.warn('Stream is not H264, streaming will be transcoded');
|
|
176
197
|
}
|
|
177
|
-
const
|
|
198
|
+
const ffmpegBaseArgs = [
|
|
178
199
|
'-hide_banner',
|
|
179
200
|
'-loglevel',
|
|
180
201
|
'verbose',
|
|
181
202
|
'-fflags',
|
|
182
|
-
'+discardcorrupt+genpts+igndts'
|
|
203
|
+
'+discardcorrupt+genpts+igndts',
|
|
183
204
|
'-err_detect',
|
|
184
205
|
'ignore_err',
|
|
185
206
|
'-max_delay',
|
|
186
207
|
'500000',
|
|
187
208
|
'-flags',
|
|
188
209
|
'low_delay',
|
|
189
|
-
// '-thread_queue_size',
|
|
190
|
-
// '1024',
|
|
191
210
|
];
|
|
192
|
-
|
|
193
|
-
|
|
211
|
+
const ffmpegInput = [];
|
|
212
|
+
if (precheckInfo.prebufferingUrl) {
|
|
213
|
+
ffmpegInput.push('-f', 'mpegts', '-i', precheckInfo.prebufferingUrl);
|
|
194
214
|
// ffmpegInput.push('-enc_time_base', '-1', '-muxdelay', '0', '-video_track_timescale', '90000');
|
|
195
215
|
}
|
|
196
216
|
else {
|
|
197
217
|
ffmpegInput.push('-avioflags', 'direct', '-rtsp_transport', 'tcp', '-i', this.cameraDevice.streamSource.urls.rtsp.default);
|
|
198
218
|
}
|
|
199
|
-
ffmpegInput.push('-reset_timestamps', '1');
|
|
200
|
-
|
|
219
|
+
// ffmpegInput.push('-reset_timestamps', '1');
|
|
220
|
+
ffmpegInput.push('-fps_mode', 'passthrough', '-reset_timestamps', '1');
|
|
221
|
+
// ffmpegInput.push('-use_wallclock_as_timestamps', '1');
|
|
222
|
+
ffmpegInput.push('-bsf:v', 'h264_mp4toannexb');
|
|
201
223
|
const videoCodec = [];
|
|
202
224
|
if (transcodeVideoStream) {
|
|
203
225
|
const idrInterval = 4;
|
|
@@ -205,18 +227,37 @@ export class StreamingSessionWrapper {
|
|
|
205
227
|
const videoBitrate = startStreamRequest.video.max_bit_rate;
|
|
206
228
|
const bufsize = videoBitrate * 2;
|
|
207
229
|
const maxrate = videoBitrate;
|
|
208
|
-
|
|
209
|
-
videoFilters
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
230
|
+
let vcodec = 'libx264';
|
|
231
|
+
const videoFilters = [`fps=fps=${fps}`];
|
|
232
|
+
if (this.cameraAccessory.cameraStorage.values.useHardwareAcceleration) {
|
|
233
|
+
this.cameraLogger.debug('Checking hardware acceleration support...');
|
|
234
|
+
await this.ffmpegHardware.initHWSupport();
|
|
235
|
+
const supportedCodecs = this.ffmpegHardware.hwCodecSupport;
|
|
236
|
+
const preferredH264Codecs = FFMpegHardware.H264Codecs;
|
|
237
|
+
const codec = preferredH264Codecs.find((c) => supportedCodecs.includes(c));
|
|
238
|
+
if (codec) {
|
|
239
|
+
vcodec = codec.codecName;
|
|
240
|
+
const vf = { width: startStreamRequest.video.width, height: startStreamRequest.video.height };
|
|
241
|
+
const reqHeight = startStreamRequest.video.height;
|
|
242
|
+
const filter = this.ffmpegHardware.hwMaxResFilter(codec, vf, reqHeight, true);
|
|
243
|
+
const deviceArgs = this.ffmpegHardware.hwDeviceArgs(codec);
|
|
244
|
+
ffmpegBaseArgs.push(...this.ffmpegHardware.hwDeviceInit([], codec));
|
|
245
|
+
videoFilters.unshift(...filter.split(','));
|
|
246
|
+
ffmpegInput.push(...deviceArgs);
|
|
247
|
+
this.cameraLogger.debug(`Using hardware acceleration with codec: ${vcodec}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
videoCodec.push('-vcodec', vcodec, '-preset', 'veryfast', '-profile:v', this.getH264Profile(startStreamRequest.video.profile), '-level:v', this.getH264Level(startStreamRequest.video.level), '-noautoscale', '-bf', '0', '-vf', videoFilters.join(','), '-force_key_frames', `expr:gte(t,n_forced*${idrInterval})`,
|
|
251
|
+
// '-g:v',
|
|
252
|
+
// (fps * idrInterval).toString(),
|
|
253
|
+
'-bufsize', bufsize.toString() + 'k', '-maxrate', maxrate.toString() + 'k', '-b:v', videoBitrate.toString() + 'k', '-pix_fmt', 'yuv420p');
|
|
213
254
|
}
|
|
214
255
|
else {
|
|
215
256
|
videoCodec.push('-vcodec', 'copy');
|
|
216
257
|
}
|
|
217
258
|
const videoArgs = [...videoCodec, '-an', '-sn', '-dn', '-f', 'rtp', `rtp://${this.videoSplitter.address}:${this.videoSplitter.port}?pkt_size=${videoMtu}`];
|
|
218
259
|
const audioArgs = [];
|
|
219
|
-
if (audioStream) {
|
|
260
|
+
if (precheckInfo.audioStream) {
|
|
220
261
|
const audioCodecArgs = [];
|
|
221
262
|
if (startStreamRequest.audio.codec === "OPUS" /* AudioStreamingCodecType.OPUS */) {
|
|
222
263
|
audioCodecArgs.push(...this.opusTranscodeArgs(startStreamRequest));
|
|
@@ -227,24 +268,24 @@ export class StreamingSessionWrapper {
|
|
|
227
268
|
audioArgs.push('-async', '1', ...audioCodecArgs, '-vn', '-sn', '-dn', '-f', 'rtp', `rtp://${this.repacketizeAudioSplitter.address}:${this.repacketizeAudioSplitter.port}?pkt_size=${audioMtu}`);
|
|
228
269
|
}
|
|
229
270
|
const ffmpegOutput = [];
|
|
230
|
-
const ffmpegArgs = [...ffmpegInput, ...videoArgs, ...audioArgs, ...ffmpegOutput];
|
|
231
|
-
this.
|
|
232
|
-
this.ffmpegProcess = spawn(ffmpegPath,
|
|
271
|
+
const ffmpegArgs = [...ffmpegBaseArgs, ...ffmpegInput, ...videoArgs, ...audioArgs, ...ffmpegOutput];
|
|
272
|
+
this.cameraLogger.debug('Starting RTSP session with ffmpeg', this.ffmpegHardware.ffmpegPath, ffmpegArgs.join(' '));
|
|
273
|
+
this.ffmpegProcess = spawn(this.ffmpegHardware.ffmpegPath, ffmpegArgs);
|
|
233
274
|
this.ffmpegProcess.stderr?.on('data', (data) => {
|
|
234
275
|
const line = data.toString().trim();
|
|
235
276
|
line.split('\n').forEach((line) => {
|
|
236
|
-
this.
|
|
277
|
+
this.cameraLogger.trace(line);
|
|
237
278
|
});
|
|
238
279
|
});
|
|
239
280
|
this.ffmpegProcess.on('error', (error) => {
|
|
240
|
-
this.
|
|
281
|
+
this.cameraLogger.error('Error starting ffmpeg process', error);
|
|
241
282
|
});
|
|
242
283
|
this.ffmpegProcess.on('exit', (code, signal) => {
|
|
243
284
|
if (code !== 0 && signal !== 'SIGKILL') {
|
|
244
|
-
this.
|
|
285
|
+
this.cameraLogger.error(`FFmpeg process exited with code ${code} (${signal})`);
|
|
245
286
|
}
|
|
246
287
|
else {
|
|
247
|
-
this.
|
|
288
|
+
this.cameraLogger.debug('FFmpeg process exited');
|
|
248
289
|
}
|
|
249
290
|
});
|
|
250
291
|
}
|
|
@@ -293,7 +334,6 @@ export class StreamingSessionWrapper {
|
|
|
293
334
|
}
|
|
294
335
|
return null;
|
|
295
336
|
});
|
|
296
|
-
const ffmpegPath = await this.cameraAccessory.api.coreManager.getFFmpegPath();
|
|
297
337
|
const returnAudioTranscoder = new ReturnAudioTranscoder({
|
|
298
338
|
prepareStreamRequest: this.prepareStreamRequest,
|
|
299
339
|
startStreamRequest,
|
|
@@ -302,11 +342,11 @@ export class StreamingSessionWrapper {
|
|
|
302
342
|
rtcpPort: 0,
|
|
303
343
|
},
|
|
304
344
|
outputArgs: [...returnAudioCodecs, '-flags', '+global_header', '-f', 'rtp', `rtp://${returnAudioTranscodedSplitter.address}:${returnAudioTranscodedSplitter.port}`],
|
|
305
|
-
ffmpegPath,
|
|
345
|
+
ffmpegPath: this.ffmpegHardware.ffmpegPath,
|
|
306
346
|
returnAudioSplitter: this.audioSplitter,
|
|
307
347
|
logger: {
|
|
308
|
-
info: (...args) => this.
|
|
309
|
-
error: (...args) => this.
|
|
348
|
+
info: (...args) => this.cameraLogger.trace('Return Audio:', ...args),
|
|
349
|
+
error: (...args) => this.cameraLogger.error('Return Audio:', ...args),
|
|
310
350
|
},
|
|
311
351
|
});
|
|
312
352
|
this.streamingSession.onCallEnded.pipe(take(1)).subscribe(() => {
|
|
@@ -320,11 +360,11 @@ export class StreamingSessionWrapper {
|
|
|
320
360
|
const { targetAddress, video: { port }, } = this.prepareStreamRequest;
|
|
321
361
|
const { video: { mtu, pt: payloadType }, } = startStreamRequest;
|
|
322
362
|
if (!rtsp) {
|
|
323
|
-
const processor = new VideoProcessor(this.cameraAccessory, this.streamingSession, this.videoSsrc, this.videoSrtp, this.videoSplitter, targetAddress, port, mtu, payloadType, this.
|
|
363
|
+
const processor = new VideoProcessor(this.cameraAccessory, this.streamingSession, this.videoSsrc, this.videoSrtp, this.videoSplitter, targetAddress, port, mtu, payloadType, this.cameraLogger);
|
|
324
364
|
this.streamingSession.addSubscriptions(this.streamingSession.onVideoRtp.subscribe((rtp) => {
|
|
325
365
|
if (!sentVideo) {
|
|
326
366
|
sentVideo = true;
|
|
327
|
-
this.
|
|
367
|
+
this.cameraLogger.debug(`Received stream data (${this.getDurationSeconds()}s)`);
|
|
328
368
|
}
|
|
329
369
|
processor.processPacket(rtp);
|
|
330
370
|
}));
|
|
@@ -335,7 +375,7 @@ export class StreamingSessionWrapper {
|
|
|
335
375
|
this.videoSplitter.addMessageHandler(({ message }) => {
|
|
336
376
|
if (!sentVideo) {
|
|
337
377
|
sentVideo = true;
|
|
338
|
-
this.
|
|
378
|
+
this.cameraLogger.debug(`Received stream data (${this.getDurationSeconds()}s)`);
|
|
339
379
|
}
|
|
340
380
|
const rtp = RtpPacket.deSerialize(message);
|
|
341
381
|
rtp.header.ssrc = this.videoSsrc;
|
|
@@ -353,7 +393,7 @@ export class StreamingSessionWrapper {
|
|
|
353
393
|
const { targetAddress, audio: { port: audioPort }, } = this.prepareStreamRequest;
|
|
354
394
|
const { audio: { pt: payloadType, codec, packet_time: packetTime, sample_rate: sampleRate }, } = startStreamRequest;
|
|
355
395
|
const isOpus = codec === "OPUS" /* AudioStreamingCodecType.OPUS */;
|
|
356
|
-
const processor = new AudioProcessor(this.cameraAccessory, this.audioSsrc, this.audioSrtp, this.audioSplitter, targetAddress, audioPort, payloadType, sampleRate, packetTime, isOpus, this.
|
|
396
|
+
const processor = new AudioProcessor(this.cameraAccessory, this.audioSsrc, this.audioSrtp, this.audioSplitter, targetAddress, audioPort, payloadType, sampleRate, packetTime, isOpus, this.cameraLogger);
|
|
357
397
|
this.repacketizeAudioSplitter.addMessageHandler(({ message }) => {
|
|
358
398
|
processor.processPacket(message);
|
|
359
399
|
return null;
|
|
@@ -369,7 +409,7 @@ export class StreamingSessionWrapper {
|
|
|
369
409
|
const serverAddresses = await this.cameraAccessory.api.coreManager.getServerAddresses();
|
|
370
410
|
const found = serverAddresses.find((address) => address.includes(sourceAddress));
|
|
371
411
|
if (!found && serverAddresses.length) {
|
|
372
|
-
this.
|
|
412
|
+
this.cameraLogger.debug(`Source address ${sourceAddress} not found in server addresses`);
|
|
373
413
|
const infos = Object.values(networkInterfaces())
|
|
374
414
|
.flat()
|
|
375
415
|
.map((i) => i?.address);
|
|
@@ -383,12 +423,12 @@ export class StreamingSessionWrapper {
|
|
|
383
423
|
});
|
|
384
424
|
const targetAddressFound = infos.find((address) => targetAddresses.includes(address));
|
|
385
425
|
if (targetAddressFound) {
|
|
386
|
-
this.
|
|
426
|
+
this.cameraLogger.debug(`Using target address ${targetAddressFound}`);
|
|
387
427
|
sourceAddress = targetAddressFound;
|
|
388
428
|
}
|
|
389
429
|
}
|
|
390
430
|
else if (found) {
|
|
391
|
-
this.
|
|
431
|
+
this.cameraLogger.debug(`Using source address ${sourceAddress}`);
|
|
392
432
|
}
|
|
393
433
|
if (isIPv6(sourceAddress)) {
|
|
394
434
|
sourceAddress = sourceAddress.split('%')[0];
|