From f0841b827a505db8e843188281fa8d6ceeda4d0b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 18 Jul 2026 09:04:41 -0700 Subject: [PATCH] remove av (#38366) --- openpilot/tools/camerastream/README.md | 12 +- .../tools/camerastream/compressed_vipc.py | 101 ++++--- .../tools/camerastream/ffmpeg_decoder.py | 261 ++++++++++++++++++ pyproject.toml | 3 - uv.lock | 17 -- 5 files changed, 315 insertions(+), 79 deletions(-) create mode 100644 openpilot/tools/camerastream/ffmpeg_decoder.py diff --git a/openpilot/tools/camerastream/README.md b/openpilot/tools/camerastream/README.md index 8b77fc5990..9671199297 100644 --- a/openpilot/tools/camerastream/README.md +++ b/openpilot/tools/camerastream/README.md @@ -44,18 +44,18 @@ To actually display the stream, run `watch3` in separate terminal: ## compressed_vipc.py usage ``` $ python3 compressed_vipc.py -h -usage: compressed_vipc.py [-h] [--nvidia] [--cams CAMS] [--silent] addr +usage: compressed_vipc.py [-h] [--cams CAMS] [--server SERVER] [--silent] addr Decode video streams and broadcast on VisionIPC positional arguments: - addr Address of comma three + addr Address of comma three options: - -h, --help show this help message and exit - --nvidia Use nvidia instead of ffmpeg - --cams CAMS Cameras to decode - --silent Suppress debug output + -h, --help show this help message and exit + --cams CAMS Cameras to decode + --server SERVER choose vipc server name + --silent Suppress debug output ``` diff --git a/openpilot/tools/camerastream/compressed_vipc.py b/openpilot/tools/camerastream/compressed_vipc.py index 35e9d3dab2..56ed12889f 100755 --- a/openpilot/tools/camerastream/compressed_vipc.py +++ b/openpilot/tools/camerastream/compressed_vipc.py @@ -1,17 +1,15 @@ #!/usr/bin/env python3 -import av -import av.video.format import os -import sys import argparse -import numpy as np import multiprocessing import time import signal +from collections import deque import openpilot.cereal.messaging as messaging from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.tools.camerastream.ffmpeg_decoder import Decoder, FFmpegError V4L2_BUF_FLAG_KEYFRAME = 8 @@ -25,23 +23,12 @@ ENCODE_SOCKETS = { VisionStreamType.VISION_STREAM_WIDE_ROAD: "wideRoadEncodeData", } -def decoder(addr, vipc_server, vst, nvidia, W, H, debug=False): +def decoder(addr, vipc_server, vst, W, H, debug=False): sock_name = ENCODE_SOCKETS[vst] if debug: print(f"start decoder for {sock_name}, {W}x{H}") - if nvidia: - os.environ["NV_LOW_LATENCY"] = "3" # both bLowLatency and CUVID_PKT_ENDOFPICTURE - sys.path += os.environ["LD_LIBRARY_PATH"].split(":") - import PyNvCodec as nvc - - nvDec = nvc.PyNvDecoder(W, H, nvc.PixelFormat.NV12, nvc.CudaVideoCodec.HEVC, 0) - cc1 = nvc.ColorspaceConversionContext(nvc.ColorSpace.BT_709, nvc.ColorRange.JPEG) - conv_yuv = nvc.PySurfaceConverter(W, H, nvc.PixelFormat.NV12, nvc.PixelFormat.YUV420, 0) - nvDwn_yuv = nvc.PySurfaceDownloader(W, H, nvc.PixelFormat.YUV420, 0) - img_yuv = np.ndarray((H*W//2*3), dtype=np.uint8) - else: - codec = av.CodecContext.create("hevc", "r") + codec = Decoder("hevc") os.environ["ZMQ"] = "1" messaging.reset_context() @@ -50,13 +37,22 @@ def decoder(addr, vipc_server, vst, nvidia, W, H, debug=False): last_idx = -1 seen_iframe = False - time_q = [] + time_q = deque() + + def resync(): + nonlocal seen_iframe + codec.reset() + seen_iframe = False + time_q.clear() + while 1: msgs = messaging.drain_sock(sock, wait_for_one=True) for evt in msgs: evta = getattr(evt, evt.which()) - if debug and evta.idx.encodeId != 0 and evta.idx.encodeId != (last_idx+1): - print("DROP PACKET!") + if last_idx != -1 and evta.idx.encodeId != (last_idx + 1): + if debug: + print("DROP PACKET!") + resync() last_idx = evta.idx.encodeId if not seen_iframe and not (evta.idx.flags & V4L2_BUF_FLAG_KEYFRAME): if debug: @@ -67,48 +63,48 @@ def decoder(addr, vipc_server, vst, nvidia, W, H, debug=False): frame_latency = ((evta.idx.timestampEof/1e9) - (evta.idx.timestampSof/1e9))*1000 process_latency = ((evt.logMonoTime/1e9) - (evta.idx.timestampEof/1e9))*1000 - # put in header (first) + # put in header (first) — VPS/SPS/PPS only, no frame expected if not seen_iframe: - if nvidia: - nvDec.DecodeSurfaceFromPacket(np.frombuffer(evta.header, dtype=np.uint8)) - else: - codec.decode(av.packet.Packet(evta.header)) + try: + codec.decode(evta.header) + except FFmpegError as e: + if debug: + print(f"HEADER ERROR: {e}") + resync() + continue seen_iframe = True - if nvidia: - rawSurface = nvDec.DecodeSurfaceFromPacket(np.frombuffer(evta.data, dtype=np.uint8)) - if rawSurface.Empty(): - if debug: - print("DROP SURFACE") - continue - convSurface = conv_yuv.Execute(rawSurface, cc1) - nvDwn_yuv.DownloadSingleSurface(convSurface, img_yuv) - else: - frames = codec.decode(av.packet.Packet(evta.data)) - if len(frames) == 0: - if debug: - print("DROP SURFACE") - continue - assert len(frames) == 1 - img_yuv = frames[0].to_ndarray(format=av.video.format.VideoFormat('yuv420p')).flatten() - uv_offset = H*W - y = img_yuv[:uv_offset] - uv = img_yuv[uv_offset:].reshape(2, -1).ravel('F') - img_yuv = np.hstack((y, uv)) + try: + img_yuv = codec.decode(evta.data) + except FFmpegError as e: + if debug: + print(f"DECODE ERROR: {e}") + resync() + continue - vipc_server.send(vst, img_yuv.data, cnt, int(time_q[0]*1e9), int(time.monotonic()*1e9)) + if img_yuv is None: + if debug: + print("DROP SURFACE") + continue + + if codec.width != W or codec.height != H: + if debug: + print(f"DECODE ERROR: decoded frame is {codec.width}x{codec.height}, expected {W}x{H}") + resync() + continue + + frame_start_time = time_q.popleft() + vipc_server.send(vst, img_yuv.data, cnt, int(frame_start_time*1e9), int(time.monotonic()*1e9)) cnt += 1 - pc_latency = (time.monotonic()-time_q[0])*1000 - time_q = time_q[1:] + pc_latency = (time.monotonic()-frame_start_time)*1000 if debug: print(f"{len(msgs):2d} {evta.idx.encodeId:4d} {evt.logMonoTime/1e9:.3f} {evta.idx.timestampEof/1e6:.3f} \ roll {frame_latency:6.2f} ms latency {process_latency:6.2f} ms + {network_latency:6.2f} ms + {pc_latency:6.2f} ms \ = {process_latency+network_latency+pc_latency:6.2f} ms", len(evta.data), sock_name) - class CompressedVipc: - def __init__(self, addr, vision_streams, server_name, nvidia=False, debug=False): + def __init__(self, addr, vision_streams, server_name, debug=False): print("getting frame sizes") os.environ["ZMQ"] = "1" messaging.reset_context() @@ -127,7 +123,7 @@ class CompressedVipc: self.procs = [] for vst in vision_streams: ed = sm[ENCODE_SOCKETS[vst]] - p = multiprocessing.Process(target=decoder, args=(addr, self.vipc_server, vst, nvidia, ed.width, ed.height, debug)) + p = multiprocessing.Process(target=decoder, args=(addr, self.vipc_server, vst, ed.width, ed.height, debug)) p.start() self.procs.append(p) @@ -143,7 +139,6 @@ class CompressedVipc: if __name__ == "__main__": parser = argparse.ArgumentParser(description="Decode video streams and broadcast on VisionIPC") parser.add_argument("addr", help="Address of comma three") - parser.add_argument("--nvidia", action="store_true", help="Use nvidia instead of ffmpeg") parser.add_argument("--cams", default="0,1,2", help="Cameras to decode") parser.add_argument("--server", default="camerad", help="choose vipc server name") parser.add_argument("--silent", action="store_true", help="Suppress debug output") @@ -156,7 +151,7 @@ if __name__ == "__main__": ] vsts = [vision_streams[int(x)] for x in args.cams.split(",")] - cvipc = CompressedVipc(args.addr, vsts, args.server, args.nvidia, debug=(not args.silent)) + cvipc = CompressedVipc(args.addr, vsts, args.server, debug=(not args.silent)) # register exit handler signal.signal(signal.SIGINT, lambda sig, frame: cvipc.kill()) diff --git a/openpilot/tools/camerastream/ffmpeg_decoder.py b/openpilot/tools/camerastream/ffmpeg_decoder.py new file mode 100644 index 0000000000..13cdc12df1 --- /dev/null +++ b/openpilot/tools/camerastream/ffmpeg_decoder.py @@ -0,0 +1,261 @@ +import ctypes +import errno +import os + +import ffmpeg +import numpy as np + + +AV_INPUT_BUFFER_PADDING_SIZE = 64 +AV_LOG_QUIET = -8 +SWS_FAST_BILINEAR = 1 + + +class FFmpegError(RuntimeError): + pass + + +class AVPacket(ctypes.Structure): + # Public prefix of AVPacket. Only data and size are modified here; the packet + # remains non-refcounted and points at Decoder._packet_buffer. + _fields_ = [ + ("buf", ctypes.c_void_p), + ("pts", ctypes.c_int64), + ("dts", ctypes.c_int64), + ("data", ctypes.POINTER(ctypes.c_uint8)), + ("size", ctypes.c_int), + ] + + +class AVFrame(ctypes.Structure): + # Public prefix of AVFrame through format. Stable within a libavutil major + _fields_ = [ + ("data", ctypes.POINTER(ctypes.c_uint8) * 8), + ("linesize", ctypes.c_int * 8), + ("extended_data", ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8))), + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("nb_samples", ctypes.c_int), + ("format", ctypes.c_int), + ] + + +def _bind(fn, restype, *argtypes): + fn.restype = restype + fn.argtypes = list(argtypes) + return fn + + +def _load_libraries(): + avutil = ctypes.CDLL(os.path.join(ffmpeg.LIB_DIR, "libavutil.so.59"), mode=ctypes.RTLD_GLOBAL) + avcodec = ctypes.CDLL(os.path.join(ffmpeg.LIB_DIR, "libavcodec.so.61"), mode=ctypes.RTLD_GLOBAL) + swscale = ctypes.CDLL(os.path.join(ffmpeg.LIB_DIR, "libswscale.so.8"), mode=ctypes.RTLD_GLOBAL) + + c_int, c_char_p, c_void_p, c_size_t = ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_size_t + c_uint8_p = ctypes.POINTER(ctypes.c_uint8) + c_void_p_p = ctypes.POINTER(c_void_p) + + _bind(avutil.av_log_set_level, None, c_int) + _bind(avutil.av_opt_set, c_int, c_void_p, c_char_p, c_char_p, c_int) + _bind(avutil.av_strerror, c_int, c_int, c_char_p, c_size_t) + _bind(avutil.av_get_pix_fmt, c_int, c_char_p) + + _bind(avcodec.avcodec_find_decoder_by_name, c_void_p, c_char_p) + _bind(avcodec.avcodec_alloc_context3, c_void_p, c_void_p) + _bind(avcodec.avcodec_open2, c_int, c_void_p, c_void_p, c_void_p) + _bind(avcodec.avcodec_free_context, None, c_void_p_p) + _bind(avcodec.avcodec_flush_buffers, None, c_void_p) + _bind(avcodec.avcodec_send_packet, c_int, c_void_p, ctypes.POINTER(AVPacket)) + _bind(avcodec.avcodec_receive_frame, c_int, c_void_p, ctypes.POINTER(AVFrame)) + _bind(avcodec.av_packet_alloc, ctypes.POINTER(AVPacket)) + _bind(avcodec.av_packet_free, None, ctypes.POINTER(ctypes.POINTER(AVPacket))) + _bind(avcodec.av_frame_alloc, ctypes.POINTER(AVFrame)) + _bind(avcodec.av_frame_free, None, ctypes.POINTER(ctypes.POINTER(AVFrame))) + _bind(avcodec.av_frame_unref, None, ctypes.POINTER(AVFrame)) + + _bind(swscale.sws_getCachedContext, c_void_p, + c_void_p, c_int, c_int, c_int, c_int, c_int, c_int, c_int, c_void_p, c_void_p, c_void_p) + _bind(swscale.sws_scale, c_int, + c_void_p, ctypes.POINTER(c_uint8_p), ctypes.POINTER(c_int), + c_int, c_int, ctypes.POINTER(c_uint8_p), ctypes.POINTER(c_int)) + _bind(swscale.sws_freeContext, None, c_void_p) + + avutil.av_log_set_level(AV_LOG_QUIET) + return avutil, avcodec, swscale +_avutil, _avcodec, _swscale = _load_libraries() + +_DataArray = ctypes.POINTER(ctypes.c_uint8) * 4 +_LinesizeArray = ctypes.c_int * 4 + +AV_PIX_FMT_NV12 = _avutil.av_get_pix_fmt(b"nv12") +assert AV_PIX_FMT_NV12 >= 0 + + +def _error_string(code: int) -> str: + buf = ctypes.create_string_buffer(256) + if _avutil.av_strerror(code, buf, len(buf)) == 0: + return buf.value.decode(errors="replace") + return f"FFmpeg error {code}" + + +def _check(code: int, operation: str) -> None: + if code < 0: + raise FFmpegError(f"{operation}: {_error_string(code)}") + + +class Decoder: + def __init__(self, codec_name: str = "hevc"): + self.closed = True + self._sws_context = ctypes.c_void_p() + self._packet_buffer = bytearray() + self._packet_address = 0 + self._packet_data = None + self._output = np.empty(0, dtype=np.uint8) + self._dst_data = _DataArray() + self._dst_linesize = _LinesizeArray() + self.width = 0 + self.height = 0 + self._src_format = -1 + + codec = _avcodec.avcodec_find_decoder_by_name(codec_name.encode()) + if not codec: + raise FFmpegError(f"decoder not found: {codec_name}") + + self._context = ctypes.c_void_p(_avcodec.avcodec_alloc_context3(codec)) + if not self._context: + raise MemoryError("avcodec_alloc_context3 failed") + + self._packet = _avcodec.av_packet_alloc() + if not self._packet: + _avcodec.avcodec_free_context(ctypes.byref(self._context)) + raise MemoryError("av_packet_alloc failed") + + self._frame = _avcodec.av_frame_alloc() + if not self._frame: + _avcodec.av_packet_free(ctypes.byref(self._packet)) + _avcodec.avcodec_free_context(ctypes.byref(self._context)) + raise MemoryError("av_frame_alloc failed") + + try: + # Frame threading holds decoded frames to populate worker pipelines. + # Slice threads can reduce decode time without adding that frame queue; + # four was the latency minimum on the replay camera workload. + _check(_avutil.av_opt_set(self._context, b"threads", b"4", 0), "set decoder threads") + _check(_avutil.av_opt_set(self._context, b"thread_type", b"slice", 0), "set decoder thread type") + _check(_avutil.av_opt_set(self._context, b"flags", b"+low_delay", 0), "set low-delay mode") + _check(_avcodec.avcodec_open2(self._context, codec, None), "open decoder") + except Exception: + _avcodec.av_frame_free(ctypes.byref(self._frame)) + _avcodec.av_packet_free(ctypes.byref(self._packet)) + _avcodec.avcodec_free_context(ctypes.byref(self._context)) + raise + + self.closed = False + + def __enter__(self): + self._ensure_open() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def _ensure_open(self) -> None: + if self.closed: + raise RuntimeError("decoder is closed") + + def _prepare_packet(self, data) -> None: + size = len(data) + required = size + AV_INPUT_BUFFER_PADDING_SIZE + if len(self._packet_buffer) < required: + # Grow-only; the address stays valid until the next reallocation. + self._packet_buffer = bytearray(required) + self._packet_address = ctypes.addressof(ctypes.c_uint8.from_buffer(self._packet_buffer)) + self._packet_data = ctypes.cast(self._packet_address, ctypes.POINTER(ctypes.c_uint8)) + + self._packet_buffer[:size] = data + ctypes.memset(self._packet_address + size, 0, AV_INPUT_BUFFER_PADDING_SIZE) + self._packet.contents.data = self._packet_data + self._packet.contents.size = size + + def _prepare_output(self, frame: AVFrame) -> None: + width, height, src_format = frame.width, frame.height, frame.format + if width <= 0 or height <= 0 or width % 2 or height % 2: + raise FFmpegError(f"unsupported frame dimensions: {width}x{height}") + if (width, height, src_format) == (self.width, self.height, self._src_format): + return + + sws_context = _swscale.sws_getCachedContext( + self._sws_context, width, height, src_format, + width, height, AV_PIX_FMT_NV12, SWS_FAST_BILINEAR, + None, None, None, + ) + if not sws_context: + raise FFmpegError("sws_getCachedContext failed") + self._sws_context = ctypes.c_void_p(sws_context) + + self.width, self.height = width, height + self._src_format = src_format + self._output = np.empty(width * height * 3 // 2, dtype=np.uint8) + output_address = self._output.ctypes.data + self._dst_data = _DataArray( + ctypes.cast(output_address, ctypes.POINTER(ctypes.c_uint8)), + ctypes.cast(output_address + width * height, ctypes.POINTER(ctypes.c_uint8)), + None, + None, + ) + self._dst_linesize = _LinesizeArray(width, width, 0, 0) + + def _receive(self) -> np.ndarray | None: + """Return one NV12 frame, or None if the decoder needs more input. + + The returned buffer is reused on the next successful decode; callers must + use or copy it before calling decode again. + """ + result = _avcodec.avcodec_receive_frame(self._context, self._frame) + if result == -errno.EAGAIN: + return None + _check(result, "receive decoded frame") + + try: + frame = self._frame.contents + self._prepare_output(frame) + rows = _swscale.sws_scale( + self._sws_context, frame.data, frame.linesize, 0, frame.height, + self._dst_data, self._dst_linesize, + ) + if rows != frame.height: + raise FFmpegError(f"convert decoded frame: produced {rows} of {frame.height} rows") + return self._output + finally: + _avcodec.av_frame_unref(self._frame) + + def decode(self, data) -> np.ndarray | None: + self._ensure_open() + if len(data) == 0: + return None + + self._prepare_packet(data) + result = _avcodec.avcodec_send_packet(self._context, self._packet) + # The packet buffer is ours, not FFmpeg's. Clear the borrowed pointer so + # packet teardown can never attempt to release it. + self._packet.contents.data = None + self._packet.contents.size = 0 + _check(result, "send packet to decoder") + return self._receive() + + def reset(self) -> None: + """Discard decoder state after a stream discontinuity.""" + self._ensure_open() + _avcodec.avcodec_flush_buffers(self._context) + + def close(self) -> None: + if self.closed: + return + self.closed = True + _swscale.sws_freeContext(self._sws_context) + _avcodec.av_frame_free(ctypes.byref(self._frame)) + _avcodec.av_packet_free(ctypes.byref(self._packet)) + _avcodec.avcodec_free_context(ctypes.byref(self._context)) + + def __del__(self): + self.close() diff --git a/pyproject.toml b/pyproject.toml index de356e4b6f..3d90dfe167 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,6 @@ dependencies = [ "comma-deps-git-lfs", "comma-deps-gcc-arm-none-eabi", - # body / webrtcd - "av", - # logging "pyzmq", "sentry-sdk", diff --git a/uv.lock b/uv.lock index d771c2aacb..6e14cbc02a 100644 --- a/uv.lock +++ b/uv.lock @@ -17,21 +17,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "av" -version = "16.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, - { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, - { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, - { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, - { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, -] - [[package]] name = "certifi" version = "2026.6.17" @@ -717,7 +702,6 @@ name = "openpilot" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "av" }, { name = "cffi" }, { name = "comma-deps-acados" }, { name = "comma-deps-bootstrap-icons" }, @@ -794,7 +778,6 @@ standalone = [ [package.metadata] requires-dist = [ - { name = "av" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "comma-deps-acados" },