diff --git a/cereal/log.capnp b/cereal/log.capnp index c569eeeaf8..a075134ef8 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2479,6 +2479,11 @@ struct Microphone { filteredSoundPressureWeightedDb @2 :Float32; } +struct AudioData { + sampleRate @0 :UInt32; + data @1 :Data; +} + struct Touch { sec @0 :Int64; usec @1 :Int64; @@ -2557,6 +2562,8 @@ struct Event { # microphone data microphone @103 :Microphone; + audioData @147 :AudioData; + audioDataNoLog @148 :AudioData; # systems stuff androidLog @20 :AndroidLogEntry; diff --git a/cereal/services.py b/cereal/services.py index 82fc04bd00..70ad2d2202 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -74,6 +74,8 @@ _services: dict[str, tuple] = { "qRoadEncodeIdx": (False, 20.), "userFlag": (True, 0., 1), "microphone": (True, 10., 10), + "audioData": (True, 20.), + "audioDataNoLog": (False, 20.), # debug "uiDebug": (True, 0., 1), diff --git a/common/params_keys.h b/common/params_keys.h index 3fd4e1b6ab..fa6146f3c9 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -99,6 +99,7 @@ inline static std::unordered_map keys = { {"PandaSomResetTriggered", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, {"PrimeType", PERSISTENT}, + {"RecordAudio", PERSISTENT}, {"RecordFront", PERSISTENT}, {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"SecOCKey", PERSISTENT | DONT_LOG}, diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 2c18e44eff..133c1fd012 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -21,6 +21,7 @@ DESCRIPTIONS = { "AlwaysOnDM": "Enable driver monitoring even when openpilot is not engaged.", 'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", "IsMetric": "Display speed in km/h instead of mph.", + "RecordAudio": "Records microphone audio.", } @@ -76,6 +77,12 @@ class TogglesLayout(Widget): toggle_item( "Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="monitoring.png" ), + toggle_item( + "Record Microphone Audio", + DESCRIPTIONS["RecordAudio"], + self._params.get_bool("RecordAudio"), + icon="monitoring.png", + ), ] self._list_widget = ListView(items) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 6529e83395..cd13ee08ec 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -68,6 +68,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/metric.png", false, }, + { + "RecordAudio", + tr("Record Microphone Audio"), + tr("Records microphone audio."), + "../assets/icons/monitoring.png", + true, + }, }; diff --git a/system/micd.py b/system/micd.py index 38f3225f55..86f84f0b8d 100755 --- a/system/micd.py +++ b/system/micd.py @@ -7,12 +7,13 @@ from cereal import messaging from openpilot.common.realtime import Ratekeeper from openpilot.common.retry import retry from openpilot.common.swaglog import cloudlog +from openpilot.common.params import Params RATE = 10 FFT_SAMPLES = 4096 REFERENCE_SPL = 2e-5 # newtons/m^2 -SAMPLE_RATE = 44100 -SAMPLE_BUFFER = 4096 # approx 100ms +SAMPLE_RATE = 16000 +SAMPLE_BUFFER = 800 # 50ms @cache @@ -45,7 +46,7 @@ def apply_a_weighting(measurements: np.ndarray) -> np.ndarray: class Mic: def __init__(self): self.rk = Ratekeeper(RATE) - self.pm = messaging.PubMaster(['microphone']) + self.pm = messaging.PubMaster(['microphone', 'audioData', 'audioDataNoLog']) self.measurements = np.empty(0) @@ -88,6 +89,14 @@ class Mic: self.measurements = self.measurements[FFT_SAMPLES:] + audio_data_service = 'audioData' if Params().get_bool("RecordAudio") else 'audioDataNoLog' + msg = messaging.new_message(audio_data_service, valid=True) + audio_field = getattr(msg, audio_data_service) + audio_field.sampleRate = SAMPLE_RATE + audio_data_int_16 = (indata[:, 0] * 32767).astype(np.int16) + audio_field.data = audio_data_int_16.tobytes() + self.pm.send(audio_data_service, msg) + @retry(attempts=7, delay=3) def get_stream(self, sd): # reload sounddevice to reinitialize portaudio