Skip to content

Re-split #25 into atomic per-issue commits - #38

Open
aa03146 wants to merge 6 commits into
mainfrom
resplit/25-from-main
Open

aa03146 wants to merge 6 commits into
mainfrom
resplit/25-from-main

Conversation

@aa03146

@aa03146 aa03146 commented Jul 24, 2026 •

Copy link
Copy Markdown
Collaborator

修复内容(每个 commit 对应一个独立问题)

  1. fix(rtsp): build SETUP URI from SDP a=control attribute — 依据 SDP a=control 正确拼接 SETUP URI。
  2. fix(rtp): inject SDP SPS/PPS ahead of key frames when absent in-band — 带内缺失 SPS/PPS 时用 SDP 中的参数集在关键帧前注入。
  3. feat(demux): add DEMUX_ProbeStreamInfo for pre-start codec detection — 启动前探测码流信息(编码类型)。
  4. fix(vcodec): propagate buffer-allocation failure in linlonv5v7 codec — linlonv5v7 缓冲分配失败时正确向上传播错误。
  5. fix(vcodec): harden DMA-BUF mmap lifecycle against leaks and double-free — 修复 DMA-BUF mmap 生命周期泄漏与重复释放。
  6. fix(vdec,venc): tear down partially-initialised channel on bring-up failure — 通道初始化失败时清理已建资源(vdec plugin_open 清理、venc pthread 初始化回滚、EnableChn 错误路径关闭 plugin)。

验证(开发板 riscv64)

  • 构建:cmake .. && make -j4 100% 通过,无错误。
  • Lint:lint_cpp.sh 对本 PR 改动的 12 个文件 0 error 0 warning。
  • 回归:sample_file_transcode test_video.mp4 out.h264 --codec h264 --frames 250 → ret=0,输出 2670575 字节,ffprobe 帧数 = 250。

weirongmin added 6 commits July 24, 2026 17:15
The DEMUX RTSP client hardcoded the SETUP request URI as
"rtsp://host:port/<path>/track<N>", which does not match servers
that advertise a different control string. mediamtx advertises
"a=control:trackID=0" with a Content-Base of ".../<path>/", so the
hardcoded form drew a 400 Bad Request and the pull failed.

Capture the raw "a=control:" value per media section in SdpInfo and
build the SETUP URI from it:
  - absolute "rtsp://" control  -> use verbatim
  - relative token/path          -> append to the aggregate URL with a
                                    single '/' separator
  - missing/"*"                   -> fall back to the legacy trackID form
Some RTSP servers (e.g. mediamtx) advertise SPS/PPS only in the SDP
fmtp sprop-parameter-sets and never repeat them in-band before each
IDR. The hardware VDEC then cannot initialise and produces zero
decoded frames.

Before emitting a key frame, splice the stored SPS/PPS
([startcode+SPS][startcode+PPS]) ahead of the frame NALs. Injection is
skipped when the frame already begins with an SPS NAL (in-band
parameter sets present) to avoid duplication, and when there is not
enough room in the frame buffer.
Add a synchronous probe API so callers can learn the real codec type and
resolution (RTSP from SDP; MP4/TS/FLV from the container header) BEFORE
creating a decoder and building the DEMUX->VDEC bind. The live connection
opened by the probe is retained and reused by the worker thread via a new
bPreConnected flag, so the source is never connected twice. If the channel
is already running, the probe degrades to returning the currently published
stream info.
On CMA exhaustion, allocateBuffers() may grant fewer buffers than
requested (or zero). Previously allocateCodecBuffers() returned void and
stream() ignored the result, so a codec could come up with too few or no
buffers, silently drop every frame, and still report init success -
leaving the pipeline stalled with no error.

- allocateCodecBuffers() now returns S32 and treats a partial grant
  (nBufNum != requested) as MPP_INIT_FAILED.
- stream() propagates the failure and calls freeCodecBuffers() to roll
  back before returning.
- al_dec_init()/al_enc_init() check stream()'s return, tear down the
  codec, close nVideoFd and fail instead of returning a dead channel.
- Init/destroy paths initialise nVideoFd to -1 and guard close()/
  destoryCodec() with >=0 / non-NULL checks so partial init cleans up
  safely and destroy is idempotent.
- getBuffer() bounds-checks the index; allocateBuffers() rolls back on a
  partial REQBUFS grant; freeBuffers() is NULL-safe and idempotent.
- Reduce ENCODER_INPUT_BUF_NUM 12->8 to lower CMA pressure.
The internal DMA-BUF map/unmap paths tracked the mapping through
pUserPtr[0]/nTotalLength, which were mutated elsewhere, so a failed or
repeated unmap could munmap the wrong length, leak the fd, or double-free.
Early returns on the first munmap/close error also skipped the remaining
cleanup, leaking the rest of the resources.

- Track the internal mapping explicitly via pInternalDmaMap /
  nInternalDmaMapLength so unmap always uses the exact base/length.
- Guard every munmap with a MAP_FAILED/NULL check and NULL the pointer
  afterwards, making memoryUnmap() idempotent and safe to call twice.
- Accumulate errors into a single ret instead of returning early, so a
  failure on one resource no longer leaks the others.
- createBuffer() fails cleanly if createDmaBufWrapper() returns NULL and
  unmaps on memoryMap() failure; destoryBuffer() is NULL-safe.
- allocDmaBuf() fd<0 checks in memoryMap() surface allocation failures.
- dmabufwrapper: initialise fds to -1, treat fd 0 as valid, guard
  MAP_FAILED, drop early returns in freeDmaBuf(), and call freeDmaBuf()
  from destoryDmaBufWrapper() to avoid leaking the buffer fd.
…ailure

When al_dec_init/al_enc_init failed part-way (e.g. CMA exhausted) or a
thread primitive could not be created, the channel was left holding a
half-built AL context, a loaded plugin handle, and initialised sync
primitives. A later retry then re-opened on top of the leaked state.

- vdec_plugin_open(): on al_dec_init failure destroy the AL context,
  unload the plugin and zero stOps so the channel is fully clean and a
  later Enable retry re-opens from scratch.
- venc_start_threads(): check the return of every pthread_mutex_init /
  pthread_cond_init / pthread_create and roll back the primitives already
  created before returning ERR_VENC_NOMEM, instead of leaking them.
- VENC_EnableChn(): call venc_plugin_close() on the error paths that leave
  the channel IDLE so the plugin/AL context is released, not leaked.
@spacemit-robot-ci

spacemit-robot-ci Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Robot CI AI Review

结论:发现 4 个需要处理的问题。

发现:

  1. [严重] al/vcodec/linlonv5v7/linlonv5v7_buffer.c:98 资源泄漏:destoryBuffer 未校验入参
    函数直接调用 memoryUnmap 和 destoryDmaBufWrapper,若 buf 为 NULL 会传播到下游函数。虽下游已加 NULL 检查,但 destoryBuffer 是对外接口,应在入口校验避免无效调用。
    建议:在函数开头增加 if (!buf) return;

  2. [中等] al/vcodec/linlonv5v7/linlonv5v7_port.c:471 边界条件:部分分配失败时 nBufNum 递增
    createBuffer 失败时立即释放所有已分配缓冲区并 REQBUFS(0),但已执行的 port->nBufNum = buf.index + 1 未回退,port->nBufNum 会指向超出有效范围的索引。虽后续 freeBuffers 将其清零,但短暂窗口内若调用 getBuffer 可能访问越界或 NULL。
    建议:失败路径在 freeBuffers 后显式设置 port->nBufNum = 0;

  3. [中等] mpi/demux/demux.c:896 接口语义不一致:ProbeStreamInfo 未清零输出参数
    pstInfo 仅在成功路径赋值,错误返回时保留调用方脏数据,与常见出参语义(失败时清零或不变)不一致,可能导致调用方误用未初始化内容。
    建议:函数入口处 memset(pstInfo, 0, sizeof(*pstInfo));

  4. [轻微] mpi/demux/protocol/rtsp/sdp_parser.c:208 类型不一致:ctrl_len 可能被截断
    pEnd - pCtrl 结果为 ptrdiff_t,但赋值给 size_t 类型的 ctrl_len。在 64 位系统上若 pEnd < pCtrl(虽不应发生),截断逻辑会产生非预期行为。实际场景中 pEnd 由 strchr 返回必定 >= pCtrl,但类型一致性可提升鲁棒性。
    建议:改用 size_t ctrl_len = (pEnd > pCtrl) ? (size_t)(pEnd - pCtrl) : 0;

备注:worker-cache review 失败,已回退 baseline review:worker-cache review failed: snode5: fetch base failed: From github.com:spacemit-com/mpp
! [rejected] main -> refs/robot-ci/base/main (non-fast-forward); From github.com:spacemit-com/mpp
! [rejected] main -> refs/robot-ci/base/main (non-fast-forward); hp-probook: sync worker helper failed: ssh: connect to host 10.0.91.193 port 22: No route to host

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant