ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3568 V4L2采集与MPP硬件编码全链路实战:从零跑通H.264/H.265推流

RK3568 V4L2采集与MPP硬件编码全链路实战:从零跑通H.264/H.265推流 RK3568这颗芯片在嵌入式视觉和边缘计算圈子里热度一直不低四核A55加上独立的NPU和VPU做多路摄像头采集编码推流这类活儿性价比很高。但真正上手把V4L2摄像头的数据喂给MPP硬件编码器再打包成H.264/H.265码流推出去中间踩的坑远比想象中多——格式对不上、DMA缓冲区拷贝开销大、编码器初始化参数配错导致花屏、时间戳错乱引起播放端卡顿这些问题在官方文档里往往一笔带过。这篇内容就把我从零跑通这套链路的完整过程拆开讲包括V4L2采集端的配置逻辑、MPP编码器的初始化细节、两者之间buffer怎么高效衔接、码流怎么封装推流以及调试过程中遇到的那些让人抓狂的报错。适合正在用RK3568做视频采集编码的嵌入式工程师也适合想了解MPP这套框架设计思路的开发者参考。1. 先搞清楚RK3568上视频链路的硬件分工在动手写代码之前必须先把RK3568这颗SoC里跟视频相关的硬件模块理清楚否则后面遇到性能瓶颈或者功能跑不通你都不知道该往哪个方向查。1.1 VPU、MPP、V4L2三者的关系RK3568内部集成了一个独立的视频处理单元也就是VPU它专门负责H.264/H.265的硬件编解码。这个VPU不是通过标准的V4L2接口暴露给上层的而是通过瑞芯微自己的一套MPP框架来调用。MPP全称Media Process Platform它本质上是一层用户态的库向下通过ioctl和内核驱动打交道向上提供了一套C接口让你可以创建编码器、送帧、取码流。而V4L2是Linux内核标准的视频采集框架摄像头传感器通过MIPI CSI接口把数据送到ISP或者直接送到内存V4L2负责管理这些采集设备向上提供/dev/videoX节点。所以整条链路的分工是这样的摄像头走V4L2出原始帧通常是NV12或YUYV格式原始帧交给MPP编码器编码器调用VPU硬件输出H.264/H.265码流最后应用层把码流封装推流。这里有个关键点很多人一开始会搞混MPP的编码器输入并不要求你通过V4L2来喂数据它接受的是内存中的原始帧buffer。你可以从任何来源拿到原始帧——V4L2采集、文件读取、GPU渲染结果——只要格式和分辨率对得上MPP都能编。所以V4L2和MPP之间是解耦的中间靠内存buffer衔接。1.2 为什么不用软件编码有人会问x264/x265软件编码也能用为什么非要折腾MPP。答案很简单性能和功耗。RK3568的A55核心跑软件编码1080p30的H.264大概能跑到但CPU占用率会飙到很高多路并行基本没戏而且发热明显。VPU硬件编码的CPU占用率可以压到个位数功耗低一个量级这是嵌入式场景下必须用硬编的根本原因。另外MPP还支持RGA配合做缩放和格式转换整条链路可以做到零CPU拷贝。1.3 版本和依赖的坑MPP这套东西版本迭代比较快不同版本的API有差异。我建议直接用瑞芯微官方GitHub上较新的release分支编译出librockchip_mpp.so。依赖方面主要是内核里的mpp_service驱动和rkvdec/rkvenc节点这些在RK3568的BSP内核里默认是开着的但你要确认设备树里VPU节点状态是okay。我遇到过设备树里VPU被disable的情况现象就是MPP初始化直接返回失败查了半天才发现是设备树的问题。2. V4L2摄像头采集端的配置细节采集端看起来简单无非是open、set format、request buffers、streamon这几步但每一步都有讲究配错了后面编码出来的画面就是绿的或者花的。2.1 设备节点和能力查询RK3568上接MIPI摄像头通常会枚举出多个video节点有的是ISP的输出有的是直通。你得先确认哪个节点对应你要用的摄像头。用v4l2-ctl --list-devices可以看到设备列表或者直接读/sys/class/video4linux/下的name。确认节点后用VIDIOC_QUERYCAP查询能力重点看capabilities里有没有V4L2_CAP_VIDEO_CAPTURE和V4L2_CAP_STREAMING。如果只有CAPTURE没有STREAMING说明这个节点不支持流式IO得用read方式但read方式效率低编码场景基本不用。2.2 格式协商NV12是首选格式设置是采集端最关键的一步。用VIDIOC_ENUM_FMT枚举支持的格式RK3568的ISP通常支持NV12、NV16、YUYV等。对于MPP编码NV12是最友好的格式因为VPU编码器原生就吃NV12不需要额外转换。如果你拿到的摄像头只出YUYV那就得用RGA做一次格式转换多一步开销。设置格式时用VIDIOC_S_FMT结构体里指定width、height和pixelformat。这里有个坑驱动可能会调整你请求的分辨率比如你请求1920x1080驱动返回1920x1088因为编码器要求高度对齐到16。所以设置完之后一定要读回实际的width和height用返回值去初始化MPP编码器否则分辨率不匹配会导致编码失败或者画面错位。struct v4l2_format fmt {0}; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; fmt.fmt.pix_mp.width 1920; fmt.fmt.pix_mp.height 1080; fmt.fmt.pix_mp.pixelformat V4L2_PIX_FMT_NV12; fmt.fmt.pix_mp.field V4L2_FIELD_ANY; if (ioctl(fd, VIDIOC_S_FMT, fmt) 0) { perror(VIDIOC_S_FMT); return -1; } // 读回实际参数 int actual_w fmt.fmt.pix_mp.width; int actual_h fmt.fmt.pix_mp.height;注意这里用的是MPLANE类型因为NV12是双平面格式Y平面和UV平面分开。如果你的驱动只支持单平面那就用V4L2_BUF_TYPE_VIDEO_CAPTUREpixelformat用V4L2_PIX_FMT_NV12M或者NV12具体看驱动实现。2.3 缓冲区申请与内存类型选择V4L2的buffer有几种内存类型MMAP、USERPTR、DMABUF。做MPP编码最理想的是DMABUF因为可以让V4L2和MPP共享同一块物理内存实现零拷贝。但DMABUF的导出和导入需要驱动支持RK3568的ISP驱动是支持的。如果DMABUF搞不定退而求其次用MMAPV4L2分配缓冲区采集完成后你把数据拷贝到MPP的输入buffer里。这个拷贝对于1080p来说每帧大概3MB30帧就是90MB/s的拷贝量CPU开销不小但还能接受。USERPTR方式需要你自己分配物理连续内存比较麻烦不推荐。申请buffer用VIDIOC_REQBUFS指定count一般4到6个和memory类型。然后用VIDIOC_QUERYBUF查询每个buffer的信息MMAP方式下用mmap把内核缓冲区映射到用户空间。struct v4l2_requestbuffers req {0}; req.count 4; req.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; req.memory V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, req) 0) { perror(VIDIOC_REQBUFS); return -1; }2.4 入队、启动流与取帧循环buffer申请完之后用VIDIOC_QBUF把每个buffer入队然后VIDIOC_STREAMON启动采集。之后就是循环用VIDIOC_DQBUF取已填充的帧处理完再用VIDIOC_QBUF还回去。这个循环是阻塞的DQBUF没数据时会卡住所以一般放在独立线程里。取帧的时候要注意v4l2_buffer结构体里的bytesused告诉你实际数据量timestamp告诉你帧的时间戳。这个时间戳一定要传给MPP编码器否则编码出来的码流没有正确的时间信息播放端会卡顿或者音视频不同步。提示DQBUF返回的buffer如果index超出你申请的count范围说明驱动有问题或者内存被踩了这种情况要立即报错退出不要继续用。3. MPP编码器的初始化与参数配置MPP的API设计跟海思的MPI有点像都是创建通道、配置参数、送帧、取流这套流程。但MPP的文档比较少很多参数得看头文件和示例代码才能搞明白。3.1 MPP上下文与编码器创建用MPP之前要先调mpp_create创建上下文然后mpp_init初始化指定编码类型MPP_VIDEO_CodingAVC对应H.264MPP_VIDEO_CodingHEVC对应H.265。初始化的时候还要指定工作模式编码场景用MPP_CTX_ENC。MppCtx ctx; MppApi *mpi; mpp_create(ctx, mpi); mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingAVC);这里有个细节mpp_init的第三个参数决定编码格式如果你后面想动态切换H.264和H.265需要销毁重建不能直接改。所以如果你的产品需要两种格式都支持最好在初始化前就确定好或者做成两个独立的编码通道。3.2 编码参数码率、GOP、profile编码参数通过MppEncCfg来设置这是一套key-value的配置接口。关键的参数有这么几个参数说明推荐值MPP_ENC_CFG_RC_MODE码率控制模式CBR或VBRMPP_ENC_CFG_BPS_TARGET目标码率根据分辨率和场景定MPP_ENC_CFG_GOP_LENGOP长度30到60MPP_ENC_CFG_PROFILE编码profileH.264用HighH.265用MainMPP_ENC_CFG_LEVEL编码level根据分辨率选MPP_ENC_CFG_FPS_IN_NUM/DEN输入帧率匹配摄像头帧率码率控制模式的选择有讲究。CBR固定码率适合网络传输带宽受限的场景码率稳定但画质会波动。VBR可变码率画质更稳定但码率会波动适合本地存储。我一般推流场景用CBR存储场景用VBR。目标码率的计算1080p30的H.264一般2到4Mbps就能有不错的画质。计算公式是 宽 x 高 x 帧率 x 运动因子 / 压缩比。实际调的时候先设一个值看编码出来的码流大小和画质再微调。MppEncCfg cfg; mpp_enc_cfg_init(cfg); mpp_enc_cfg_set_s32(cfg, rc:mode, MPP_ENC_RC_MODE_CBR); mpp_enc_cfg_set_s32(cfg, rc:bps_target, 4000000); mpp_enc_cfg_set_s32(cfg, rc:bps_max, 4000000 * 17 / 16); mpp_enc_cfg_set_s32(cfg, rc:bps_min, 4000000 * 15 / 16); mpp_enc_cfg_set_s32(cfg, rc:gop, 60); mpp_enc_cfg_set_s32(cfg, codec:type, MPP_VIDEO_CodingAVC); mpp_enc_cfg_set_s32(cfg, h264:profile, 100); // High profile mpp_enc_cfg_set_s32(cfg, h264:level, 40); mpi-control(ctx, MPP_ENC_SET_CFG, cfg);3.3 输入帧格式与分辨率对齐MPP编码器的输入格式通过MppFrame来指定关键字段是width、height、hor_stride、ver_stride和fmt。hor_stride和ver_stride是对齐后的跨度通常hor_stride对齐到16ver_stride对齐到16。如果你从V4L2拿到的分辨率是1920x1080那ver_stride可能是1088。这里是最容易出问题的地方如果你设置的stride跟实际buffer的stride不一致编码出来的画面会斜或者花。所以一定要用V4L2返回的实际stride来设置MppFrame。V4L2的v4l2_pix_format_mplane里有bytesperline字段那就是stride。MppFrame frame NULL; mpp_frame_init(frame); mpp_frame_set_width(frame, width); mpp_frame_set_height(frame, height); mpp_frame_set_hor_stride(frame, hor_stride); mpp_frame_set_ver_stride(frame, ver_stride); mpp_frame_set_fmt(frame, MPP_FMT_YUV420SP); // NV123.4 码流buffer的获取与释放编码完成后通过MPP_ENC_GET_PACKET或者mpi-encode_get_packet拿到码流包。这个packet里包含编码后的数据和元信息时间戳、帧类型等。拿到之后要尽快处理推流或存文件处理完调mpp_packet_deinit释放。如果不及时释放buffer池会耗尽编码器会阻塞。码流包的时间戳来自输入帧的时间戳所以前面V4L2的时间戳一定要正确传递。MPP内部不做时间戳转换你给什么它就原样输出。4. V4L2与MPP之间的buffer衔接方案这是整条链路里最考验工程能力的地方。采集和编码是两个独立的模块中间的数据传递方式直接决定了CPU占用率和延迟。4.1 方案一MMAP加memcpy最简单V4L2用MMAP方式申请bufferDQBUF拿到帧后把数据memcpy到MPP的输入MppBuffer里然后送编码。这个方案实现简单不依赖DMABUF支持兼容性最好。缺点是拷贝开销。1080p NV12一帧是1920x1088x1.5约3.1MB30帧就是93MB/s的拷贝量。在A55上这个拷贝大概占用10%到15%的CPU。如果只跑一路可以接受多路就不行了。优化技巧拷贝的时候用memcpy就行不要自己写循环glibc的memcpy对连续内存做了SIMD优化比手写的快。另外可以只拷贝bytesused指定的长度不要整个buffer都拷。4.2 方案二DMABUF零拷贝推荐DMABUF是Linux内核提供的一种跨设备共享内存的机制。V4L2采集的buffer可以通过VIDIOC_EXPBUF导出成DMABUF fd然后MPP通过MppBuffer的导入接口把这个fd导入进来两边共享同一块物理内存完全不需要拷贝。具体步骤V4L2申请buffer时用V4L2_MEMORY_MMAP然后用VIDIOC_EXPBUF把每个buffer导出成fd。MPP这边创建MppBufferGroup时用MPP_BUFFER_TYPE_ION或者MPP_BUFFER_TYPE_DRM然后调mpp_buffer_import把V4L2的fd导入。// V4L2导出DMABUF struct v4l2_exportbuffer expbuf {0}; expbuf.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; expbuf.index buf.index; expbuf.plane 0; ioctl(fd, VIDIOC_EXPBUF, expbuf); int dma_fd expbuf.fd; // MPP导入 MppBuffer mpp_buf; mpp_buffer_import(mpp_buf, dma_fd, NULL);DMABUF方案的关键是两边对内存的布局理解要一致。NV12是Y平面加交错的UV平面V4L2的MPLANE模式下两个平面是分开的导出的时候要分别导出plane 0和plane 1。MPP导入的时候也要对应设置。如果布局对不上编码出来就是花的。4.3 方案三RGA做中间转换如果摄像头只出YUYV或者RGB而MPP只吃NV12那就需要RGA做格式转换。RGA是RK3568的2D加速器可以做缩放、旋转、格式转换而且支持DMABUF输入输出整个转换过程也是零拷贝的。RGA的用法是通过librga库创建src和dst的buffer设置格式和分辨率然后调用im2d接口执行。转换完的dst buffer直接给MPP编码。这个方案适合摄像头格式不理想的场景多了一步RGA但仍然是硬件加速CPU占用很低。4.4 三种方案的对比与选型方案CPU占用延迟实现难度适用场景MMAPmemcpy中低低单路、快速验证DMABUF零拷贝极低最低高多路、量产RGA转换低低中格式不匹配我的建议是原型阶段先用MMAPmemcpy跑通功能验证编码参数和推流链路。功能没问题后再切DMABUF优化性能。如果摄像头格式不对中间加RGA。不要一上来就搞DMABUF调试成本高容易卡在内存布局问题上。5. 码流封装与推流实现编码出来的码流是裸流elementary stream要推给播放端或者服务器通常需要封装成某种容器格式或者实时传输协议。5.1 裸流的帧边界识别H.264的裸流由NALU组成每个NALU以起始码0x00000001或0x000001分隔。H.265类似但NALU头结构不同。MPP输出的packet通常是一个完整的帧可能包含多个NALU但有时候也会分片。你需要根据NALU类型判断帧边界H.264的SPS是7PPS是8IDR是5非IDR是1。H.265的VPS是32SPS是33PPS是34IDR是19或20。识别帧边界的目的有两个一是推流时按帧发送二是判断关键帧。关键帧对于推流很重要新客户端接入时需要从关键帧开始发否则解不出来。5.2 RTMP推流的基本流程RTMP是最常用的推流协议基于TCP延迟一般在1到3秒。推流的基本流程是握手、连接、创建流、发送音视频数据。C语言实现RTMP推流可以用librtmp虽然老但稳定。发送视频数据时需要把裸流打包成FLV tag。FLV tag的VideoTag里第一个字节是帧类型和编码类型关键帧AVC就是0x17后面跟AVC packet type和composition time再后面是NALU数据。H.265在FLV里的封装有几种方案标准FLV不支持H.265需要用扩展字段或者用enhanced RTMP。// 简化的FLV video tag构造 uint8_t tag_header[5]; tag_header[0] 0x17; // key frame AVC tag_header[1] 0x01; // AVC NALU tag_header[2] 0x00; // composition time tag_header[3] 0x00; tag_header[4] 0x00; // 后面跟NALU数据5.3 时间戳与音视频同步推流的时间戳单位是毫秒从0开始递增。视频帧的时间戳根据帧率计算30fps就是每帧33.3ms。MPP输出的packet里带的时间戳是微秒或者纳秒取决于你送帧时设的单位推流前要转换成毫秒。如果同时推音频音视频的时间戳要基于同一个时钟源。一般用系统单调时钟采集时记录时间推流时转换成相对时间。音视频不同步是推流最常见的问题根源往往是时间戳计算错误或者采集和编码的延迟没有补偿。5.4 断线重连与缓冲策略网络推流必须考虑断线重连。RTMP连接断开后要重新握手连接然后从最近的关键帧开始发。所以本地要缓存一个GOP的数据重连后先发缓存的GOP保证新连接能立即解码。缓冲策略上编码器输出的码流不要立即发送先放进一个队列由独立的发送线程消费。这样可以解耦编码和网络网络抖动时不会阻塞编码。队列长度一般设1到2秒的数据量太长会增加延迟太短容易丢帧。6. 调试过程中踩过的坑与排查思路这部分是我实际调试中遇到的具体问题每个都花了不少时间才定位到记录下来希望能帮你少走弯路。6.1 编码出来的画面全绿或者花屏这是最常见的问题原因通常有三个输入格式不对、stride不匹配、buffer内容没填对。排查顺序先确认V4L2出的格式是不是NV12用v4l2-ctl --get-fmt-video查。然后确认MppFrame的fmt设置是不是MPP_FMT_YUV420SP。再看strideV4L2的bytesperline和MPP的hor_stride必须一致。最后检查buffer内容可以把V4L2采集的原始帧dump成文件用YUV查看器打开看正不正常。如果原始帧就是绿的那是摄像头或者ISP的问题如果原始帧正常但编码后花那是MPP参数的问题。我遇到过一次是ver_stride设成了1080但实际buffer是1088导致UV平面偏移了8行画面下半部分全花。改成1088就好了。6.2 MPP初始化失败返回错误码mpp_init返回失败错误码一般是MPP_ERR_UNSUPPORT或者MPP_ERR_DEVICE。先查设备树里VPU节点是不是okay再看/dev/mpp_service存不存在。如果设备节点在但初始化还是失败可能是内核驱动版本和用户态库版本不匹配。MPP的用户态库和内核驱动有版本对应关系用官方release里配套的版本最稳妥。还有一种情况是权限问题非root用户访问/dev/mpp_service需要相应的权限可以加udev规则或者用root跑。6.3 编码帧率上不去现象是采集30fps但编码输出只有十几帧。原因可能是编码器性能不够、buffer池太小、或者送帧逻辑阻塞。先看MPP的日志mpp_log_level设成debug可以看到每帧的处理耗时。如果单帧编码耗时超过33ms那编码器参数可能太重了比如GOP太小、码率太高、或者用了B帧。RK3568的VPU编码1080p30是没问题的但如果同时跑多路或者分辨率到4K就可能不够。buffer池太小也会导致阻塞编码器拿不到空闲buffer就得等。增加buffer数量或者及时释放已处理的packet。6.4 推流播放端卡顿或者绿屏播放端卡顿通常是时间戳问题。检查推流的时间戳是不是单调递增有没有跳变。如果时间戳重复或者倒退播放器会卡住。另外检查关键帧间隔如果GOP太长新接入的播放器要等很久才能出画面。绿屏一般是SPS/PPS没发或者发错。推流开始时必须先发SPS和PPS然后才能发IDR帧。有些播放器要求SPS/PPS在每次IDR前都发有些只要求开头发一次。保险起见每次IDR前都发。6.5 DMABUF导入失败mpp_buffer_import返回失败先确认V4L2的EXPBUF有没有成功fd是不是有效。然后确认MPP的buffer group类型对不对RK3568上一般用MPP_BUFFER_TYPE_DRM。如果内核的DRM驱动没使能DMABUF就用不了。还有一个坑是plane数量。NV12是双平面V4L2导出的时候要导出两个planeMPP导入的时候也要分别导入。如果只导了plane 0UV平面就是空的编码出来只有亮度没有色彩。7. 性能优化与多路并行的思路单路跑通之后如果要做多路性能优化就是绕不开的话题。7.1 线程模型设计推荐的线程模型是每路摄像头一个采集线程一个编码线程所有路共用一个推流线程或者每路一个推流线程。采集线程只负责DQBUF和送帧编码线程负责取码流和入队推流线程负责发送。采集和编码之间用环形buffer队列衔接队列满时采集线程阻塞等待避免内存无限增长。队列深度设2到3帧就够了太深会增加延迟。7.2 CPU亲和性与优先级RK3568是四核A55可以把采集线程绑到CPU0编码线程绑到CPU1推流线程绑到CPU2中断处理留给CPU3。用pthread_setaffinity_np设置亲和性。编码线程可以适当提高优先级保证送帧及时。7.3 内存带宽的考量多路1080p同时跑内存带宽是瓶颈。NV12一帧3MB4路30fps就是360MB/s的读写量加上编码器的读写很容易把DDR带宽吃满。用DMABUF零拷贝能省掉一次拷贝对带宽帮助很大。另外RGA转换也走硬件不占CPU但占带宽要算进总账里。7.4 温度与降频RK3568长时间满负荷跑温度上去后会降频编码帧率就掉了。量产设备要做好散热加散热片或者风扇。软件上可以监控温度超过阈值时动态降码率或者降帧率保证不卡死。我在实际项目里遇到过一次夏天高温环境下连续跑几小时后帧率从30掉到20的情况后来加了散热片就稳定了。所以散热设计在RK3568这类芯片上不能省尤其是多路编码场景。7.5 编码参数的动态调整码率不是设一次就不管了。网络状况变化时动态调整码率能避免卡顿。MPP支持运行时通过MPP_ENC_SET_CFG更新码率参数不需要重建编码器。可以根据推流队列的积压情况反馈调节队列积压多就降码率积压少就升回去。这套动态码率控制逻辑需要自己实现MPP只提供接口。实现时要注意调整的步长不要太大每次调10%到20%避免画质剧烈波动。8. 一些容易忽略的工程细节最后补充几个实际项目中容易忽略但很关键的细节。8.1 编码器flush与重建停止推流或者切换分辨率时需要flush编码器把缓存的帧都编出来。调mpi-control(ctx, MPP_ENC_SET_IDR_FRAME)可以强制下一帧为IDR调mpp_enc_flush或者销毁重建可以清空状态。如果不flush直接销毁最后几帧会丢。8.2 异常处理与资源释放MPP和V4L2的资源释放顺序很重要。先停流STREAMOFF再释放buffer再销毁MPP上下文最后关fd。顺序错了可能导致内核里资源泄漏下次打开失败。我遇到过忘记STREAMOFF就close fd的情况结果video节点被占用要重启才能恢复。8.3 日志与调试手段MPP的日志通过mpp_log_level控制调试时设成MPP_LOG_DEBUG能看到每帧的详细信息。V4L2这边可以用v4l2-ctl抓帧存文件用ffplay播放验证。推流端可以用ffmpeg拉流验证或者用Wireshark抓RTMP包分析。把编码前后的帧都dump出来对比是定位花屏问题最快的方法。编码前的YUV用YUV Player看编码后的码流用ffplay播放两边一对比就知道问题出在采集还是编码。8.4 关于H.265的额外注意点H.265的压缩率比H.264高30%到50%同画质下码率更低但兼容性差一些有些播放器和浏览器不支持。推流用H.265要考虑接收端能不能解。另外H.265的NALU头是两字节跟H.264的一字节不同封装的时候要区分处理。MPP对H.265的支持跟H.264一样完善参数配置上profile用Mainlevel根据分辨率选。RK3568上跑H.265编码1080p30的CPU占用跟H.264差不多因为都是硬件编码。码率可以设得比H.264低一些比如H.264用4MbpsH.265用2.5Mbps就能达到类似画质。这套链路我从最开始跑通到稳定运行前后花了大概两周时间大部分时间都耗在调试花屏和性能优化上。现在回头看如果一开始就把格式对齐、stride匹配、时间戳传递这几个基础点做扎实能省掉很多返工。希望这些经验对正在做类似项目的你有帮助。
返回列表