当前位置: 首页 > news >正文

rk3588

RK3588 libmedia VO:把视频帧送到 HDMI、DSI 显示屏

公开示例:https://github.com/libmedia886/rk3588-vo-demo

VO 是视频链路的显示出口。VI、VDEC、VPSS、RGA、OSD 等模块产生的视频帧,可以通过 libmedia pipeline 送进 VO,再由 DRM/KMS plane 显示到 HDMI、DP、eDP 或 MIPI DSI 屏幕。

下图用于说明 VO 的数据路径,不是开发板运行截图。真实效果仍要以目标板卡、connector、plane 和显示器上的画面为准。

VO 解决什么问题

VO 负责三件事:

  • 选择物理显示输出,例如HDMI-A-1DSI-1
  • 为视频画面创建一个 DRM plane,确定位置、尺寸、stride、队列深度和像素格式。
  • 接收上游视频帧并持续提交到显示平面。

一条常见显示链路是:

VI / VDEC -> VPSS / RGA -> OSD -> VO input0 -> DRM/KMS -> HDMI / DSI 屏幕

VO 不负责采集、解码或图像增强。它接收已经准备好的视频帧,并把这些帧放到指定显示平面上。

先分清 output 和 chn

VO 接入中最容易混淆的是output_idchn

  • output_id表示一个显示输出实例,关联物理 connector 和显示时序。
  • chn表示这个输出上的一个显示平面。
  • 通道chn = 0对应 VO 的输入端口input0chn = 1对应input1

因此,下面这条绑定中的目标 id 是output_id,目标端口才用于选择通道:

MEDIA_SYS_Bind("OSD",0,"output0","VO",output_id,"input0");

端口名必须显式填写,不能把output0input0省略成NULL或空字符串。

当前 VO API 主流程

配置阶段固定为:

  1. MEDIA_VO_ProbeConnectors:查询当前 DRM connector。
  2. MEDIA_VO_SetAttr:设置 connector、显示时序和 plane 数量。
  3. MEDIA_VO_CreateChn:创建显示平面和对应的inputN端口。

送帧有两种方式:

  • 绑定模式:先MEDIA_SYS_Bind,再MEDIA_VO_Start,由上游模块持续输出。
  • 手动模式:先MEDIA_VO_Start,再用MEDIA_SYS_SendFrame把 buffer 送到inputN

退出时先停止上游送帧,再停止 VO、解绑并销毁通道,最后调用MEDIA_SYS_Exit

1. 查询可用 connector

当前MEDIA_VO_ATTR.connector是必填字段。不要只设置MEDIA_VO_INTF_HDMI就假定目标一定是某个 HDMI 口。

MEDIA_VO_CONNECTOR_INFO connectors[8]={0};intconnector_count=0;if(MEDIA_SYS_Init()!=0){return-1;}if(MEDIA_VO_ProbeConnectors(connectors,8,&connector_count)!=0){return-1;}for(inti=0;i<connector_count&&i<8;++i){printf("connector=%s connected=%d mode=%dx%d@%d\n",connectors[i].name,connectors[i].connected,connectors[i].width,connectors[i].height,connectors[i].fps);}

应用应从查询结果中选择目标 connector。常见名称包括HDMI-A-1HDMI-A-2DSI-1,实际名称以当前设备返回值为准。

2. 配置显示输出

下面以HDMI-A-1、1920x1080@60 为例:

MEDIA_VO_ATTR vo={0};vo.intf=MEDIA_VO_INTF_HDMI;vo.connector="HDMI-A-1";vo.width=1920;vo.height=1080;vo.plane_count=1;vo.fps=60;vo.policy=MEDIA_VO_MODE_NEGOTIATE;if(MEDIA_VO_SetAttr(0,&vo)!=0){fprintf(stderr,"MEDIA_VO_SetAttr failed\n");return-1;}

MEDIA_VO_MODE_NEGOTIATE要求显示设备支持请求的时序,不会自动把任意分辨率强行设置到屏幕上。接入前可以用MEDIA_VO_GetSinkCaps查询支持的 mode,设置后用MEDIA_VO_GetActualModeMEDIA_VO_GetOutputInfo检查实际结果。

只有明确知道目标硬件允许强制时序时,才使用MEDIA_VO_MODE_FORCE

3. 创建显示平面

MEDIA_VO_CreateChn配置的是画面在屏幕上的位置和输入格式,不是物理输出时序。

intoutput_id=0;intchn=0;intview_x=0;intview_y=0;intview_w=1920;intview_h=1080;intstride=1920;intdepth=4;if(MEDIA_VO_CreateChn(output_id,chn,view_x,view_y,view_w,view_h,stride,depth,MEDIA_VO_PLANE_TYPE_AUTO,MEDIA_FORMAT_NV12,0)!=0){fprintf(stderr,"MEDIA_VO_CreateChn failed\n");return-1;}

关键参数含义:

  • xywidthheight:画面在屏幕上的窗口。
  • stride:输入视频帧每行的实际跨度,必须与上游 buffer 一致。
  • depth:VO 输入队列深度。
  • plane_typeAUTO会优先选择合适的 overlay plane,必要时再考虑 primary plane。
  • format:输入视频帧的像素格式,这里使用 NV12。
  • 最后一个参数小于等于 0 时不强制指定 DRM plane id。

分辨率是 1920 并不代表 stride 一定也是 1920。应使用上游模块或 buffer 的真实 stride,不能只按可见宽度猜测。

4. 绑定上游模块

实际工程更常用 pipeline 绑定,让上游模块持续把帧送进 VO:

if(MEDIA_SYS_Bind("OSD",0,"output0","VO",output_id,"input0")!=0){fprintf(stderr,"bind OSD to VO failed\n");return-1;}if(MEDIA_VO_Start(output_id,chn)!=0){fprintf(stderr,"MEDIA_VO_Start failed\n");return-1;}

通常先创建并绑定整条链路,再启动下游 VO,最后启动上游采集或解码模块。这样可以减少刚启动时的空队列和黑屏时间。

5. 手动发送视频帧

如果应用已经持有一个格式和尺寸匹配的MEDIA_BUFFER,也可以直接送到 VO:

if(MEDIA_VO_Start(output_id,chn)!=0){return-1;}if(MEDIA_SYS_SendFrame("VO",output_id,"input0",frame,1000)!=0){fprintf(stderr,"send frame to VO failed\n");}

手动送帧时,buffer 的尺寸、stride、格式和 metadata 必须与MEDIA_VO_CreateChn的配置一致。buffer 的取得、失败归还和成功发送后的所有权处理,要遵守产生该 buffer 的 Pool/API 约定。

6. 冻结和隐藏画面

VO 可以对指定 plane 做冻结和隐藏:

MEDIA_VO_FreezePlane(output_id,chn,1);/* 保持当前画面 */MEDIA_VO_FreezePlane(output_id,chn,0);/* 恢复刷新 */MEDIA_VO_HidePlane(output_id,chn,1);/* 隐藏画面 */MEDIA_VO_HidePlane(output_id,chn,0);/* 恢复显示 */

冻结是保持最后一帧,隐藏是让这个 plane 不再可见,两者用途不同。

较早的示例可能还在调用MEDIA_VO_FreezeMainMEDIA_VO_UnfreezeMain。当前公开头文件已经统一使用带output_idplane_idMEDIA_VO_FreezePlane,接入当前 SDK 时不要继续依赖旧接口。

7. 停止和清理

绑定链路的典型退出顺序如下:

/* 先停止上游模块继续产生视频帧 */MEDIA_VO_FreezePlane(output_id,chn,0);MEDIA_VO_HidePlane(output_id,chn,0);MEDIA_VO_Stop(output_id,chn);MEDIA_SYS_UnBind("OSD",0,"output0","VO",output_id,"input0");MEDIA_VO_DestroyChn(output_id,chn);MEDIA_SYS_Exit();

如果应用还有其它模块或线程在调用MEDIA_*API,必须先停止并等待这些线程退出,再执行进程级的MEDIA_SYS_Exit

正常显示时不需要 GetFrame

MEDIA_VO_GetFrame不是普通上屏链路的必要步骤。正常显示使用 pipeline 绑定或MEDIA_SYS_SendFrame,VO 内部完成 DRM plane commit。

MEDIA_VO_GetFrame是从 VO 的输入端口拉取一帧的特殊用法。只有确实需要这种输入端口取帧语义时才调用,并且成功后必须配对MEDIA_VO_ReleaseFrame

如何判断接入成功

原理示意图只能解释结构,不能证明板端显示成功。真实设备上至少检查以下结果:

  • MEDIA_VO_ProbeConnectors能找到目标 connector,并且connected = 1
  • MEDIA_VO_GetActualMode返回的宽、高和帧率符合预期。
  • 屏幕持续显示上游最新帧,没有黑屏、撕裂、颜色异常或明显错位。
  • 冻结时画面保持不动,解冻后继续刷新。
  • 隐藏后 plane 消失,恢复后画面继续显示。
  • 停止链路后可以正常解绑、销毁通道和退出,没有 buffer 或 plane 占用残留。

常见问题

MEDIA_VO_SetAttr 直接失败

先检查connector是否填写,以及名称是否来自MEDIA_VO_ProbeConnectors的当前查询结果。只设置intf、宽和高已经不够。

提示 mode 不支持

MEDIA_VO_GetSinkCaps查看屏幕支持的时序,再调整宽、高和 fps。默认NEGOTIATE策略不会自动接受屏幕不支持的 mode。

屏幕黑屏

依次检查目标 connector 是否已连接、VO 是否已经Start、绑定端口是否写成正确的inputN、上游模块是否真的在出帧,以及输入格式和 stride 是否匹配。

画面颜色异常或位置错乱

重点核对formatstride、输入 buffer metadata 和窗口尺寸。NV12、RGB 和 ARGB 的内存布局不能混用。

Bind 找不到目标端口

VO 的端口名由通道号决定。chn = 0使用input0chn = 1使用input1。绑定时的 VO id 是output_id,不要把通道号和输出 id 混为一谈。

与 X11 或桌面系统冲突

同一个显示输出只能有一个 modeset master。如果分辨率由 X11 等外部系统管理,应使用 VO 的 external display owner 路径,让外部系统管理 mode,VO 只负责 plane commit。

版本兼容提示

配套公开示例便于理解项目结构,但较早版本存在两处需要按当前 SDK 调整的写法:

  • MEDIA_VO_ATTR必须填写connector
  • MEDIA_VO_FreezeMainMEDIA_VO_UnfreezeMain已由MEDIA_VO_FreezePlane统一替代。

编译前应以所使用 SDK 中的media_api.h为准,不要只照搬旧示例。

小结

VO 最小接入可以记成下面这条主线:

查询 connector -> 配置 output -> 创建 plane -> Bind 或 SendFrame -> Start / 持续显示 -> 停止上游 -> Stop -> UnBind -> DestroyChn

真正决定能否稳定上屏的关键点有四个:connector 选对、mode 可用、inputN端口写对,以及输入格式和 stride 与 buffer 完全一致。

联系方式

如有技术交流或合作需求,请联系:allinjetson@gmail.com

http://www.jsqmd.com/news/1351913/

相关文章:

  • Wand-Enhancer架构解析:WeMod客户端增强工具的技术实现与实战指南
  • MCP协议:AI Agent万能工具箱,打破语言与进程壁垒
  • 2026年北京公司注册如何避免地址异常? - 万相科技
  • 交互设计实战:二次确认与即时执行+容错模式的技术选型与实现
  • 基于YOLO26饮料检测系统1:饮料检测数据集说明(含下载链接)
  • 靠谱的儿童写字课推荐:【简知科技】师资雄厚
  • NE555定时器在智能车硬件中的延时电路设计与实战应用
  • 三月七小助手:5分钟快速入门《崩坏:星穹铁道》全自动游戏指南
  • 智慧树刷课插件:3步配置实现视频自动化学习的完整指南
  • C++入门实战:从环境搭建到核心概念的系统学习指南
  • 08 逻辑回归入门:从概率到二分类预测
  • I2S、PCM与TDM音频接口协议详解:从立体声到多通道传输
  • 手机安全科普:警惕远程监控软件的风险
  • 磁盘空间管理机制:空闲区表与位示图详解
  • 2026年通风柜实力厂家**单:全钢/PP/钢木实验室通风柜,安全排风与耐用防腐工艺全解析 - 卓企推荐
  • 京东开源JoyAI-Video-Edit:实时流式视频编辑模型实践指南
  • MATLAB实现鸢尾花数据集的K-means聚类分析
  • Unity性能优化新思路:Amplify Impostors替身渲染技术详解
  • 深入解析CPU四大标志位:CF、ZF、SF、OF的原理与应用
  • windows下使用python删除pdf文件保护密码的方法
  • 别再瞎学Python了!四年实战血泪:这才是机器学习代码的命门
  • ESP32 ADC精度提升实战:从硬件噪声抑制到软件校准全解析
  • FFT算法原理与工程实践:从信号处理到电机故障诊断
  • MATLAB高效调试与性能优化实战指南
  • Win10深度学习环境搭建:PyTorch与Anaconda配置全攻略
  • 焦作本地防水补漏哪家靠谱?屋顶/卫生间/外墙/地下室/阳台渗水师傅筛查(2026年8月新) - 北京金修达天津维修部
  • AI会不会乱来?为什么转账这种事永远轮不到AI拍板
  • RAG系统全链路拆解:从文档索引到检索生成的实战指南
  • 【焦作市】2026CPPM采购经理报考指南|正规机构甄选产业适配全攻略 - 中采供培
  • Redis从入门到精通:安装配置与高级应用实战