Ubuntu上Qt+Tcp网络编程+V4L2之视频监控
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。
在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。
应用程序通过V4L2进行视频采集的原理
V4L2支持内存映射方式(mmap)和直接读取方式(read)来采集数据,前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集,本文重点讨论内存映射方式的视频采集。
应用程序通过V4L2接口采集视频数据分为五个步骤:
首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
第五,停止视频采集。
具体的程序实现流程可以参考下面的流程图:
其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示。
V4L2 编程
1. 定义
V4L2(Video ForLinux Two) 是内核提供给应用程序访问音、视频驱动的统一接口。
2. 工作流程:
打开设备-> 检查和设置设备属性->设置帧格式-> 设置一种输入输出方法(缓冲区管理)-> 循环获取数据-> 关闭设备。
3. 设备的打开和关闭:
#include<fcntl.h>
int open(constchar *device_name, int flags);
#include <unistd.h>
int close(intfd);
int fd=open(“/dev/video0”,O_RDWR);// 打开设备 close(fd);// 关闭设备
注意:V4L2 的相关定义包含在头文件<linux/videodev2.h>中.
4. 查询设备属性: VIDIOC_QUERYCAP
相关函数:
int ioctl(int fd, int request, struct v4l2_capability *argp);
相关结构体:
struct v4l2_capability { __u8 driver[16]; // 驱动名字 __u8 card[32]; // 设备名字 __u8 bus_info[32]; // 设备在系统中的位置 __u32 version; // 驱动版本号 __u32 capabilities; // 设备支持的操作 __u32 reserved[4]; // 保留字段 }; capabilities 常用值: V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
例:显示设备信息
struct v4l2_capability cap; ioctl(fd,VIDIOC_QUERYCAP,&cap); printf("DriverName:%s/nCard Name:%s/nBus info:%s/nDriverVersion:%u.%u.%u/n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF,(cap.version>>8)&0XFF,cap.version&OXFF);
5. 帧格式:
(1)VIDIOC_ENUM_FMT// 显示所有支持的格式
相关函数:
int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
相关结构体:
struct v4l2_fmtdesc { __u32 index; // 要查询的格式序号,应用程序设置 enum v4l2_buf_type type; // 帧类型,应用程序设置 __u32 flags; // 是否为压缩格式 __u8 description[32]; // 格式名称 __u32 pixelformat; // 格式 __u32 reserved[4]; // 保留 };
例:显示所有支持的格式
struct v4l2_fmtdesc fmtdesc; fmtdesc.index=0; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("Supportformat:/n"); while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1) { printf("/t%d.%s/n",fmtdesc.index+1,fmtdesc.description); fmtdesc.index++; }
// 查看或设置当前格式
VIDIOC_G_FMT,VIDIOC_S_FMT
// 检查是否支持某种格式
VIDIOC_TRY_FMT
相关函数:
int ioctl(int fd, int request, struct v4l2_format *argp);
相关结构体:
struct v4l2_format { enumv4l2_buf_type type;// 帧类型,应用程序设置 union fmt { struct v4l2_pix_format pix;// 视频设备使用 struct v4l2_window win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; __u8 raw_data[200]; }; };
struct v4l2_pix_format { __u32 width; // 帧宽,单位像素 __u32 height; // 帧高,单位像素 __u32 pixelformat; // 帧格式 enum v4l2_fieldfield; __u32 bytesperline; __u32 sizeimage; enum v4l2_colorspace colorspace; __u32 priv; };
例:显示当前帧的相关信息
struct v4l2_format fmt; fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd,VIDIOC_G_FMT,&fmt); printf(“Currentdata format information: /n/twidth:%d/n/theight:%d/n”,fmt.fmt.width,fmt.fmt.height); struct v4l2_fmtdesc fmtdesc; fmtdesc.index=0; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1) { if(fmtdesc.pixelformat& fmt.fmt.pixelformat) { printf(“/tformat:%s/n”,fmtdesc.description); break; } fmtdesc.index++; }
例:检查是否支持某种帧格式
struct v4l2_format fmt; fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32; if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1) if(errno==EINVAL) printf(“notsupport format RGB32!/n”);
6. 图像的缩放:VIDIOC_CROPCAP
相关函数:
int ioctl(int fd,int request, struct v4l2_crop *argp);
相关结构体:
struct v4l2_crop { enum v4l2_buf_type type;// 应用程序设置 struct v4l2_rectbounds;// 最大边界 struct v4l2_rectdefrect;// 默认值 struct v4l2_fract pixelaspect; };
例:设置缩放 VIDIOC_G_CROP,VIDIOC_S_CROP
int ioctl(intfd, int request, struct v4l2_crop *argp); int ioctl(intfd, int request, const struct v4l2_crop *argp); struct v4l2_crop { enum v4l2_buf_type type;// 应用程序设置 struct v4l2_rectc; }
7.申请和管理缓冲区
(1)向设备申请缓冲区 VIDIOC_REQBUFS
相关函数:
int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
相关结构体:
structv 4l2_requestbuffers { __u32 count; // 缓冲区内缓冲帧的数目 enum v4l2_buf_type type; // 缓冲帧数据格式 enum v4l2_memorymemory; // 区别是内存映射还是用户指针方式 __u32 reserved[2]; }; enum v4l2_memoy{V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR}; //count,type,memory都要应用程序设置
例:申请一个拥有四个缓冲帧的缓冲区
struct v4l2_requestbuffers req; req.count=4; req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory=V4L2_MEMORY_MMAP; ioctl(fd,VIDIOC_REQBUFS,&req);
(2)获取缓冲帧的地址,长度 VIDIOC_QUERYBUF
int ioctl(int fd, int request, struct v4l2_buffer *argp);
相关结构体:
struct v4l2_buffer { __u32 index; //buffer 序号 enum v4l2_buf_type type; //buffer 类型 __u32 byteused; //buffer 中已使用的字节数 __u32 flags; // 区分是MMAP 还是USERPTR enum v4l2_fieldfield; struct timevaltimestamp;// 获取第一个字节时的系统时间 struct v4l2_timecode timecode; __u32 sequence;// 队列中的序号 enum v4l2_memorymemory;//IO 方式,被应用程序设置 union m { __u32 offset;// 缓冲帧地址,只对MMAP 有效 unsigned longuserptr; }; __u32 length;// 缓冲帧长度 __u32 input; __u32 reserved; };
(3) MMAP
相关函数:
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void*addr, size_t length);// 断开映射
定义一个结构体来映射每个缓冲帧:
struct buffer { void* start; unsigned intlength; }*buffers;
例:将四个已申请到的缓冲帧映射到应用程序,用buffers 指针记录
buffers =(buffer*)calloc (req.count, sizeof (*buffers)); if (!buffers) { fprintf (stderr,"Out of memory/n"); exit(EXIT_FAILURE); } for (unsignedint n_buffers = 0; n_buffers < req.count; ++n_buffers) { struct v4l2_bufferbuf; memset(&buf,0,sizeof(buf)); buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory =V4L2_MEMORY_MMAP; buf.index =n_buffers; // 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小 if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) exit(-1); buffers[n_buffers].length= buf.length; // 映射内存 buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset); if (MAP_FAILED== buffers[n_buffers].start) exit(-1); }
8. 缓冲区处理好之后,就可以开始获取数据了 VIDIOC_STREAMON,VIDIOC_STREAMOFF,VIDIOC_QBUF,VIDIOC_DQBUF
相关函数:
int ioctl(int fd, int request, const int *argp); int ioctl(int fd, int request, struct v4l2_buffer *argp);
例:把四个缓冲帧放入队列,并启动数据流
unsigned int i; enum v4l2_buf_type type; // 将缓冲帧放入队列 for (i = 0; i< 4; ++i) { struct v4l2_buffer buf; buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory =V4L2_MEMORY_MMAP; buf.index = i; ioctl (fd,VIDIOC_QBUF, &buf); } type =V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd,VIDIOC_STREAMON, &type);
例:获取一帧并处理
struct v4l2_buffer buf; buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory =V4L2_MEMORY_MMAP; // 从缓冲区取出一个缓冲帧 ioctl (fd,VIDIOC_DQBUF, &buf); // 图像处理 process_image(buffers[buf.index].start); // 将取出的缓冲帧放回缓冲区 ioctl (fd, VIDIOC_QBUF,&buf);
利用zedboard平台进行网络监控项目实验:
>>Qt提供了QtNetwork模块提供网络编程,该模块提供了QTcpSocket类和QTcpServer类
QTcpSocket类继承自QAbstractSocket类,一般用于创建TCP连接和数据流连接;QTcpServer类继承自QObject类,一般用于编写服务器端程序。
>>zedboard作为服务器端,连上usb摄像头
>>pc端作为客户端,与zedboard通过网线连接
在任何数据传输之前:
(1)客户端
必须建立一个TCP连接到远程主机和端口上,客户端可以条用QTcpSocket::connectToHost()函数连接到指定主机和端口。
一旦连接被建立,peer(对使用TCP协议连接在一起的主机的通称)的IP地址和端口可分别使用QTcpSocket::peerAddress()和QTcpSocket::peerPort()来获取。
在任何时间,peer都可以关闭连接,这样数据传输就会立即停止。QTcpSocket依靠时间循环来检测到来的数据,并且自动刷新输出的数据。
可以使用QTcpSocket::write()来写入数据,使用QTcpSocket::read()来读出数据。QTcpSocket代表了两个独立的数据流:一个用来读取,另一个用来写入。
因为QTcpSocket继承自QIODevice,所以可以使用QTextStream和QDataStream类对象来存储数据。从一个QTcpSocket中读取数据前必须先调用QTcpSocket::bytesAvailable()函数,以确保已经有足够的数据可用。
在客户端,可以重载timerEvent()函数进行连接请求,一旦有数据到来就会发出readyRead()信号,这时可以关联此信号到自定义槽进行数据的接收。
(2)服务器
可以通过QTcpServer类来处理接收到的TCP连接。
首先调用QTcpServer::listen()来监听所有的连接,每当一个新的客户端连接到服务器端就会发射信号 QTcpServer::newConnection()。
可以关联这个信号到槽函数,在槽中,调用QTcpServer::nextPendingConnection()来接收这个连接,然后使用该函数返回的QTcpSocket对象与客户端通信,进行数据的发送和接收。
程序设计。(注意:要给所有工程.pro文件添加如下代码)
Qt += network
(1)服务器端:控制摄像头进行视频采集;箭筒客户端的连接并将采集到视频一帧帧地传给客户端。
新建Qt Gui工程,项目名称Server,类名Server,基类QWidget。完成后,为工程再新添加一个Mycamera类。
server.h
#ifndef SERVER_H #define SERVER_H #include <QWidget> #include "mycamera.h" class QTcpServer; class QTcpSocket; //类前置声明 namespace Ui { class Server; } class Server : public QWidget { Q_OBJECT public: explicit Server(QWidget *parent = 0); ~Server(); private: Ui::Server *ui; QTcpServer *tcpServer; //监听套接字对象指针 QTcpSocket *tcpConnection; //连接套接字指针 Mycamera *camera; //Mycameta类指针 unsigned char * pointer_yuv_buffer; //yuv格式的帧数据缓冲区指针 unsigned int len; //帧数据长度 unsigned char rgb_buffer[IMG_HEIGTH*IMG_WIDTH*3]; //rgb格式的帧数据缓冲区指针 bool state; //帧数据获取状态指示标记 int convert_yuv_to_rgb_buffer(); //将帧数据转换为rgb格式并保存至缓存区 private slots: void sendImage(); //私有槽,从摄像头获取帧数据进行格式转换后发送 }; #endif // SERVER_H
server.cpp
#include "server.h" #include "ui_server.h" #include <QtNetwork> Server::Server(QWidget *parent) : QWidget(parent), ui(new Ui::Server) { ui->setupUi(this); //启动摄像头设备 camera = new Mycamera(tr("/dev/video0")); //启动监听套接字 tcpServer = new QTcpServer(this); //设置可以监听的IP范围和端口地址,将端口设置为8888 if(!tcpServer->listen(QHostAddress::Any,8888)) { qDebug() << tcpServer->errorString(); close(); } //将sendImage槽与newConnection()信号关联 connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendImage())); } Server::~Server() { delete camera; camera = NULL; delete tcpServer; tcpServer = NULL; delete ui; } void Server::sendImage() { //从摄像头获取数据 this->len = 0; this->pointer_yuv_buffer = NULL; state = camera->get_frame(&this->pointer_yuv_buffer,&this->len); if(state) { convert_yuv_to_rgb_buffer(); //转化格式并保存数据 QByteArray block; //用于暂存要发送的数据 QDataStream out(&block,QIODevice::WriteOnly); //数据流 //设置数据流的版本,与客户端和服务器端所使用的版本相同 out.setVersion(QDataStream::Qt_4_8); int bytesWritten = out.writeRawData((const char *)rgb_buffer,IMG_WIDTH*IMG_HEIGTH*3); if(!(bytesWritten == IMG_HEIGTH*IMG_WIDTH*3)) { qDebug() << "error:writing data to socket !\n"; } //获取已经建立连接的套接字 QTcpSocket *clientConnection = tcpServer->nextPendingConnection(); connect(clientConnection,SIGNAL(disconnected()),clientConnection,SLOT(deleteLater())); clientConnection->write(block); clientConnection->disconnectFromHost(); //发送成功后,释放一帧数据 camera->unget_frame(); } else { qDebug() << "error:cannot get frame from cameta!\n"; } } int Server::convert_yuv_to_rgb_buffer() { unsigned char * pointer; int i,j; unsigned char y1,y2,u,v; int r1,g1,b1,r2,g2,b2; pointer = pointer_yuv_buffer; for(i=0;i<IMG_HEIGTH;i++) { for(j=0;j<(IMG_WIDTH/2);j++) { y1 = *(pointer + (i*(IMG_WIDTH/2) + j) *4); u = *(pointer + (i*(IMG_WIDTH/2) + j) *4 + 1); y2 = *(pointer + (i*(IMG_WIDTH/2) + j) *4 + 2); v = *(pointer + (i*(IMG_WIDTH/2) + j) *4 + 3); r1 = y1 + 1.042 * (v - 128); g1 = y1 - 0.34414 * (u - 128) - 0.71414 * (v - 128); b1 = y1 + 1.772 * (u - 128); r2 = y2 + 1.042 * (v - 128); g2 = y2 - 0.34414 * (u - 128) - 0.71414 * (v - 128); b2 = y2 + 1.772 * (u - 128); if(r1 > 255) r1 = 255; else if(r1 < 0) r1 = 0; if(b1 > 255) b1 = 255; else if(b1 < 0) b1 = 0; if(g1 > 255) g1 = 255; else if(g1 < 0) g1 = 0; if(r2 > 255) r2 = 255; else if(r2 < 0) r2 = 0; if(b2 > 255) b2 = 255; else if(b2 < 0) b2 = 0; if(g2 > 255) g2 = 255; else if(g2 < 0) g2 = 0; *(rgb_buffer +(i * (IMG_WIDTH/2) + j) * 6) = (unsigned char) r1; *(rgb_buffer +(i * (IMG_WIDTH/2) + j) * 6 + 1) = (unsigned char) g1; *(rgb_buffer +(i * (IMG_WIDTH/2) + j) * 6 + 2) = (unsigned char) b1; *(rgb_buffer +(i * (IMG_WIDTH/2) + j) * 6 + 3) = (unsigned char) r2; *(rgb_buffer +(i * (IMG_WIDTH/2) + j) * 6 + 4) = (unsigned char) g2; *(rgb_buffer +(i * (IMG_WIDTH/2) + j) * 6 + 5) = (unsigned char) b2; } } return 0; }
mycamera.h
#ifndef MYCAMERA_H #define MYCAMERA_H #include <QObject> //QObject类是所有Qt类的基类,也是Qt对象模型的核心 #include <stdio.h> //#include <stddef.h> //包含了size_t 的定义 size_t实际就是unsigned int,会根据系统位数不同而不同 #include <stdlib.h> #include <string.h> #include <fcntl.h> //包含open #include <unistd.h> //包含close #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/mman.h> //mmap和munmap #include <linux/videodev2.h> //v4l2必包含 #include <linux/types.h> #include <asm/types.h> #define FILE_VIDEO "/dev/video0" #define IMG_WIDTH 640 #define IMG_HEIGTH 480 class Mycamera:public QObject { public: Mycamera(QString camera_name); //构造函数 ~Mycamera(); //析构函数 int get_frame(unsigned char * * pointer_yuv_buffer,unsigned int * len); //获取帧数据 int unget_frame(); //释放帧数据 private: struct buffer { void * start; size_t length; }; //帧数据缓冲区 数据结构 QString camera_name; //摄像头头名 (一般为/dev/video0) int fd; //摄像头设备文件ID buffer * buffers; //缓冲区指针 unsigned int n_buffers; //缓存个数 int index; //缓存索引 int open_camera(); //开启摄像头 int init_camera(); //初始化摄像头 int start_capture(); //启动摄像头 int init_mmap(); //申请视频缓冲区,并mmap到用户空间 int stop_capture(); //停止视频采集 int uninit_camera(); //释放申请的帧缓冲区 int close_camera(); //关闭摄像头设备文件 }; #endif // MYCAMERA_H
mycamera.cpp
#include "mycamera.h" Mycamera::Mycamera(QString camera_name) { this->camera_name = camera_name; this->fd = -1; this->buffers = NULL; this->n_buffers = 0; this->index = -1; if(open_camera() == false) { close_camera();// 定义在头文件<unistd.h>中 } if(init_camera() == false) { close_camera(); } if(start_capture() == false) { stop_capture(); close_camera(); } } Mycamera::~Mycamera() { stop_capture(); uninit_camera(); close_camera(); } int Mycamera::open_camera() //打开摄像头设备文件 { fd = open(FILE_VIDEO,O_RDWR); if(fd == -1) { printf("Error opening V4L2 interface\n"); return false; } return true; } int Mycamera::init_camera() //初始化摄像头 { v4l2_capability cap; v4l2_fmtdesc fmtdesc; v4l2_format fmt; v4l2_streamparm setfps; //查询设备属性 if(ioctl(fd,VIDIOC_QUERYCAP,&cap) == -1) { printf("Error opening device %s: unable to query device.\n",FILE_VIDEO); return false; } else { printf("driver:\t\t%s\n",cap.driver); printf("card:\t\t%s\n",cap.card); printf("bus_info:\t\t%s\n",cap.bus_info); printf("version:\t\t%d\n",cap.version); printf("capabilities:\t\t%x\n",cap.capabilities); if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) { printf("Device %s:supports capture.\n",FILE_VIDEO); } if((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) { printf("Device %s:supports streaming\n",FILE_VIDEO); } } //列举摄像头所支持的像素格式 fmtdesc.index = 0; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("Support format:\n"); while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) != -1) { printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description); fmtdesc.index++; } //设置像素格式 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.height = IMG_HEIGTH; fmt.fmt.pix.width = IMG_WIDTH; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if(ioctl(fd,VIDIOC_S_FMT,&fmt) == -1) { return false; } //为了确保设置的格式作用到摄像头上,再通过命令VIDIOC_G_FMT将摄像头设置读取回来 if(ioctl(fd,VIDIOC_G_FMT,&fmt) == -1) { printf("fmt.type:\t\t%d\n",fmt.type); printf("pix.pixelformat:\t\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF,(fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF,(fmt.fmt.pix.pixelformat >> 24) & 0xFF); printf("pix.height:\t\t%d\n",fmt.fmt.pix.height); printf("pix.width:\t\t%d\n",fmt.fmt.pix.width); printf("pix.field:\t\t%d\n",fmt.fmt.pix.field); } //设置帧率 setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; setfps.parm.capture.timeperframe.numerator = 1; setfps.parm.capture.timeperframe.denominator = 10; if(ioctl(fd,VIDIOC_S_PARM,&setfps) == -1) { return false; } else { printf("init %s \t[OK]\n",FILE_VIDEO); } //调用init_mmap()函数来申请缓冲区,并mmap到用户空间 if(init_mmap() == false) { return false; } return true; } int Mycamera::init_mmap() //申请缓冲区,并mmap到用户空间 { //申请视频缓冲区 v4l2_requestbuffers req; req.count = 2; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if(ioctl(fd,VIDIOC_REQBUFS,&req) == -1) { printf("request for buffers error\n"); return false; } if(req.count < 2) { return false; } //分配缓存区内存 buffers = (buffer *)calloc(req.count,sizeof(*buffers)); if(!buffers) { printf("Out of memory\n"); return false; } //mmap到用户空间 for(n_buffers = 0;n_buffers < req.count;++n_buffers) { v4l2_buffer buf; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; if(ioctl(fd,VIDIOC_QUERYBUF,&buf) == -1) { printf("Query buffer error\n"); return false; } buffers[n_buffers].length = buf.length; buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,buf.m.offset); if(buffers[n_buffers].start == MAP_FAILED) { printf("buffer map error\n"); return false; } } return true; } int Mycamera::stop_capture() //停止视频采集 { v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd,VIDIOC_STREAMOFF,&type) == -1) { printf("streaming off error\n"); return false; } return true; } int Mycamera::start_capture() //开始视频采集 { unsigned int i; for(i = 0; i < n_buffers; ++i) { v4l2_buffer buf; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if(ioctl(fd,VIDIOC_QBUF,&buf) == -1) { return false; } } v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd,VIDIOC_STREAMON,&type) == -1) { printf("streaming on error\n"); return false; } return true; } int Mycamera::get_frame(unsigned char * *pointer_yuv_buffer, unsigned int *len) //获取视频帧 { v4l2_buffer queue_buf; queue_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; queue_buf.memory = V4L2_MEMORY_MMAP; if(ioctl(fd,VIDIOC_DQBUF,&queue_buf) == -1) { return false; } *pointer_yuv_buffer = (unsigned char *)buffers[queue_buf.index].start; *len = buffers[queue_buf.index].length; index = queue_buf.index; return true; } int Mycamera::unget_frame() //释放视频帧,让出缓存空间,准备新的视频帧数据 { if(index != -1) { v4l2_buffer queue_buf; queue_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; queue_buf.memory = V4L2_MEMORY_MMAP; queue_buf.index = index; if(ioctl(fd,VIDIOC_QBUF,&queue_buf) == -1) { return false; } return true; } return false; } int Mycamera::uninit_camera() //释放申请的帧缓冲区 { unsigned int i; for(i = 0; i < n_buffers; ++i) { if(munmap(buffers[i].start,buffers[i].length) == -1) { return false; } } delete buffers; return true; } int Mycamera::close_camera() //关闭摄像头设备文件 { int flag; flag = close(fd); if(flag == -1) { return false; } return true; }
(2)客户端:连接服务器,接收服务器传来的视频数据并显示。
利用定时器,每隔一定时间(本程序设计为40ms)就像服务器端发送请求,建立TCP连接,获取一帧图像并显示。
新建Qt Gui应用,名称为Client,类名Client,基类QDialog。
client.h
#ifndef CLIENT_H #define CLIENT_H #include <QDialog> #include <QAbstractSocket> #define IMG_WIDTH 640 #define IMG_HEIGTH 480 class QTcpSocket; namespace Ui { class Client; } class Client : public QDialog { Q_OBJECT public: explicit Client(QWidget *parent = 0); ~Client(); private: Ui::Client *ui; QTcpSocket *tcpSocket; unsigned char rgb_buffer[IMG_HEIGTH*IMG_WIDTH*3]; //帧数据缓存区 QImage *frame; //图像类变量 int id; //计时器ID private slots: void newConnection(); //建立新的连接 void readMessage(); //读取数据 void displayError(QAbstractSocket::SocketError); //显示错误信息 void on_pushButton_link_clicked(); //连接按钮 void on_pushButton_unlink_clicked(); //断开按钮 protected: void timerEvent(QTimerEvent *); }; #endif // CLIENT_H
client.cpp
#include "client.h" #include "ui_client.h" #include "QtNetwork" Client::Client(QWidget *parent) : QDialog(parent), ui(new Ui::Client) { ui->setupUi(this); tcpSocket = new QTcpSocket(this); connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage())); connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError))); ui->pushButton_unlink->setEnabled(false); } Client::~Client() { delete tcpSocket; tcpSocket = NULL; delete ui; } void Client::newConnection() { //取消已有连接 tcpSocket->abort(); //用输入的主机名和端口号连接服务器 tcpSocket->connectToHost(ui->LineEdit_host->text(),ui->LineEdit_port->text().toInt()); } void Client::readMessage() { QDataStream in(tcpSocket); //设置数据流版本,这里要和服务器端的版本相同 in.setVersion(QDataStream::Qt_4_8); //如果没有得到全部的数据,则返回,继续接收收据 if(tcpSocket->bytesAvailable() < IMG_HEIGTH * IMG_WIDTH * 3) return; //将接收到的数据存放到变量中 in.readRawData((char *)rgb_buffer,IMG_WIDTH * IMG_HEIGTH *3); //显示接收到的数据 frame = new QImage(rgb_buffer,IMG_WIDTH,IMG_HEIGTH,QImage::Format_RGB888); ui->label_video->setPixmap(QPixmap::fromImage(* frame,Qt::AutoColor)); } void Client::displayError(QAbstractSocket::SocketError) { qDebug() << tcpSocket->errorString(); } void Client::timerEvent(QTimerEvent *) { newConnection(); //计数器溢出时调用newconnect函数 } void Client::on_pushButton_link_clicked() { id = startTimer(150); ui->pushButton_link->setEnabled(false); ui->pushButton_unlink->setEnabled(true); } void Client::on_pushButton_unlink_clicked() { killTimer(id); ui->pushButton_link->setEnabled(true); }
client.ui
验证:
(1)主机验证:主机PC上连接USB摄像头,并先后运行服务器和客户端程序;主机名输入localhost(或查询出的ip),端口程序中设置的为8888,点击客户端的连接即可。
(2)zedboard验证:Kit选择为交叉编译版Qt,build选择Debug重新运行;将ARM版本的Server可执行程序拷贝到rootfs分区的home文件夹下;启动开发板,运行程序
cd /home
./Server -qws
注意:前提是已经将交叉编译版Qt的库文件拷贝到zedboard的rootfs分区中的 /usr/local目录下。