From 39a33f6143f4a95080e39fdf6e19dbb1d2b5e22a Mon Sep 17 00:00:00 2001 From: JP Verdejo Date: Mon, 26 Jun 2023 19:33:11 +0000 Subject: [PATCH] Expose dBFS when doing audio analysis --- frigate/app.py | 1 + frigate/events/audio.py | 15 ++++++++++++++- frigate/http.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/frigate/app.py b/frigate/app.py index 15fb2ff69..ff948b2e6 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -259,6 +259,7 @@ class FrigateApp: self.onvif_controller, self.external_event_processor, self.plus_api, + self.dispatcher, ) def init_onvif(self) -> None: diff --git a/frigate/events/audio.py b/frigate/events/audio.py index 68491b8a6..8df0e4209 100644 --- a/frigate/events/audio.py +++ b/frigate/events/audio.py @@ -167,9 +167,22 @@ class AudioEventMaintainer(threading.Thread): if not self.feature_metrics[self.config.name]["audio_enabled"].value: return - waveform = (audio / AUDIO_MAX_BIT_RANGE).astype(np.float32) + audio_as_float = audio.astype(np.float32) + waveform = audio_as_float / AUDIO_MAX_BIT_RANGE model_detections = self.detector.detect(waveform) + # Calculate RMS (Root-Mean-Square) which represents the average signal amplitude + # Note: np.float32 isn't serializable, we must use np.float64 to publish the message + rms = np.sqrt(np.mean(np.absolute(audio_as_float**2))).astype(np.float64) + + # Transform RMS to dBFS (decibels relative to full scale) + dBFS = 20 * np.log10(np.abs(rms) / AUDIO_MAX_BIT_RANGE) + + requests.post( + f"http://127.0.0.1:5000/api/{self.config.name}/metadata", + json={"dBFS": dBFS, "rms": rms}, + ) + for label, score, _ in model_detections: if label not in self.config.audio.listen: continue diff --git a/frigate/http.py b/frigate/http.py index 127b5defe..570d9bf13 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -28,6 +28,7 @@ from peewee import DoesNotExist, SqliteDatabase, fn, operator from playhouse.shortcuts import model_to_dict from tzlocal import get_localzone_name +from frigate.comms.dispatcher import Dispatcher from frigate.config import FrigateConfig from frigate.const import CLIPS_DIR, MAX_SEGMENT_DURATION, RECORD_DIR from frigate.events.external import ExternalEventProcessor @@ -61,6 +62,7 @@ def create_app( onvif: OnvifController, external_processor: ExternalEventProcessor, plus_api: PlusApi, + dispatcher: Dispatcher, ): app = Flask(__name__) @@ -81,6 +83,7 @@ def create_app( app.onvif = onvif app.external_processor = external_processor app.plus_api = plus_api + app.dispatcher = dispatcher app.camera_error_image = None app.hwaccel_errors = [] @@ -1396,6 +1399,38 @@ def recording_clip(camera_name, start_ts, end_ts): return response +@bp.route("//metadata", methods=["POST"]) +def create_metadata_message(camera_name): + if not camera_name or not current_app.frigate_config.cameras.get(camera_name): + return jsonify( + {"success": False, "message": f"{camera_name} is not a valid camera."}, 404 + ) + + request_json = request.get_json(silent=True) + if request_json == {}: + return jsonify( + {"success": False, "message": "Metadata json cannot be empty."}, 404 + ) + + try: + current_app.dispatcher.publish( + "metadata", json.dumps(request_json), retain=False + ) + except Exception as e: + logger.error(f"The error is {e}") + return jsonify( + {"success": False, "message": f"An unknown error occurred: {e}"}, 404 + ) + + return jsonify( + { + "success": True, + "message": "Successfully published metadata message.", + }, + 200, + ) + + @bp.route("/vod//start//end/") @bp.route("/vod//start//end/") def vod_ts(camera_name, start_ts, end_ts):