Qt网络编程之UDP通信(二)视频传输

    技术2022-07-13  85

    上一篇博客中介绍了Qt下UDP传输流程与文本数据的传输过程,根据UDP的特点而言(与TCP对比),它注重的是数据传输的效率而不是可靠性,因此在很多对于实时性要求较高而可靠性要求不是那么高的场景下,如视频、语音传输,多采用UDP传输方式,故本文介绍一下基于UDP的视频传输过程。

    一、基本流程

    我们知道,UDP通信无需建立连接,每一个应用程序端绑定IP和端口之后便可以实现读写操作,我么们只要在发送端获取视频信息,将获取到的每一帧画面转换为可传输的数据发送到接收端,然后接收端再将数据转换为图片,放到窗口显示,即可实现视频的传输。

    在之前的博客中我们知道,使用OpenCV打开视频设备获取的原始图像是Mat格式的,如果要显示在窗口中则要将其转换为QImage格式;而UDP的读写操作对象都是QByteArray类型,所以将QImage转为QByteArray便可发送;接收端收到数据后再将QByteArray转为QImage即可显示画面,所以总体流程大致如下:

    发送端:

    Created with Raphaël 2.2.0 开始 创建通信套接字对 象并绑定IP与端口 连接定时器槽函数 是否点击开始? 获取目的IP和端口号 开启视频设备 开启定时器 进入定时器槽函数: { 读取Mat数据 Mat转为QImage并显示 QImage转为QByteArray并发送 } 是否点击停止? 关闭定时器 关闭视频设备 关闭窗口 结束 yes no yes no

    接收端的处理相对简单一些,它只需要绑定好自己的IP和端口,当发送端向该端口发送数据时便会触发它(接收端)的readyRead()信号,所以只需在该信号对应的槽函数中将QByteArray转为QImage并显示即可。

    Created with Raphaël 2.2.0 开始 创建通信套接字对 象并绑定IP和端口号 连接readyRead()信号 与数据接收槽函数 是否有数据 发送过来? 读取数据 转为QImage 窗口显示 yes no

    可以看到流程图并不复杂,其中关键的地方在于数据类型的转换,主要有以下几个地方:

    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),所以源码编译可能不会直接通过,请修改外部库的地址后再尝试编译。

    Processed: 0.018, SQL: 9