{"record":{"id":"ee4568ac03e25279","repo":"commaai/openpilot","slug":"convert-decoded-frame-produced-rows-of-frame-h","errorCode":null,"errorMessage":"convert decoded frame: produced {rows} of {frame.height} rows","messagePattern":"convert decoded frame: produced (.+?) of (.+?) rows","errorType":"exception","errorClass":"FFmpegError","httpStatus":null,"severity":"error","filePath":"openpilot/tools/camerastream/ffmpeg_decoder.py","lineNumber":227,"sourceCode":"    \"\"\"Return one NV12 frame, or None if the decoder needs more input.\n\n    The returned buffer is reused on the next successful decode; callers must\n    use or copy it before calling decode again.\n    \"\"\"\n    result = _avcodec.avcodec_receive_frame(self._context, self._frame)\n    if result == -errno.EAGAIN:\n      return None\n    _check(result, \"receive decoded frame\")\n\n    try:\n      frame = self._frame.contents\n      self._prepare_output(frame)\n      rows = _swscale.sws_scale(\n        self._sws_context, frame.data, frame.linesize, 0, frame.height,\n        self._dst_data, self._dst_linesize,\n      )\n      if rows != frame.height:\n        raise FFmpegError(f\"convert decoded frame: produced {rows} of {frame.height} rows\")\n      return self._output\n    finally:\n      _avcodec.av_frame_unref(self._frame)\n\n  def decode(self, data) -> np.ndarray | None:\n    self._ensure_open()\n    if len(data) == 0:\n      return None\n\n    self._prepare_packet(data)\n    result = _avcodec.avcodec_send_packet(self._context, self._packet)\n    # The packet buffer is ours, not FFmpeg's. Clear the borrowed pointer so\n    # packet teardown can never attempt to release it.\n    self._packet.contents.data = None\n    self._packet.contents.size = 0\n    _check(result, \"send packet to decoder\")\n    return self._receive()\n","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/tools/camerastream/ffmpeg_decoder.py#L209-L245","documentation":"In FFmpegDecoder's frame-receive path, sws_scale() was asked to convert frame.height rows from the decoder's pixel format to the output format, but it returned fewer rows. That means the swscale context and the decoded frame disagree (wrong dimensions or pixel format), so the produced NV12/RGB buffer is incomplete and unusable. The decoder raises rather than return a partially converted frame.","triggerScenarios":"Receiving a decoded frame whose width/height/pixel format differs from what the sws_context was created for (stream resolution change mid-stream); calling _prepare_output/frame processing with a stale _sws_context after the codec renegotiated the frame size; an unusual source pix_fmt sws_scale cannot fully convert.","commonSituations":"Camera stream starts with different resolution than probed (e.g. adaptive encoder); switching cameras/formats on one decoder instance; ffplay/ffmpeg version differences in default output formats.","solutions":["Recreate the swscale context when frame.width/height/format change: compare against the values used at context creation and call sws_getContext again","Verify the sender/probe resolution matches what the decoder was opened with (avcodec_open2 with the correct width/height)","Inspect frame.format at failure time - if it is an unexpected pix_fmt, negotiate the format upstream or add an intermediate conversion"],"exampleFix":"# before (stale context)\nrows = _swscale.sws_scale(self._sws_context, frame.data, frame.linesize, 0, frame.height, ...)\n\n# after (rebuild on change)\nif (frame.width, frame.height, frame.format) != self._sws_spec:\n    _swscale.sws_freeContext(self._sws_context)\n    self._sws_context = _swscale.sws_getContext(\n        frame.width, frame.height, frame.format,\n        frame.width, frame.height, self._dst_fmt,\n        _swscale.SWS_BILINEAR, None, None, None)\n    self._sws_spec = (frame.width, frame.height, frame.format)\nrows = _swscale.sws_scale(self._sws_context, frame.data, frame.linesize, 0, frame.height, self._dst_data, self._dst_linesize)","handlingStrategy":"type-guard","validationCode":"frame = self._frame.contents\nif (frame.width, frame.height) != self._expected_dims:\n    raise RuntimeError(f\"stream resolution changed to {frame.width}x{frame.height}; rebuild sws context\")","typeGuard":"def frame_matches_context(frame, sws_spec) -> bool:\n    \"\"\"True when the decoded frame's dims/format match the swscale context spec.\"\"\"\n    return (frame.width, frame.height, frame.format) == sws_spec","tryCatchPattern":"try:\n    return self._receive_frame_internal()\nexcept FFmpegError as e:\n    if 'produced' in str(e) and 'rows' in str(e):\n        self._rebuild_sws_context(self._frame.contents)  # recreate, then retry once\n        return self._receive_frame_internal()\n    raise","preventionTips":["Cache the (width, height, pix_fmt) triple used to create the sws context and rebuild whenever the frame's triple differs","Never reuse a decoder across streams with different resolutions without draining and reopening the codec context"],"tags":["ffmpeg","video-decoding","swscale","resolution-change","openpilot"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}