From 27f98f049bfec72170cb03835eb49c55489315ff Mon Sep 17 00:00:00 2001 From: Julian Vollmer Date: Mon, 18 May 2026 18:57:28 +0200 Subject: [PATCH] Add microphone gain control with live slider on dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Software gain applied per chunk in audio_ws via struct pack/unpack (S16_LE, clamped) - Default gain 2.0 (200%) – compensates for quiet USB mics - GET/POST /api/audio-gain to read and persist gain to /var/lib/babycam/audio-gain - index.html: Mikrofon-Lautstärke slider card (0–500%), updates live with 300ms debounce - Gain survives service restarts (loaded from file at startup) Co-Authored-By: Claude Sonnet 4.6 --- app/main.py | 48 +++++++++++++++++++++++++++++++++++++++- app/templates/index.html | 33 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 7b28561..88eebfc 100644 --- a/app/main.py +++ b/app/main.py @@ -10,6 +10,7 @@ import subprocess import re import time import os +import struct import threading from flask import Flask, Response, render_template, request, jsonify from flask_sock import Sock @@ -114,6 +115,30 @@ def stream(): AUDIO_SAMPLERATE = 16000 AUDIO_CHUNK = 512 # ~32ms bei 16kHz +GAIN_FILE = "/var/lib/babycam/audio-gain" +_audio_gain: float = 2.0 # Default 200 % – USB-Mics sind oft leise + + +def _load_audio_gain(): + global _audio_gain + try: + with open(GAIN_FILE) as f: + _audio_gain = max(0.0, min(10.0, float(f.read().strip()))) + except Exception: + pass + + +_load_audio_gain() + + +def _apply_gain(chunk: bytes, gain: float) -> bytes: + """Verstärkt 16-bit PCM LE in-place (clamped).""" + if abs(gain - 1.0) < 0.005: + return chunk + n = len(chunk) // 2 + samples = struct.unpack(f"<{n}h", chunk) + return struct.pack(f"<{n}h", *(max(-32768, min(32767, int(s * gain))) for s in samples)) + def find_audio_device() -> str: """Findet die USB-Soundkarte dynamisch anhand des Namens.""" @@ -148,7 +173,7 @@ def audio_ws(ws): chunk = process.stdout.read(AUDIO_CHUNK) if not chunk: break - ws.send(chunk) + ws.send(_apply_gain(chunk, _audio_gain)) except Exception: pass finally: @@ -300,6 +325,27 @@ def wifi_connect(): }) +# --------------------------------------------------------------------------- +# Audio-Gain API +# --------------------------------------------------------------------------- + +@app.route("/api/audio-gain", methods=["GET"]) +def api_audio_gain_get(): + return jsonify({"gain": _audio_gain}) + + +@app.route("/api/audio-gain", methods=["POST"]) +def api_audio_gain_set(): + global _audio_gain + data = request.get_json() + value = max(0.0, min(10.0, float(data.get("gain", 1.0)))) + _audio_gain = value + os.makedirs(os.path.dirname(GAIN_FILE), exist_ok=True) + with open(GAIN_FILE, "w") as f: + f.write(str(value)) + return jsonify({"success": True, "gain": _audio_gain}) + + # --------------------------------------------------------------------------- # System # --------------------------------------------------------------------------- diff --git a/app/templates/index.html b/app/templates/index.html index a70001d..120b6c5 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -237,6 +237,14 @@
{{ status.uptime }}
+
+ +
+ + 200% +
+
@@ -264,6 +272,31 @@