【UDP+v4L2】UDP编程+v4L2框架实现Linux下视频实时传输

编程入门 行业动态 更新时间:2024-10-09 11:23:50

【UDP+v4L2】UDP编程+v4L2框架实现Linux下视频<a href=https://www.elefans.com/category/jswz/34/1771422.html style=实时传输"/>

【UDP+v4L2】UDP编程+v4L2框架实现Linux下视频实时传输

【UDP+v4L2】实现Linux下视频实时传输

  • 硬件准备工作
    • Linux系统烧写
  • 软件准备工作
    • Ubuntu 下开启SSH 服务
    • Ubuntu安装交叉编译器
    • windows与开发板互ping
  • 功能实现

  • 开发板是正点原子ALPHA I.MX开发板,用户名:root;无密码

硬件准备工作

Linux系统烧写

  1. 开发板启动方式设置为usb启动
  2. 将开发板USB-OTG接口与电脑连接,烧写系统。
  • 正点原子Linux系统位于mfgtool文件夹中,双击即可烧写

  • 判断烧写哪一个系统:

    • emmc会比nand小一些
    • 正点原子的emmc的核心板只有512M的

  1. 开发板与电脑串口通信
  • 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 架构的。

  1. 将 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	#解压
  1. 修改环境变量
sudo vi /etc/profile
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin	#末尾加入
  1. 安装相关库
sudo apt-get install lsb-core lib32stdc++6
  1. 验证
arm-linux-gnueabihf-gcc -v

  arm 表示这是编译 arm 架构代码的编译器
  linux 表示运行在 linux 环境下
  gnueabihf 表示嵌入式二进制接口
  gcc 表示是 gcc 工具

windows与开发板互ping

  我是通过网线连接的,没找到网卡驱动;设置的开发板ip是临时ip,每次复位或重新上电需要更新设置ip
  主要就是要保证需要互ping的设备在同一个网段下,具体参考:

  1. 设置网线的 IP 为 192.168.10.200
  2. 设置开发板的动态 IP
ifconfig eth0 up
ifconfig eth0 192.168.10.50
ifconfig
  1. 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. 内存映射
主要步骤:

  1. 使用VIDIOC_QUERYBUF命令查询每个缓冲区的物理地址和长度。
  2. 使用mmap()函数将查询得到的物理地址映射到用户空间,获取缓冲区的虚拟地址。
  3. 将缓冲区的虚拟地址存储在用户空间指针数组中,以便后续读取和处理视频数据。
  4. 使用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下视频实时传输

本文发布于:2024-02-06 08:33:08,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1747546.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:实时   框架   视频   UDP   Linux

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!