上一篇博客中介绍了Qt下UDP传输流程与文本数据的传输过程,根据UDP的特点而言(与TCP对比),它注重的是数据传输的效率而不是可靠性,因此在很多对于实时性要求较高而可靠性要求不是那么高的场景下,如视频、语音传输,多采用UDP传输方式,故本文介绍一下基于UDP的视频传输过程。
一、基本流程
我们知道,UDP通信无需建立连接,每一个应用程序端绑定IP和端口之后便可以实现读写操作,我么们只要在发送端获取视频信息,将获取到的每一帧画面转换为可传输的数据发送到接收端,然后接收端再将数据转换为图片,放到窗口显示,即可实现视频的传输。
在之前的博客中我们知道,使用OpenCV打开视频设备获取的原始图像是Mat
格式的,如果要显示在窗口中则要将其转换为QImage
格式;而UDP的读写操作对象都是QByteArray
类型,所以将QImage
转为QByteArray
便可发送;接收端收到数据后再将QByteArray
转为QImage
即可显示画面,所以总体流程大致如下:
发送端:
接收端的处理相对简单一些,它只需要绑定好自己的IP和端口,当发送端向该端口发送数据时便会触发它(接收端)的readyRead()
信号,所以只需在该信号对应的槽函数中将QByteArray
转为QImage
并显示即可。
可以看到流程图并不复杂,其中关键的地方在于数据类型的转换,主要有以下几个地方:
- OpenCV读取的
Mat
类型图像其默认颜色通道为BGR,首先要使用cvtColor(InputArray src, OutputArray dst, int code);
将其转为我们常用的RGB通道,否则视频的颜色会看起来怪怪的。函数中src
表示源数据,dst
表示输出数据,code
转换方式,在本例中按照如下方式转换:Mat frame; camera.read(frame); cvtColor(frame,frame,CV_BGR2RGB);
Mat
转QImage
:QImage image((unsigned char *)(frame.data),frame.cols,frame.rows,QImage::Format_RGB888);
QImage
转QByteArray
,这个要借助QBuffer
类:QByteArray byte; QBuffer buff(&byte); buff.open(QIODevice::WriteOnly); image.save(&buff,"JPEG");
- 为了方便传输,可以选择数据压缩:
参数QByteArray ss = qCompress(byte,5);
5
代表压缩比。
在接收端要按照从后往前的顺序,先解压缩,再将QByteArray
转为QImage
:
- 解压缩
QByteArray buff; receiver.readDatagram(buff.data(),buff.size(),&adrr,&port); buff = qUncompress(buff);
QByteArray
转为QImage
:QBuffer buffer(&buff); QImageReader reader(&buffer,"JPEG"); QImage image = reader.read();
二、项目创建
发送端:
widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <opencv/cv.hpp>
#include <QUdpSocket>
#include <QTimer>
#include <QBuffer>
using namespace cv;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_open_clicked();
void on_pushButton_close_clicked();
void VideoSend();
private:
Ui::Widget *ui;
QUdpSocket *udpSocket;
VideoCapture camera;
QTimer fps_timer;
};
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
udpSocket = new QUdpSocket(this);
udpSocket->bind(QHostAddress::Any,8888);
connect(&fps_timer,SIGNAL(timeout()),this,SLOT(VideoSend()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_open_clicked()
{
camera.open(0);
fps_timer.start(33);
}
void Widget::on_pushButton_close_clicked()
{
fps_timer.stop();
camera.release();
this->close();
}
void Widget::VideoSend()
{
QHostAddress dstip = (QHostAddress)(ui->lineEdit_ip->text());
quint16 dstport = ui->lineEdit_port->text().toInt();
Mat frame;
camera.read(frame);
cvtColor(frame,frame,CV_BGR2RGB);
QImage image((unsigned char *)(frame.data),frame.cols,frame.rows,QImage::Format_RGB888);
ui->label_video->setPixmap(QPixmap::fromImage(image));
ui->label_video->resize(image.width(),image.height());
QByteArray byte;
QBuffer buff(&byte);
buff.open(QIODevice::WriteOnly);
image.save(&buff,"JPEG");
QByteArray ss = qCompress(byte,5);
udpSocket->writeDatagram(ss,dstip,dstport);
}
接收端:
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void video_receive_show();
private:
Ui::MainWindow *ui;
QUdpSocket receiver;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QHostAddress>
#include<QPixmap>
#include<QImageReader>
#include<QBuffer>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
receiver.bind(QHostAddress::Any,6666);
connect(&receiver,SIGNAL(readyRead()),this,SLOT(video_receive_show()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::video_receive_show()
{
quint64 size = receiver.pendingDatagramSize();
QByteArray buff;
buff.resize(size);
QHostAddress adrr ;
quint16 port;
receiver.readDatagram(buff.data(),buff.size(),&adrr,&port);
buff = qUncompress(buff);
QBuffer buffer(&buff);
QImageReader reader(&buffer,"JPEG");//可读入磁盘文件、设备文件中的图像、以及其他图像数据如pixmap和image,相比较更加专业。
//buffer属于设备文件一类,
QImage image = reader.read();//read()方法用来读取设备图像,也可读取视频,读取成功返回QImage*,否则返回NULL
ui->label->setPixmap(QPixmap::fromImage(image));
ui->label->resize(image.width(),image.height());
}
三、测试效果:
测试时还是发送端在主机上,接收端在虚拟机中的Linux系统中,通过主机打开视频设备将画面传送到虚拟机中。至此,整个项目完成。
项目源码:https://gitee.com/Mr-Yslf/BlogResources.git
由于项目中使用了外部库(OpenCV),所以源码编译可能不会直接通过,请修改外部库的地址后再尝试编译。