buzz-me-in 2.2.0 → 2.2.1
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/homekit.js +16 -119
- package/package.json +2 -2
- package/src/core/inbound-ffmpeg-args.js +163 -0
package/homekit.js
CHANGED
|
@@ -26,6 +26,7 @@ const { spawn } = require('child_process');
|
|
|
26
26
|
const config = require('./src/core/config');
|
|
27
27
|
const state = require('./src/core/state');
|
|
28
28
|
const { sendDtmfSequence, sendMulawAudio } = require('./src/core/mulaw-audio');
|
|
29
|
+
const { buildInboundFfmpegArgs } = require('./src/core/inbound-ffmpeg-args');
|
|
29
30
|
const {
|
|
30
31
|
removeReturnAudioSdp,
|
|
31
32
|
removeSdpDir,
|
|
@@ -281,127 +282,23 @@ function _startSession(sessionID, s, request, callback) {
|
|
|
281
282
|
// Reopening the live view inside the grace window means the user is not
|
|
282
283
|
// finished after all.
|
|
283
284
|
cancelPendingHangUp('homekit-session-restarted');
|
|
284
|
-
const videoParams = srtpParams(s.hkVideoKey, s.hkVideoSalt);
|
|
285
|
-
const audioParams = srtpParams(s.hkAudioKey, s.hkAudioSalt);
|
|
286
|
-
const video = request.video;
|
|
287
285
|
const audio = request.audio;
|
|
288
|
-
const videoBitrate = Math.max(64, video.max_bit_rate || 200);
|
|
289
|
-
const videoBufferSize = Math.max(videoBitrate * 2, 128);
|
|
290
|
-
const mtu = video.mtu || 1316;
|
|
291
286
|
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// requests it when the live view is first opened; without frequent IDRs
|
|
308
|
-
// the video stays blank until the next natural keyframe.
|
|
309
|
-
//
|
|
310
|
-
// Two separate SRTP outputs — no muxing, no pts coupling between streams.
|
|
311
|
-
// -------------------------------------------------------------------------
|
|
312
|
-
const ffIn = spawn('ffmpeg', [
|
|
313
|
-
'-y',
|
|
314
|
-
'-loglevel',
|
|
315
|
-
'warning',
|
|
316
|
-
|
|
317
|
-
// ---- Input 0: raw mulaw from Twilio ----
|
|
318
|
-
'-thread_queue_size',
|
|
319
|
-
'512',
|
|
320
|
-
'-f',
|
|
321
|
-
'mulaw',
|
|
322
|
-
'-ar',
|
|
323
|
-
'8000',
|
|
324
|
-
'-ch_layout',
|
|
325
|
-
'mono',
|
|
326
|
-
'-i',
|
|
327
|
-
'pipe:0',
|
|
328
|
-
|
|
329
|
-
// ---- Input 1: blank video ----
|
|
330
|
-
'-f',
|
|
331
|
-
'lavfi',
|
|
332
|
-
'-i',
|
|
333
|
-
'color=black:s=1280x720:r=15',
|
|
334
|
-
|
|
335
|
-
// ---- Video output → HomeKit SRTP ----
|
|
336
|
-
'-map',
|
|
337
|
-
'1:v',
|
|
338
|
-
'-c:v',
|
|
339
|
-
'libx264',
|
|
340
|
-
'-profile:v',
|
|
341
|
-
'baseline',
|
|
342
|
-
'-level:v',
|
|
343
|
-
'3.1',
|
|
344
|
-
'-preset',
|
|
345
|
-
'ultrafast',
|
|
346
|
-
'-tune',
|
|
347
|
-
'zerolatency',
|
|
348
|
-
'-pix_fmt',
|
|
349
|
-
'yuv420p',
|
|
350
|
-
'-b:v',
|
|
351
|
-
`${videoBitrate}k`,
|
|
352
|
-
'-maxrate',
|
|
353
|
-
`${videoBitrate}k`,
|
|
354
|
-
'-bufsize',
|
|
355
|
-
`${videoBufferSize}k`,
|
|
356
|
-
'-g',
|
|
357
|
-
String(video.fps || 15),
|
|
358
|
-
'-keyint_min',
|
|
359
|
-
String(video.fps || 15),
|
|
360
|
-
'-payload_type',
|
|
361
|
-
String(video.pt),
|
|
362
|
-
'-ssrc',
|
|
363
|
-
String(s.videoSsrc),
|
|
364
|
-
'-f',
|
|
365
|
-
'rtp',
|
|
366
|
-
'-srtp_out_suite',
|
|
367
|
-
'AES_CM_128_HMAC_SHA1_80',
|
|
368
|
-
'-srtp_out_params',
|
|
369
|
-
videoParams,
|
|
370
|
-
`srtp://${s.targetAddress}:${s.hkVideoPort}?rtcpport=${s.hkVideoPort}&localrtcpport=${s.hkVideoPort}&pkt_size=${mtu}`,
|
|
371
|
-
|
|
372
|
-
// ---- Audio output → HomeKit SRTP (Opus/16kHz) ----
|
|
373
|
-
//
|
|
374
|
-
// Note on codec choice: libopus is in every standard ffmpeg build.
|
|
375
|
-
// If you prefer AAC-ELD (required by some older HomeKit devices), compile
|
|
376
|
-
// ffmpeg with --enable-libfdk-aac --enable-nonfree and change:
|
|
377
|
-
// '-c:a', 'libfdk_aac', '-profile:a', 'aac_eld',
|
|
378
|
-
// and update streamingOptions.audio.codecs below to AAC_ELD.
|
|
379
|
-
'-map',
|
|
380
|
-
'0:a',
|
|
381
|
-
'-c:a',
|
|
382
|
-
'libopus',
|
|
383
|
-
'-ar',
|
|
384
|
-
'16000',
|
|
385
|
-
'-ac',
|
|
386
|
-
'1',
|
|
387
|
-
'-b:a',
|
|
388
|
-
'24k',
|
|
389
|
-
'-application',
|
|
390
|
-
'voip',
|
|
391
|
-
'-frame_duration',
|
|
392
|
-
'20',
|
|
393
|
-
'-payload_type',
|
|
394
|
-
String(audio.pt),
|
|
395
|
-
'-ssrc',
|
|
396
|
-
String(s.audioSsrc),
|
|
397
|
-
'-f',
|
|
398
|
-
'rtp',
|
|
399
|
-
'-srtp_out_suite',
|
|
400
|
-
'AES_CM_128_HMAC_SHA1_80',
|
|
401
|
-
'-srtp_out_params',
|
|
402
|
-
audioParams,
|
|
403
|
-
`srtp://${s.targetAddress}:${s.hkAudioPort}?rtcpport=${s.hkAudioPort}&localrtcpport=${s.hkAudioPort}`,
|
|
404
|
-
]);
|
|
287
|
+
// Inbound ffmpeg: see src/core/inbound-ffmpeg-args.js for the pipeline.
|
|
288
|
+
const ffIn = spawn(
|
|
289
|
+
'ffmpeg',
|
|
290
|
+
buildInboundFfmpegArgs({
|
|
291
|
+
video: request.video,
|
|
292
|
+
audio,
|
|
293
|
+
videoParams: srtpParams(s.hkVideoKey, s.hkVideoSalt),
|
|
294
|
+
audioParams: srtpParams(s.hkAudioKey, s.hkAudioSalt),
|
|
295
|
+
targetAddress: s.targetAddress,
|
|
296
|
+
hkVideoPort: s.hkVideoPort,
|
|
297
|
+
hkAudioPort: s.hkAudioPort,
|
|
298
|
+
videoSsrc: s.videoSsrc,
|
|
299
|
+
audioSsrc: s.audioSsrc,
|
|
300
|
+
})
|
|
301
|
+
);
|
|
405
302
|
|
|
406
303
|
ffIn.stderr.on('data', (d) => {
|
|
407
304
|
logger.warn('Inbound ffmpeg stderr', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buzz-me-in",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "HomeKit-compatible apartment intercom via Twilio media streams",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"homekit",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"main": "server.js",
|
|
17
17
|
"bin": {
|
|
18
|
-
"buzz-me-in": "
|
|
18
|
+
"buzz-me-in": "server.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"server.js",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Argument list for the inbound ffmpeg process: Twilio mu-law on stdin →
|
|
5
|
+
* Opus/SRTP audio plus a synthetic H.264/SRTP video track to the controller.
|
|
6
|
+
* Kept apart from homekit.js so the arguments can be tested without spawning
|
|
7
|
+
* ffmpeg or standing up a HomeKit session.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {{
|
|
12
|
+
* video: { max_bit_rate?: number, mtu?: number, fps?: number, pt: number },
|
|
13
|
+
* audio: { pt: number },
|
|
14
|
+
* videoParams: string,
|
|
15
|
+
* audioParams: string,
|
|
16
|
+
* targetAddress: string,
|
|
17
|
+
* hkVideoPort: number,
|
|
18
|
+
* hkAudioPort: number,
|
|
19
|
+
* videoSsrc: number,
|
|
20
|
+
* audioSsrc: number,
|
|
21
|
+
* }} input
|
|
22
|
+
* @returns {string[]}
|
|
23
|
+
*/
|
|
24
|
+
function buildInboundFfmpegArgs(input) {
|
|
25
|
+
const { video, audio, videoParams, audioParams } = input;
|
|
26
|
+
const videoBitrate = Math.max(64, video.max_bit_rate || 200);
|
|
27
|
+
const videoBufferSize = Math.max(videoBitrate * 2, 128);
|
|
28
|
+
const mtu = video.mtu || 1316;
|
|
29
|
+
|
|
30
|
+
// -------------------------------------------------------------------------
|
|
31
|
+
// Inbound ffmpeg
|
|
32
|
+
//
|
|
33
|
+
// Input 0 – raw mulaw/8kHz from Twilio via stdin
|
|
34
|
+
// Let the raw audio demuxer derive PTS from sample count. Using wall-clock
|
|
35
|
+
// timestamps breaks when ffmpeg drains the buffered startup audio burst.
|
|
36
|
+
// -ch_layout declares mono rather than -ac counting one channel: a count
|
|
37
|
+
// leaves the layout unset, so the decoder guesses it and logs "Guessed
|
|
38
|
+
// Channel Layout: mono" on every call. Our stderr handler is a warn, so
|
|
39
|
+
// that guess looked like a fault in the journal. Needs ffmpeg >= 5.1.
|
|
40
|
+
// -probesize 32 / -analyzeduration 0 skip stream probing: format, rate
|
|
41
|
+
// and layout are all declared, but by default ffmpeg still reads ~3 s of
|
|
42
|
+
// stdin before it outputs anything, then sends that backlog to the
|
|
43
|
+
// controller in one burst, and the caller's first words are lost (#102).
|
|
44
|
+
//
|
|
45
|
+
// Input 1 – synthetic black video (lavfi color source)
|
|
46
|
+
// Generates H.264 Baseline/3.1 frames at 15 fps.
|
|
47
|
+
// HomeKit requires a video track; there is no real camera feed.
|
|
48
|
+
// keyint_min=15 / -g 15 forces an IDR frame every second — HomeKit
|
|
49
|
+
// requests it when the live view is first opened; without frequent IDRs
|
|
50
|
+
// the video stays blank until the next natural keyframe.
|
|
51
|
+
//
|
|
52
|
+
// Two separate SRTP outputs — no muxing, no pts coupling between streams.
|
|
53
|
+
// -------------------------------------------------------------------------
|
|
54
|
+
return [
|
|
55
|
+
'-y',
|
|
56
|
+
'-loglevel',
|
|
57
|
+
'warning',
|
|
58
|
+
|
|
59
|
+
// ---- Input 0: raw mulaw from Twilio ----
|
|
60
|
+
'-thread_queue_size',
|
|
61
|
+
'512',
|
|
62
|
+
'-probesize',
|
|
63
|
+
'32',
|
|
64
|
+
'-analyzeduration',
|
|
65
|
+
'0',
|
|
66
|
+
'-f',
|
|
67
|
+
'mulaw',
|
|
68
|
+
'-ar',
|
|
69
|
+
'8000',
|
|
70
|
+
'-ch_layout',
|
|
71
|
+
'mono',
|
|
72
|
+
'-i',
|
|
73
|
+
'pipe:0',
|
|
74
|
+
|
|
75
|
+
// ---- Input 1: blank video ----
|
|
76
|
+
'-f',
|
|
77
|
+
'lavfi',
|
|
78
|
+
'-i',
|
|
79
|
+
'color=black:s=1280x720:r=15',
|
|
80
|
+
|
|
81
|
+
// ---- Video output → HomeKit SRTP ----
|
|
82
|
+
'-map',
|
|
83
|
+
'1:v',
|
|
84
|
+
'-c:v',
|
|
85
|
+
'libx264',
|
|
86
|
+
'-profile:v',
|
|
87
|
+
'baseline',
|
|
88
|
+
'-level:v',
|
|
89
|
+
'3.1',
|
|
90
|
+
'-preset',
|
|
91
|
+
'ultrafast',
|
|
92
|
+
'-tune',
|
|
93
|
+
'zerolatency',
|
|
94
|
+
'-pix_fmt',
|
|
95
|
+
'yuv420p',
|
|
96
|
+
'-b:v',
|
|
97
|
+
`${videoBitrate}k`,
|
|
98
|
+
'-maxrate',
|
|
99
|
+
`${videoBitrate}k`,
|
|
100
|
+
'-bufsize',
|
|
101
|
+
`${videoBufferSize}k`,
|
|
102
|
+
'-g',
|
|
103
|
+
String(video.fps || 15),
|
|
104
|
+
'-keyint_min',
|
|
105
|
+
String(video.fps || 15),
|
|
106
|
+
'-payload_type',
|
|
107
|
+
String(video.pt),
|
|
108
|
+
'-ssrc',
|
|
109
|
+
String(input.videoSsrc),
|
|
110
|
+
'-f',
|
|
111
|
+
'rtp',
|
|
112
|
+
'-srtp_out_suite',
|
|
113
|
+
'AES_CM_128_HMAC_SHA1_80',
|
|
114
|
+
'-srtp_out_params',
|
|
115
|
+
videoParams,
|
|
116
|
+
`srtp://${input.targetAddress}:${input.hkVideoPort}?rtcpport=${input.hkVideoPort}&localrtcpport=${input.hkVideoPort}&pkt_size=${mtu}`,
|
|
117
|
+
|
|
118
|
+
// ---- Audio output → HomeKit SRTP (Opus/16kHz) ----
|
|
119
|
+
//
|
|
120
|
+
// Note on codec choice: libopus is in every standard ffmpeg build.
|
|
121
|
+
// If you prefer AAC-ELD (required by some older HomeKit devices), compile
|
|
122
|
+
// ffmpeg with --enable-libfdk-aac --enable-nonfree and change:
|
|
123
|
+
// '-c:a', 'libfdk_aac', '-profile:a', 'aac_eld',
|
|
124
|
+
// and update streamingOptions.audio.codecs below to AAC_ELD.
|
|
125
|
+
//
|
|
126
|
+
// RTP clock: HomeKit clocks Opus RTP timestamps at the negotiated sample
|
|
127
|
+
// rate (16 kHz, 320 ticks per 20 ms packet), which is also what the iPhone
|
|
128
|
+
// sends on the return leg. ffmpeg's RTP muxer always uses RFC 7587's
|
|
129
|
+
// 48 kHz for Opus (960 ticks), so the controller saw audio arriving three
|
|
130
|
+
// times slower than its timestamps said, and dropped words (#102).
|
|
131
|
+
// libopus stamps packets in 1/16000; relabelling that time base as
|
|
132
|
+
// 1/48000 stops the muxer rescaling, so each packet advances by 320.
|
|
133
|
+
'-map',
|
|
134
|
+
'0:a',
|
|
135
|
+
'-c:a',
|
|
136
|
+
'libopus',
|
|
137
|
+
'-ar',
|
|
138
|
+
'16000',
|
|
139
|
+
'-ac',
|
|
140
|
+
'1',
|
|
141
|
+
'-b:a',
|
|
142
|
+
'24k',
|
|
143
|
+
'-application',
|
|
144
|
+
'voip',
|
|
145
|
+
'-frame_duration',
|
|
146
|
+
'20',
|
|
147
|
+
'-bsf:a',
|
|
148
|
+
'setts=time_base=1/48000',
|
|
149
|
+
'-payload_type',
|
|
150
|
+
String(audio.pt),
|
|
151
|
+
'-ssrc',
|
|
152
|
+
String(input.audioSsrc),
|
|
153
|
+
'-f',
|
|
154
|
+
'rtp',
|
|
155
|
+
'-srtp_out_suite',
|
|
156
|
+
'AES_CM_128_HMAC_SHA1_80',
|
|
157
|
+
'-srtp_out_params',
|
|
158
|
+
audioParams,
|
|
159
|
+
`srtp://${input.targetAddress}:${input.hkAudioPort}?rtcpport=${input.hkAudioPort}&localrtcpport=${input.hkAudioPort}`,
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = { buildInboundFfmpegArgs };
|