Ubuntu上Qt+Tcp网络编程+V4L2之视频监控

时间:2018-05-30 22:45:40   收藏:0   阅读:1418

  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.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;
}
server.cpp

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.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;
}
mycamera.cpp

(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.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.cpp

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目录下。

 

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!