## WebRTC Auto Gain & Noise Suppression This PR adds support for WebRTC Auto Gain and Noise Suppression as previously implemented in the Wyoming Satellite project. ### What's New Two new entities have been created a number slider for gain and another one for noise suppression. The processing happens inside of the `webrtc.py` file and is implemented in the `__main__.py` file's `process_audio` function. The slider for gain has a limit of 0-31 and noise suppression has a limit of 0-4. The Auto Gain and Noise Suppression persist through restarts and are saved as preferences. The values are dynamically chnged without needing a restart. CLI flags called `--mic-auto-gain` and `--mic-noise-suppression` have also been added. > When both values are 0, WebRTC processing is bypassed entirely with no performance overhead. Closes #25 ### Testing When saving the audio recorded by LVA there is a noticeable increase. The following two files have been recorded with similar audio being played through them with these results (Gain-31 and NS-4): | Metric | With AGC+NS | Without | |---|---|---| | Max amplitude | 0.329 | 0.054 | | RMS amplitude | 0.008905 | 0.002862 | | Volume adjustment needed | 3.034x | 13.479x | | Rough frequency | 916 Hz | 705 Hz |
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
import logging
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
class WebRTCProcessor:
|
|
def __init__(self, agc_level: int = 0, ns_level: int = 0):
|
|
from webrtc_noise_gain import AudioProcessor # type: ignore[import-untyped]
|
|
|
|
self.apm = AudioProcessor(agc_level, ns_level)
|
|
self.agc_level = agc_level
|
|
self.ns_level = ns_level
|
|
self._buffer = bytearray()
|
|
self.FRAME_SIZE_BYTES = 320 # 160 samples * 2 bytes (16-bit PCM)
|
|
|
|
def update_settings(self, agc_level: int, ns_level: int):
|
|
"""Re-initialize processor if settings changed."""
|
|
if self.agc_level != agc_level or self.ns_level != ns_level:
|
|
from webrtc_noise_gain import AudioProcessor
|
|
|
|
_LOGGER.debug("Updating WebRTC settings: Gain=%s, NS=%s", agc_level, ns_level)
|
|
self.apm = AudioProcessor(agc_level, ns_level)
|
|
self.agc_level = agc_level
|
|
self.ns_level = ns_level
|
|
|
|
def process(self, raw_bytes: bytes) -> bytes:
|
|
"""
|
|
Buffer and process audio.
|
|
Returns processed bytes (may be shorter than input if buffering).
|
|
"""
|
|
self._buffer.extend(raw_bytes)
|
|
processed_chunks: list[bytes] = []
|
|
|
|
while len(self._buffer) >= self.FRAME_SIZE_BYTES:
|
|
frame = bytes(self._buffer[: self.FRAME_SIZE_BYTES])
|
|
del self._buffer[: self.FRAME_SIZE_BYTES] # drain in-place
|
|
|
|
result = self.apm.Process10ms(frame)
|
|
processed_chunks.append(result.audio)
|
|
|
|
return b"".join(processed_chunks)
|