资讯动态

hi3516ev200 RTMP推流遇VENC超时:编码链路断点排查指南

发布时间:2026/10/24 5:07:17 来源:尧图企业网站定制
如果你在调 hi3516ev200 这颗芯片的 RTMP 推流程序跑起来之后控制台里突然刷出这么一行[SAMPLE_COMM_VENC_GetVencStreamProc]-2024: get venc stream time out, exit thr我猜你第一反应是去查推流库、查网络、甚至怀疑 RTMP 服务器是不是有问题。说实话我最初也这么干过翻了半天一无所获。后来才明白这个报错根本不是网络问题问题出在视频编码链路你的程序在向编码器要一帧 H264/H265 数据的时候等了几百毫秒甚至一秒编码器一帧都没吐出来。它其实是采集、前处理、编码整条链路断掉之后在编码器取流环节暴露出的一个信号。这篇文章会把链路拆开告诉你哪些环节断了会导致这个提示哪些坑是 RTMP 推流场景下特有的并且给出一套可以直接照做的排查流程。准备用这颗芯片做 IPC、做嵌入式推流设备的开发者应该都会遇到类似问题尤其是正在拿 sample_venc 改业务的朋友这篇文章能帮你从“瞎试”变成“按图索骥”。1. 这个报错到底在报什么1.1 报错位置和它的词义拆解先看日志来源。[SAMPLE_COMM_VENC_GetVencStreamProc]是 SDK 里 sample_comm 公共函数库中的一个函数名字翻译过来就是“公共 VENC 获取码流线程处理函数”。这个函数做的事情非常基础在一个循环里反复调用 VENC 通道的取流接口拿到一组编码后的码流包然后交给上层逻辑做封装处理。它的核心逻辑大致长这样while (pstParam-bThreadStart) { VENC_STREAM_S stStream; memset(stStream, 0, sizeof(stStream)); s32Ret HI_MPI_VENC_GetStream(vencChn, stStream, 1000); if (s32Ret HI_ERR_VENC_BUF_EMPTY) { printf([SAMPLE_COMM_VENC_GetVencStreamProc]-%d: get venc stream time out, exit thr\n, __LINE__); break; } // 处理码流比如送给 RTMP 封装 handle_stream(stStream); // 用完必须释放否则 buffer 会被占满 HI_MPI_VENC_ReleaseStream(vencChn, stStream); }注意日志里的-2024不是错误码是源文件里的行号不同 SDK 版本行号会不一样。真正关键的是HI_ERR_VENC_BUF_EMPTY这个错误意思是你向编码器通道要码流但编码器输出缓冲区是空的。在超时时间内示例里是 1000ms没有任何一帧编码数据被生产出来。很多人一看到time out就以为是超时时间设短了直接往大了改比如改成 3000ms 甚至 5000ms。这个操作本身没有意义。25fps 的编码帧间隔只有 40ms正常编码器 1000ms 内能出二十多帧一帧都不出说明不是“慢”是“断”。1.2 超时背后是一条闭合链路要理解这个报错必须把视频链路摆在桌面上。从 sensor 到 RTMP 推流中间是一条单向流水线sensor - VI/ISP - VPSS - VENC - 用户取流线程 - RTMP 封装发送VI 负责把 sensor 的数据变成图像帧VPSS 负责缩放、裁剪、降噪这类预处理VENC 负责把图像编成 H264/H265 码流最后取流线程从 VENC 通道里把码流包拿走拼成 FLV 推出去。这条链路上任何一个环节断了VENC 都生产不出数据最终都会在GetStream那个位置爆出time out。所以这个日志是一个典型的“下游报错、上游生病”的案例。这里可以用一个生活类比你在食堂取餐窗口等饭窗口里一直没人递饭出来问题未必是窗口师傅偷懒很可能是后厨压根没开火、菜没送过来、甚至师傅还没到岗。你要做的是去后厨查而不是盯着窗口催。弄清楚了这一点排查思路就清晰了从 VI 开始一层一层往下查看数据到底断在哪。2. 逐个排查哪一个环节没有出数据2.1 先确认 VI 是真的出图了所有问题的第一站就是 VI。如果 sensor 都没出图后面全是空谈。很多情况下sensor 初始化返回 0 不代表真的在出图驱动加载成功和 ISP 正常出帧是两回事。我建议先做一个最简单的验证启动 VI 之后调用一次取帧接口看能不能从 VI 通道拿到一帧原始图像。VIDEO_FRAME_INFO_S stFrameInfo; s32Ret HI_MPI_VI_GetChnFrame(0, 0, stFrameInfo, 1000); if (s32Ret HI_SUCCESS) { printf(VI chn get frame ok, w%u, h%u\n, stFrameInfo.stVFrame.u32Width, stFrameInfo.stVFrame.u32Height); // 注意用完必须释放否则 VI 内部缓冲会被耗尽 HI_MPI_VI_ReleaseChnFrame(0, 0, stFrameInfo); } else { printf(VI chn get frame failed: 0x%x\n, s32Ret); }这一步能在最上游验证数据有没有产生。如果连这一步都失败重点检查sensor 驱动模块有没有加载成功加载顺序对不对sensor 的类型、分辨率、I2C 地址是否匹配实际硬件ISP 是否完成了初始化有没有给它足够的启动时间VI 通道是否执行了HI_MPI_VI_EnableChn。我踩过的一个经典坑是某款 sensor 在初始化之后需要额外延时才能稳定输出我把延时去掉之后ISP 没起来VI 自然一帧不出VENC 必然超时。后来在 VI 启动后加了 200ms 延时问题立刻消失。很多“玄学超时”其实都跟初始化时序有关。2.2 VPSS 通道绑定问题高发区如果你确认 VI 已经能出图下一个要查的是 VPSS。VPSS 负责把 VI 送进来的图像做大分辨率转换和预处理编码器通常不直接绑 VI而是绑 VPSS 的某一个输出通道。原因很简单sensor 输出的分辨率可能很大、格式也不是编码器直接想要的中间得靠 VPSS 转一手。这里最容易犯的错有三个第一只启动了 VI没启动 VPSS第二VPSS Group 创建了但忘了StartGrp或者对应的输出通道没EnableChn第三HI_MPI_SYS_Bind绑定关系填错了把源和目标搞反或者通道号对不上。绑定关系长这样MPP_CHN_S stSrcChn, stDestChn; memset(stSrcChn, 0, sizeof(stSrcChn)); memset(stDestChn, 0, sizeof(stDestChn)); stSrcChn.enModId HI_ID_VPSS; stSrcChn.s32DevId 0; stSrcChn.s32ChnId 0; stDestChn.enModId HI_ID_VENC; stDestChn.s32DevId 0; stDestChn.s32ChnId 0; s32Ret HI_MPI_SYS_Bind(stSrcChn, stDestChn); if (s32Ret ! HI_SUCCESS) { printf(bind vpss-venc failed: 0x%x\n, s32Ret); }绑定之前要保证 VPSS 通道已经使能、VENC 通道已经创建否则会返回失败。还有一个容易忽略的点VPSS 输出通道的分辨率要和 VENC 编码输入分辨率匹配或者由 VENC 端做裁剪如果两边宽高不一致编码器也可能一直吃不到有效帧。检查的时候不要只看SYS_Bind返回成功就放心要用cat /proc/umap/vpss看一眼通道状态。2.3 VENC 通道自己没有开始接收帧VI 和 VPSS 都正常时可别高兴太早问题也可能出在 VENC 自己身上。VENC 通道创建成功不代表它开始工作了部分 SDK 里创建通道之后还必须调用HI_MPI_VENC_StartRecvFrame编码器才会真正开始接收输入图像。如果漏了这一步VENC 通道就是挂着的空壳取流必然超时。另外检查VENC_ATTR_S里的配置编码协议必须是PT_H264或PT_H265千万别拿默认值直接跑u32FrameRate不能设成 0否则编码器不知道按什么节奏出帧分辨率、宽高要和 VPSS 输出对齐且最好满足对齐要求码率控制模式建议先用 CBR 固定码率排查问题时变量更少。VENC 的启动顺序一般是这样HI_MPI_VENC_CreateChn(0, stVencAttr); HI_MPI_VENC_StartRecvFrame(0, stRecvParam);很多人只记得CreateChn忘了StartRecvFrame尤其从老 SDK 迁移过来的时候容易踩。如果代码里确实调用了StartRecvFrame就用状态查询接口确认一下编码器是否真的在收帧这也是我在后面要重点说的方法。2.4 取流超时时间不是越大越好回到超时时间本身。官方示例里GetStream的超时参数通常给 1000ms这个值是合理的。我见过有人改成 100ms那当然容易超时因为帧率 25fps 时帧间隔都 40ms100ms 只能等两三帧稍微抖一下就超时。但如果你已经是 1000ms 还超时那就别再加时间了。计算依据很简单超时时间至少要覆盖 3 到 5 个帧间隔。帧间隔等于 1000 除以帧率25fps帧间隔 40ms1000ms 可以等 25 帧10fps帧间隔 100ms1000ms 可以等 10 帧5fps帧间隔 200ms1000ms 也可以等 5 帧。所以只要帧率不是低得离谱1000ms 在理论上绝对够用。如果这个时长内一帧都拿不到说明编码器压根没在工作或者上游根本没把帧送进来。把超时加到 3000ms、5000ms只会让程序在出错时卡得更久页面看起来像死机一样反而更难看。不过有一种例外如果只是启动阶段初始化慢比如 ISP tuning 还没完成卡住一瞬间后才会出帧。这种情况可以通过延迟启动取流线程来规避后面排查章节会详细说。3. 叠加 RTMP 推流后这几个坑别忽略3.1 发送线程阻塞导致 VENC buffer 占满很多人的第一版代码长这样一个线程里既调GetStream取码流又调 RTMP 库把数据发出去。这个写法看着省事但非常危险。RTMP 底层是 TCP网络一抖动socket 写缓冲可能瞬间塞满RTMP_SendPacket会阻塞在那里几百毫秒甚至更久。这段阻塞时间内取流循环停摆没有调用ReleaseStream把 VENC 的 buffer 还回去。VENC 内部的输出缓冲是有限的。一直不释放缓冲池就会被占满。缓冲池满后编码器新编码出来的帧可能无处安放某些 SDK 版本甚至会直接丢弃新帧更严重的会出现内部状态异常导致后续GetStream一直拿不到数据最终表现为time out。解决思路很简单把取流和发送拆成两个线程中间用一个队列缓冲。取流线程只管GetStream后立刻放入队列、立刻ReleaseStream网络线程只管从队列里拿数据发送。这样即使网络卡住顶多是队列积压不会堵住编码器取流的节奏。如果网络长时间卡死还可以选择丢帧策略优先丢掉非关键帧保住画面的实时性。3.2 裸流里的 SPS/PPS 别漏掉这个问题虽然不直接导致time out但排查报错的时候容易让人分心。VENC 输出的裸流里IDR 帧前通常会带上 SPS/PPS 信息但它们是独立的数据包不是直接拼在 IDR 帧头里的。做 RTMP 封装时你要从取到的VENC_PACK_S里根据数据类型把 SPS、PPS 找出来单独组成 FLV 的 sequence header 发送。如果这里处理不对播放器会黑屏或者花屏这时候你会误以为推流出问题了往回查网络、查服务器结果什么都查不出来。所以我建议排查time out的时候先把 RTMP 封装逻辑简化取到码流先写本地文件用播放器确认本地文件能正常播放再把封装逻辑加回来。这样能把“视频链路断没断”和“封装对不对”两个问题彻底分开。3.3 内存与 MMZ 分配低端芯片的隐形杀手hi3516ev200 这类低成本芯片的内存本来就紧张MMZ多媒体内存更是要精打细算。如果你把编码分辨率开得过高、码率开得过大或者同时开了多路MMZ 分配不到位VENC 通道创建时可能部分 buffer 申请失败但初始化流程未必会给你打印明显的错误日志最后就会变成“莫名其妙的取流超时”。排查方法很直接跑起来后执行cat /proc/media-mem cat /proc/umap/venc看 VENC 通道的 buffer 状态。如果发现某些通道的 buffer 数量是 0或者根本没有 VENC buffer 的日志段那就是内存分配出了问题。可以先把分辨率降到 720p、或者把输出帧率降到 15fps 验证一下如果不再超时基本可以断定是资源不足。调整方法一般是修改系统启动时加载芯片库的脚本里的 MMZ 配置给 VENC 预留更合理的 buffer 大小。这里提醒一句MMZ 不是越大越好系统运行时还需要内存做应用和数据缓冲留出余地很重要具体大小得根据实际场景反复压测。3.4 停止推流时别乱关通道最后一个 RTMP 场景特有的坑就是程序退出时的线程销毁顺序。我见过有人为了“干净退出”在推流线程还在跑的时候直接调用HI_MPI_VENC_DestroyChn结果取流线程瞬间返回错误打印出来的恰好就是这个time out然后线程异常退出。实际上这不是故障是销毁顺序不对造成的“假超时”。正确的退出顺序应该是先把取流线程停下来再执行HI_MPI_VENC_StopRecvFrame停止接收帧接着做HI_MPI_SYS_Unbind解绑最后才销毁 VENC 通道。如果是多线程结构还要先把网络发送线程停掉再停取流线程避免队列还有数据没发完就释放了一堆资源。4. 一套可以直接照做的定位流程4.1 先把 RTMP 摘掉验证编码本身遇到这个报错最高效的第一步不是去打日志而是做“减法实验”。把 RTMP 相关代码全部注释掉只保留 VI VPSS VENC 的编码链路然后直接跑官方提供的 sample_venc 示例程序。如果 sample_venc 能正常输出 H264 码流文件说明底层编码链路是通的问题出在你的推流线程、队列、封装逻辑上。如果 sample_venc 也超时那问题就在 VI、VPSS、VENC 这三个底层模块里跟 RTMP 一点关系都没有。这一步信息量非常大直接帮你划定了排查范围省掉一半的无头苍蝇时间。我在实际项目里几乎没有例外只要用户说推流超时我第一件事都是让他跑 sample_venc 试试。4.2 状态打印能省一半时间海思 SDK 提供了状态查询接口这是定位编码问题最好的武器。在启动 VENC 之后单独开一个地方每隔 500ms 查询一次VENC_CHN_STATUS_S stStatus; memset(stStatus, 0, sizeof(stStatus)); HI_MPI_VENC_QueryStatus(0, stStatus); printf(uCurFrames%u, uChnBufCnt%u\n, stStatus.uCurFrames, stStatus.uChnBufCnt);uCurFrames表示当前编码器已经编码输出的帧数。如果这个值一直为 0说明编码器根本没有收到帧问题在 VI 或 VPSS如果它在持续增长说明编码器工作正常问题在取流调用方式或超时参数上。同样可以查 VPSS 的状态比如通过/proc/umap/vpss看各个通道有没有数据。我习惯把这几个状态汇总打印到同一个日志里形成一个“链路仪表盘”。这样报错一出现我马上就能从最近的日志里看到是哪一段先没了数据。4.3 善用错误码和日志时间戳GetStream返回的错误码值得单独打印一下不要只盯着time out。不同错误码代表的状态不一样。拿到返回值之后用0x%x格式打印出来再去查 SDK 的 errno 定义看看是 buffer 空、参数错误还是通道状态不对。有些情况下错误码会明确告诉你通道没有使能这时候就根本不用纠结超时。另外日志一定要带相对时间戳。我在现场排查时养成了一个习惯所有关键动作打印时间精确到毫秒。这样你能看出 sensor 初始化花了多久、VENC 创建花了多久、从启动到第一次取流超时经过了多久。如果每次都是初始化后 500ms 左右超时大概率是时序问题如果是运行很久之后才超时大概率是资源泄漏或 buffer 堆积。4.4 从后往前分段放宽快速锁定还有一个很实用的对照实验法。保持超时时间 1000ms 不变在启动取流线程前加一个usleep(1000 * 1000)也就是等 1 秒再开始取流。如果加了延时之后报错消失说明链路本身没问题只是启动阶段 VI/sensor 还没就绪取流线程开太早了。这是最常见的“假超时”。如果延时后问题依旧就把超时时间从 1000ms 临时改成 3000ms观察报错是否出现。如果改成 3000ms 后仍然超时说明链路完全中断如果不再报错说明链路“很慢但不完全断”这时候继续往上查 VI/VPSS 的帧率。这个方法比盲目打日志更快因为对照实验一次就能排除一半的可能原因。排查模块常用检查点常见异常表现VIVI_GetChnFrame能否拿到帧sensor 无输出、ISP 未起、通道未使能VPSSGroup/Chn 是否 Enable、SYS_Bind返回通道未启动、绑定关系错误、分辨率不匹配VENCStartRecvFrame是否调用、QueryStatus参数不对、未开始接收、MMZ 不足取流线程GetStream返回值、超时时间超时过短、发送阻塞、线程退出顺序错误5. 能直接参考的代码与配置片段5.1 正确的绑定与启动顺序把几个关键动作按顺序列一下初始化 VI确认能拿到帧初始化 VPSS Group 和输出通道EnableChn创建 VENC 通道设置编码属性HI_MPI_SYS_Bind绑定 VPSS 通道到 VENC 通道HI_MPI_VENC_StartRecvFrame开始接收帧延时 500ms 左右再启动取流线程。绑定关系的参考代码我在 2.2 节已经给过了这里补充一个可能被忽略的点HI_MPI_SYS_Bind绑定之后不要马上启取流线程。虽然示例里 SYS_Bind 是同步返回的但实际硬件上数据通路建立是需要一点时间的尤其 ISP 还在做自动曝光收敛的时候。给个 300 到 500ms 的延时能避免很多启动瞬间的超时问题。5.2 取流循环参考取流线程里最关键的两个动作是GetStream和ReleaseStream必须成对出现。每次都不要忘记释放while (bThreadRunning) { VENC_STREAM_S stStream; memset(stStream, 0, sizeof(stStream)); s32Ret HI_MPI_VENC_GetStream(0, stStream, 1000); if (s32Ret ! HI_SUCCESS) { // 这里不要立刻 break可以记录计数并continue continue; } for (u32 i 0; i stStream.u32PackCount; i) { VENC_PACK_S *pstPack stStream.pstPack[i]; // pstPack-DataType 区分 SPS/PPS/IDR/P // pstPack-pAddr pstPack-u32Len 是码流数据 } HI_MPI_VENC_ReleaseStream(0, stStream); }注意一点GetStream返回错误时不要直接break退出线程否则一次小抖动就把整个推流干掉。更稳妥的做法是记录连续失败次数超过比如 20 次再退出同时打印现场状态。这样程序有一定的容错能力排查问题时日志也更有价值。5.3 启动参数、MMZ 和线程设计建议编码参数方面第一版跑通建议用最稳的组合H264、CBR 码率控制、GOP 设成帧率的两倍左右、分辨率匹配实际 sensor 输出。等整条链路稳定了再逐步调码率、帧率和编码质量避免一上来一堆变量互相干扰。内存方面先跑cat /proc/media-mem看当前占用再决定是否调整加载脚本里的 MMZ 参数。如果 VENC buffer 明显不够在加载脚本里为 VENC 模块多分配一些 buffer如果只是单路 1080p默认配置很多时候够用先不要动动了反而容易引入新的不稳定因素。线程设计方面建议取流线程和网络发送线程分离队列长度按“网络抖动 1 秒”的量级来设计。比如 25fps、每帧平均 20KB 码流1 秒大约 500KB队列开 1MB 左右就能容忍 2 秒的网络卡顿超过之后直接丢帧确保画面延迟不会越来越大。丢帧策略上优先丢 P 帧保留下一个 IDR 帧到来之前的画面连贯性。最后说点个人的真实体会。遇到这个报错时最容易做的事情就是延长超时时间我刚开始也这么干结果偶尔能跑、偶尔又崩反而把问题掩盖了。后来我把每次出现这个日志时的QueryStatus数据都记下来才发现大部分时候其实是编码器一开始就没吃进帧超时只是最后的结果。调这种底层链路说到底就是拆。把 VI、VPSS、VENC、推流四段拆开测哪一段断了补哪一段而不是在一整条链路上瞎猜。希望这篇内容能帮你尽早定位问题少熬几个夜。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑