Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
00107b5
athenad and webrtcd updates
stefpi May 11, 2026
fe36daf
remove feature stream services from webrtcd split
stefpi May 11, 2026
8fddb97
stream encoder thread
stefpi May 11, 2026
4296796
reduce diff
stefpi May 11, 2026
65f2332
wire webrtc to livestream camera encoder
stefpi May 11, 2026
8c59f83
request livestream camera switch service
stefpi May 11, 2026
3499ac6
add frame timing headers
stefpi May 11, 2026
9ae526b
rfctr
stefpi May 11, 2026
753a6a1
diff
stefpi May 11, 2026
48c56fa
clean
stefpi May 11, 2026
6deb216
remove camera list in favour of init camera field
stefpi May 11, 2026
49af84c
remove cors
stefpi May 12, 2026
ab7795e
clean
stefpi May 12, 2026
d3ff632
remove unused
stefpi May 12, 2026
503b085
remove extra try except
stefpi May 12, 2026
dfe1182
remove video_tracks unused
stefpi May 12, 2026
e860431
add back exception trace
stefpi May 12, 2026
ea25eac
add stream road camera info to stream cameras
stefpi May 12, 2026
6427d17
fix
stefpi May 12, 2026
d736aea
add back assert
stefpi May 12, 2026
cc0a8c0
clean diff
stefpi May 12, 2026
0f38abe
clean diff
stefpi May 19, 2026
1c32d41
add testJoystick only on body
stefpi May 21, 2026
be391a1
fix camera list
stefpi May 21, 2026
2170f6b
remove reference to future service
stefpi May 21, 2026
c5d3580
Merge branch 'split/athenad-webrtcd' into split/stream-encoder-thread
stefpi May 21, 2026
8281226
Merge branch 'split/athenad-webrtcd' into split/frame-timing-headers
stefpi May 21, 2026
08071fe
video_tracks list add back
stefpi May 21, 2026
b6b548a
Merge branch 'master' into split/stream-encoder-thread
stefpi May 22, 2026
6edffe7
Merge branch 'master' into split/stream-encoder-thread
stefpi May 22, 2026
96b76fb
encode all cameras and swap in video track in webrtc
stefpi May 22, 2026
fc3bdac
clean
stefpi May 22, 2026
2cdd6f6
explicitly gate bridge send
stefpi May 22, 2026
207fefe
clean leftover
stefpi May 22, 2026
ebc04de
Merge branch 'master' into split/frame-timing-headers
stefpi May 22, 2026
1b65d75
Merge branch 'split/stream-encoder-thread' into split/frame-timing-he…
stefpi May 22, 2026
1d2c44f
rearrange video.py
stefpi May 22, 2026
bf6b537
Merge branch 'master' into split/frame-timing-headers
stefpi May 23, 2026
33c83cd
fix lint
stefpi May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions system/webrtc/device/video.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import struct
import time

import av
Expand All @@ -7,6 +8,13 @@
from cereal import messaging
from openpilot.common.realtime import DT_MDL, DT_DMON

# arbitrary 16-byte UUID identifying openpilot frame-timing SEI messages
TIMING_SEI_UUID = bytes([
0xa5, 0xe0, 0xc4, 0xa4, 0x5b, 0x6e, 0x4e, 0x1e,
0x9c, 0x7e, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
])
_SEI_PREFIX = b'\x00\x00\x00\x01\x06\x05\x30' + TIMING_SEI_UUID


class LiveStreamVideoStreamTrack(TiciVideoStreamTrack):
camera_to_sock_mapping = {
Expand All @@ -22,23 +30,36 @@ def __init__(self, camera_type: str):
self._sock = self._make_sock(camera_type)
self._pts = 0
self._t0_ns = time.monotonic_ns()
self.timing_sei_enabled = False

def _make_sock(self, camera_type: str) -> messaging.SubSocket:
return messaging.sub_sock(self.camera_to_sock_mapping[camera_type], conflate=True)

def switch_camera(self, camera_type: str) -> None:
self._sock = self._make_sock(camera_type)

def _build_frame_data(self, msg) -> bytes:
encode_data = getattr(msg, msg.which())
if not self.timing_sei_enabled:
return encode_data.header + encode_data.data

idx = encode_data.idx
sei_nal = _SEI_PREFIX + struct.pack('>4d',
(idx.timestampEof - idx.timestampSof) / 1e6,
(msg.logMonoTime - idx.timestampEof) / 1e6,
(time.monotonic_ns() - msg.logMonoTime) / 1e6,
time.time() * 1000, # noqa: TID251
) + b'\x80'
return encode_data.header + sei_nal + encode_data.data

async def recv(self):
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())

packet = av.Packet(evta.header + evta.data)
packet = av.Packet(self._build_frame_data(msg))
packet.time_base = self._time_base

self._pts = ((time.monotonic_ns() - self._t0_ns) * self._clock_rate) // 1_000_000_000
Expand Down
31 changes: 25 additions & 6 deletions system/webrtc/webrtcd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3

import time
import argparse
import asyncio
import contextlib
Expand Down Expand Up @@ -173,12 +174,30 @@ async def get_answer(self):
def message_handler(self, message: bytes):
assert self.incoming_bridge is not None
try:
msg_json = json.loads(message)
if msg_json.get("type") == "livestreamCameraSwitch" and hasattr(self.video_track, "switch_camera"):
self.video_track.switch_camera(msg_json["data"]["camera"])
return

if msg_json.get("type") not in self.incoming_bridge_services:
payload = json.loads(message) if isinstance(message, (bytes, str)) else None
if isinstance(payload, dict):
msg_type = payload.get("type")

if msg_type == "livestreamCameraSwitch":
self.video_track.switch_camera(payload["data"]["camera"])
return

if msg_type == "clockSync":
data = payload.get("data", {})
pong = json.dumps({"type": "clockSync", "data": {
"action": "pong", "browserSendTime": data.get("browserSendTime"), "deviceTime": time.time() * 1000, # noqa: TID251
}})
self.stream.get_messaging_channel().send(pong)
return

if msg_type == "enableTimingSei":
enabled = bool(payload.get("data", {}).get("enabled"))
for track in self.video_tracks:
if hasattr(track, 'timing_sei_enabled'):
track.timing_sei_enabled = enabled
return

if payload.get("type") not in self.incoming_bridge_services:
return
self.incoming_bridge.send(message)
except Exception:
Expand Down
Loading