src/zm_audio_detector.h
Rama master · réplica de lectura del repositorio original.
/*
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ZM_AUDIO_DETECTOR_H
#define ZM_AUDIO_DETECTOR_H
#include "zm_ffmpeg.h"
#include <atomic>
#include <cstdint>
// Reports how loud a monitor's audio is, on the same 0-100 scale the monitor
// editor asks the user to set a threshold on.
//
// The level is a dBFS reading, not a raw amplitude ratio. Linear RMS is close
// to useless for a threshold: ordinary speech sits around 1-3% of full scale,
// so every interesting sound would be crowded into the bottom couple of points
// of the range and the setting would be impossible to tune. Mapping
// AUDIO_FLOOR_DB..0 dBFS onto 0..100 spreads the useful range out instead.
class AudioDetector {
public:
// Anything at or below this is reported as 0. -60 dBFS is comfortably under a
// quiet room's noise floor on the hardware this was written against.
static constexpr double AUDIO_FLOOR_DB = -60.0;
AudioDetector() = default;
~AudioDetector();
AudioDetector(const AudioDetector &) = delete;
AudioDetector &operator=(const AudioDetector &) = delete;
// Takes a reference to the stream's parameters. Returns false if no decoder
// is available for the codec, in which case the detector stays disabled and
// Level() keeps reporting 0 rather than the caller having to track that.
//
// A codec that has already failed is refused immediately and silently. The
// caller retries on every audio packet, so without this a stream ZoneMinder
// cannot decode logs a warning at the audio packet rate forever. A reconnect
// that brings a different codec is still tried.
bool Open(const AVCodecParameters *codecpar);
bool IsOpen() const { return codec_context_ != nullptr; }
void Close();
// Decodes the packet and updates the current level. Returns the level, or
// the previous one if the packet produced no frames (a decoder priming a
// buffer is normal, not an error).
int Process(const AVPacket *packet);
int Level() const { return level_.load(std::memory_order_relaxed); }
// Highest level seen since the last call, and resets to 0 so the next call
// covers the next interval. Frames rows are written well below the capture
// rate, so recording Level() at write time would miss a bang that happened
// between two rows and had already decayed; the peak survives it.
//
// Called from the analysis thread while the capture thread is still writing,
// hence the exchange rather than a read followed by a store.
int TakePeak() { return peak_.exchange(0, std::memory_order_relaxed); }
// Raises the peak to level if it is higher. Called by Process for each
// decoded packet; public so the peak can be exercised without standing up a
// decoder, the same reason RmsS16 and LevelFromRms are.
void RaisePeak(int level);
// --- Pure helpers, split out so the scale is testable without a decoder ----
// Root-mean-square of interleaved or planar signed 16-bit samples, as a
// fraction of full scale (0.0 .. 1.0).
static double RmsS16(const int16_t *samples, size_t count);
// Same, for the float formats the AAC and Opus decoders emit. Float samples
// are nominally -1.0 .. 1.0 but decoders do overshoot, so this clamps.
static double RmsFloat(const float *samples, size_t count);
// Maps a full-scale fraction onto 0-100. Silence and anything below
// AUDIO_FLOOR_DB give 0; full scale gives 100.
static int LevelFromRms(double rms);
// Whether a level should count as an alarm. A threshold of 0 means the
// feature is off rather than "alarm on absolute silence", which is what a
// naive >= comparison would give.
static bool IsAlarm(int level, int threshold);
// Whether anything wants a reading: the monitor scores on audio, or someone
// asked for one and the request has not expired. request_until is a wall
// clock second, 0 meaning nobody asked. Split out from Monitor so the
// expiry rule is testable without a monitor or its shared memory.
static bool LevelWanted(bool audio_detection, uint32_t request_until, uint32_t now);
private:
double RmsFromFrame(const AVFrame *frame) const;
AVCodecContext *codec_context_ = nullptr;
// The codec a decoder lookup already failed for, or AV_CODEC_ID_NONE.
AVCodecID failed_codec_ = AV_CODEC_ID_NONE;
// Written by the capture thread, read by the analysis thread.
std::atomic<int> level_{0};
// Written by the capture thread, taken and cleared by the analysis thread.
std::atomic<int> peak_{0};
};
#endif // ZM_AUDIO_DETECTOR_H