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 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
- // Inbound ffmpeg
294
- //
295
- // Input 0 – raw mulaw/8kHz from Twilio via stdin
296
- // Let the raw audio demuxer derive PTS from sample count. Using wall-clock
297
- // timestamps breaks when ffmpeg drains the buffered startup audio burst.
298
- // -ch_layout declares mono rather than -ac counting one channel: a count
299
- // leaves the layout unset, so the decoder guesses it and logs "Guessed
300
- // Channel Layout: mono" on every call. Our stderr handler is a warn, so
301
- // that guess looked like a fault in the journal. Needs ffmpeg >= 5.1.
302
- //
303
- // Input 1 – synthetic black video (lavfi color source)
304
- // Generates H.264 Baseline/3.1 frames at 15 fps.
305
- // HomeKit requires a video track; there is no real camera feed.
306
- // keyint_min=15 / -g 15 forces an IDR frame every second — HomeKit
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.0",
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": "./server.js"
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 };