1- from collections import deque
21import math
32import numpy as np
4- import threading
53import time
64import wave
75
1715
1816SAMPLE_RATE = 48000
1917SAMPLE_BUFFER = 4096 # (approx 100ms)
20- MAX_WEBRTC_BUFFER_SAMPLES = SAMPLE_RATE
21- WEBRTC_START_BUFFER_SAMPLES = SAMPLE_BUFFER + (SAMPLE_RATE // 50 ) # keep headroom over 20ms WebRTC chunks
2218MAX_VOLUME = 1.0
2319MIN_VOLUME = 0.1
2420ALERT_RAMP_TIME = 4 # seconds to ramp to max volume for warningImmediate
@@ -79,11 +75,6 @@ def __init__(self):
7975 self .selfdrive_timeout_alert = False
8076
8177 self .spl_filter_weighted = FirstOrderFilter (0 , 2.5 , FILTER_DT , initialized = False )
82- self .webrtc_buffer : deque [np .ndarray ] = deque ()
83- self .webrtc_buffer_offset = 0
84- self .webrtc_buffer_size = 0
85- self .webrtc_playing = False
86- self .webrtc_lock = threading .Lock ()
8778
8879 def load_sounds (self ):
8980 self .loaded_sounds : dict [int , np .ndarray ] = {}
@@ -121,85 +112,10 @@ def get_sound_data(self, frames): # get "frames" worth of data from the current
121112
122113 return ret * self .current_volume
123114
124- def _trim_webrtc_buffer (self ):
125- overflow = self .webrtc_buffer_size - MAX_WEBRTC_BUFFER_SAMPLES
126- while overflow > 0 and self .webrtc_buffer :
127- chunk = self .webrtc_buffer [0 ]
128- available = chunk .size - self .webrtc_buffer_offset
129- drop = min (overflow , available )
130- self .webrtc_buffer_offset += drop
131- self .webrtc_buffer_size -= drop
132- overflow -= drop
133-
134- if self .webrtc_buffer_offset >= chunk .size :
135- self .webrtc_buffer .popleft ()
136- self .webrtc_buffer_offset = 0
137-
138- def _clear_webrtc_buffer_locked (self ):
139- self .webrtc_buffer .clear ()
140- self .webrtc_buffer_offset = 0
141- self .webrtc_buffer_size = 0
142-
143- def add_webrtc_audio (self , audio_data : bytes , sample_rate : int ):
144- if sample_rate != SAMPLE_RATE :
145- cloudlog .warning (f"soundd dropping webrtc audio with unexpected sample rate: { sample_rate } " )
146- return
147- if not audio_data :
148- return
149-
150- samples = np .frombuffer (audio_data , dtype = np .int16 ).astype (np .float32 ) / (2 ** 15 )
151- if samples .size == 0 :
152- return
153-
154- with self .webrtc_lock :
155- self .webrtc_buffer .append (samples )
156- self .webrtc_buffer_size += samples .size
157- self ._trim_webrtc_buffer ()
158-
159- def get_webrtc_audio (self , frames : int ) -> np .ndarray :
160- out = np .zeros (frames , dtype = np .float32 )
161-
162- with self .webrtc_lock :
163- if not self .webrtc_playing :
164- if self .webrtc_buffer_size < max (frames , WEBRTC_START_BUFFER_SAMPLES ):
165- return out
166- self .webrtc_playing = True
167-
168- if self .webrtc_buffer_size < frames :
169- self ._clear_webrtc_buffer_locked ()
170- self .webrtc_playing = False
171- return out
172-
173- written = 0
174- while written < frames and self .webrtc_buffer :
175- chunk = self .webrtc_buffer [0 ]
176- available = chunk .size - self .webrtc_buffer_offset
177- take = min (frames - written , available )
178- out [written :written + take ] = chunk [self .webrtc_buffer_offset :self .webrtc_buffer_offset + take ]
179- written += take
180- self .webrtc_buffer_offset += take
181-
182- if self .webrtc_buffer_offset >= chunk .size :
183- self .webrtc_buffer .popleft ()
184- self .webrtc_buffer_offset = 0
185-
186- self .webrtc_buffer_size -= written
187-
188- return out
189-
190- def webrtc_audio_thread (self , sock ) -> None :
191- while True :
192- for msg in messaging .drain_sock (sock , wait_for_one = True ):
193- audio = msg .webrtcAudioData
194- self .add_webrtc_audio (audio .data , audio .sampleRate )
195-
196115 def callback (self , data_out : np .ndarray , frames : int , time , status ) -> None :
197116 if status :
198117 cloudlog .warning (f"soundd stream over/underflow: { status } " )
199- sound = self .get_sound_data (frames )
200- sound += self .get_webrtc_audio (frames )
201- np .clip (sound , - 1.0 , 1.0 , out = sound )
202- data_out [:frames , 0 ] = sound
118+ data_out [:frames , 0 ] = self .get_sound_data (frames )
203119
204120 def update_alert (self , new_alert ):
205121 current_alert_played_once = self .current_alert == AudibleAlert .none or self .current_sound_frame > len (self .loaded_sounds [self .current_alert ])
@@ -211,14 +127,7 @@ def update_alert(self, new_alert):
211127 self .current_sound_frame = 0
212128
213129 def get_audible_alert (self , sm ):
214- sound_request_updated = False
215- if sm .updated ['soundRequest' ]:
216- new_alert = sm ['soundRequest' ].sound .raw
217- if new_alert != AudibleAlert .none :
218- self .update_alert (new_alert )
219- sound_request_updated = True
220-
221- if sm .updated ['selfdriveState' ] and not sound_request_updated :
130+ if sm .updated ['selfdriveState' ]:
222131 new_alert = sm ['selfdriveState' ].alertSound .raw
223132 self .update_alert (new_alert )
224133 elif check_selfdrive_timeout_alert (sm ):
@@ -227,6 +136,10 @@ def get_audible_alert(self, sm):
227136 elif self .selfdrive_timeout_alert :
228137 self .update_alert (AudibleAlert .none )
229138 self .selfdrive_timeout_alert = False
139+ elif sm .updated ['soundRequest' ]:
140+ new_alert = sm ['soundRequest' ].sound .raw
141+ if new_alert != AudibleAlert .none :
142+ self .update_alert (new_alert )
230143
231144 def calculate_volume (self , weighted_db ):
232145 volume = ((weighted_db - AMBIENT_DB ) / DB_SCALE ) * (MAX_VOLUME - MIN_VOLUME ) + MIN_VOLUME
@@ -244,8 +157,6 @@ def soundd_thread(self):
244157 import sounddevice as sd
245158
246159 sm = messaging .SubMaster (['selfdriveState' , 'soundPressure' , 'soundRequest' ])
247- webrtc_audio_sock = messaging .sub_sock ('webrtcAudioData' , conflate = False )
248- threading .Thread (target = self .webrtc_audio_thread , args = (webrtc_audio_sock ,), daemon = True ).start ()
249160
250161 with self .get_stream (sd ) as stream :
251162 rk = Ratekeeper (20 )
0 commit comments