11#!/usr/bin/env python3
2+ import logging
23import queue
34import threading
5+ import traceback
46from enum import Enum , auto
57from typing import Optional
68
911from msgq .visionipc import VisionIpcServer , VisionStreamType
1012from openpilot .tools .lib .framereader import FrameReader
1113
14+ log = logging .getLogger ("replay" )
15+
1216BUFFER_COUNT = 40
1317
1418
19+ def get_nv12_info (width : int , height : int ) -> tuple [int , int , int ]:
20+ """Calculate NV12 buffer parameters matching C++ VENUS macros."""
21+ # VENUS_Y_STRIDE for NV12: align to 128
22+ nv12_width = (width + 127 ) & ~ 127
23+ # VENUS_Y_SCANLINES for NV12: align to 32
24+ nv12_height = (height + 31 ) & ~ 31
25+ # Buffer size from v4l2_format (matches C++ implementation)
26+ nv12_buffer_size = 2346 * nv12_width
27+ return nv12_width , nv12_height , nv12_buffer_size
28+
29+
1530class CameraType (Enum ):
1631 ROAD = 0
1732 DRIVER = 1
@@ -31,6 +46,7 @@ def __init__(self, cam_type: CameraType):
3146 self .stream_type = CAMERA_STREAM_TYPES [cam_type ]
3247 self .width = 0
3348 self .height = 0
49+ self .nv12_buffer_size = 0 # Padded buffer size for VisionIPC
3450 self .thread : Optional [threading .Thread ] = None
3551 self .queue : queue .Queue = queue .Queue ()
3652 self .cached_frames : dict [int , np .ndarray ] = {}
@@ -71,8 +87,13 @@ def _start_vipc_server(self) -> None:
7187 cam .cached_frames .clear ()
7288
7389 if cam .width > 0 and cam .height > 0 :
74- print (f"camera[{ cam .type .name } ] frame size { cam .width } x{ cam .height } " )
75- self ._vipc_server .create_buffers (cam .stream_type , BUFFER_COUNT , cam .width , cam .height )
90+ nv12_width , nv12_height , nv12_buffer_size = get_nv12_info (cam .width , cam .height )
91+ cam .nv12_buffer_size = nv12_buffer_size
92+ log .info (f"camera[{ cam .type .name } ] frame size { cam .width } x{ cam .height } , nv12 buffer size { nv12_buffer_size } " )
93+ self ._vipc_server .create_buffers_with_sizes (
94+ cam .stream_type , BUFFER_COUNT , cam .width , cam .height ,
95+ nv12_buffer_size , nv12_width , nv12_width * nv12_height
96+ )
7697
7798 if cam .thread is None or not cam .thread .is_alive ():
7899 cam .thread = threading .Thread (
@@ -108,18 +129,20 @@ def _camera_thread(self, cam: Camera) -> None:
108129 # Get the frame
109130 yuv = self ._get_frame (cam , fr , segment_id , frame_id )
110131 if yuv is not None :
111- # Send via VisionIPC
132+ # Send via VisionIPC - flatten to 1D and pad to match buffer size
112133 timestamp_sof = eidx .timestampSof
113134 timestamp_eof = eidx .timestampEof
114- self ._vipc_server .send (cam .stream_type , yuv .data , frame_id , timestamp_sof , timestamp_eof )
115- else :
116- print (f"camera[{ cam .type .name } ] failed to get frame: { segment_id } " )
135+ yuv_bytes = yuv .flatten ().tobytes ()
136+ # Pad to match the NV12 buffer size expected by VisionIPC
137+ if len (yuv_bytes ) < cam .nv12_buffer_size :
138+ yuv_bytes = yuv_bytes + bytes (cam .nv12_buffer_size - len (yuv_bytes ))
139+ self ._vipc_server .send (cam .stream_type , yuv_bytes , frame_id , timestamp_sof , timestamp_eof )
117140
118141 # Prefetch next frame
119142 self ._get_frame (cam , fr , segment_id + 1 , frame_id + 1 )
120143
121144 except Exception as e :
122- print (f"camera[{ cam .type .name } ] error: { e } " )
145+ log . error (f"camera[{ cam .type .name } ] error: { e } \n { traceback . format_exc () } " )
123146
124147 with self ._publishing_lock :
125148 self ._publishing -= 1
@@ -142,7 +165,7 @@ def _get_frame(self, cam: Camera, fr: FrameReader, segment_id: int, frame_id: in
142165 del cam .cached_frames [oldest ]
143166 return yuv
144167 except Exception as e :
145- print (f"Failed to decode frame { frame_id } : { e } " )
168+ log . warning (f"Failed to decode frame { frame_id } : { e } " )
146169 return None
147170
148171 def push_frame (self , cam_type : CameraType , fr : FrameReader , event ) -> None :
0 commit comments