ARTICLE DETAIL

资讯详情

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

Zynq-7000 USB摄像头图像采集:从V4L2到VDMA完整实践

Zynq-7000 USB摄像头图像采集:从V4L2到VDMA完整实践 这段时间一直在折腾Zynq-7000的图像采集手头这块板子的双核ARM Cortex-A9跑LinuxUSB口接个免驱UVC摄像头一路从V4L2裸驱动写到VDMA显示最终把图像帧稳定采下来。今天这篇就把Zynq-7000上USB摄像头图像采集的完整思路和踩坑过程整理出来适合刚开始接触Zynq、想在PS端用Linux做视频采集的读者参考。全文从方案选型讲到内核配置再到V4L2核心代码和常见故障排查按实际操作顺序来写尽量做到看过就能照着跑。1. 整体方案与架构选择1.1 Zynq-7000图像采集的两种主流做法Zynq-7000是Xilinx的异构SoC平台PS端是一颗双核ARM Cortex-A9PL端则是FPGA可编程逻辑。USB摄像头图像采集在Zynq上通常走两条路线一条是纯PS方案摄像头接在PS的USB控制器上Linux里的uvcvideo驱动负责枚举、流控和帧缓冲应用层用V4L2 API取图像然后保存、网络发送或做轻量处理另一条是PSPL协同方案摄像头同样从PS端USB进来采集数据放在DDR里再用AXI VDMA把图像帧搬到PL端在FPGA里做ISP、缩放、字符叠加最后从HDMI或DisplayPort输出。两条路线的选择直接决定你的工程量。纯PS方案的特点是系统简单Linux把USB协议栈、驱动和内存管理都封装好了应用层代码量少适合快速跑通功能、验证摄像头时序和数据通路。缺点是数据一直在DDR和CPU之间倒腾A9双核虽然比单核A9好点但做视频处理还是比较吃力尤其高分辨率场景CPU占用会明显升高。PSPL方案则是把帧搬运和逐像素计算交给PLCPU只做控制面适合做高分辨率实时处理但需要写VDMA驱动、处理缓存一致性、设计PL端数据通路工作量一下子大很多。从学习角度讲我建议绝大多数新手先跑通纯PS采集把帧缓冲、像素格式、V4L2的buffer管理这些基础概念搞明白再考虑往PL端引数据。否则一堆底层问题摞在一起很难定位问题根源。我自己就是这么交叉验证过来的先用纯PS把UVC摄像头跑起来确认带宽和帧率都正常然后才开始接VDMA。1.2 为什么先从V4L2裸驱动写起很多刚接触嵌入式Linux的人上来就想用OpenCV的VideoCapture或者GStreamer去读摄像头。这在x86 PC上没什么问题但在Zynq-7000的交叉编译环境里我强烈建议先写一版不带第三方库的V4L2裸驱动。原因有三第一OpenCV往ARM板上交叉编译很费时间依赖库一大堆为了读个摄像头搭这么大环境不值第二GStreamer的管线一旦出问题报错信息抽象比如“no decoder available”“not-linked”这类提示大概率不是管线语法错而是底层V4L2格式没配对你没写过裸驱动的话很难看出来第三手写V4L2能逼着你把驱动、缓冲区、格式、帧率这些概念一次性理清之后再接GStreamer、接VDMA或者自己写RTSP都有底气。我见过不少项目写完GStreamer管线发现摄像头出图慢排查半天最后发现是摄像头内部默认输出格式和管线要求不匹配在UVC驱动层面就已经暴露了。如果你懂V4L2一眼就能从VIDIOC_S_FMT返回值判断出问题。所以别嫌裸写法“原始”它反而是嵌入式视频开发最好的入门方式。1.3 一帧图像的数据链路从摄像头到应用层一帧图像在Zynq上大致经过这样一条链路USB摄像头传感器输出原始画面经过内部ISP合成YUV数据或压缩成MJPEG然后通过USB总线发送给PS端USB控制器。Linux的uvcvideo驱动把URB收上来的数据交给videobuf2框架videobuf2负责管理一块块缓冲区用户态应用通过V4L2 API把缓冲区排进队列驱动填充完一帧后再把缓冲区交回应用。这个流程可以理解成快递分拣摄像头是发件人驱动是快递柜应用是收件人缓冲区就是柜子里的储物箱。你把空箱挂出去QBUF快递员塞满后把箱子转给你DQBUF你看完再还回去循环利用。之所以用这种“多缓冲队列”的模型是为了让USB传输和应用处理能并行起来。USB控制器在DMA搬运第3帧的时候CPU可能正在处理第1帧第2帧已经在队列里等着时间被重叠利用。如果只有一块缓存摄像头只能等应用处理完才能传下一帧帧率会直接掉到应用处理速度的一半以下。V4L2默认支持多缓冲就是这个原因。2. 环境准备与内核配置2.1 硬件与软件清单开始动手前先把环境列清楚免得做到一半发现缺东西。我用的硬件大致如下组件推荐选型说明开发板Zynq-7000系列如Zedboard、ZC706、黑金PS双核A9自带USB OTG控制器USB摄像头罗技C270、C920等UVC免驱摄像头驱动兼容性好格式支持列表清晰调试工具USB转串口、HDMI显示器串口看内核日志显示器验证输出启动介质SD卡放BOOT.bin、image.ub和根文件系统软件方面我用的是PetaLinux来打包内核和根文件系统版本可以选2020.x到2022.x之间新版本也能跑但要注意设备树和驱动的兼容性。交叉编译工具链用arm-linux-gnueabihf-gcc目标机上的工具集里必须带上v4l-utils里面包含v4l2-ctl这个工具在排查格式问题时非常好用。一个很容易被忽略的点是USB口的供电能力。很多Zynq开发板上的USB Host口直接从板载电源取电电流有限带那种无源USB HUB再接摄像头时经常掉线或者枚举失败。我先期没有外接电源HUB摄像头在串口日志里反复地“usb 1-1: new high-speed USB device”一天能掉几十次。后来换了一个带供电的USB HUB问题基本绝迹。2.2 内核配置与设备树Zynq-7000的Linux内核默认支持UVC只要在配置里把对应的模块打开就行。如果你用PetaLinux配置内核常用petalinux-config -c kernel在里面搜索并确认以下配置项CONFIG_MEDIA_SUPPORTy CONFIG_MEDIA_CAMERA_SUPPORTy CONFIG_VIDEO_DEVy CONFIG_VIDEO_V4L2y CONFIG_USB_VIDEO_CLASSy CONFIG_USB_VIDEO_CLASS_INPUT_EVDEVy CONFIG_USB_EHCI_HCDy CONFIG_USB_OHCI_HCDy CONFIG_USB_XHCI_HCDyCONFIG_USB_VIDEO_CLASS是最关键的它就是uvcvideo驱动。如果没有它lsusb能看到摄像头设备但/dev/video0不会出现。UVC设备不需要在设备树里有专门节点来绑定驱动驱动靠USB VID/PID自动匹配。设备树方面主要确认USB控制器节点的状态。Zynq-7000的设备树一般已经有usb0和usb1节点需要看一下status okay是否打开以及dr_mode是否设置正确。如果是接U盘和摄像头这种Host设备dr_mode要设成host如果开发板上这个USB口复用OTG功能也常配置成otg但为了排查方便建议直接固定为host。usb1 { status okay; dr_mode host; };改完设备树重新编译生成BOOT.bin和image.ub从SD卡启动后用一组命令检查驱动加载情况dmesg | grep -i usb dmesg | grep -i uvc lsusb ls -l /dev/video* cat /sys/class/video4linux/video0/name正常情况下dmesg里会有uvcvideo: Found UVC 1.00 device类似的日志/dev/video0也存在查看name能显示摄像头具体的型号名称。如果这些都不满足按上面几项配置逐一排查。2.3 确认摄像头支持的格式UVC摄像头虽然免驱但不同型号的传感器能输出的分辨率和像素格式千差万别。在写采集代码之前第一件事就是确认摄像头的真实能力。用v4l2-ctl可以列出所有支持的格式v4l2-ctl --device/dev/video0 --list-formats-ext以罗技C920为例输出大致是这样ioctl: VIDIOC_ENUM_FMT Type: Video Capture [0]: YUYV (YUYV 4:2:2) Size: Discrete 640x480 Interval: Discrete 0.033s (30.000 fps) Size: Discrete 1280x720 Interval: Discrete 0.100s (10.000 fps) [1]: MJPG (Motion-JPEG) Size: Discrete 1920x1080 Interval: Discrete 0.033s (30.000 fps)这个支持列表直接决定你后续能做什么。留意到没有YUYV格式在720p下只能跑到10fps而MJPEG在1080p下能到30fps。原因是YUYV是未压缩格式一帧720p的裸数据是12807202字节大约1.76MB30fps就是52.8MB/s已经超过USB 2.0 High-Speed的有效带宽上限。摄像头固件为了压缩带宽在YUYV高分辨率下主动限制帧率。MJPEG则用JPEG压缩把单帧体积降到几百KB甚至更小所以才能维持30fps。开始编码前先看这份列表能省掉后面大量试错。3. 用V4L2实现图像采集3.1 采集程序工作流程V4L2采集的标准流程按顺序大概是七个步骤open(/dev/video0, O_RDWR)打开设备节点。VIDIOC_QUERYCAP查询设备能力确认支持视频采集和流式IO。VIDIOC_S_FMT设置像素格式、宽高。VIDIOC_REQBUFS申请缓冲区通常4到8个。对每个缓冲区执行VIDIOC_QUERYBUF获得地址和长度mmap映射到用户空间。VIDIOC_QBUF把所有缓冲区放入采集队列然后VIDIOC_STREAMON启动采集。循环里用select或poll等待数据有帧到达就VIDIOC_DQBUF取帧、处理完再VIDIOC_QBUF还帧。为什么用mmap而不是read两个原因。一是V4L2的read接口每次调用都会把一整帧从内核空间拷贝到用户空间属于重复劳动mmap则是把驱动的DMA缓冲区直接映射到应用地址空间CPU读取这块内存时其实读的就是USB控制器DMA写入的数据省掉一次内存拷贝这对A9级别的嵌入式平台尤其重要。二是mmap对应的多缓冲队列能充分发挥USB DMA的并行特性read接口通常只能一个缓冲处理一帧时下一帧没法同时落盘帧率上限低。3.2 核心代码片段解析直接看代码。下面这些片段来自我实际的采集测试代码每段都加了注释说明关键点。打开设备和设置格式#include linux/videodev2.h #include sys/ioctl.h #include sys/mman.h #include fcntl.h #include poll.h #include unistd.h int fd open(/dev/video0, O_RDWR); if (fd 0) { perror(open video0); return -1; } struct v4l2_format fmt; memset(fmt, 0, sizeof(fmt)); fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width 640; fmt.fmt.pix.height 480; fmt.fmt.pix.pixelformat V4L2_PIX_FMT_YUYV; fmt.fmt.pix.field V4L2_FIELD_NONE; if (ioctl(fd, VIDIOC_S_FMT, fmt) 0) { perror(VIDIOC_S_FMT); return -1; }field V4L2_FIELD_NONE表示摄像头输出的是逐行扫描帧不是隔行。对于UVC摄像头基本都用这个值。设置完后fmt.fmt.pix.sizeimage和fmt.fmt.pix.bytesperline会被驱动回填这两个值在申请缓冲区时会用到。申请缓冲区和mmapstruct v4l2_requestbuffers req; memset(req, 0, sizeof(req)); req.count 4; req.type V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, req) 0) { perror(VIDIOC_REQBUFS); return -1; } struct v4l2_buffer buf; for (int i 0; i 4; i) { memset(buf, 0, sizeof(buf)); buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; buf.index i; ioctl(fd, VIDIOC_QUERYBUF, buf); void *addr mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (addr MAP_FAILED) { perror(mmap); return -1; } // 把addr保存到buffers[i].startbuffers[i].length buf.length }REQBUFS的count可以多申请几个但不要贪多。缓冲区太多会占用大量DMA内存太少又会因为应用处理不及时把队列占满而掉帧。640x48030我用4个缓冲就够1080p MJPEG我建议至少6到8个因为JPEG解码耗时处理不过来时队列还能撑一会儿。采集主循环// 先把四个缓冲全部入队 for (int i 0; i 4; i) { memset(buf, 0, sizeof(buf)); buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; buf.index i; ioctl(fd, VIDIOC_QBUF, buf); } enum v4l2_buf_type type V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_STREAMON, type); while (1) { struct pollfd pfd; pfd.fd fd; pfd.events POLLIN; pfd.revents 0; int ret poll(pfd, 1, 3000); if (ret 0) { printf(poll timeout\n); continue; } memset(buf, 0, sizeof(buf)); buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; ioctl(fd, VIDIOC_DQBUF, buf); // 处理buffers[buf.index].start处的数据长度为buf.bytesused process_frame(buffers[buf.index].start, buf.bytesused); ioctl(fd, VIDIOC_QBUF, buf); }这里特别说明一下poll加超时的重要性。USB摄像头偶尔会因为带宽竞争、USBHUB时序问题丢一帧如果在DQBUF上无限阻塞一旦驱动因为异常没有新帧入队整个应用就卡死了。有了3秒超时即使掉帧也只是打印一条提示继续跑程序不会僵在那里。调试阶段这个超时还能当“丢帧检测器”用如果日志频繁输出poll timeout说明采集链路已经不稳定了得回头查供电或带宽。3.3 帧率统计与USB带宽评估帧率并不需要特别复杂的工具来测在采集循环里加一个计数器就能大概测出来unsigned int frame_count 0; struct timeval last_time, cur_time; gettimeofday(last_time, NULL); while (1) { // ... poll和DQBUF ... frame_count; gettimeofday(cur_time, NULL); if (cur_time.tv_sec ! last_time.tv_sec) { printf(fps: %u\n, frame_count); frame_count 0; last_time cur_time; } }这个办法粗糙但是直观每秒打印一次实际帧率多少一眼就能看到。如果要更精确可以用DQBUF返回的buf.timestamp字段这是内核在驱动收到帧的时刻打的时间戳能用来分析采集本身是否有抖动。帧率上不去首先要会估算带宽。USB 2.0 High-Speed的理论速率是480Mbps换算下来60MB/s但因为协议开销和传输调度实际有效数据带宽大概只有40MB/s多一点。Zynq-7000大多数板载USB控制器就是这个规格。看几个典型格式的带宽占用格式分辨率帧率单帧大小所需带宽YUYV640x48030fps614KB18.4MB/sYUYV1280x72030fps1.76MB52.8MB/sMJPEG1920x108030fps约200~500KB6~15MB/sMJPEG1280x72030fps约100~300KB3~9MB/s从上表可以看到YUYV 720p30已经明显超出USB 2.0有效带宽摄像头固件通常不开放这个组合所以应用设置这个格式时大概率返回失败。MJPEG则要友好得多省下来的带宽还能同时挂多个设备。在Zynq这种带宽敏感的平台上合理利用MJPEG是提高实时性的第一步。如果后续做颜色识别又必须要YUV原始数据就需要在应用层调用libjpeg解码MJPEG帧付出的代价是CPU占用会上升A9跑1080p MJPEG解码基本到30fps就是极限了。4. 图像保存、显示与协议输出4.1 YUYV转BMP保存采集链路跑通以后最先想做的就是“看得见”。保存成BMP是最快、依赖最少的验证方式BMP格式本身不压缩写起来也简单。YUYV每4字节表示两个像素排列是Y0 U Y1 V。解码时每个像素都有自己的Y但相邻两个像素共享同一个U和V。转换公式有很多近似版本实测下来下面这一组在嵌入式平台上效果不错// y, u, v 取值范围都是0~255 int r y 1.402 * (v - 128); int g y - 0.344 * (u - 128) - 0.714 * (v - 128); int b y 1.772 * (u - 128); // 结果截断到0~255写BMP文件时有两个坑。第一BMP的像素行是从下往上存的也就是说文件里第一行数据对应图像最下面一行如果直接按从上到下的顺序写打开图片会上下颠倒。有两种解法一种是把像素数组从最后一行开始倒着写另一种是在文件头里把高度填成负数表示自顶向下存储部分解码器支持但不是所有都支持最稳妥的还是倒序写。第二BMP每一行的字节数必须是4的倍数YUYV转RGB24后每行像素数是宽度的3倍如果不是4的倍数要补齐否则图像会出现斜线错位。保存成BMP还有一个隐藏的开销每帧都要做YUYV到RGB的转换这个转换在A9上并不便宜。我做过一个粗略测试640x480的帧转换一次大概要几十毫秒已经超过单帧16ms的预算。所以正式项目里如果要做显示或传输尽量保持YUV或MJPEG格式到最后一刻RGB只在需要的地方临时转。4.2 通过VDMA送到HDMI显示如果不想只在文件系统里存图而是想实时看到摄像头画面Zynq上最典型的做法是走VDMA。大致架构是PS端播放器把V4L2采集到的帧写到DDR的某个物理连续区域PL端VDMA按AXI总线突发读这块地址把数据转换成AXI4-Stream流再接一个视频时序生成器输出到HDMI接口。实现上要做几件事。第一在PS端分配物理地址连续的DMA缓冲区常见做法是内核模块里使用dma_alloc_coherent分配然后把物理地址通过驱动暴露给应用层。第二把V4L2采集的数据拷贝到这块DMA缓冲区。这个拷贝本身也是开销但如果只是把640x480的数据拷一次A9还能接受。第三把分辨率、缓冲区的物理地址、帧率等参数写入VDMA的寄存器启动搬运。这里最折磨人的是缓存一致性问题。CPU写数据到DMA缓冲区后如果缓冲区被映射成cacheableCPU写入的数据还停留在cache里没有真正落回DDRVDMA从DDR读到的就是旧数据画面上出现花屏、错行。很多人第一次接VDMA都会卡在这里。用dma_alloc_coherent分配的内存就是non-cacheable的能规避这个问题如果非要用普通物理内存那在启动VDMA之前必须手动做cache flush操作。不同驱动框架下flush接口不一样但思路是同一个。如果你只是想先把显示弄出来验证不去深究内核模块也可以临时用/dev/mem直接操作寄存器并指定一个已知物理地址比如0x1F000000然后把数据memcpy过去。但这种做法只适合调试因为它绕过了内存伙伴系统的管理正式项目还是要按规范的DMA API来写否则地址被其他模块抢占后会出各种莫名其妙的问题。4.3 用GStreamer把USB摄像头推成RTSP流最近看到RK3588平台上有人拿USB摄像头直接转RTSP流这个需求在Zynq-7000上也能实现差别主要在于编码能力和带宽资源。Zynq的A9做H264软编码非常吃力720p30基本不可能1080p更是想都别想。所以我的建议是如果只是内网监控预览优先用MJPEG源直接打包成RTP不走H264编码CPU占用会低很多gst-launch-1.0 v4l2src device/dev/video0 \ ! image/jpeg,width1280,height720,framerate15/1 \ ! rtpjpegpay \ ! udpsink host192.168.1.100 port5000这条管线让UVC摄像头输出MJPEG帧GStreamer不做任何转码直接把JPEG帧打包成RTP UDP包发到目标地址。解码端用VLC或GStreamer的rtpjpegdepay就能播放。这种方式的好处是整个链路上CPU几乎不参与像素处理只做数据搬运A9完全扛得住。如果非要H264Zynq-7000一般靠PL端挂硬件H264编码IP核来实现比如Video Codec Unit相关方案但这是另一套系统工程不是简单改一条管线就能解决的。没有硬件编码资源的话x264enc软编码在720p10fps还能勉强跑再高就等不到了。所以做方案评估的时候一定要提前想清楚数据出口格式别等采集做完了才发现编码根本跟不上。这部分严格来说不算纯图像采集的范畴但如果你最终要做的是视频流产品方向就是在4.3节这个思路上延伸。5. 常见问题与排查技巧实录5.1 设备节点消失与枚举失败这是我在Zynq上碰到最多的一类问题现象五花八门/dev/video0时有时无、插拔摄像头后设备节点不出现、应用打开摄像头时open失败、READ阻塞。大概率不是代码问题是USB链路本身就不稳定。现象可能原因处理方式lsusb无设备USB口供电不足、USB HUB无源换带供电HUB或外接电源有设备但无/dev/video0内核没编UVC驱动检查CONFIG_USB_VIDEO_CLASS重编内核设备节点出现但open失败设备节点被其他进程占用fuser /dev/video0看占用的进程dmesg里大量URB失败USB带宽不足或控制器时序不稳降低分辨率/帧率换质量好的USB线插拔后节点不恢复HUB枚举时序问题重新插拔电源或调整dr_modehost尤其注意Zedboard这类老开发板的USB口供电余量普遍不足。我最早测试时摄像头直插板载USB口跑几分钟就出现uvcvideo: Failed to submit URB 0然后/dev/video0直接消失。用示波器量了一下USB口的5V在摄像头启动时会跌到4.5V左右明显带不动。后来接了一个带独立电源的USB HUB问题彻底消失。所以遇到掉线不要急着翻驱动代码先怀疑电源。5.2 设置格式失败与花屏、丢帧如果你在VIDIOC_S_FMT时收到EINVAL大概率是这个分辨率/像素格式组合摄像头根本不支持。别急着改代码先跑一遍v4l2-ctl --list-formats-ext看看支持列表。我遇到过有人想用YUYV 1080p30结果摄像头只支持YUYV 640x48030和1080p30 MJPEG代码怎么改都返回EINVAL最后就是换MJPEG解决。这个属于“一开始就该看列表而不是看代码”的教训。花屏问题要分场景看。如果是MJPEG花屏多半是USB带宽不足导致某个JPEG帧被截断应用拿到的是半个JPEG解码后自然是一半正常一半花。这时降低分辨率或帧率就解决了。如果花屏表现为画面出现大量绿色条纹检查YUYV设置是否正确特别是bytesperline和width是否匹配。如果花屏只在接VDMA后出现那基本就是缓存一致性问题需要回看4.2节的flush处理。丢帧又是另一个维度。V4L2采集丢帧的典型表现是应用打印fps比预期低但驱动层面一切正常。这时dmesg可能出现uvcvideo: Frame missed之类的提示。带宽不足时掉帧尤其频繁比如同时开多个USB摄像头或者摄像头和高速U盘共用同一个USB控制器。遇到丢帧优先降低带宽需求而不是无脑加缓冲区缓冲区加再多物理带宽不够也白搭。5.3 帧率上不去与带宽瓶颈帧率上不去很多人第一反应是代码效率低开始优化循环、改编译器优化选项但实际瓶颈往往在USB带宽。用我前面给的带宽估算表先算一下当前分辨率和格式的理论带宽是否超过40MB/s。如果超过了再怎么优化应用层都没用因为数据在物理链路上就传不过来。另一个容易被忽略的瓶颈是内存拷贝链。V4L2的read模式每取一帧都要把数据从内核空间拷到用户空间1280x72015每秒就是27MB以上的拷贝量A9单核处理这个拷贝会占用不少CPU。改成mmap模式后这个拷贝就省掉了。如果应用里还要做格式转换、颜色空间变换、保存文件尽量把它们放到独立的处理线程不要让采集线程等这些耗时操作。一个简单的多线程模型是采集线程只做DQBUF/ QBUF取到帧就丢给队列。处理线程从队列拿帧做转换、保存或网络发送。这样可以避免处理耗时导致缓冲区队列占满反过来阻塞摄像头驱动提交新URB。还有一个检查手段是看中断。cat /proc/interrupts | grep -i usbUSB中断在采集过程中应该持续增长。如果采集线程在跑但USB中断增长很慢说明USB控制器带宽已经饱和设备在等待传输间隙。如果USB中断涨得飞快但应用fps还是低那就是应用处理速度跟不上瓶颈在CPU。这种判断逻辑能帮你很快锁定优化方向不用瞎猜。最后再分享一个实际心得在Zynq-7000上调USB摄像头优先级一定是先把v4l2-ctl和裸V4L2代码跑通确认采集链路稳定再考虑VDMA和RTSP这些上层应用。我第一次做的时候上来就搭GStreamer加VDMA结果所有问题全堆在一起摄像头掉线、显示花屏、编码卡顿排查了整整两天才发现根因只是USB供电不足。后来老老实实从裸代码开始一步步测反而一天就完成了整个链路的验证。另外MJPEG在这块平台上是真的香带宽省出来的余量能让你少踩很多坑。如果后续要做RTSP流也建议参考4.3节的方式先跑通内网预览再根据实际帧率和CPU占用决定是否需要硬件编码。
返回列表