Files
zoneminder/web/ajax/monitor.php
T
Isaac ConnorandClaude Opus 5 428c048bac feat: measure the audio level on demand, with a meter in the editor
Reverts the previous commit's always-on measurement. Decoding audio for every
monitor that has it spends CPU on a number almost nothing reads, which is the
wrong trade even though it did solve the chicken and egg of picking a
threshold without ever seeing a level.

Measure when something is actually going to use the reading instead:

 - AudioDetection is on, as before, so nothing changes for a monitor that
   scores on audio; or
 - somebody asked. SharedData gains audio_level_until, a wall clock second
   the capture thread keeps measuring up to. The monitor editor's new level
   meter pushes it forward while it is on screen and the measurement lapses a
   few seconds after the page is left, so nothing has to send a stop and a
   crashed browser cannot leave a monitor decoding forever.

When the reading stops being wanted the decoder is released and the published
level and peak are cleared, so a stale number is not left looking current and
an old peak does not land on the next frame row written.

audio_level_until is carved out of analysis_pad rather than appended, so
SharedData stays 888 bytes and no existing offset moves; the static_asserts,
Memory.pm and Monitor.php are updated together and all three now agree the
field is at +880.

The meter itself is on the audio settings, shown whether or not
AudioDetection is checked, because the level is what you need in order to
choose a threshold. It draws the threshold currently in the input as a mark on
the bar so a reading can be judged against it before saving, and a monitor
whose zmc is not running reads "no reading" rather than a confident 0, which
would be indistinguishable from silence.

Frames.AudioLevel is therefore 0 again on monitors that do not score on audio.
That is what the graph already treats as "no audio data", so it draws no line
rather than a flat one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpiSWBmtQkR5bcgpHWY4ME
2026-09-20 18:17:44 -05:00

70 lines
2.9 KiB
PHP

<?php
ini_set('display_errors', '0');
if ( canView('Monitors') || (isset($_REQUEST['mid']) && $_REQUEST['mid'] !== '' && canView('Monitors', $_REQUEST['mid'])) ) {
$mid = isset($_REQUEST['mid']) ? $_REQUEST['mid'] : null;
if ($mid === null || $mid === '') {
ajaxError(translate('RequestMissing') . ' "mid".');
}
$action = $_REQUEST['action'] ?? '';
if ($action === '') {
ajaxError(translate('RequestMissing') . ' "action".');
}
switch ( $action ) {
case 'validateName' :
require_once('includes/Monitor.php');
$monitor = new ZM\Monitor($mid);
$filterRegexp = $monitor->getDefaults()['Name']['filter_regexp'];
$result = true;
$badChars = [];
$message = '';
if (isset($_REQUEST['monitorName']) && is_string($_REQUEST['monitorName']) && $_REQUEST['monitorName'] !== '') {
$monitorName = $_REQUEST['monitorName'];
$trimmedMonitorName = trim($monitorName);
$cleanedMonitorName = preg_replace($filterRegexp, '', $trimmedMonitorName);
if ($trimmedMonitorName != $cleanedMonitorName){
preg_match_all($filterRegexp, $trimmedMonitorName, $badChars);
$result = false;
$message = translate('BadNameCharsList') . ' "' . implode('","', array_unique($badChars[0])) . '".~~' . translate('BadNameChars');
}
ajaxResponse(array('response'=>$result, 'monitorName'=>$monitorName, 'cleanedMonitorName'=>$cleanedMonitorName, 'badChars'=>$badChars, 'messageBadNameChars'=>$message));
} else {
ajaxError(translate('ErrorVerifyingMonitorName'));
}
break;
case 'audioLevel' :
// Live reading for the level meter in the monitor editor's audio
// settings. Polling this is also what keeps zmc measuring: a monitor with
// AudioDetection off does not decode audio until asked, and stops again a
// few seconds after the polling does. See Monitor::AudioLevelWanted.
require_once('includes/Monitor.php');
$monitor = new ZM\Monitor($mid);
if (!$monitor->Id()) {
ajaxError('Not found: monitor id '.validHtmlStr($mid));
break;
}
if (!$monitor->canView()) {
ajaxError(translate('insufficientPermissionsUser').' "'.validHtmlStr($user->Username()).'"');
break;
}
$monitor->requestAudioLevel();
$level = $monitor->audioLevel();
ajaxResponse(array(
// null rather than 0 when zmc is not running, so the meter can say so
// instead of showing a confident silent reading.
'level' => is_null($level) ? null : intval($level),
'alarm' => $monitor->audioAlarm(),
'threshold' => intval($monitor->AudioThreshold()),
'detection' => $monitor->AudioDetection() ? true : false,
));
break;
} // end switch action
} // end if canView('Monitors')
ajaxError(translate('UnrecognisedAction').' "'.validHtmlStr($_REQUEST['action'] ?? '').'" '.translate('ConjOr').' '.translate('insufficientPermissionsUser').' "'.validHtmlStr($user->Username()).'"');
?>