Linux环境下 通过V4L2读取视频+UDP发送图片文件

news2025/1/13 14:24:05

 

该图为整个项目的流程图

其中左边的流程为总流程,包括通过中断读取摄像头的帧数据,通过内存映射将内核态的数据映射到用户态,读取用户态的数据,采用循环发送图片数据。

右边是发送图片的流程图,将用户态的缓冲区的数据通过内存映射到用户态,通过内存拷贝将用户态的指针地址对应的数据保存在一个字符数组中,然后通过循环读取字符数组中的数据并通过socket 将数据发送给服务器。

项目场景:

该项目是为了在linux arm开发板上通过linux内置的v4l2接口读取摄像头数据,并通过网络协议将图片数据发送给服务器。因为之前用过opencv  ffmpeg等开源工具读取过linux三的usb摄像头,可以看到这两种接口都是封装了v4l2的接口,所以我想在底层深入了解怎么采用v4l2查看usb摄像头的数据。


问题描述

如何正确的读取用户态下的数据:


原因分析:

如何正确的读取用户态下的数据:

我在通过ioctl 接口调用读取到单帧的数据,得到的是一个指针地址,以及该指针地址后面length个字节。

eg:       ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);

得到的数据首地址在 mptr[readbuffer.index] 保存的地址中,并且数据长度为 readbuffer.length

这里我需要怎样读取 mptr[readbuffer.index]地址里面的数据


解决方案:

初始化一个指针,指向数据的首地址,通过移动指针的位置,来访问数据的不同位置:

void *ptr = mptr[readbuffer.index];

int send_num = sendto(sockfd, ptr,1024 ,0,(SA)&saddr,sizeof(saddr));

ptr += 1024;

问题描述

UDP发送数据时,会出现数据丢失的情况:

比如 char sent[614400] 总共是614400大小的一个字符数组,但是udp接收端接收到小于614400大小的数据


原因分析:

通过查阅资料,发现,是由于udp发送端发送数据速度较快,而接收端不仅要接收数据还要处理数据,导致接收端处理数据较慢,由于接收端有一个接收缓冲区,当发送端发送的数据超过了缓冲区的大小,那么缓冲区里面的数据就会被覆盖掉:

udp发送端发送数据速度较快,而接收端不仅要接收数据还要处理数据,导致接收端处理数据较慢,由于接收端有一个接收缓冲区,当发送端发送的数据超过了缓冲区的大小,那么缓冲区里面的数据就会被覆盖掉:


解决方案:

两个解决方案,接收端采用多线程读写两个线程进行处理,或者发送端发送数据后,休眠1微秒:

这里我采用的发送端在发送完数据后,休眠一微秒,然后再发送剩下的数据,如此循环往复。

以上就是我在做项目中遇到的两个问题,及解决办法。总共耗时两天。

接下来我给出整体代码。

server端

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <time.h>
#include <fcntl.h>

typedef struct sockaddr * (SA);

int main(int argc, const char *argv[])
{
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字描述符
	if(-1 == sockfd)
	{
		perror("sockfd");
		exit(1);
	}

	struct sockaddr_in ser,cli;	
	bzero(&ser,sizeof(ser));
	bzero(&cli,sizeof(cli));
	ser.sin_family =AF_INET;
	ser.sin_port = htons(50000);
	ser.sin_addr.s_addr = inet_addr("192.168.0.89");
	int ret = bind(sockfd,(SA)&ser,sizeof(ser));
	if(-1 == ret)
	{
		perror("bind");
		exit(1);
	}
	int len = sizeof(cli);
	char save_name[10];
	int save_idx = 0 ; 
	while(1)
	{
	char buf1[1024]={0};
	int rd_num1 = recvfrom(sockfd,buf1,1024,0,(SA)&cli,&len);
	if(0 == strcmp(buf1,"^_^"))
	{
	sprintf(save_name,"my_%d.jpg",save_idx++);
	}
	int fd = open(save_name,  O_CREAT |O_WRONLY|O_TRUNC,0666);
	if(-1 == fd)
	{
		perror("open error!\n");
		exit(1);
	}
	int cnt = 0 ; 
	while(1)
	{
		char buf[1024]={0};int left = 614400 - cnt;int rd_num ;
		//if(left < 1024 )
		//rd_num = recvfrom(sockfd,buf,left,0,(SA)&cli,&len);
		//else
		rd_num = recvfrom(sockfd,buf,1024,0,(SA)&cli,&len);
		//printf("%d \n ",rd_num);
		cnt += rd_num;
		//if(cnt >= 614400){printf("%d \n",cnt);break;}
		//收到发送完毕的信号,退出 
		if(0 == strcmp(buf,"^_^"))
		{
			printf("file end  %d \n",cnt);
			break;
		}
		write(fd,buf,rd_num);
		bzero(buf,sizeof(buf));
                //usleep(0.01);
	}
	close(fd);
	}
	close(sockfd);
	return 0;
}


client端

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
#include <jpeglib.h>
#include <linux/fb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr * (SA);
//如下函数read_JPEG_file为封装好的函数,需要转换格式的时候直接调用即可,不用深入理解
int read_JPEG_file (const char *jpegData, char *rgbdata, int size)
{
	struct jpeg_error_mgr jerr;
	struct jpeg_decompress_struct cinfo;
	cinfo.err = jpeg_std_error(&jerr);
	//1创建解码对象并且初始化
	jpeg_create_decompress(&cinfo);
	//2.装备解码的数据
	//jpeg_stdio_src(&cinfo, infile);
	jpeg_mem_src(&cinfo,jpegData, size);
	//3.获取jpeg图片文件的参数
	(void) jpeg_read_header(&cinfo, TRUE);
	/* Step 4: set parameters for decompression */
	//5.开始解码
	(void) jpeg_start_decompress(&cinfo);
	//6.申请存储一行数据的内存空间
	int row_stride = cinfo.output_width * cinfo.output_components;
	unsigned char *buffer = malloc(row_stride);
	int i=0;
	while (cinfo.output_scanline < cinfo.output_height) {
		//printf("****%d\n",i);
		(void) jpeg_read_scanlines(&cinfo, &buffer, 1); 
		memcpy(rgbdata+i*640*3, buffer, row_stride );
		i++;
	}
	//7.解码完成
	(void) jpeg_finish_decompress(&cinfo);
	//8.释放解码对象
	jpeg_destroy_decompress(&cinfo);
	return 1;
}

int fd_fb;                                                    
static struct fb_var_screeninfo var; /* LCD可变参数 */
static unsigned int *fb_base = NULL; /* Framebuffer映射基地址 */                    
int lcd_w = 800 ,lcd_h= 480; //定义显示器分辨率

//将数据流以3字节为单位拷贝到rgb显存中
void lcd_show_rgb(unsigned char *rgbdata, int w ,int h)
{
    unsigned int *ptr = fb_base;
    for(int i = 0; i <h; i++) {
        for(int j = 0; j < w; j++) {
                memcpy(ptr+j,rgbdata+j*3,3);//
        }
        ptr += lcd_w;
        rgbdata += w*3;
    }
}

int main(void) 
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    assert( sockfd != -1 );
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(50000);
    saddr.sin_addr.s_addr = inet_addr("192.168.0.89");
    char buff[614400] = {0};
    //虚拟机-ubuntu
   lcd_w = var.xres_virtual; //xres_virtual参数可以自动获取当前虚拟机显示器分辨率
   lcd_h = var.yres_virtual;

   //建立显示器fb内存映射 方便控制
   fb_base = (unsigned int*)mmap(NULL,lcd_w*lcd_h*4,PROT_READ|PROT_WRITE,MAP_SHARED, fd_fb,0);
   if(fb_base == NULL)
   {
      printf("can't mmap Framebuffer\n");
      goto err1;
   }

    int fd = open("/dev/video0",O_RDWR); //打开摄像头设备
    if (fd < 0)
    {
        perror("打开设备失败");
        return -1;
    }

    struct v4l2_format vfmt;

    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集
    vfmt.fmt.pix.width = 640; //设置摄像头采集参数,不可以任意设置
    vfmt.fmt.pix.height = 480;
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //设置视频采集格式为mjpg格式
    
    int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
    if (ret < 0)
    {
        perror("设置格式失败1");
    }

    //申请内核空间
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = 4; //申请4个缓冲区
    reqbuffer.memory = V4L2_MEMORY_MMAP;  //映射方式

    ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
    if (ret < 0)
    {
        perror("申请空间失败");
    }
   
    //映射
    unsigned char *mptr[4];//保存映射后用户空间的首地址
    unsigned int size[4];
    struct v4l2_buffer mapbuffer;
    //初始化type和index
    mapbuffer.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] = (unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
        size[i] = mapbuffer.length; //保存映射长度用于后期释放
        //查询后通知内核已经放回
        ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer); 
        if (ret < 0)
        {
            perror("放回失败");
        }
    }
    //开始采集
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd,VIDIOC_STREAMON,&type); 
    if (ret < 0){ perror("开启失败");}

    //定义一个空间存储解码后的rgb
    unsigned char rgbdata[640*480*3];
    int jpg_idx = 0 ; 
    char jpg_name[10];
    char * buf = malloc(1024);
    int sum = 100;//这里是用来控制读取的帧的数量 实际场景下 可以不要这个限制
    while(1)
    {
	if(sum -- < 0 ){return -1 ; }
        //strcpy(buf,"^_^");
        //sendto(sockfd,buf,strlen(buf),0,(SA)&saddr,sizeof(saddr));
        //从队列中提取一帧数据
        struct v4l2_buffer readbuffer;
        readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //每个结构体都需要设置type为这个参赛要记住
        ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer); 
    	int read_len = mapbuffer.length;
    	int send_len = 1024;
    	printf("%d \n",read_len);
        if (ret < 0)
         {
         perror("读取数据失败");
         }
	if(1)
	{
        void *ptr = mptr[readbuffer.index];
        strcpy(buf,"^_^");//发送UDP头 方便接收方确认数据开始
        sendto(sockfd,buf,strlen(buf),0,(SA)&saddr,sizeof(saddr));
	int cnt = 0 ; //用来保存已经发送的字节数量
        while(1)
        {
                if(cnt >= readbuffer.length)
                {
                        break;
                }
		cnt += 1024; 
                int send_num = sendto(sockfd, ptr,1024 ,0,(SA)&saddr,sizeof(saddr));
		ptr += 1024;
		usleep(1);
                bzero(buf,sizeof(buf));
        }
        strcpy(buf,"^_^");//发送UDP尾巴 方便接收方确认数据结束
        sendto(sockfd,buf,strlen(buf),0,(SA)&saddr,sizeof(saddr));
        usleep(5);
        }

        //通知内核使用完毕
        ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
        if(ret < 0)
            {
                perror("放回队列失败");
            }
        }
        //停止采集
        ret = ioctl(fd,VIDIOC_STREAMOFF,&type);

        //释放映射
        for(int i=0; i<4; i)munmap(mptr[i], size[i]);

        close(fd); //关闭文件
        return 0;

    err1:
       close(fd_fb);
       return -1;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444127.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

780E编译底包教程

这里写目录标题 准备1 安装开发环境准备2 拉取编译工程源码 代码编译修改或者增加用户程序说明 准备 1 安装开发环境准备 需要用户自行安装好Xmake\vscode\git 环境教程传送门 2 拉取编译工程源码 注意, 需要两个库 主库 https://gitee.com/openLuat/LuatOS bsp库 https://…

LAMP架构中的网站搭建

前言&#xff1a;本次操作依赖于LAMP的环境已经配置完全&#xff0c;网站也是依赖于开发人员现有的网站包框架&#xff0c;实施在LAMP已搭建好的环境进行安装部署 1. 对mysql进行操作 ——创建数据库&#xff0c;并进行授权 1.创建数据库&#xff0c;并进行授权 mysql -u roo…

Redis6学习

Redis6 1. NoSQL数据库简介 1.1 技术发展 技术的分类 1、解决功能性的问题&#xff1a;Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN。 2、解决扩展性的问题&#xff1a;Struts、Spring、SpringMVC、Hibernate、Mybatis。 3、解决性能的问题&#xff1a;NoSQL、Jav…

国产什么牌子的蓝牙耳机音质好?国产适合听音乐的蓝牙耳机推荐

现如今&#xff0c;蓝牙耳机的性能越来越多&#xff0c;一款蓝牙耳机不可能将各种性能做到极致。大家在选择蓝牙耳机时&#xff0c;无外乎从佩戴、音质、降噪、延迟等因素出发&#xff0c;那么&#xff0c;国产什么牌子的蓝牙耳机音质好&#xff1f;根据这个问题&#xff0c;我…

Docker更换国内镜像源

什么是Docker Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全…

【三十天精通Vue 3】第十二天 Vue 3 的函数式组件详解(过滤器已废弃)

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue3 中的函数式组件1.1 函数式组件的概念和特点1.2 函数…

DolphinDB 计算节点使用指南

导读 为了提升 DolphinDB 在高并发读写场景下的性能与稳定性&#xff0c;DolphinDB 在架构上引入了计算节点 &#xff08;compute node&#xff09; 。计算节点接管了数据节点的部分职能&#xff0c;负责响应客户端的请求并返回结果。在架构层面&#xff0c;将集群的计算与存储…

QT - 布局方式

Qt里面的头文件和类名是一致的&#xff0c;知道头文件就知道类名&#xff0c;反之亦然 Qt头文件是没有.h的&#xff0c;基本都是以大写的Q开头 后续的代码编写都在widget.h和widget.cpp 一. widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget>/*QT系统自己使…

机器学习(六):基于高斯贝叶斯对面部皮肤进行预测分析

基于高斯贝叶斯对面部皮肤进行预测分析 作者&#xff1a;i阿极 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏&#x1f4…

ROS--机器人小车仿真rviz

URDF练习 需求描述: 创建一个四轮圆柱状机器人模型&#xff0c;机器人参数如下,底盘为圆柱状&#xff0c;半径 10cm&#xff0c;高 8cm&#xff0c;四轮由两个驱动轮和两个万向支撑轮组成&#xff0c;两个驱动轮半径为 3.25cm,轮胎宽度1.5cm&#xff0c;两个万向轮为球状&…

企业业财数字化建设-财务管理领域的产品设计

数字经济时代&#xff0c;企业数字化建设成为发展的必经之路&#xff0c;更多的企业搭建全渠道的营销&#xff0c;交易&#xff0c;履约和售后体系触达和服务用户&#xff0c;特别是面向小B和C端用户&#xff0c;交易变得更灵活多变。使得资源管控&#xff0c;稳定保守为底层基…

C语言的缺陷/错误处理问题探讨

最近遇到一个问题&#xff0c;先看看如下代码&#xff1a; uint8_t Bcd2Dec01(uint8_t bcd) {uint8_t one (bcd & 0x0F);uint8_t ten (bcd & 0xF0) >> 4;if ((one > 9) || (ten > 9)){printf("请输入合法的BCD码&#xff01;");return 0;}retu…

《C++内存管理》

本文主要介绍C内存管理的知识&#xff0c;主要包括new和delete&#xff0c;其实很简单&#xff0c;类比我们的C语言的内存管理malloc/free&#xff0c;就是在堆上申请内存的 小知识点&#xff1a; C构造对象的顺序&#xff1a;先构造全局&#xff0c;再构造局部静态对象&#x…

一些解决方案

文件异步下载方案 1 set QueryBussessType manually different type --> different resolving code、wherecondition 2. frontend request with the type 3. get excelHeader --> groovyUtil load from db 4. getData from db with pagination 5. saveData in an excel 6…

【测试面试】你要的宝典,软件接口测试面试题大全(总结)--附答案

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、接口测试主要目…

将war包发布到容器中的tomcat

文章目录 将war包直接发布到容器中使用数据卷将war包持久化到docker的宿主机(CentOS7) 将war包直接发布到容器中 1、将windows中的文件通过xftp程序传到centOS7中 2、创建容器&#xff0c;通过docker中的命令将web.jar复制到tomcat容器中 # 查看docker中的镜像 ~]$ docker imag…

0.8秒捕捉,速度超乎想象,小米和WPS用户太激动,office用户已用

只需0.8秒就能捕捉 距离4月18日小米13ultra发布会时间还剩一天。这几天雷军的微博已经把小米13ultra的亮点已经做了很多铺垫宣传。 除了系统和硬件之外&#xff0c;就是这次小米13ultra最大的“杀手锏”的就是“徕卡相机”。连宣传文案都改成了&#xff1a;一个伟大的瞬间&…

【UE】玩家位置存档

在上一篇博客中&#xff08;【UE】将存档的值显示在控件蓝图上&#xff09;我们介绍了如何将存档的值显示在控件蓝图上&#xff0c;本篇博客要介绍的是如何将玩家位置进行存档。 效果 可以看到重新进入游戏时&#xff0c;角色在存档点出现&#xff0c;而不是玩家出生点 步骤 …

XMU 算法分析与设计第三次上机题解

文章目录 一、BFS试炼之微博转发二、DFS试炼之不同路径数三、并查集试炼之合并集合并查集的介绍 四、堆排序堆排序的介绍 五、厦大GPA&#xff08;分组背包&#xff09;分组背包介绍 六、消防安全指挥问题&#xff08;最短路Floyd&#xff09;七、铺设光纤问题(最小生成树Prim)…

干货满满~如何解决跨域!!

1. 为什么会存在跨域 首先要知道&#xff0c;在浏览器/app中使用异步请求(ajax)发送到服务器时&#xff0c;会出现跨域问题。若是服务与服务之间通信是没有跨域这一说的 2. 浏览器为什么要设置跨域的存在&#xff1f; 为了防止恶意网页可以获取其他网站的本地数据&#xff0…