mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-02-21 11:00:32 +01:00
Update DOCS.md
This commit is contained in:
@@ -95,11 +95,11 @@ sleep 60
|
|||||||
|
|
||||||
# Run focusrite and autogain scripts if present
|
# Run focusrite and autogain scripts if present
|
||||||
if [ -f "$HOME/focusrite.sh" ]; then
|
if [ -f "$HOME/focusrite.sh" ]; then
|
||||||
"$HOME/focusrite.sh" >/tmp/log_focusrite 2>/tmp/log_focusrite_error &
|
python3 -u "$HOME/focusrite.sh" >/tmp/log_focusrite 2>/tmp/log_focusrite_error &
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$HOME/autogain.py" ]; then
|
if [ -f "$HOME/autogain.py" ]; then
|
||||||
"$HOME/autogain.py" >/tmp/log_autogain 2>/tmp/log_autogain_error &
|
python3 -u "$HOME/autogain.py" >/tmp/log_autogain 2>/tmp/log_autogain_error &
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ Add this content in "$HOME/autogain.py" && chmod +x "$HOME/autogain.py"
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Microphone Gain Adjustment Script with THD and Overload Detection
|
Microphone Gain Adjustment Script with THD and Overload Detection
|
||||||
def set_gain_db
|
|
||||||
This script captures audio from an RTSP stream, processes it to calculate the RMS
|
This script captures audio from an RTSP stream, processes it to calculate the RMS
|
||||||
within the 2000-8000 Hz frequency band, detects clipping, calculates Total Harmonic
|
within the 2000-8000 Hz frequency band, detects clipping, calculates Total Harmonic
|
||||||
Distortion (THD) over the full frequency range, and adjusts the microphone gain based
|
Distortion (THD) over the full frequency range, and adjusts the microphone gain based
|
||||||
@@ -431,25 +431,20 @@ Changelog:
|
|||||||
- 2024-10-27: Enhanced debug output with logging levels.
|
- 2024-10-27: Enhanced debug output with logging levels.
|
||||||
- 2024-10-28: Added summary log mode for simplified output.
|
- 2024-10-28: Added summary log mode for simplified output.
|
||||||
- 2024-10-28: Removed gain stabilization delay for immediate gain adjustments.
|
- 2024-10-28: Removed gain stabilization delay for immediate gain adjustments.
|
||||||
- 2024-10-28: Max gain capped at 40 dB, suppressed amixer logging.
|
|
||||||
- 2024-10-28: Implemented sampling rate reduction, capture duration reduction, scipy.fft usage, lower filter order, and multiprocessing for efficiency.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy.signal import butter, sosfilt, find_peaks
|
from scipy.signal import butter, sosfilt, find_peaks
|
||||||
from scipy.fft import rfft, rfftfreq
|
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import multiprocessing as mp
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# ---------------------------- Configuration ----------------------------
|
# ---------------------------- Configuration ----------------------------
|
||||||
|
|
||||||
# Microphone Settings
|
# Microphone Settings
|
||||||
MICROPHONE_NAME = "Line In 1 Gain"
|
MICROPHONE_NAME = "Line In 1 Gain"
|
||||||
MIN_GAIN_DB = 20
|
MIN_GAIN_DB = 20
|
||||||
MAX_GAIN_DB = 40 # Capped at 40 dB
|
MAX_GAIN_DB = 40
|
||||||
DECREASE_GAIN_STEP_DB = 1
|
DECREASE_GAIN_STEP_DB = 1
|
||||||
INCREASE_GAIN_STEP_DB = 5
|
INCREASE_GAIN_STEP_DB = 5
|
||||||
CLIPPING_REDUCTION_DB = 3
|
CLIPPING_REDUCTION_DB = 3
|
||||||
@@ -462,10 +457,7 @@ NOISE_THRESHOLD_LOW = 0.00035
|
|||||||
TREND_COUNT_THRESHOLD = 3
|
TREND_COUNT_THRESHOLD = 3
|
||||||
|
|
||||||
# Sampling Rate
|
# Sampling Rate
|
||||||
SAMPLING_RATE = 44100 # Reduced from 48000 Hz to 44100 Hz
|
SAMPLING_RATE = 44100
|
||||||
|
|
||||||
# Audio Capture Duration
|
|
||||||
AUDIO_CAPTURE_DURATION = 2 # Reduced from 5 seconds to 2 seconds
|
|
||||||
|
|
||||||
# RTSP Stream URL
|
# RTSP Stream URL
|
||||||
RTSP_URL = "rtsp://192.168.178.124:8554/birdmic"
|
RTSP_URL = "rtsp://192.168.178.124:8554/birdmic"
|
||||||
@@ -485,11 +477,6 @@ REFERENCE_PRESSURE = 20e-6
|
|||||||
THD_FUNDAMENTAL_THRESHOLD_DB = 60
|
THD_FUNDAMENTAL_THRESHOLD_DB = 60
|
||||||
MAX_THD_PERCENTAGE = 5.0
|
MAX_THD_PERCENTAGE = 5.0
|
||||||
|
|
||||||
# Filter Settings
|
|
||||||
LOWCUT = 2000
|
|
||||||
HIGHCUT = 8000
|
|
||||||
FILTER_ORDER = 3 # Reduced from 5 to 3 for efficiency
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -499,7 +486,7 @@ def debug_print(msg, level="info"):
|
|||||||
:param msg: The debug message to print.
|
:param msg: The debug message to print.
|
||||||
:param level: Logging level - "info", "warning", "error".
|
:param level: Logging level - "info", "warning", "error".
|
||||||
"""
|
"""
|
||||||
if DEBUG and not SUMMARY_MODE:
|
if DEBUG:
|
||||||
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
print(f"[{current_time}] [{level.upper()}] {msg}")
|
print(f"[{current_time}] [{level.upper()}] {msg}")
|
||||||
|
|
||||||
@@ -528,25 +515,32 @@ def get_gain_db(mic_name):
|
|||||||
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode()
|
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode()
|
||||||
match = re.search(r'\[(-?\d+(\.\d+)?)dB\]', output)
|
match = re.search(r'\[(-?\d+(\.\d+)?)dB\]', output)
|
||||||
if match:
|
if match:
|
||||||
return float(match.group(1))
|
gain_db = float(match.group(1))
|
||||||
|
debug_print(f"Retrieved gain: {gain_db} dB", "info")
|
||||||
|
return gain_db
|
||||||
else:
|
else:
|
||||||
|
debug_print("No gain information found in amixer output.", "warning")
|
||||||
return None
|
return None
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError as e:
|
||||||
|
debug_print(f"amixer sget failed: {e}", "error")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def set_gain_db(mic_name, gain_db):
|
def set_gain_db(mic_name, gain_db):
|
||||||
"""
|
"""
|
||||||
Sets the gain of the specified microphone using amixer.
|
Sets the gain of the specified microphone using amixer.
|
||||||
Suppresses output by redirecting to /dev/null.
|
|
||||||
"""
|
"""
|
||||||
if int(gain_db) > MAX_GAIN_DB:
|
gain_db_int = int(gain_db)
|
||||||
return True # Do not exceed max gain
|
if gain_db_int > MAX_GAIN_DB:
|
||||||
|
debug_print(f"Requested gain {gain_db_int} dB exceeds MAX_GAIN_DB {MAX_GAIN_DB} dB. Skipping.", "warning")
|
||||||
|
return False # Do not exceed max gain
|
||||||
cmd = ['amixer', 'sset', mic_name, f'{gain_db}dB']
|
cmd = ['amixer', 'sset', mic_name, f'{gain_db}dB']
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(cmd, stderr=subprocess.STDOUT, stdout=subprocess.DEVNULL)
|
subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||||
|
debug_print(f"Set gain to: {gain_db} dB", "info")
|
||||||
return True
|
return True
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError as e:
|
||||||
|
debug_print(f"amixer sset failed: {e}", "error")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -577,8 +571,8 @@ def thd_calculation(audio, sampling_rate, num_harmonics=5):
|
|||||||
"""
|
"""
|
||||||
Calculates Total Harmonic Distortion (THD) for the audio signal.
|
Calculates Total Harmonic Distortion (THD) for the audio signal.
|
||||||
"""
|
"""
|
||||||
fft_vals = rfft(audio)
|
fft_vals = np.fft.rfft(audio)
|
||||||
fft_freqs = rfftfreq(len(audio), 1 / sampling_rate)
|
fft_freqs = np.fft.rfftfreq(len(audio), 1 / sampling_rate)
|
||||||
fft_magnitude = np.abs(fft_vals)
|
fft_magnitude = np.abs(fft_vals)
|
||||||
fundamental_freq, fundamental_amplitude = find_fundamental_frequency(fft_freqs, fft_magnitude)
|
fundamental_freq, fundamental_amplitude = find_fundamental_frequency(fft_freqs, fft_magnitude)
|
||||||
|
|
||||||
@@ -628,14 +622,13 @@ def detect_microphone_overload(spl, mic_clipping_spl):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def calculate_noise_rms_and_thd(rtsp_url, bandpass_sos, sampling_rate):
|
def calculate_noise_rms_and_thd(rtsp_url, bandpass_sos, sampling_rate, num_bins=5):
|
||||||
"""
|
"""
|
||||||
Captures audio from an RTSP stream, calculates RMS, THD, and SPL, and detects microphone overload.
|
Captures audio from an RTSP stream, calculates RMS, THD, and SPL, and detects microphone overload.
|
||||||
"""
|
"""
|
||||||
cmd = [
|
cmd = [
|
||||||
'ffmpeg', '-loglevel', 'error', '-rtsp_transport', 'tcp', '-i', rtsp_url,
|
'ffmpeg', '-loglevel', 'error', '-rtsp_transport', 'tcp', '-i', rtsp_url,
|
||||||
'-vn', '-f', 's16le', '-acodec', 'pcm_s16le', '-ar', str(sampling_rate),
|
'-vn', '-f', 's16le', '-acodec', 'pcm_s16le', '-ar', str(sampling_rate), '-ac', '1', '-t', '5', '-'
|
||||||
'-ac', '1', '-t', str(AUDIO_CAPTURE_DURATION), '-'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
retries = 3
|
retries = 3
|
||||||
@@ -672,25 +665,18 @@ def calculate_noise_rms_and_thd(rtsp_url, bandpass_sos, sampling_rate):
|
|||||||
return None, None, None, False
|
return None, None, None, False
|
||||||
|
|
||||||
|
|
||||||
def audio_capture_process(queue, rtsp_url, sos, sampling_rate):
|
|
||||||
"""
|
|
||||||
Separate process for capturing and processing audio.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
rms, thd, spl, overload = calculate_noise_rms_and_thd(rtsp_url, sos, sampling_rate)
|
|
||||||
queue.put((rms, thd, spl, overload))
|
|
||||||
time.sleep(60) # Wait for the next capture cycle
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
Main loop that continuously monitors background noise, detects clipping, calculates THD,
|
Main loop that continuously monitors background noise, detects clipping, calculates THD,
|
||||||
and adjusts microphone gain with multiprocessing for audio capture and processing.
|
and adjusts microphone gain with retry logic for RTSP stream resilience.
|
||||||
"""
|
"""
|
||||||
TREND_COUNT = 0
|
TREND_COUNT = 0
|
||||||
PREVIOUS_TREND = 0
|
PREVIOUS_TREND = 0
|
||||||
|
|
||||||
# Precompute bandpass filter coefficients with updated SAMPLING_RATE and lower FILTER_ORDER
|
# Precompute bandpass filter coefficients with updated SAMPLING_RATE
|
||||||
|
LOWCUT = 2000
|
||||||
|
HIGHCUT = 8000
|
||||||
|
FILTER_ORDER = 5
|
||||||
sos = butter(FILTER_ORDER, [LOWCUT, HIGHCUT], btype='band', fs=SAMPLING_RATE, output='sos')
|
sos = butter(FILTER_ORDER, [LOWCUT, HIGHCUT], btype='band', fs=SAMPLING_RATE, output='sos')
|
||||||
|
|
||||||
# Set the microphone gain to the maximum gain at the start
|
# Set the microphone gain to the maximum gain at the start
|
||||||
@@ -699,26 +685,14 @@ def main():
|
|||||||
print(f"Microphone gain set to {MAX_GAIN_DB} dB at start.")
|
print(f"Microphone gain set to {MAX_GAIN_DB} dB at start.")
|
||||||
else:
|
else:
|
||||||
print("Failed to set microphone gain at start. Exiting.")
|
print("Failed to set microphone gain at start. Exiting.")
|
||||||
sys.exit(1)
|
return
|
||||||
|
|
||||||
# Initialize multiprocessing queue
|
|
||||||
manager = mp.Manager()
|
|
||||||
queue = manager.Queue()
|
|
||||||
|
|
||||||
# Start audio capture and processing in a separate process
|
|
||||||
p = mp.Process(target=audio_capture_process, args=(queue, RTSP_URL, sos, SAMPLING_RATE))
|
|
||||||
p.start()
|
|
||||||
debug_print("Started audio capture and processing process.", "info")
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if not queue.empty():
|
rms, thd, spl, overload = calculate_noise_rms_and_thd(RTSP_URL, sos, SAMPLING_RATE)
|
||||||
rms, thd, spl, overload = queue.get()
|
|
||||||
else:
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if rms is None:
|
if rms is None:
|
||||||
print("Failed to compute noise RMS. Retrying in 1 minute...")
|
print("Failed to compute noise RMS. Retrying in 1 minute...")
|
||||||
|
time.sleep(60)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Adjust gain if overload detected
|
# Adjust gain if overload detected
|
||||||
@@ -728,10 +702,12 @@ def main():
|
|||||||
NEW_GAIN_DB = max(current_gain_db - CLIPPING_REDUCTION_DB, MIN_GAIN_DB)
|
NEW_GAIN_DB = max(current_gain_db - CLIPPING_REDUCTION_DB, MIN_GAIN_DB)
|
||||||
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
||||||
print(f"Clipping detected. Reduced gain to {NEW_GAIN_DB} dB")
|
print(f"Clipping detected. Reduced gain to {NEW_GAIN_DB} dB")
|
||||||
current_gain_db = NEW_GAIN_DB # Update current gain
|
debug_print(f"Gain reduced to {NEW_GAIN_DB} dB due to clipping.", "warning")
|
||||||
# Output summary log
|
# No stabilization delay; continue to next iteration
|
||||||
|
# Skip trend adjustment in case of clipping
|
||||||
summary_log(current_gain_db if current_gain_db else MIN_GAIN_DB, True, rms, thd)
|
summary_log(current_gain_db if current_gain_db else MIN_GAIN_DB, True, rms, thd)
|
||||||
continue # Skip trend adjustment in case of clipping
|
time.sleep(60)
|
||||||
|
continue
|
||||||
|
|
||||||
# Handle THD if SPL is above threshold
|
# Handle THD if SPL is above threshold
|
||||||
if spl >= THD_FUNDAMENTAL_THRESHOLD_DB:
|
if spl >= THD_FUNDAMENTAL_THRESHOLD_DB:
|
||||||
@@ -742,7 +718,7 @@ def main():
|
|||||||
NEW_GAIN_DB = max(current_gain_db - DECREASE_GAIN_STEP_DB, MIN_GAIN_DB)
|
NEW_GAIN_DB = max(current_gain_db - DECREASE_GAIN_STEP_DB, MIN_GAIN_DB)
|
||||||
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
||||||
print(f"High THD detected. Decreased gain to {NEW_GAIN_DB} dB")
|
print(f"High THD detected. Decreased gain to {NEW_GAIN_DB} dB")
|
||||||
current_gain_db = NEW_GAIN_DB # Update current gain
|
debug_print(f"Gain decreased to {NEW_GAIN_DB} dB due to high THD.", "info")
|
||||||
else:
|
else:
|
||||||
debug_print("THD within acceptable limits.", "info")
|
debug_print("THD within acceptable limits.", "info")
|
||||||
else:
|
else:
|
||||||
@@ -773,8 +749,11 @@ def main():
|
|||||||
|
|
||||||
if current_gain_db is None:
|
if current_gain_db is None:
|
||||||
print("Failed to get current gain level. Retrying in 1 minute...")
|
print("Failed to get current gain level. Retrying in 1 minute...")
|
||||||
|
time.sleep(60)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
debug_print(f"Current gain: {current_gain_db} dB", "info")
|
||||||
|
|
||||||
# Output summary log for the current state
|
# Output summary log for the current state
|
||||||
summary_log(current_gain_db, overload, rms, thd)
|
summary_log(current_gain_db, overload, rms, thd)
|
||||||
|
|
||||||
@@ -785,14 +764,14 @@ def main():
|
|||||||
NEW_GAIN_DB = max(current_gain_db - DECREASE_GAIN_STEP_DB, MIN_GAIN_DB)
|
NEW_GAIN_DB = max(current_gain_db - DECREASE_GAIN_STEP_DB, MIN_GAIN_DB)
|
||||||
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
||||||
print(f"Background noise high. Decreased gain to {NEW_GAIN_DB} dB")
|
print(f"Background noise high. Decreased gain to {NEW_GAIN_DB} dB")
|
||||||
current_gain_db = NEW_GAIN_DB # Update current gain
|
debug_print(f"Gain decreased to {NEW_GAIN_DB} dB due to high noise.", "info")
|
||||||
TREND_COUNT = 0
|
TREND_COUNT = 0
|
||||||
elif CURRENT_TREND == -1:
|
elif CURRENT_TREND == -1:
|
||||||
# Increase gain by INCREASE_GAIN_STEP_DB dB
|
# Increase gain by INCREASE_GAIN_STEP_DB dB
|
||||||
NEW_GAIN_DB = min(current_gain_db + INCREASE_GAIN_STEP_DB, MAX_GAIN_DB)
|
NEW_GAIN_DB = min(current_gain_db + INCREASE_GAIN_STEP_DB, MAX_GAIN_DB)
|
||||||
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
if set_gain_db(MICROPHONE_NAME, NEW_GAIN_DB):
|
||||||
print(f"Background noise low. Increased gain to {NEW_GAIN_DB} dB")
|
print(f"Background noise low. Increased gain to {NEW_GAIN_DB} dB")
|
||||||
current_gain_db = NEW_GAIN_DB # Update current gain
|
debug_print(f"Gain increased to {NEW_GAIN_DB} dB due to low noise.", "info")
|
||||||
TREND_COUNT = 0
|
TREND_COUNT = 0
|
||||||
else:
|
else:
|
||||||
debug_print("No gain adjustment needed based on noise trend.", "info")
|
debug_print("No gain adjustment needed based on noise trend.", "info")
|
||||||
|
|||||||
Reference in New Issue
Block a user