Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 70 additions & 29 deletions plugins/doorbird/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ class DoorbirdCamera extends ScryptedDeviceBase implements Intercom, Camera, Vid
title: 'Speech Enhancement',
value: this.storage.getItem('audioSpeechEnhancement') === 'true',
description: 'Apply band filtering and dynamic normalization to both audio streams.',
},
{
key: 'continuousAudio',
type: 'boolean',
subgroup: 'Advanced',
title: 'Continuous Audio',
value: (this.storage.getItem('continuousAudio') ?? 'true') === 'true',
description: 'Continuously stream audio from the Doorbird microphone. '
+ 'When disabled, audio is only available during two-way intercom. '
+ 'Enable this for HomeKit Secure Video recording audio and live view audio.',
}
];
}
Expand Down Expand Up @@ -476,35 +486,56 @@ class DoorbirdCamera extends ScryptedDeviceBase implements Intercom, Camera, Vid
if (this.audioSilenceProcess)
return;

this.console.log('Doorbird: starting audio silence generator...')

const ffmpegPath = await mediaManager.getFFmpegPath();
const ffmpegArgs = [
// Suppress printing the FFmpeg banner.
'-hide_banner',
// Disable periodic progress/statistics logging.
'-nostats',
// Read input at its native frame rate to ensure real-time processing.
'-re',
// Use the lavfi (libavfilter) virtual input device.
'-f', 'lavfi',
// Specify the input source as a null audio source (silence) with a sample rate of 8000 Hz and mono channel layout.
'-i', 'anullsrc=r=8000:cl=mono',

// --- Low-latency Output Flags ---
// Bypass FFmpeg's internal I/O buffering, writing directly to the output pipe.
'-avioflags', 'direct',
// Force flushing packets to the output immediately after encoding.
'-flush_packets', '1',
// Set the maximum demux-decode delay to zero, preventing buffering in the muxer.
'-muxdelay', '0',

// --- Output Format Specification ---
// Force the output container format to raw µ-law.
'-f', 'mulaw',
// Output the processed audio to file descriptor 3 (the pipe).
'pipe:3'
];
let ffmpegArgs: string[];

if (this.getContinuousAudio()) {
// Connect to the Doorbird's microphone for continuous audio.
// The Doorbird does not include audio in its RTSP video stream — audio is only
// available via the separate HTTP audio-receive.cgi endpoint. Without this,
// HomeKit live view and HKSV recordings have no audio.
const audioRxUrl = `${this.getHttpBaseAddress()}/bha-api/audio-receive.cgi`;
this.console.log('Doorbird: starting continuous audio receiver...');
ffmpegArgs = [
'-hide_banner',
'-nostats',
'-fflags', '+flush_packets+nobuffer',
'-analyzeduration', '0',
'-probesize', '32',
// Auto-reconnect if the HTTP stream drops or the Doorbird reboots.
'-reconnect', '1',
'-reconnect_at_eof', '1',
'-reconnect_streamed', '1',
'-reconnect_delay_max', '5',
'-re',
'-ar', '8000',
'-ac', '1',
'-f', 'mulaw',
'-i', audioRxUrl,
'-flags', '+global_header+low_delay',
'-avioflags', 'direct',
'-flush_packets', '1',
'-muxdelay', '0',
'-acodec', 'copy',
'-f', 'mulaw',
'pipe:3'
];
} else {
// Generate silence as a placeholder audio track.
this.console.log('Doorbird: starting audio silence generator...');
ffmpegArgs = [
'-hide_banner',
'-nostats',
'-re',
'-f', 'lavfi',
'-i', 'anullsrc=r=8000:cl=mono',
'-avioflags', 'direct',
'-flush_packets', '1',
'-muxdelay', '0',
'-f', 'mulaw',
'pipe:3'
];
}

safePrintFFmpegArguments(console, ffmpegArgs);
const cp = child_process.spawn(ffmpegPath, ffmpegArgs, {
Expand All @@ -514,8 +545,14 @@ class DoorbirdCamera extends ScryptedDeviceBase implements Intercom, Camera, Vid
ffmpegLogInitialOutput(console, cp);

cp.on('exit', () => {
this.console.log('Doorbird: audio silence generator ended.')
this.audioSilenceProcess = undefined;
if (this.getContinuousAudio() && this.audioRXClientSocket) {
// Auto-restart if the connection drops and the socket is still active.
this.console.log('Doorbird: continuous audio receiver ended, restarting...');
setTimeout(() => this.startSilenceGenerator(), 2000);
} else {
this.console.log('Doorbird: audio silence generator ended.');
}
});
cp.stdout.on('data', data => this.console.log(data.toString()));
cp.stderr.on('data', data => this.console.log(data.toString()));
Expand Down Expand Up @@ -620,6 +657,10 @@ class DoorbirdCamera extends ScryptedDeviceBase implements Intercom, Camera, Vid
return this.storage.getItem('audioSpeechEnhancement') === 'true';
}

getContinuousAudio(): boolean {
return (this.storage.getItem('continuousAudio') ?? 'true') === 'true';
}

private getAudioFilter() {
const filters: string[] = [];
if (this.getAudioDenoise()) {
Expand Down