实时传输"/>
【UDP+v4L2】UDP编程+v4L2框架实现Linux下视频实时传输
【UDP+v4L2】实现Linux下视频实时传输
- 硬件准备工作
- Linux系统烧写
- 软件准备工作
- Ubuntu 下开启SSH 服务
- Ubuntu安装交叉编译器
- windows与开发板互ping
- 功能实现
- 开发板是正点原子ALPHA I.MX开发板,用户名:root;无密码
硬件准备工作
Linux系统烧写
- 开发板启动方式设置为usb启动
- 将开发板USB-OTG接口与电脑连接,烧写系统。
-
正点原子Linux系统位于mfgtool文件夹中,双击即可烧写
-
判断烧写哪一个系统:
- emmc会比nand小一些
- 正点原子的emmc的核心板只有512M的
- 开发板与电脑串口通信
-
usb接口换成USB-TTL连接,将开发板启动方式设置成EMMC启动
-
串口通信:我用的mobaxterm软件,设置如下
-
连接成功的话,是这样的
-
如果出现乱码
软件准备工作
Ubuntu 下开启SSH 服务
用于后面在mobaxterm上通过SSH连接Ubuntu,从而实现在windows上命令行操作Ubuntu,免得切来切去麻烦
- 安装 SSH (Secure Shell)服务
sudo apt-get install openssh-server
Ubuntu安装交叉编译器
Ubuntu自带一个编译器,就是日常用的gcc命令,但是这个编译器是针对 X86 架构的。
- 将 Linux/tool 下的gcc交叉编译器复制到arm目录进行解压
sudo mkdir /usr/local/arm
sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f #移动文件
sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz #解压
- 修改环境变量
sudo vi /etc/profile
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin #末尾加入
- 安装相关库
sudo apt-get install lsb-core lib32stdc++6
- 验证
arm-linux-gnueabihf-gcc -v
arm 表示这是编译 arm 架构代码的编译器
linux 表示运行在 linux 环境下
gnueabihf 表示嵌入式二进制接口
gcc 表示是 gcc 工具
windows与开发板互ping
我是通过网线连接的,没找到网卡驱动;设置的开发板ip是临时ip,每次复位或重新上电需要更新设置ip
主要就是要保证需要互ping的设备在同一个网段下,具体参考:
- 设置网线的 IP 为 192.168.10.200
- 设置开发板的动态 IP
ifconfig eth0 up
ifconfig eth0 192.168.10.50
ifconfig
- ping成功
功能实现
在Linux设备上实现读取摄像头数据的方法有:v4L2框架(Video for Linux 2)、OpenCV库、ffmpeg库、GStreamer框架。opencv操作起来是最简便的,但是效果要差一些
1. 查询设备属性
在嵌入式设备和Ubuntu下都能查询,我是在Ubuntu下用命令查询,简单快捷,而且结果清楚
v4l2-ctl --all -d /dev/video0
v4l2-ctl --list-formats-ext -d /dev/video0
查询结果我分成了几个图介绍:
描述了一些设备信息:
- Driver name : uvcvideo 表示驱动是uvcvideo,v4l2支持这种驱动
- Bus info : usb-0000:03:00.0-2 USB总线信息。表示摄像头连接在第三个PCIe控制器(PCIe bus 0000:03)的第二个接口(设备号2)上
- Capabilities : 0x84A0000。 转化为二进制后是 10000100101000000000000000,代表摄像头支持下面这些格式:
- Video Capture表示设备支持视频捕获;
- Metadata Capture表示设备支持元数据捕获;
- Streaming表示设备支持数据流传输;
- Extended Pix Format表示设备支持扩展像素格式;
- Device Capabilities表示设备有其他能力,如支持缩放、旋转等功能。
下面的主要就是介绍支持的视频格式什么的:
- Priority: 表示摄像头支持的格式中当前正在使用的优先级
- Video input: 表示视频输入的编号,比如此处的 0 表示第一个视频输入
- Format Video Capture: 表示当前摄像头视频采集的格式信息
- Width/Height:摄像头视频采集的分辨率,此处为 1920x1080。
- Pixel Format:摄像头视频采集的像素格式,此处为 MJPG。
- Field:采集的视频场模式,此处为 None,即没有场的概念,是逐行扫描的。
- Bytes per Line:一行视频数据所占用的字节数,为 0 表示由设备自动计算。
- Size Image:一帧图像的大小,即整张图片占用的字节数。
- Colorspace:视频采集的颜色空间,此处为 sRGB。
- Transfer Function:视频采集的转换函数,此处为默认值,即映射到 sRGB 颜色空间。
- YCbCr/HSV Encoding:视频的编码方式,此处为默认值,即ITU-R 601。
- Quantization:视频采集的量化方式,此处为默认值,即全范围量化。
- Flags:标志
- Crop Capability Video Capture:摄像头的裁剪能力,其中:
- Bounds:指摄像头的可截取区域,其左上角坐标为(0, 0),宽度为1920,高度为1080。
- Default:指默认的截取区域,与Bounds相同。
- Pixel Aspect:指像素的长宽比,默认为1:1,即正方形。
- Selection:可以选择的裁剪参数,其中
- crop_default表示默认参数,crop_bounds表示边界参数,都与上面的Default、Bounds相同
- Streaming Parameters Video Capture:视频流采集参数
- Capabilities:采集能力,此处是timeperframe,表示该摄像头支持定时帧率。
- Frames per second:每秒的帧率,这里是30fps。
- Read buffers:缓冲区的数量,这里是0,表示不需要缓冲区,数据可以直接从驱动中读取。
- brightness、contrast后面这些就是设置图像的亮度、饱和度这些,一般不用修改
包括详细的分辨率帧率也能查到:
我的这款摄像头支持两种格式:YUYV 、MJPG
2. 打开摄像头设备
使用v4l2_open()函数打开摄像头设备,该函数会返回一个文件描述符。
O_RDWR就是给权限:可读可写
通过ls /dev/vi*
查看摄像头挂载在哪里(插拔)
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {perror("Failed to open video device");return -1;
}
3. 根据之前获取到的信息,设置视频格式
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获设备流
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
//fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {perror("Failed to set video format");return -1;
}
4. 申请内核空间
因为前面查询到Read buffers是0,所以说明我的这款摄像头不支持基于缓冲区的数据传输方式,所以使用基于内存映射的数据传输方式
- 内存映射方式:在内核空间中为缓冲区分配一段内存,然后将这段内存映射到用户空间中,这样在用户空间中就可以直接读取或写入这个缓冲区中的数据
struct v4l2_requestbuffers req = {0};
memset(&req, 0, sizeof(req));
req.count = 4; // 内存映射方式不需要请求缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //指定缓冲区的类型为视频捕获类型
req.memory = V4L2_MEMORY_MMAP; //指定缓冲区的内存类型为 mmap 类型
ret = ioctl(fd, VIDIOC_REQBUFS, &req);if (ret == -1) {perror("Failed to request buffers");return -1;}
5. 内存映射
主要步骤:
- 使用VIDIOC_QUERYBUF命令查询每个缓冲区的物理地址和长度。
- 使用mmap()函数将查询得到的物理地址映射到用户空间,获取缓冲区的虚拟地址。
- 将缓冲区的虚拟地址存储在用户空间指针数组中,以便后续读取和处理视频数据。
- 使用VIDIOC_QBUF命令将已经映射到用户空间的缓冲区放回内核空间,以便在下一次采集时重新使用。
//将视频缓冲区映射到用户空间中unsigned char *mptr[4];//保存映射后用户空间的首地址unsigned int size[4]; //用于保存映射长度以便后期释放struct v4l2_buffer mapbuffer;//初始化type和indexmapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //初始化查询类型为视频捕获for(int i = 0; i <4;i++) //每次迭代都会查询一个缓冲区并将其映射到用户空间{ mapbuffer.index = i; //指定要查询的缓冲区的索引ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer); //从内核空间中查询一个空间作映射if (ret < 0){perror("查询内核空间失败");}//映射到用户空间 mptr[i]保存映射后的地址mptr[i] = (unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);if (mptr[i] == MAP_FAILED) {perror("mmap failed");exit(1);}size[i] = mapbuffer.length; //保存映射长度用于后期释放//将缓冲区放回内核空间ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer); if (ret < 0){perror("放回失败");}}
6. 初始化 UDP socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("Failed to create socket");return EXIT_FAILURE;}
7. 启动视频采集
VIDIOC_STREAMON 命令向驱动程序发送请求,让它开始向用户空间传输视频数据
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_STREAMON, &type);if(ret < 0) {perror("Failed to start streaming");return -1;}
8. 开始循环采集和发送视频数据
int num_frames_to_capture = 100; // 采集100帧数据,可以根据需求修改
int frame_count = 0; // 已经采集到的帧数
while (frame_count < num_frames_to_capture) {// 从摄像头读取一帧数据struct v4l2_buffer buf = {0};buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {perror("Failed to dequeue buffer");return -1;}// 将数据发送到指定地址if (sendto(sockfd, buffers[buf.index].start, buf.bytesused, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == -1) {perror("Failed to send frame");return -1;}// 将缓冲区重新放回队列中if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {perror("Failed to requeue buffer");return -1;}frame_count++;
}
9. 停止视频采集
close(sockfd);
if (munmap(buffers[0].start, buffer_size) == -1) {perror("Failed to stop streaming");return -1;}
close(fd);
更多推荐
【UDP+v4L2】UDP编程+v4L2框架实现Linux下视频实时传输
发布评论