当前位置:首页 > 消费电子 > 消费电子
[导读]智能电视应用不是“开个播放器+装个投屏App”拼起来就行。本地播放走硬件解码器,投屏又要收网络码流再解码,两条路若各开各的显示通道,就会抢DRM图层、重复拷贝、音画不同步。正确架构是:解封装/收流 → 硬解码 → DMA-BUF帧 → 统一送DRM合成上屏。


智能电视应用不是“开个播放器+装个投屏App”拼起来就行。本地播放走硬件解码器,投屏又要收网络码流再解码,两条路若各开各的显示通道,就会抢DRM图层、重复拷贝、音画不同步。正确架构是:解封装/收流 → 硬解码 → DMA-BUF帧 → 统一送DRM合成上屏。

一、本地视频硬解码管线

嵌入式ARM平台别指望VA-API,主流是V4L2 M2M或厂商VPU(RK用MPP、i.MX用VPU、全志用VE)。流程:

demux分离H.264/H.265/AAC

视频包送硬解

解码器输出YUV/NV12帧

导出dma-buf fd

DRM Plane直接扫描输出

GStreamer快速原型:

gst-launch-1.0 filesrc location=1.mp4 ! qtdemux ! \

 v4l2slh264dec ! videoconvert ! kmssink

产品化建议自己写解封装,只调v4l2_m2m或MPP,buffer流转可控。

核心零拷贝片段:

/* V4L2 M2M取解码帧 */

struct v4l2_buffer buf = {.type=V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,

                         .memory=V4L2_MEMORY_MMAP};

ioctl(dec_fd, VIDIOC_DQBUF, &buf);


/* 导出DMA-BUF */

struct v4l2_exportbuffer exp = {.type=buf.type,.index=buf.index};

ioctl(dec_fd, VIDIOC_EXPBUF, &exp);   // exp.fd


/* DRM导入 */

uint32_t handle;

drmPrimeFDToHandle(drm_fd, exp.fd, &handle);

drmModeAddFB2(drm_fd, w, h, DRM_FORMAT_NV12,

             &handle, &pitch, &off, &fb_id, 0);

drmModeSetPlane(drm_fd, plane_id, crtc_id, fb_id,

              0,0,0,0,0,0,w<<16,h<<16);

CPU不参与像素搬运,4K也压得住。

二、投屏两条路怎么选

DLNA(UPnP AV):适合点播本地/网盘媒体

SSDP发现设备

SOAP控制播放/暂停

HTTP拉流或拉取原文件

电视端本质是“再起一个播放器”

轻量方案用gmrender-resurrect+GStreamer,或MiniDLNA做服务端、自己写renderer。

Miracast/WFD:手机镜像屏幕

Wi-Fi P2P组网

RTSP协商SDP(M1~M8)

RTP over UDP收H.264

Sink硬解渲染

Linux下Miracast常卡在Wi-Fi Direct驱动,量产电视一般用厂商WFD Sink方案。

三、投屏接收线程

Miracast收流关键不是“收到就解”,是RTP组帧+丢包保护:

void rtp_recv_loop(int sock){

   uint8_t pkt[1500];

   while(recvfrom(sock,pkt,sizeof(pkt),0,&addr,&len)>0){

       uint16_t seq = ntohs(*(uint16_t*)(pkt+2));

       if(seq != expected){          // 丢包

           mark_frame_incomplete();  // 等下一个IDR再送解

           expected = seq+1; continue;

       }

       append_nal(pkt+12, len-12);   // 去RTP头

       expected++;

   }

}

H.264单NAL模式直接拼NAL;SPS/PPS变化要更新解码器csd,不然黑屏。

解码出来同样是dma-buf帧,和本地播放走同一套DRM合成。

四、协同调度核心

别让本地播放和投屏各开线程直接写屏,冲突必花屏。统一模型:

typedef struct {

   int dma_fd;

   uint32_t w,h;

   int64_t pts;

   src_type_t src;   // LOCAL / CAST

} frame_t;


QueueHandle_t disp_q;


/* 本地解码线程 */

disp_q_push(decode_local_frame());

/* 投屏解码线程 */

disp_q_push(decode_rtp_frame());


/* 单一显示线程 */

void disp_task(void*){

   frame_t f;

   while(xQueueReceive(disp_q,&f,portMAX_DELAY)){

       drm_show_frame(f.dma_fd,f.w,f.h);

       av_sync_check(f.pts);     // 本地才用音频时钟

       release_dec_buf(f);

   }

}

规则:

显示只由一个线程管

本地播放带音频时钟做AV同步

投屏镜像以视频RTP时间戳为准,音频另路混音

切换信源先drmModeSetPlane禁用旧层,再上新房

投屏结束回收V4L2 buffer,别留dma-buf泄漏

五、状态机

typedef enum { IDLE, LOCAL_PLAY, CASTING, SWITCHING } app_st;

本地播放中收到投屏请求 → 暂停本地解码线程,保留进度

投屏结束 → 恢复本地或回待机

来电/暂停 → 本地进PAUSE,投屏发RTSP PAUSE

Wi-Fi断连 → RTP超时100ms判断流,清屏回UI

六、落坑清单

解码帧memcpy到fb0→CPU爆、断帧

本地和投屏同时drmModeSetCrtc→图层互踩

RTP丢包直接喂解码器→花屏卡死

SPS变更不更新→后续全黑

Miracast依赖Wi-Fi Direct驱动缺失→连不上

投屏也走音频时钟同步→镜像延迟飘

dma-buf用完不关fd→几小时泄漏

GStreamer默认vo=x11→嵌入式无桌面直接废

切换信源不释放plane→残影

七、落地顺序

先跑通本地V4L2 M2M硬解+DRA显示

封装dma-buf帧队列

显示线程统一调度

DLNA renderer接同一队列(最快出Demo)

Miracast走P2P+RTSP+RTP收流

RTP组帧+丢包等到IDR

信源切换状态机

音量/按键/CEC事件分发

智能电视“本地+投屏”的本质不是两个功能并存,是多条码流进同一个硬解显示管线。本地文件解封装、DLNA拉流、Miracast收RTP,到最后都是H.264/H.265包喂解码器,出来dma-buf帧交给DRM Plane。协同的关键在“显示归一、解码并流、切换有状态”——谁直接写屏谁就背锅,单一显示线程+帧队列是避坑底线。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭