Skip to content

Commit

Permalink
micd: scale sound volume with ambient noise level (commaai#26399)
Browse files Browse the repository at this point in the history
* test changing sound volume

* test changing sound volume

* create system/hardware/pc/hardware.h

* implement Hardware::set_volume using pactl

* soundd: use Hardware::set_volume

* add sounddevice dependency

* sounddevice example

* simple micd

* cleanup

* remove this

* fix process config

* add to release files

* hardware: get sound input device

* no more offroad

* debug

* calculate volume from all measurements since last update

* use microphone noise level to update sound volume

* fix scale

* mute microphone during alerts

* log raw noise level

* hardware: reduce tici min volume

* improve scale

* add package

* clear measurements on muted

* change default to min volume and respond quicker

* fixes

Co-authored-by: Shane Smiskol <shane@smiskol.com>

* logarithmic scaling

* fix

* respond quicker

* fixes

* tweak scaling

* specify default device

* Revert "hardware: get sound input device"

This reverts commit 50f594f.

* tuning

* forgot to update submaster

* tuning

* don't mute microphone, and clip measurement

* remove submaster

* fixes

* tuning

* implement Hardware::set_volume using pactl

* Revert "test changing sound volume"

This reverts commit 4bbd870.

* draft

* draft

* calculate sound pressure level in dB

* fix setting

* faster filter

* start at initial value

* don't run command in background

* pactl: use default sink

* use sound pressure db

* tuning

* bump up max volume threshold

* update filter slower

* fix divide by zero

* bump cereal

Co-authored-by: Shane Smiskol <shane@smiskol.com>
  • Loading branch information
2 people authored and krkeegan committed Jan 4, 2023
1 parent d76fadf commit c1fa641
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 14 deletions.
32 changes: 22 additions & 10 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ sentry-sdk = "^1.6.0"
setproctitle = "^1.2.3"
six = "^1.16.0"
smbus2 = "^0.4.2"
sounddevice = "^0.4.5"
sympy = "^1.10.1"
timezonefinder = "^6.0.1"
tqdm = "^4.64.0"
Expand Down
1 change: 1 addition & 0 deletions release/files_common
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ selfdrive/rtshield.py
selfdrive/statsd.py

system/logmessaged.py
system/micd.py
system/swaglog.py
system/version.py

Expand Down
1 change: 1 addition & 0 deletions selfdrive/manager/process_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def logging(started, params, CP: car.CarParams) -> bool:
NativeProcess("logcatd", "system/logcatd", ["./logcatd"]),
NativeProcess("proclogd", "system/proclogd", ["./proclogd"]),
PythonProcess("logmessaged", "system.logmessaged", offroad=True),
PythonProcess("micd", "system.micd"),
PythonProcess("timezoned", "system.timezoned", enabled=not PC, offroad=True),

DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"),
Expand Down
6 changes: 3 additions & 3 deletions selfdrive/ui/soundd/sound.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// TODO: detect when we can't play sounds
// TODO: detect when we can't display the UI

Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) {
Sound::Sound(QObject *parent) : sm({"controlsState", "deviceState", "microphone"}) {
qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName();

for (auto &[alert, fn, loops] : sound_list) {
Expand Down Expand Up @@ -47,8 +47,8 @@ void Sound::update() {
}

// scale volume with speed
if (sm.updated("carState")) {
float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.f);
if (sm.updated("microphone")) {
float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureDb(), 58.f, 77.f, 0.f, 1.f);
volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale);
Hardware::set_volume(volume);
}
Expand Down
2 changes: 1 addition & 1 deletion system/hardware/tici/hardware.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class HardwareTici : public HardwareNone {
public:
static constexpr float MAX_VOLUME = 0.9;
static constexpr float MIN_VOLUME = 0.2;
static constexpr float MIN_VOLUME = 0.1;
static bool TICI() { return true; }
static bool AGNOS() { return true; }
static std::string get_os_version() {
Expand Down
67 changes: 67 additions & 0 deletions system/micd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
import sounddevice as sd
import numpy as np

from cereal import messaging
from common.filter_simple import FirstOrderFilter
from common.realtime import Ratekeeper
from system.swaglog import cloudlog

RATE = 10
DT_MIC = 1. / RATE
REFERENCE_SPL = 2 * 10 ** -5 # newtons/m^2


class Mic:
def __init__(self, pm):
self.pm = pm
self.rk = Ratekeeper(RATE)

self.measurements = np.empty(0)
self.spl_filter = FirstOrderFilter(0, 4, DT_MIC, initialized=False)

def update(self):
# self.measurements contains amplitudes from -1 to 1 which we use to
# calculate an uncalibrated sound pressure level
if len(self.measurements) > 0:
# https://www.engineeringtoolbox.com/sound-pressure-d_711.html
sound_pressure = np.sqrt(np.mean(self.measurements ** 2)) # RMS of amplitudes
sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) if sound_pressure > 0 else 0 # dB
self.spl_filter.update(sound_pressure_level)
else:
sound_pressure = 0
sound_pressure_level = 0

self.measurements = np.empty(0)

msg = messaging.new_message('microphone')
msg.microphone.soundPressure = float(sound_pressure)
msg.microphone.soundPressureDb = float(sound_pressure_level)
msg.microphone.filteredSoundPressureDb = float(self.spl_filter.x)

self.pm.send('microphone', msg)
self.rk.keep_time()

def callback(self, indata, frames, time, status):
self.measurements = np.concatenate((self.measurements, indata[:, 0]))

def micd_thread(self, device=None):
if device is None:
device = "sysdefault"

with sd.InputStream(device=device, channels=1, samplerate=44100, callback=self.callback) as stream:
cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}")
while True:
self.update()


def main(pm=None, sm=None):
if pm is None:
pm = messaging.PubMaster(['microphone'])

mic = Mic(pm)
mic.micd_thread()


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions tools/ubuntu_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ function install_ubuntu_common_requirements() {
libomp-dev \
libopencv-dev \
libpng16-16 \
libportaudio2 \
libssl-dev \
libsqlite3-dev \
libusb-1.0-0-dev \
Expand Down

0 comments on commit c1fa641

Please sign in to comment.