You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							159 lines
						
					
					
						
							5.1 KiB
						
					
					
				
			
		
		
	
	
							159 lines
						
					
					
						
							5.1 KiB
						
					
					
				| import asyncio
 | |
| import io
 | |
| import numpy as np
 | |
| import pyaudio
 | |
| import wave
 | |
| 
 | |
| from aiortc.contrib.media import MediaBlackhole
 | |
| from aiortc.mediastreams import AudioStreamTrack, MediaStreamError, MediaStreamTrack
 | |
| from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE
 | |
| from aiortc.rtcrtpsender import RTCRtpSender
 | |
| from av import CodecContext, Packet
 | |
| from pydub import AudioSegment
 | |
| import cereal.messaging as messaging
 | |
| 
 | |
| AUDIO_RATE = 16000
 | |
| SOUNDS = {
 | |
|   'engage': '../../selfdrive/assets/sounds/engage.wav',
 | |
|   'disengage': '../../selfdrive/assets/sounds/disengage.wav',
 | |
|   'error': '../../selfdrive/assets/sounds/warning_immediate.wav',
 | |
| }
 | |
| 
 | |
| 
 | |
| def force_codec(pc, sender, forced_codec='video/VP9', stream_type="video"):
 | |
|   codecs = RTCRtpSender.getCapabilities(stream_type).codecs
 | |
|   codec = [codec for codec in codecs if codec.mimeType == forced_codec]
 | |
|   transceiver = next(t for t in pc.getTransceivers() if t.sender == sender)
 | |
|   transceiver.setCodecPreferences(codec)
 | |
| 
 | |
| 
 | |
| class EncodedBodyVideo(MediaStreamTrack):
 | |
|   kind = "video"
 | |
| 
 | |
|   _start: float
 | |
|   _timestamp: int
 | |
| 
 | |
|   def __init__(self):
 | |
|     super().__init__()
 | |
|     sock_name = 'livestreamDriverEncodeData'
 | |
|     messaging.context = messaging.Context()
 | |
|     self.sock = messaging.sub_sock(sock_name, None, conflate=True)
 | |
|     self.pts = 0
 | |
| 
 | |
|   async def recv(self) -> Packet:
 | |
|     while True:
 | |
|       msg = messaging.recv_one_or_none(self.sock)
 | |
|       if msg is not None:
 | |
|         break
 | |
|       await asyncio.sleep(0.005)
 | |
| 
 | |
|     evta = getattr(msg, msg.which())
 | |
|     self.last_idx = evta.idx.encodeId
 | |
| 
 | |
|     packet = Packet(evta.header + evta.data)
 | |
|     packet.time_base = VIDEO_TIME_BASE
 | |
|     packet.pts = self.pts
 | |
|     self.pts += 0.05 * VIDEO_CLOCK_RATE
 | |
|     return packet
 | |
| 
 | |
| 
 | |
| class WebClientSpeaker(MediaBlackhole):
 | |
|   def __init__(self):
 | |
|     super().__init__()
 | |
|     self.p = pyaudio.PyAudio()
 | |
|     self.buffer = io.BytesIO()
 | |
|     self.channels = 2
 | |
|     self.stream = self.p.open(format=pyaudio.paInt16, channels=self.channels, rate=48000, frames_per_buffer=9600,
 | |
|                               output=True, stream_callback=self.pyaudio_callback)
 | |
| 
 | |
|   def pyaudio_callback(self, in_data, frame_count, time_info, status):
 | |
|     if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2:
 | |
|       buff = np.zeros((frame_count, 2), dtype=np.int16).tobytes()
 | |
|     elif self.buffer.getbuffer().nbytes > 115200:  # 3x the usual read size
 | |
|       self.buffer.seek(0)
 | |
|       buff = self.buffer.read(frame_count * self.channels * 4)
 | |
|       buff = buff[:frame_count * self.channels * 2]
 | |
|       self.buffer.seek(2)
 | |
|     else:
 | |
|       self.buffer.seek(0)
 | |
|       buff = self.buffer.read(frame_count * self.channels * 2)
 | |
|       self.buffer.seek(2)
 | |
|     return (buff, pyaudio.paContinue)
 | |
| 
 | |
|   async def consume(self, track):
 | |
|     while True:
 | |
|       try:
 | |
|         frame = await track.recv()
 | |
|       except MediaStreamError:
 | |
|         return
 | |
|       bio = bytes(frame.planes[0])
 | |
|       self.buffer.write(bio)
 | |
| 
 | |
|   async def start(self):
 | |
|     for track, task in self._MediaBlackhole__tracks.items():
 | |
|       if task is None:
 | |
|         self._MediaBlackhole__tracks[track] = asyncio.ensure_future(self.consume(track))
 | |
| 
 | |
|   async def stop(self):
 | |
|     for task in self._MediaBlackhole__tracks.values():
 | |
|       if task is not None:
 | |
|         task.cancel()
 | |
|     self._MediaBlackhole__tracks = {}
 | |
|     self.stream.stop_stream()
 | |
|     self.stream.close()
 | |
|     self.p.terminate()
 | |
| 
 | |
| 
 | |
| class BodyMic(AudioStreamTrack):
 | |
|   def __init__(self):
 | |
|     super().__init__()
 | |
| 
 | |
|     self.sample_rate = AUDIO_RATE
 | |
|     self.AUDIO_PTIME = 0.020  # 20ms audio packetization
 | |
|     self.samples = int(self.AUDIO_PTIME * self.sample_rate)
 | |
|     self.FORMAT = pyaudio.paInt16
 | |
|     self.CHANNELS = 2
 | |
|     self.RATE = self.sample_rate
 | |
|     self.CHUNK = int(AUDIO_RATE * 0.020)
 | |
|     self.p = pyaudio.PyAudio()
 | |
|     self.mic_stream = self.p.open(format=self.FORMAT, channels=1, rate=self.RATE, input=True, frames_per_buffer=self.CHUNK)
 | |
| 
 | |
|     self.codec = CodecContext.create('pcm_s16le', 'r')
 | |
|     self.codec.sample_rate = self.RATE
 | |
|     self.codec.channels = 2
 | |
|     self.audio_samples = 0
 | |
|     self.chunk_number = 0
 | |
| 
 | |
|   async def recv(self):
 | |
|     mic_data = self.mic_stream.read(self.CHUNK)
 | |
|     mic_sound = AudioSegment(mic_data, sample_width=2, channels=1, frame_rate=self.RATE)
 | |
|     mic_sound = AudioSegment.from_mono_audiosegments(mic_sound, mic_sound)
 | |
|     mic_sound += 3  # increase volume by 3db
 | |
|     packet = Packet(mic_sound.raw_data)
 | |
|     frame = self.codec.decode(packet)[0]
 | |
|     frame.pts = self.audio_samples
 | |
|     self.audio_samples += frame.samples
 | |
|     self.chunk_number = self.chunk_number + 1
 | |
|     return frame
 | |
| 
 | |
| 
 | |
| async def play_sound(sound):
 | |
|   chunk = 5120
 | |
|   with wave.open(SOUNDS[sound], 'rb') as wf:
 | |
|     def callback(in_data, frame_count, time_info, status):
 | |
|       data = wf.readframes(frame_count)
 | |
|       return data, pyaudio.paContinue
 | |
| 
 | |
|     p = pyaudio.PyAudio()
 | |
|     stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
 | |
|                     channels=wf.getnchannels(),
 | |
|                     rate=wf.getframerate(),
 | |
|                     output=True,
 | |
|                     frames_per_buffer=chunk,
 | |
|                     stream_callback=callback)
 | |
|     stream.start_stream()
 | |
|     while stream.is_active():
 | |
|       await asyncio.sleep(0)
 | |
|     stream.stop_stream()
 | |
|     stream.close()
 | |
|     p.terminate()
 | |
| 
 |