mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-11-08 13:43:04 -05:00
ZoneMinder has carried audio for years without ever listening to it: the packets go into the event and nothing reads them. AudioDetector decodes them in the capture thread and reports a 0-100 level, which Analyse() turns into score alongside motion, ONVIF and Amcrest. The level is dBFS-derived, not a raw amplitude ratio. Linear RMS is unusable as a setting: ordinary speech sits at 1-3% of full scale, so every sound worth catching would be crammed into the bottom two points of the range and no operator could tune it. Mapping -60..0 dBFS onto 0..100 puts speech around 43 instead. Three columns on Monitors: AudioDetection to enable it, AudioThreshold for the level to alarm at, and AudioAlarmScore for what it contributes. AudioAlarmScore defaults to 9, matching what an ONVIF or Amcrest alarm already adds. A threshold of 0 means off, so enabling detection without choosing a threshold cannot alarm on silence -- a plain level >= threshold test would alarm on every packet in that state. The level and the alarm flag go in SharedData's two spare bytes, renamed from reserved1/reserved2. Offsets and sizes are unchanged, so the 888-byte cross-process layout and the Memory.pm and Monitor.php offset tables all still agree; the readers are renamed in the same commit so the level is available to the web UI. The decoder is opened lazily on the first audio packet rather than at camera setup, so a monitor with detection off never carries one and a stream that gains audio on reconnect still gets picked up. Scoring reads the capture thread's most recent level rather than scoring per audio packet, because the score belongs to a video frame and audio packets do not arrive in step with them. Tested: 242 assertions over the pure helpers - the RMS of both sample formats, the dB scale's monotonicity and endpoints, full-scale clamping of decoder overshoot, and the threshold-0 case. The repo's Catch2 harness needs v3 and only v2 is installed here, so tests/zm_audio_detector.cpp was compiled against a v2 shim to check it, and an identical set of assertions was run standalone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvTCzCbvGt8xKQRNCSA7o8
69 lines
2.4 KiB
SQL
69 lines
2.4 KiB
SQL
--
|
|
-- Add audio detection settings to Monitors.
|
|
--
|
|
-- ZoneMinder has always carried audio without listening to it: the packets go
|
|
-- into the event and nothing reads them. These three columns let a monitor
|
|
-- score on how loud it is, alongside motion.
|
|
--
|
|
-- AudioThreshold and the level it is compared against are both on a 0-100
|
|
-- dBFS-derived scale rather than a raw amplitude ratio. Linear amplitude is
|
|
-- unusable as a setting -- ordinary speech sits at 1-3% of full scale, so every
|
|
-- sound worth catching would be crammed into the bottom two points of the range.
|
|
--
|
|
-- A threshold of 0 means detection is off for that monitor, which is why it is
|
|
-- also the default: enabling AudioDetection without choosing a threshold must
|
|
-- not alarm on silence.
|
|
--
|
|
-- AudioAlarmScore defaults to 9 to match what an ONVIF or Amcrest alarm already
|
|
-- contributes, so an audio alarm is worth about the same as a camera-reported
|
|
-- one until the operator decides otherwise.
|
|
--
|
|
|
|
SELECT 'Checking for AudioDetection in Monitors';
|
|
SET @s = (SELECT IF(
|
|
(SELECT COUNT(*)
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE table_name = 'Monitors'
|
|
AND table_schema = DATABASE()
|
|
AND column_name = 'AudioDetection'
|
|
) > 0,
|
|
"SELECT 'Column AudioDetection already exists in Monitors'",
|
|
"ALTER TABLE `Monitors` ADD COLUMN `AudioDetection` tinyint(1) unsigned NOT NULL default '0' AFTER `RecordAudio`"
|
|
));
|
|
|
|
PREPARE stmt FROM @s;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
SELECT 'Checking for AudioThreshold in Monitors';
|
|
SET @s = (SELECT IF(
|
|
(SELECT COUNT(*)
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE table_name = 'Monitors'
|
|
AND table_schema = DATABASE()
|
|
AND column_name = 'AudioThreshold'
|
|
) > 0,
|
|
"SELECT 'Column AudioThreshold already exists in Monitors'",
|
|
"ALTER TABLE `Monitors` ADD COLUMN `AudioThreshold` tinyint(3) unsigned NOT NULL default '0' AFTER `AudioDetection`"
|
|
));
|
|
|
|
PREPARE stmt FROM @s;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
SELECT 'Checking for AudioAlarmScore in Monitors';
|
|
SET @s = (SELECT IF(
|
|
(SELECT COUNT(*)
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE table_name = 'Monitors'
|
|
AND table_schema = DATABASE()
|
|
AND column_name = 'AudioAlarmScore'
|
|
) > 0,
|
|
"SELECT 'Column AudioAlarmScore already exists in Monitors'",
|
|
"ALTER TABLE `Monitors` ADD COLUMN `AudioAlarmScore` smallint(5) unsigned NOT NULL default '9' AFTER `AudioThreshold`"
|
|
));
|
|
|
|
PREPARE stmt FROM @s;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|