* WebRTCClient and WebRTCServer abstractions * webrtc client implementation * Interactive test scripts * Send localDescriptions as offer/asnwer, as they are different * Tracks need to be added after setting remote description for multi-cam streaming to work * Remove WebRTCStreamingMetadata * Wait for tracks * Move stuff to separate files, rename some things * Refactor everything, create WebRTCStreamBuilder for both offer and answers * ta flight done time to grind * wait for incoming tracks and channels * Dummy track and frame reader track. Fix timing. * dt based on camera type * first trial of the new api * Fix audio track * methods for checking for incoming tracks * Web migration part 2 * Fixes for stream api * use rtc description for web.py * experimental cereal proxy * remove old code from bodyav * fix is_started * serialize session description * fix audio * messaging channel wrapper * fix audiotrack * h264 codec preference * Add codec preference to tracks * override sdp codecs * add logging * Move cli stuff to separate file * slight cleanup * Fix audio track * create codec_mime inside force_codec function * fix incoming media estimation * move builders to __init__ * stream updates following builders * Update example script * web.py support for new builder * web speaker fixes * StreamingMediaInfo API * Move things around * should_add_data_channel rename * is_connected_and_ready * fix linter errors * make cli executable * remove dumb comments * logging support * fix parse_info_from_offer * improve type annotations * satisfy linters * Support for waiting for disconnection * Split device tracks into video/audio files. Move audio speaker to audio.py * default dt for dummy video track * Fix cli * new speaker fixes * Remove almost all functionality from web.py * webrtcd * continue refactoring web.py * after handling joystick reset in controlsd with #30409, controls are not necessary anymore * ping endpoint * Update js files to at least support what worked previously * Fixes after some tests on the body * Streaming fixes * Remove the use of WebRTCStreamBuilder. Subclass use is now required * Add todo * delete all streams on shutdown * Replace lastPing with lastChannelMessageTime * Update ping text only if rtc is still on * That should affect the chart too * Fix paths in web * use protocol in SSLContext * remove warnings since aiortc is not used directly anymore * check if task is done in stop * remove channel handler wrapper, since theres only one channel * Move things around * Moved webrtc abstractions to separate repository * Moved webrtcd to tools/webrtc * Update imports * Add bodyrtc as dependency * Add webrtcd to process_config * Remove usage of DummyVideoStreamTrack * Add main to webrtcd * Move webrtcd to system * Fix imports * Move cereal proxy logic outside of runner * Incoming proxy abstractions * Add some tests * Make it executable * Fix process config * Fix imports * Additional tests. Add tests to pyproject.toml * Update poetry lock * New line * Bump aiortc to 1.6.0 * Added teleoprtc_repo as submodule, and linked its source dir * Add init file to webrtc module * Handle aiortc warnings * Ignore deprecation warnings * Ignore resource warning too * Ignore the warnings * find free port for test_webrtcd * Start process inside the test case * random sleep test * test 2 * Test endpoint function instead * Update comment * Add system/webrtc to release * default arguments for body fields * Add teleoprtc to release * Bump teleoprtc * Exclude teleoprtc from static analysis * Use separate event loop for stream session tests old-commit-hash: f058b5d64eed31878533a85f25eb53386ff9e330
124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
import asyncio
|
|
import dataclasses
|
|
import json
|
|
import logging
|
|
import os
|
|
import ssl
|
|
import subprocess
|
|
|
|
from aiohttp import web, ClientSession
|
|
import pyaudio
|
|
import wave
|
|
|
|
from openpilot.common.basedir import BASEDIR
|
|
from openpilot.system.webrtc.webrtcd import StreamRequestBody
|
|
|
|
logger = logging.getLogger("bodyteleop")
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
TELEOPDIR = f"{BASEDIR}/tools/bodyteleop"
|
|
WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001
|
|
|
|
|
|
## UTILS
|
|
async def play_sound(sound):
|
|
SOUNDS = {
|
|
'engage': 'selfdrive/assets/sounds/engage.wav',
|
|
'disengage': 'selfdrive/assets/sounds/disengage.wav',
|
|
'error': 'selfdrive/assets/sounds/warning_immediate.wav',
|
|
}
|
|
assert sound in SOUNDS
|
|
|
|
chunk = 5120
|
|
with wave.open(os.path.join(BASEDIR, 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()
|
|
|
|
## SSL
|
|
def create_ssl_cert(cert_path, key_path):
|
|
try:
|
|
proc = subprocess.run(f'openssl req -x509 -newkey rsa:4096 -nodes -out {cert_path} -keyout {key_path} \
|
|
-days 365 -subj "/C=US/ST=California/O=commaai/OU=comma body"',
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
proc.check_returncode()
|
|
except subprocess.CalledProcessError as ex:
|
|
raise ValueError(f"Error creating SSL certificate:\n[stdout]\n{proc.stdout.decode()}\n[stderr]\n{proc.stderr.decode()}") from ex
|
|
|
|
|
|
def create_ssl_context():
|
|
cert_path = os.path.join(TELEOPDIR, 'cert.pem')
|
|
key_path = os.path.join(TELEOPDIR, 'key.pem')
|
|
if not os.path.exists(cert_path) or not os.path.exists(key_path):
|
|
logger.info("Creating certificate...")
|
|
create_ssl_cert(cert_path, key_path)
|
|
else:
|
|
logger.info("Certificate exists!")
|
|
ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER)
|
|
ssl_context.load_cert_chain(cert_path, key_path)
|
|
|
|
return ssl_context
|
|
|
|
## ENDPOINTS
|
|
async def index(request):
|
|
with open(os.path.join(TELEOPDIR, "static", "index.html"), "r") as f:
|
|
content = f.read()
|
|
return web.Response(content_type="text/html", text=content)
|
|
|
|
|
|
async def ping(request):
|
|
return web.Response(text="pong")
|
|
|
|
|
|
async def sound(request):
|
|
params = await request.json()
|
|
sound_to_play = params["sound"]
|
|
|
|
await play_sound(sound_to_play)
|
|
return web.json_response({"status": "ok"})
|
|
|
|
|
|
async def offer(request):
|
|
params = await request.json()
|
|
body = StreamRequestBody(params["sdp"], ["driver"], ["testJoystick"], ["carState"])
|
|
body_json = json.dumps(dataclasses.asdict(body))
|
|
|
|
logger.info("Sending offer to webrtcd...")
|
|
webrtcd_url = f"http://{WEBRTCD_HOST}:{WEBRTCD_PORT}/stream"
|
|
async with ClientSession() as session, session.post(webrtcd_url, data=body_json) as resp:
|
|
assert resp.status == 200
|
|
answer = await resp.json()
|
|
return web.json_response(answer)
|
|
|
|
|
|
def main():
|
|
# App needs to be HTTPS for microphone and audio autoplay to work on the browser
|
|
ssl_context = create_ssl_context()
|
|
|
|
app = web.Application()
|
|
app['mutable_vals'] = {}
|
|
app.router.add_get("/", index)
|
|
app.router.add_get("/ping", ping, allow_head=True)
|
|
app.router.add_post("/offer", offer)
|
|
app.router.add_post("/sound", sound)
|
|
app.router.add_static('/static', os.path.join(TELEOPDIR, 'static'))
|
|
web.run_app(app, access_log=None, host="0.0.0.0", port=5000, ssl_context=ssl_context)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|