From abf8e99f957bfe22ed2a295e32b875c84342a337 Mon Sep 17 00:00:00 2001 From: zty199 <46324746+zty199@users.noreply.github.com> Date: Sun, 21 Mar 2021 20:12:40 +0800 Subject: [PATCH] Support Multithread Use subthreads to process data, avoid lags in GUI. --- README.md | 26 ++- README_zh.md | 12 +- src/UDPMulticast/UDPMulticast.pro | 4 + src/UDPMulticast/audiopacksender.cpp | 98 ++++++++++ src/UDPMulticast/audiopacksender.h | 41 +++++ src/UDPMulticast/mainwindow.cpp | 245 ++++++++----------------- src/UDPMulticast/mainwindow.h | 16 +- src/UDPMulticast/screenpen.cpp | 13 +- src/UDPMulticast/screenpen.h | 9 +- src/UDPMulticast/videoframesender.cpp | 110 +++++++++++ src/UDPMulticast/videoframesender.h | 40 ++++ src/UDPMulticast/videosurface.h | 7 +- src/UDPReceiver/UDPReceiver.pro | 8 +- src/UDPReceiver/audiopackreceiver.cpp | 91 +++++++++ src/UDPReceiver/audiopackreceiver.h | 46 +++++ src/UDPReceiver/mainwindow.cpp | 196 ++++++-------------- src/UDPReceiver/mainwindow.h | 23 ++- src/UDPReceiver/videoframereceiver.cpp | 105 +++++++++++ src/UDPReceiver/videoframereceiver.h | 48 +++++ 19 files changed, 787 insertions(+), 351 deletions(-) create mode 100644 src/UDPMulticast/audiopacksender.cpp create mode 100644 src/UDPMulticast/audiopacksender.h create mode 100644 src/UDPMulticast/videoframesender.cpp create mode 100644 src/UDPMulticast/videoframesender.h create mode 100644 src/UDPReceiver/audiopackreceiver.cpp create mode 100644 src/UDPReceiver/audiopackreceiver.h create mode 100644 src/UDPReceiver/videoframereceiver.cpp create mode 100644 src/UDPReceiver/videoframereceiver.h diff --git a/README.md b/README.md index eccd35d..38c7eb5 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,30 @@ A simple network classroom Demo based on UDP Multicast, including basic multimedia transmission. Only works in LAN. ## Realized Features -* Support basic multimedia transmission based on UDP Multicast , including cam, screen share, mic, stereo mixer, etc. +* Support basic multimedia transmission based on **UDP Multicast** , including cam, screen share, mic, stereo mixer, etc. -* Support 1080p video transmission. (Temporarily limit to 720p because of HIGH CPU and Network consumption) +* Support 1080p video transmission. (Temporarily limit to 720p because of HIGH **CPU** and **Network** consumption) * Support dynamic switch between different devices (cam / audio IO). * Support cam resolution adjustment. -* Support volume adjustment. (Only current audio stream affected) +* Support volume adjustment. (Only **current audio stream** affected, global device volume is controlled by system) -* Support rescan available devices. +* Support available devices rescan. -*Support white board and screen mark when sharing screen. +* Support white board and screen mark when sharing screen. + +* Support multithread data processing to avoid lags in GUI. ## Existing Issues -* When using wireless cards, UDP Multicast is extremely Unreliable, both sending and receiving ends meet serious packet loss. Ethernet cards recommended. +* When using wireless cards, UDP Multicast is extremely **Unreliable**, both sending and receiving ends meet serious packet loss. **Ethernet cards** recommended. -* Video frames captured from cam and screen are directly encoded into JPEG by CPU(?), source resolution higher than 720p can cause serious lags on GUI. +* Video frames captured from cam and screen are directly encoded into **JPEG** by CPU(?), source resolution higher than **720p** can cause serious lags on GUI. * Cam / Audio IO devices' names are Inaccurate on Linux (based on device drivers), most of audio IO devices shown in combobox don't work (not physically connected to any devices, just ports reserved). -* Available devices list can't refresh in real time. Choosing one that is removed may cause crash, and new devices connected won't show up. +* Available devices list can't refresh in real time. Choosing one that is removed may cause crash, and new devices connected won't show up unless corresponding function button is clicked. ## Future Plan File Transfer, Text Message Transmission, Student Sign-in, etc. @@ -47,5 +49,13 @@ File Transfer, Text Message Transmission, Student Sign-in, etc. * [海天鹰屏幕笔](https://github.com/sonichy/HTYScreenPen) +* [QT多线程的使用](https://www.cnblogs.com/coolcpp/p/qt-thread.html) + +* [Qt QRunnable的使用](https://blog.csdn.net/qq_43711348/article/details/103983857) + +* [QT编程问题小结(编译、多线程、UDP Socket等)](https://blog.csdn.net/rabbitjerry/article/details/70947807) + +* [QT基于UDP通信的多线程编程问题](https://blog.csdn.net/kamereon/article/details/49582617) + ## Clarification This project is my graduation design for undergraduates. Only for study and communication. diff --git a/README_zh.md b/README_zh.md index b6b0d5c..00d5f2c 100644 --- a/README_zh.md +++ b/README_zh.md @@ -18,6 +18,8 @@ * 支持屏幕共享时使用白板和屏幕标注。 +* 支持多线程收发数据,避免单线程造成 GUI 卡顿。 + ## 目前存在的问题 * 使用无线网卡时,UDP 组播极为 **不稳定** ,收发数据包均存在严重丢包问题。推荐使用 **有线网卡** 。 @@ -25,7 +27,7 @@ * Linux 下获取的音频/视频设备名称 **不明确** (由设备驱动决定),声音设备列表中大多数设备不可用。(多数是声卡硬件原始预留的端口,未连接任何物理设备) -* 可用设备列表不能实时刷新,使用设备过程中移除设备可能造成程序崩溃,新插入设备不会自动识别。 +* 可用设备列表不能实时刷新,使用设备过程中移除设备可能造成程序崩溃,新插入设备不会自动识别,需要关闭对应功能后才能刷新设备列表。 ## 后续计划实现的功能 文件传输,文字通信,学生签到等。 @@ -47,5 +49,13 @@ * [海天鹰屏幕笔](https://github.com/sonichy/HTYScreenPen) +* [QT多线程的使用](https://www.cnblogs.com/coolcpp/p/qt-thread.html) + +* [Qt QRunnable的使用](https://blog.csdn.net/qq_43711348/article/details/103983857) + +* [QT编程问题小结(编译、多线程、UDP Socket等)](https://blog.csdn.net/rabbitjerry/article/details/70947807) + +* [QT基于UDP通信的多线程编程问题](https://blog.csdn.net/kamereon/article/details/49582617) + ## 声明 该项目为大学本科毕业设计,仅供学习交流使用。 diff --git a/src/UDPMulticast/UDPMulticast.pro b/src/UDPMulticast/UDPMulticast.pro index 54f5b29..8033b84 100644 --- a/src/UDPMulticast/UDPMulticast.pro +++ b/src/UDPMulticast/UDPMulticast.pro @@ -25,14 +25,18 @@ DEFINES += QT_DEPRECATED_WARNINGS CONFIG += c++11 SOURCES += \ + audiopacksender.cpp \ main.cpp \ mainwindow.cpp \ screenpen.cpp \ + videoframesender.cpp \ videosurface.cpp HEADERS += \ + audiopacksender.h \ mainwindow.h \ screenpen.h \ + videoframesender.h \ videosurface.h FORMS += \ diff --git a/src/UDPMulticast/audiopacksender.cpp b/src/UDPMulticast/audiopacksender.cpp new file mode 100644 index 0000000..9d63a63 --- /dev/null +++ b/src/UDPMulticast/audiopacksender.cpp @@ -0,0 +1,98 @@ +#include "audiopacksender.h" + +AudioPackSender::AudioPackSender(char *ap) +{ + this->ap = new AudioPack; + memcpy(this->ap, ap, sizeof(*(this->ap))); + setAutoDelete(true); +} + +AudioPackSender::~AudioPackSender() +{ + delete audio_socket; + delete ap; +} + +void AudioPackSender::run() +{ + groupAddress = QHostAddress("239.0.0.1"); + audio_port = 8889; + + audio_socket = new QUdpSocket; + audio_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1); // 设置套接字属性 + audio_socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 1024 * 64); // 缓冲区最大存储 4个 数据包(单个 16K) + + qint32 res; + qint32 dataLength = ap->len; + uchar *dataBuffer = (uchar *)ap->data; + + qint32 packetNum = dataLength / UDP_MAX_SIZE; + qint32 lastPaketSize = dataLength % UDP_MAX_SIZE; + qint32 currentPacketIndex = 0; + if(lastPaketSize != 0) + { + packetNum++; + } + + PackageHeader packageHead; + packageHead.uTransPackageHdrSize = sizeof(packageHead); + packageHead.uDataSize = dataLength; + packageHead.uDataPackageNum = packetNum; + + uchar frameBuffer[sizeof(packageHead) + UDP_MAX_SIZE]; + memset(frameBuffer, 0, sizeof(packageHead) + UDP_MAX_SIZE); + + packageHead.uTransPackageSize = packageHead.uTransPackageHdrSize + UDP_MAX_SIZE; + packageHead.uDataPackageCurrIndex = 0; + packageHead.uDataPackageOffset = 0; + memcpy(frameBuffer, &packageHead, packageHead.uTransPackageHdrSize); + res = audio_socket->writeDatagram( + (const char *)frameBuffer, packageHead.uTransPackageSize, + groupAddress, audio_port); + if(res < 0) + { + qDebug() << "audio_socket: Audio Pack Size Send Failed!"; + } + + while(currentPacketIndex < packetNum) + { + if(currentPacketIndex < (packetNum - 1)) + { + packageHead.uTransPackageSize = packageHead.uTransPackageHdrSize + UDP_MAX_SIZE; + packageHead.uDataPackageCurrIndex = currentPacketIndex + 1; + packageHead.uDataPackageOffset = currentPacketIndex * UDP_MAX_SIZE; + memcpy(frameBuffer, &packageHead, packageHead.uTransPackageHdrSize); + memcpy(frameBuffer + packageHead.uTransPackageHdrSize, dataBuffer + packageHead.uDataPackageOffset, UDP_MAX_SIZE); + + res = audio_socket->writeDatagram( + (const char *)frameBuffer, packageHead.uTransPackageSize, + groupAddress, audio_port); + + if(res < 0) + { + qDebug() << "audio_socket: Packet Send Failed!"; + } + + currentPacketIndex++; + } + else + { + packageHead.uTransPackageSize = packageHead.uTransPackageHdrSize + (dataLength - currentPacketIndex * UDP_MAX_SIZE); + packageHead.uDataPackageCurrIndex = currentPacketIndex + 1; + packageHead.uDataPackageOffset = currentPacketIndex * UDP_MAX_SIZE; + memcpy(frameBuffer, &packageHead, packageHead.uTransPackageHdrSize); + memcpy(frameBuffer + packageHead.uTransPackageHdrSize, dataBuffer + packageHead.uDataPackageOffset, dataLength - currentPacketIndex * UDP_MAX_SIZE); + + res = audio_socket->writeDatagram( + (const char *)frameBuffer, packageHead.uTransPackageSize, + groupAddress, audio_port); + + if(res < 0) + { + qDebug() << "video_socket: Packet Send Failed!"; + } + + currentPacketIndex++; + } + } +} diff --git a/src/UDPMulticast/audiopacksender.h b/src/UDPMulticast/audiopacksender.h new file mode 100644 index 0000000..7bf68fe --- /dev/null +++ b/src/UDPMulticast/audiopacksender.h @@ -0,0 +1,41 @@ +#ifndef AUDIOPACKSENDER_H +#define AUDIOPACKSENDER_H + +#include +#include + +#define UDP_MAX_SIZE 1200 // UDP 数据包最大长度 * MTU = 1500,故数据包大小 1500 - 20(IP头)- 8(UDP头) + +class AudioPackSender : public QRunnable +{ +public: + explicit AudioPackSender(char *ap); + ~AudioPackSender(); + +protected: + void run() override; + +private: + QUdpSocket *audio_socket; + QHostAddress groupAddress; + quint16 audio_port; + + struct PackageHeader + { + quint32 uTransPackageHdrSize; + quint32 uTransPackageSize; + quint32 uDataSize; + quint32 uDataPackageNum; + quint32 uDataPackageCurrIndex; + quint32 uDataPackageOffset; + }; + + struct AudioPack + { + char data[1024 * 16]; // 单个音频数据包大小设为 16K,音质 44K/128Kbps(?) + int len; + } *ap; + +}; + +#endif // AUDIOPACKSENDER_H diff --git a/src/UDPMulticast/mainwindow.cpp b/src/UDPMulticast/mainwindow.cpp index 847a393..06a8f59 100644 --- a/src/UDPMulticast/mainwindow.cpp +++ b/src/UDPMulticast/mainwindow.cpp @@ -20,10 +20,10 @@ MainWindow::MainWindow(QWidget *parent) : availableDevices(QAudioDeviceInfo::availableDevices(QAudio::AudioInput)), flag_audio(false), video_socket(new QUdpSocket(this)), - audio_socket(new QUdpSocket(this)), groupAddress("239.0.0.1"), video_port(8888), - audio_port(8889) + video_threadPool(new QThreadPool(this)), + audio_threadPool(new QThreadPool(this)) { ui->setupUi(this); @@ -46,11 +46,26 @@ MainWindow::MainWindow(QWidget *parent) : m_timer->stop(); qDebug() << "Initialization Finished!"; + + video_threadPool->setMaxThreadCount(2); + audio_threadPool->setMaxThreadCount(1); } MainWindow::~MainWindow() { video_socket->writeDatagram(QString("Stop").toUtf8().data(), QString("Stop").toUtf8().size(), groupAddress, video_port); + video_threadPool->clear(); + video_threadPool->waitForDone(); + delete video_threadPool; + + audio_threadPool->clear(); + audio_threadPool->waitForDone(); + delete audio_threadPool; + + flag_camera = true; + emit ui->btn_camera->clicked(); + delete m_camera; + delete ui; } @@ -74,7 +89,6 @@ void MainWindow::initUdpConnections() // video_socket->bind(QHostAddress::AnyIPv4, video_port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress); // 绑定广播地址端口(发送端可以不绑定) // video_socket->setMulticastInterface(intf); // 设置组播网卡 video_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1); // 设置套接字属性 - audio_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1); // 设置套接字属性 // video_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0); // 禁止本机接收 } @@ -102,35 +116,19 @@ void MainWindow::initInputDevice() void MainWindow::initUI() { - m_screenPen->hide(); // 启动时不显示屏幕画笔 + m_screenPen->hide(); // 启动时不显示屏幕画笔 ui->btn_screenPen->setDisabled(true); // 禁用屏幕画笔按钮 ui->cb_resolution->setDisabled(true); // 禁用摄像头分辨率下拉框(摄像头设备启动后才可以使用) - // 初始化主界面设备列表(可用设备列表为空则禁用相关选项) - if(availableCameras.isEmpty()) - { - ui->btn_camera->setDisabled(true); - ui->cb_camera->setDisabled(true); - } - else + // 初始化主界面设备列表 + foreach(const QCameraInfo &camera, availableCameras) { - foreach(const QCameraInfo &camera, availableCameras) - { - ui->cb_camera->addItem(camera.description(), availableCameras.indexOf(camera)); - } + ui->cb_camera->addItem(camera.description(), availableCameras.indexOf(camera)); } - if(availableDevices.isEmpty()) + foreach(const QAudioDeviceInfo &device, availableDevices) { - ui->btn_audio->setDisabled(true); - ui->cb_device->setDisabled(true); - } - else - { - foreach(const QAudioDeviceInfo &device, availableDevices) - { - ui->cb_device->addItem(device.deviceName(), availableDevices.indexOf(device)); - } + ui->cb_device->addItem(device.deviceName(), availableDevices.indexOf(device)); } } @@ -149,9 +147,10 @@ void MainWindow::initCamera() foreach(const QCameraViewfinderSettings &viewSet, viewSets) { // qDebug() << "max rate = " << viewSet.maximumFrameRate() << " min rate = " << viewSet.minimumFrameRate() << " resolution = " << viewSet.resolution() << " Format = " << viewSet.pixelFormat() << "" << viewSet.pixelAspectRatio(); - if(viewSet.pixelFormat() == QVideoFrame::Format_Jpeg) + QString resolution = QString::number(viewSet.resolution().width()) + "x" + QString::number(viewSet.resolution().height()); + if(ui->cb_resolution->findText(resolution) < 0) { - ui->cb_resolution->addItem(QString::number(viewSet.resolution().width()) + "x" + QString::number(viewSet.resolution().height()), viewSets.indexOf(viewSet)); + ui->cb_resolution->addItem(resolution, viewSets.indexOf(viewSet)); } } } @@ -172,7 +171,7 @@ void MainWindow::on_btn_camera_clicked() // 摄像头功能与屏幕共享功能互斥(暂时) flag_screen = true; - on_btn_screen_clicked(); + emit ui->btn_screen->clicked(); } else { @@ -201,18 +200,22 @@ void MainWindow::on_btn_camera_clicked() } int index = -1; - QCameraInfo curCam = availableCameras.at(ui->cb_camera->currentIndex()); + QCameraInfo curCam; + if(ui->cb_camera->currentIndex() > index) + { + curCam = availableCameras.at(ui->cb_camera->currentIndex()); + } ui->cb_camera->disconnect(); ui->cb_camera->clear(); availableCameras = QCameraInfo::availableCameras(); if(availableCameras.isEmpty()) { - ui->btn_camera->setDisabled(true); - ui->cb_camera->setDisabled(true); + connect(ui->cb_camera, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_camera_currentIndexChanged(int))); } else { + // 记录当前设备选项 for(int i = 0; i < availableCameras.size(); i++) { ui->cb_camera->addItem(availableCameras.at(i).description(), i); @@ -221,15 +224,16 @@ void MainWindow::on_btn_camera_clicked() index = i; } } - } - if(index < 0) - { - index = 0; + if(index < 0) + { + index = 0; + } + + connect(ui->cb_camera, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_camera_currentIndexChanged(int))); + ui->cb_camera->setCurrentIndex(index); + emit ui->cb_camera->currentIndexChanged(index); } - connect(ui->cb_camera, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_camera_currentIndexChanged(int))); - ui->cb_camera->setCurrentIndex(index); - emit ui->cb_camera->currentIndexChanged(index); } } @@ -269,13 +273,11 @@ void MainWindow::on_cb_resolution_currentIndexChanged(int index) void MainWindow::on_videoFrameChanged(QVideoFrame frame) { - // static QImage oldImage; - QVideoFrame tmp(frame); tmp.map(QAbstractVideoBuffer::ReadOnly); if(!tmp.isValid()) { - qDebug() << "Empty Frame!"; + // qDebug() << "Empty Frame!"; return; } @@ -288,6 +290,8 @@ void MainWindow::on_videoFrameChanged(QVideoFrame frame) // 如果图像未变化则不发送(占 CPU,需要其他方式处理) /* + static QImage oldImage; + if(oldImage == image) { return; @@ -303,49 +307,7 @@ void MainWindow::on_videoFrameChanged(QVideoFrame frame) #endif image.scaled(image.size().boundedTo(QSize(1280, 720)), Qt::KeepAspectRatio, Qt::FastTransformation); // 分辨率高于 720p 则压缩 - // 本地窗口预览 - ui->videoViewer->setPixmap(QPixmap::fromImage(image).scaled(ui->videoViewer->size(), Qt::KeepAspectRatio, Qt::FastTransformation)); - - // 暂存帧图像 - QByteArray byteArray; - QBuffer buffer(&byteArray); - - buffer.open(QIODevice::ReadWrite); - image.save(&buffer, "JPEG"); - - qint64 res, // UDP 结果 - sentBytes = 0, // 已发送字节数 - len = UDP_MAX_SIZE; // 本次发送字节数 - - video_socket->writeDatagram(QString("size=%1").arg(byteArray.size()).toUtf8(), - QString("size=%1").arg(byteArray.size()).toUtf8().size(), - groupAddress, - video_port); - video_socket->waitForBytesWritten(); // 等待数据发送完成 - - // qDebug() << "totalBytes = " << byteArray.size(); - - while(sentBytes < byteArray.size()) - { - if(sentBytes + len > byteArray.size()) - { - len = byteArray.size() - sentBytes; - } - else - { - len = UDP_MAX_SIZE; - } - - res = video_socket->writeDatagram(byteArray.data() + sentBytes, len, groupAddress, video_port); - if(res < 0) - { - qDebug() << "video_socket: Write Datagram Failed!"; - break; - } - video_socket->waitForBytesWritten(); - - sentBytes += len; - } + video_threadPool->start(new VideoFrameSender(image, this)); } void MainWindow::on_btn_screen_clicked() @@ -353,11 +315,11 @@ void MainWindow::on_btn_screen_clicked() if(!flag_screen) { ui->btn_screenPen->setEnabled(true); // 启用屏幕画笔按钮 - m_timer->start(40); // 每隔 40ms 触发(等同于 25Hz 刷新率) + m_timer->start(15); // 每隔 15ms 触发(约等于 60Hz 刷新率) qDebug() << "Screen Share Started!"; flag_screen = true; flag_camera = true; - on_btn_camera_clicked(); + emit ui->btn_camera->clicked(); } else { @@ -372,11 +334,11 @@ void MainWindow::on_btn_screen_clicked() void MainWindow::on_timeOut() { - // static QImage oldImage; - QImage image = m_screen->grabWindow(0).toImage(); // 截取桌面图像 /* + static QImage oldImage; + if(oldImage == image) { return; @@ -384,48 +346,9 @@ void MainWindow::on_timeOut() oldImage = image; */ - image.scaled(image.size().boundedTo(QSize(1280, 720)), Qt::KeepAspectRatio, Qt::SmoothTransformation); + image.scaled(image.size().boundedTo(QSize(1280, 720)), Qt::KeepAspectRatio, Qt::FastTransformation); - // 本地窗口预览 - ui->videoViewer->setPixmap(QPixmap::fromImage(image).scaled(ui->videoViewer->size(), Qt::KeepAspectRatio, Qt::FastTransformation)); - - QByteArray byteArray; - QBuffer buffer(&byteArray); - - buffer.open(QIODevice::ReadWrite); - image.save(&buffer, "JPEG"); - - qint64 res, sentBytes = 0, len = UDP_MAX_SIZE; - - video_socket->writeDatagram(QString("size=%1").arg(byteArray.size()).toUtf8(), - QString("size=%1").arg(byteArray.size()).toUtf8().size(), - groupAddress, - video_port); - video_socket->waitForBytesWritten(); - - // qDebug() << "totalBytes = " << byteArray.size(); - - while(sentBytes < byteArray.size()) - { - if(sentBytes + len > byteArray.size()) - { - len = byteArray.size() - sentBytes; - } - else - { - len = UDP_MAX_SIZE; - } - - res = video_socket->writeDatagram(byteArray.data() + sentBytes, len, groupAddress, video_port); - if(res < 0) - { - qDebug() << "video_socket: Write Datagram Failed!"; - break; - } - video_socket->waitForBytesWritten(); - - sentBytes += len; - } + video_threadPool->start(new VideoFrameSender(image, this)); } void MainWindow::on_btn_screenPen_clicked() @@ -434,6 +357,12 @@ void MainWindow::on_btn_screenPen_clicked() m_screenPen->showFullScreen(); } +void MainWindow::on_videoFrameSent(QImage image) +{ + // 本地窗口预览(由子线程回传图像) + ui->videoViewer->setPixmap(QPixmap::fromImage(image).scaled(ui->videoViewer->size(), Qt::KeepAspectRatio, Qt::FastTransformation)); +} + void MainWindow::on_btn_audio_clicked() { if(!flag_audio) @@ -459,16 +388,18 @@ void MainWindow::on_btn_audio_clicked() } int index = -1; - QAudioDeviceInfo curInput = availableDevices.at(ui->cb_device->currentIndex()); - qDebug() << curInput.deviceName(); + QAudioDeviceInfo curInput; + if(ui->cb_device->currentIndex() > index) + { + curInput = availableDevices.at(ui->cb_device->currentIndex()); + } ui->cb_device->disconnect(); ui->cb_device->clear(); availableDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); if(availableDevices.isEmpty()) { - ui->btn_audio->setDisabled(true); - ui->cb_device->setDisabled(true); + connect(ui->cb_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_device_currentIndexChanged(int))); } else { @@ -480,15 +411,16 @@ void MainWindow::on_btn_audio_clicked() index = i; } } - } - if(index < 0) - { - index = 0; + if(index < 0) + { + index = 0; + } + + connect(ui->cb_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_device_currentIndexChanged(int))); + ui->cb_device->setCurrentIndex(index); + emit ui->cb_device->currentIndexChanged(index); } - connect(ui->cb_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_device_currentIndexChanged(int))); - ui->cb_device->setCurrentIndex(index); - emit ui->cb_device->currentIndexChanged(index); } } @@ -537,39 +469,14 @@ void MainWindow::on_volumeChanged(int value) void MainWindow::on_deviceReadyRead() { // 初始化音频数据包结构 - videoPack vp; - memset(&vp, 0, sizeof(vp)); + static AudioPack ap; + memset(&ap, 0, sizeof(ap)); // 读入音频输入数据 - vp.lens = static_cast(m_audioDevice->read(vp.data, sizeof(vp.data))); + ap.len = static_cast(m_audioDevice->read(ap.data, sizeof(ap.data))); // qDebug() << QString(vp.data).toUtf8(); - qint64 res, sentBytes = 0, len = UDP_MAX_SIZE; - - audio_socket->writeDatagram(QString("Begin").toUtf8(), QString("Begin").toUtf8().size(), groupAddress, audio_port); - audio_socket->waitForBytesWritten(); - - while(sentBytes < static_cast(sizeof(vp))) - { - if(sentBytes + len > static_cast(sizeof(vp))) - { - len = static_cast(sizeof(vp)) - sentBytes; - } - else - { - len = UDP_MAX_SIZE; - } - - res = audio_socket->writeDatagram(reinterpret_cast(&vp) + sentBytes, len, groupAddress, audio_port); - if(res < 0) - { - qDebug() << "audio_socket: Write Datagram Failed!"; - break; - } - audio_socket->waitForBytesWritten(); - - sentBytes += len; - } + audio_threadPool->start(new AudioPackSender((char *)&ap)); } void MainWindow::on_mouseMove() diff --git a/src/UDPMulticast/mainwindow.h b/src/UDPMulticast/mainwindow.h index 4a10afa..68a6107 100644 --- a/src/UDPMulticast/mainwindow.h +++ b/src/UDPMulticast/mainwindow.h @@ -10,13 +10,13 @@ #include #include #include -#include #include "videosurface.h" - #include "screenpen.h" -#define UDP_MAX_SIZE 1472 // UDP 数据包最大长度 * MTU = 1500,故数据包大小 1500 - 20(IP头)- 8(UDP头) +#include "videoframesender.h" +#include "audiopacksender.h" +#include #define SAMPLE_RATE 44100 // 采样频率 #define SAMPLE_SIZE 16 // 采样位数 @@ -55,15 +55,16 @@ class MainWindow : public QMainWindow bool flag_audio; QUdpSocket *video_socket; - QUdpSocket *audio_socket; QHostAddress groupAddress; quint16 video_port; - quint16 audio_port; - struct videoPack + QThreadPool *video_threadPool; + QThreadPool *audio_threadPool; + + struct AudioPack { char data[1024 * 16]; // 单个音频数据包大小设为 16K,音质 44K/128Kbps(?) - int lens; + int len; }; void initUdpConnections(); @@ -80,6 +81,7 @@ private slots: void on_btn_screen_clicked(); void on_timeOut(); void on_btn_screenPen_clicked(); + Q_INVOKABLE void on_videoFrameSent(QImage); // Q_INVOKABLE 用来修饰成员函数,使其能够被 QMetaObject 调用(从 QRunnable 子线程中调用) void on_btn_audio_clicked(); void on_cb_device_currentIndexChanged(int index); void on_slider_volume_valueChanged(int value); diff --git a/src/UDPMulticast/screenpen.cpp b/src/UDPMulticast/screenpen.cpp index ba073a2..ee337ae 100644 --- a/src/UDPMulticast/screenpen.cpp +++ b/src/UDPMulticast/screenpen.cpp @@ -50,6 +50,11 @@ void ScreenPen::mousePressEvent(QMouseEvent *e) } } +void ScreenPen::mouseReleaseEvent(QMouseEvent *) +{ + image = image_temp; +} + void ScreenPen::mouseMoveEvent(QMouseEvent *e) { if(e->buttons() & Qt::LeftButton) @@ -60,7 +65,7 @@ void ScreenPen::mouseMoveEvent(QMouseEvent *e) } endPnt = e->pos(); - if (draw_type == BRUSH_DRAW) + if(draw_type == BRUSH_DRAW) { image = image_temp; } @@ -72,12 +77,6 @@ void ScreenPen::mouseMoveEvent(QMouseEvent *e) } } -void ScreenPen::mouseReleaseEvent(QMouseEvent *e) -{ - Q_UNUSED(e) - image = image_temp; -} - void ScreenPen::paintEvent(QPaintEvent *) { QPainter painter(this); diff --git a/src/UDPMulticast/screenpen.h b/src/UDPMulticast/screenpen.h index 73dd2b4..685cb37 100644 --- a/src/UDPMulticast/screenpen.h +++ b/src/UDPMulticast/screenpen.h @@ -18,7 +18,10 @@ class ScreenPen : public QWidget ~ScreenPen(); protected: - void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *) override; private: Ui::ScreenPen *ui; @@ -43,10 +46,6 @@ class ScreenPen : public QWidget QColor bg_color; // 背景颜色(白板或透明) bool flag_whiteboard; // 白板模式 - void mousePressEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - void drawCursor(); void draw(QImage &image); void clear(); diff --git a/src/UDPMulticast/videoframesender.cpp b/src/UDPMulticast/videoframesender.cpp new file mode 100644 index 0000000..6b90e78 --- /dev/null +++ b/src/UDPMulticast/videoframesender.cpp @@ -0,0 +1,110 @@ +#include "videoframesender.h" + +#include + +VideoFrameSender::VideoFrameSender(QImage image, QObject *parent) : + parent(parent) +{ + this->image = new QImage(image); + setAutoDelete(true); +} + +VideoFrameSender::~VideoFrameSender() +{ + delete video_socket; + delete image; +} + +void VideoFrameSender::run() +{ + QMetaObject::invokeMethod(parent, "on_videoFrameSent", Q_ARG(QImage, *image)); + + groupAddress = QHostAddress("239.0.0.1"); + video_port = 8888; + + // 初始化 video_socket + video_socket = new QUdpSocket; + video_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1); // 设置套接字属性 + video_socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 1920 * 1080 * 16); // 缓冲区最大存储 4 张 1080p 位图 + + // 暂存帧图像 + QByteArray byteArray; + QBuffer buffer(&byteArray); + buffer.open(QIODevice::ReadWrite); + image->save(&buffer, "JPEG"); + + qint32 res; + quint32 dataLength = buffer.data().size(); + uchar *dataBuffer = (uchar *)buffer.data().data(); + + qint32 packetNum = dataLength / UDP_MAX_SIZE; + qint32 lastPaketSize = dataLength % UDP_MAX_SIZE; + qint32 currentPacketIndex = 0; + if(lastPaketSize != 0) + { + packetNum++; + } + + PackageHeader packageHead; + packageHead.uTransPackageHdrSize = sizeof(packageHead); + packageHead.uDataSize = dataLength; + packageHead.uDataPackageNum = packetNum; + + uchar frameBuffer[sizeof(packageHead) + UDP_MAX_SIZE]; + memset(frameBuffer, 0, sizeof(packageHead) + UDP_MAX_SIZE); + + // 发送空数据包(仅包含帧数据包大小) + packageHead.uTransPackageSize = packageHead.uTransPackageHdrSize + UDP_MAX_SIZE; + packageHead.uDataPackageCurrIndex = 0; + packageHead.uDataPackageOffset = 0; + memcpy(frameBuffer, &packageHead, packageHead.uTransPackageHdrSize); + res = video_socket->writeDatagram( + (const char *)frameBuffer, packageHead.uTransPackageSize, + groupAddress, video_port); + if(res < 0) + { + qDebug() << "video_socket: Video Frame Size Send Failed!"; + } + + while(currentPacketIndex < packetNum) + { + if(currentPacketIndex < (packetNum - 1)) + { + packageHead.uTransPackageSize = packageHead.uTransPackageHdrSize + UDP_MAX_SIZE; + packageHead.uDataPackageCurrIndex = currentPacketIndex + 1; + packageHead.uDataPackageOffset = currentPacketIndex * UDP_MAX_SIZE; + memcpy(frameBuffer, &packageHead, packageHead.uTransPackageHdrSize); + memcpy(frameBuffer + packageHead.uTransPackageHdrSize, dataBuffer + packageHead.uDataPackageOffset, UDP_MAX_SIZE); + + res = video_socket->writeDatagram( + (const char *)frameBuffer, packageHead.uTransPackageSize, + groupAddress, video_port); + + if(res < 0) + { + qDebug() << "video_socket: Packet Send Failed!"; + } + + currentPacketIndex++; + } + else + { + packageHead.uTransPackageSize = packageHead.uTransPackageHdrSize + (dataLength - currentPacketIndex * UDP_MAX_SIZE); + packageHead.uDataPackageCurrIndex = currentPacketIndex + 1; + packageHead.uDataPackageOffset = currentPacketIndex * UDP_MAX_SIZE; + memcpy(frameBuffer, &packageHead, packageHead.uTransPackageHdrSize); + memcpy(frameBuffer + packageHead.uTransPackageHdrSize, dataBuffer + packageHead.uDataPackageOffset, dataLength - currentPacketIndex * UDP_MAX_SIZE); + + res = video_socket->writeDatagram( + (const char *)frameBuffer, packageHead.uTransPackageSize, + groupAddress, video_port); + + if(res < 0) + { + qDebug() << "video_socket: Packet Send Failed!"; + } + + currentPacketIndex++; + } + } +} diff --git a/src/UDPMulticast/videoframesender.h b/src/UDPMulticast/videoframesender.h new file mode 100644 index 0000000..6db5694 --- /dev/null +++ b/src/UDPMulticast/videoframesender.h @@ -0,0 +1,40 @@ +#ifndef VIDEOFRAMESENDER_H +#define VIDEOFRAMESENDER_H + +#include +#include +#include + +#define UDP_MAX_SIZE 1200 // UDP 数据包最大长度 * MTU = 1500,故数据包大小 1500 - 20(IP头)- 8(UDP头) + +class VideoFrameSender : public QRunnable +{ +public: + explicit VideoFrameSender(QImage image, QObject *parent = nullptr); + ~VideoFrameSender(); + +protected: + void run() override; + +private: + QImage *image; + QObject *parent; + + QUdpSocket *video_socket; + QHostAddress groupAddress; + quint16 video_port; + + // UDP 包头 + struct PackageHeader + { + quint32 uTransPackageHdrSize; // 包头大小(sizeof(PackageHeader)) + quint32 uTransPackageSize; // 当前包头的大小(sizeof(PackageHeader) + 当前数据包长度) + quint32 uDataSize; // 数据的总大小 + quint32 uDataPackageNum; // 数据被分成包的个数 + quint32 uDataPackageCurrIndex; // 数据包当前的帧号 + quint32 uDataPackageOffset; // 数据包在整个数据中的偏移 + }; + +}; + +#endif // VIDEOFRAMESENDER_H diff --git a/src/UDPMulticast/videosurface.h b/src/UDPMulticast/videosurface.h index 5ece1fc..a2babc1 100644 --- a/src/UDPMulticast/videosurface.h +++ b/src/UDPMulticast/videosurface.h @@ -7,12 +7,13 @@ class VideoSurface : public QAbstractVideoSurface { Q_OBJECT + public: explicit VideoSurface(QObject *parent = nullptr); - virtual QList supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const; - bool present(const QVideoFrame &frame); + QList supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType) const override; + bool present(const QVideoFrame &frame) override; signals: void videoFrameChanged(QVideoFrame); diff --git a/src/UDPReceiver/UDPReceiver.pro b/src/UDPReceiver/UDPReceiver.pro index 0f3632e..6994b00 100644 --- a/src/UDPReceiver/UDPReceiver.pro +++ b/src/UDPReceiver/UDPReceiver.pro @@ -25,11 +25,15 @@ DEFINES += QT_DEPRECATED_WARNINGS CONFIG += c++11 SOURCES += \ + audiopackreceiver.cpp \ main.cpp \ - mainwindow.cpp + mainwindow.cpp \ + videoframereceiver.cpp HEADERS += \ - mainwindow.h + audiopackreceiver.h \ + mainwindow.h \ + videoframereceiver.h FORMS += \ mainwindow.ui diff --git a/src/UDPReceiver/audiopackreceiver.cpp b/src/UDPReceiver/audiopackreceiver.cpp new file mode 100644 index 0000000..b13c547 --- /dev/null +++ b/src/UDPReceiver/audiopackreceiver.cpp @@ -0,0 +1,91 @@ +#include "audiopackreceiver.h" + +AudioPackReceiver::AudioPackReceiver() +{ + +} + +AudioPackReceiver::~AudioPackReceiver() +{ + audio_socket->close(); + delete audio_socket; +} + +void AudioPackReceiver::run() +{ + groupAddress = QHostAddress("239.0.0.1"); + audio_port = 8889; + + audio_socket = new QUdpSocket; + audio_socket->bind(QHostAddress::AnyIPv4, audio_port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress); // 绑定组播地址端口 + audio_socket->joinMulticastGroup(groupAddress); // 添加到组播,绑定到读套接字上 + audio_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 1024 * 64); // 缓冲区最大存储 4个 数据包(单个 16K) + + connect(audio_socket, SIGNAL(readyRead()), this, SLOT(on_audioReadyRead()), Qt::DirectConnection); + exec(); +} + +void AudioPackReceiver::on_audioReadyRead() +{ + qint32 res; + + while(audio_socket->hasPendingDatagrams()) + { + QByteArray byteArray; + byteArray.resize(static_cast(audio_socket->pendingDatagramSize())); + res = audio_socket->readDatagram(byteArray.data(), audio_socket->pendingDatagramSize()); + if(res < 0) + { + qDebug() << "audio_socket: Read Datagram Failed!"; + return; + } + + static quint32 num = 1; + static quint32 size = 0; + static AudioPack ap; + + PackageHeader *packageHead = (PackageHeader *)byteArray.data(); + if(packageHead->uDataPackageCurrIndex == 0) + { + // qDebug() << "New Packet Arrived!"; + num = 1; + size = 0; + memset(&ap, 0, sizeof(ap)); + return; + } + + num++; + size += packageHead->uTransPackageSize - packageHead->uTransPackageHdrSize; + + if(size > packageHead->uDataSize) + { + // qDebug() << "packet too big 1"; + num = 1; + size = 0; + memset(&ap, 0, sizeof(ap)); + return; + } + + if(packageHead->uDataPackageOffset > 1024 * 16) + { + // qDebug() << "packet too big 2"; + num = 1; + size = 0; + memset(&ap, 0, sizeof(ap)); + return; + } + + memcpy(ap.data + packageHead->uDataPackageOffset, byteArray.data() + packageHead->uTransPackageHdrSize, + packageHead->uTransPackageSize - packageHead->uTransPackageHdrSize); + + if((packageHead->uDataPackageCurrIndex == packageHead->uDataPackageNum) && (size == packageHead->uDataSize)) + { + ap.len = packageHead->uDataSize; + emit audioPackReceived(ap); + + num = 1; + size = 0; + memset(&ap, 0, sizeof(ap)); + } + } +} diff --git a/src/UDPReceiver/audiopackreceiver.h b/src/UDPReceiver/audiopackreceiver.h new file mode 100644 index 0000000..108b6a8 --- /dev/null +++ b/src/UDPReceiver/audiopackreceiver.h @@ -0,0 +1,46 @@ +#ifndef AUDIOPACKRECEIVER_H +#define AUDIOPACKRECEIVER_H + +#include +#include + +class AudioPackReceiver : public QThread +{ + Q_OBJECT +public: + AudioPackReceiver(); + ~AudioPackReceiver(); + +protected: + void run() override; + +private: + QUdpSocket *audio_socket; + QHostAddress groupAddress; + quint16 audio_port; + + struct PackageHeader + { + quint32 uTransPackageHdrSize; + quint32 uTransPackageSize; + quint32 uDataSize; + quint32 uDataPackageNum; + quint32 uDataPackageCurrIndex; + quint32 uDataPackageOffset; + }; + + struct AudioPack + { + char data[1024 * 16]; + int len; + }; + +private slots: + void on_audioReadyRead(); + +signals: + void audioPackReceived(AudioPack); + +}; + +#endif // AUDIOPACKRECEIVER_H diff --git a/src/UDPReceiver/mainwindow.cpp b/src/UDPReceiver/mainwindow.cpp index 68972c3..c548286 100644 --- a/src/UDPReceiver/mainwindow.cpp +++ b/src/UDPReceiver/mainwindow.cpp @@ -1,18 +1,15 @@ #include "mainwindow.h" #include "ui_mainwindow.h" -#include -#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), availableDevices(QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)), flag_audio(true), - video_socket(new QUdpSocket(this)), audio_socket(new QUdpSocket(this)), groupAddress("239.0.0.1"), - video_port(8888), audio_port(8889) { ui->setupUi(this); @@ -21,27 +18,42 @@ MainWindow::MainWindow(QWidget *parent) : initOutputDevice(); initUI(); initConnections(); + + video_receiver = new VideoFrameReceiver; + connect(video_receiver, SIGNAL(videoFrameReceived(QImage)), this, SLOT(on_videoFrameReceived(QImage))); + video_receiver->start(); + + audio_receiver = new AudioPackReceiver; + /* + * 在线程中通过信号槽传递信息时,参数默认放到队列中 + * AudioPack 是自定义的结构体,不是 Qt 自带的参数结构,无法放入队列 + * 将不识别的参数结构进行注册,让 Qt 能够识别 + */ + qRegisterMetaType("AudioPack"); // 注册 AudioPack 类型 + connect(audio_receiver, SIGNAL(audioPackReceived(AudioPack)), this, SLOT(on_audioPackReceived(AudioPack))); + audio_receiver->start(); + qDebug() << "Initialization Finished!"; } MainWindow::~MainWindow() { + video_receiver->quit(); + video_receiver->wait(); + delete video_receiver; + + audio_receiver->quit(); + audio_receiver->wait(); + delete audio_receiver; + delete ui; } void MainWindow::initUdpConnections() { - video_socket->bind(QHostAddress::AnyIPv4, video_port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress); // 绑定广播地址端口 - video_socket->joinMulticastGroup(groupAddress); // 添加到组播,绑定到读套接字上 - video_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 1920 * 1080 * 4); // 缓冲区最大存储 1080p 位图 - - // video_socket->bind(QHostAddress::LocalHost, video_port); - - audio_socket->bind(QHostAddress::AnyIPv4, audio_port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress); // 绑定广播地址端口 - audio_socket->joinMulticastGroup(groupAddress); // 添加到组播,绑定到读套接字上 - audio_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 1024 * 64); // 缓冲区最大存储 4个 数据包(单个 16K) - - // audio_socket->bind(QHostAddress::LocalHost, audio_port); + // audio_socket->bind(QHostAddress::AnyIPv4, audio_port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress); // 绑定组播地址端口 + // audio_socket->joinMulticastGroup(groupAddress); // 添加到组播,绑定到读套接字上 + // audio_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 1024 * 64); // 缓冲区最大存储 4个 数据包(单个 16K) } void MainWindow::initOutputDevice() @@ -65,99 +77,48 @@ void MainWindow::initOutputDevice() void MainWindow::initUI() { - if(availableDevices.isEmpty()) - { - ui->cb_device->setDisabled(true); - } - else + foreach(const QAudioDeviceInfo &device, availableDevices) { - foreach(const QAudioDeviceInfo &device, availableDevices) - { - ui->cb_device->addItem(device.deviceName(), availableDevices.indexOf(device)); - } + ui->cb_device->addItem(device.deviceName(), availableDevices.indexOf(device)); } } void MainWindow::initConnections() { - connect(video_socket, SIGNAL(readyRead()), this, SLOT(on_videoReadyRead())); - connect(audio_socket, SIGNAL(readyRead()), this, SLOT(on_audioReadyRead())); connect(this, SIGNAL(volumeChanged(int)), this, SLOT(on_volumeChanged(int))); } -void MainWindow::on_videoReadyRead() +void MainWindow::on_videoFrameReceived(QImage image) { - qint64 res; // UDP 结果 - static qint64 totalBytes = 0, // 总字节数 * static 保证变量只初始化一次 - receivedBytes = 0; // 已接收字节数 - - QByteArray byteArray; - byteArray.resize(static_cast(video_socket->pendingDatagramSize())); - res = video_socket->readDatagram(byteArray.data(), video_socket->pendingDatagramSize()); - if(res < 0) - { - qDebug() << "video_socket: Read Datagram Failed!"; - return; - } - - // 接收到停止信号则清空画面 - if(QString(byteArray) == "Stop") + if(image.isNull()) { ui->videoViewer->clear(); - return; } - - // 接收到帧大小信息则暂存 - if(byteArray.contains(QString("size=").toUtf8())) - { - QStringList list = QString(byteArray).split("="); - totalBytes = list.at(1).toInt(); - // qDebug() << "totalBytes = " << totalBytes; - receivedBytes = 0; - return; - } - - static uchar buf[1920 * 1080 * 4]; // 暂存图片数据(最大 1080p 位图) - memcpy(buf + receivedBytes, byteArray.data(), static_cast(res)); - receivedBytes += res; - - // 帧图像接收完成则显示 - if(receivedBytes >= totalBytes) + else { - QPixmap pixmap; - pixmap.loadFromData(buf, static_cast(receivedBytes), "JPEG"); - ui->videoViewer->setPixmap(pixmap.scaled(ui->videoViewer->size(), Qt::KeepAspectRatio, Qt::FastTransformation)); - - // 清空数据 - totalBytes = 0; - receivedBytes = 0; - memset(buf, 0, sizeof(buf)); + ui->videoViewer->setPixmap(QPixmap::fromImage(image).scaled(ui->videoViewer->size(), Qt::KeepAspectRatio, Qt::FastTransformation)); } } void MainWindow::on_btn_audio_clicked() { + static int curVolume; + if(!flag_audio) { - m_audioDevice = m_audioOutput->start(); - /* - * 当接收到的音频数据包完成拼接后,触发单独的信号槽写入设备, - * 便于切换音频输出设备时,只断开音频设备输入输出流, - * 而不断开 audio_socket 的信号槽,保证音频数据继续接收 - */ - connect(this, SIGNAL(readyWrite()), this, SLOT(on_deviceReadyWrite())); - qDebug() << "Audio Share Started!"; + // 仅静音当前音频流 + ui->slider_volume->setValue(curVolume); + emit ui->slider_volume->valueChanged(curVolume); + qDebug() << "Audio Share Unmuted!"; flag_audio = true; - - emit audio_socket->readyRead(); // 音频共享终止接收后恢复播放,需要手动触发 audio_socket->readyRead() 信号 } else { - // 终止音频传输时,先断开设备输入输出流,并且断开 audio_cocket 信号槽停止接收音频数据 - disconnect(this, SIGNAL(readyWrite()), this, SLOT(on_deviceReadyWrite())); - m_audioOutput->stop(); + curVolume = ui->slider_volume->value(); + ui->slider_volume->setValue(0); + emit ui->slider_volume->valueChanged(0); - qDebug() << "Audio Share Stopped!"; + qDebug() << "Audio Share Muted!"; flag_audio = false; // 刷新音频输出可用设备列表 @@ -167,15 +128,18 @@ void MainWindow::on_btn_audio_clicked() } int index = -1; - QAudioDeviceInfo curOutput = availableDevices.at(ui->cb_device->currentIndex()); + QAudioDeviceInfo curOutput; + if(ui->cb_device->currentIndex() > index) + { + curOutput = availableDevices.at(ui->cb_device->currentIndex()); + } ui->cb_device->disconnect(); ui->cb_device->clear(); availableDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); if(availableDevices.isEmpty()) { - ui->btn_audio->setDisabled(true); - ui->cb_device->setDisabled(true); + connect(ui->cb_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_device_currentIndexChanged(int))); } else { @@ -187,15 +151,16 @@ void MainWindow::on_btn_audio_clicked() index = i; } } - } - if(index < 0) - { - index = 0; + if(index < 0) + { + index = 0; + } + + connect(ui->cb_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_device_currentIndexChanged(int))); + ui->cb_device->setCurrentIndex(index); + emit ui->cb_device->currentIndexChanged(index); } - connect(ui->cb_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cb_device_currentIndexChanged(int))); - ui->cb_device->setCurrentIndex(index); - emit ui->cb_device->currentIndexChanged(index); } } @@ -203,7 +168,6 @@ void MainWindow::on_cb_device_currentIndexChanged(int index) { if(flag_audio) { - disconnect(this, SIGNAL(readyWrite()), this, SLOT(on_deviceReadyWrite())); m_audioOutput->stop(); } @@ -224,7 +188,6 @@ void MainWindow::on_cb_device_currentIndexChanged(int index) if(flag_audio) { m_audioDevice = m_audioOutput->start(); - connect(this, SIGNAL(readyWrite()), this, SLOT(on_deviceReadyWrite())); } qDebug() << "Output Device: " << info.deviceName(); @@ -244,51 +207,10 @@ void MainWindow::on_volumeChanged(int value) ui->volume->setText("Volume: " + QString::number(value)); } -void MainWindow::on_audioReadyRead() -{ - // 音频共享关闭接收时不执行 - if(!flag_audio) - { - return; - } - - qint64 res; // UDP 结果 - static qint64 totalBytes = sizeof(videoPack::data) + sizeof(int), // 总字节数 * static 保证变量只初始化一次 - receivedBytes = 0; // 已接收字节数 - - QByteArray byteArray; - byteArray.resize(static_cast(audio_socket->pendingDatagramSize())); - res = audio_socket->readDatagram(byteArray.data(), audio_socket->pendingDatagramSize()); - if(res < 0) - { - qDebug() << "audio_socket: Read Datagram Failed!"; - return; - } - - // 接收到开始信号则清空上个数据包并准备写入新的数据包 - if(QString(byteArray) == "Begin") - { - receivedBytes = 0; - memset(&vp, 0, sizeof(vp)); - m_audioDevice->reset(); // 返回输入缓冲区开头,避免溢出 - return; - } - - memcpy(reinterpret_cast(&vp) + receivedBytes, byteArray.data(), static_cast(res)); - receivedBytes += res; - - // 音频数据包接收完成则触发写入音频输出设备信号槽 - if(receivedBytes >= totalBytes) - { - emit readyWrite(); - } -} - -void MainWindow::on_deviceReadyWrite() +void MainWindow::on_audioPackReceived(AudioPack ap) { - m_audioDevice->write(vp.data, vp.lens); + m_audioDevice->write(ap.data, ap.len); m_audioDevice->waitForBytesWritten(-1); - memset(&vp, 0, sizeof(vp)); m_audioDevice->reset(); } diff --git a/src/UDPReceiver/mainwindow.h b/src/UDPReceiver/mainwindow.h index fad7ba9..c6df75d 100644 --- a/src/UDPReceiver/mainwindow.h +++ b/src/UDPReceiver/mainwindow.h @@ -2,12 +2,12 @@ #define MAINWINDOW_H #include -#include -#include #include #include +#include -#define UDP_MAX_SIZE 1472 // UDP 数据包最大长度 * MTU = 1500,故数据包大小 1500 - 20(IP头)- 8(UDP头) +#include "videoframereceiver.h" +#include "audiopackreceiver.h" #define SAMPLE_RATE 44100 // 采样频率 #define SAMPLE_SIZE 16 // 采样位数 @@ -34,17 +34,18 @@ class MainWindow : public QMainWindow QAudioFormat format; bool flag_audio; - QUdpSocket *video_socket; QUdpSocket *audio_socket; QHostAddress groupAddress; - quint16 video_port; quint16 audio_port; - struct videoPack + VideoFrameReceiver *video_receiver; + AudioPackReceiver *audio_receiver; + + struct AudioPack { char data[1024 * 16]; - int lens; - } vp; + int len; + }; void initUdpConnections(); void initOutputDevice(); @@ -52,17 +53,15 @@ class MainWindow : public QMainWindow void initConnections(); private slots: - void on_videoReadyRead(); + void on_videoFrameReceived(QImage); void on_btn_audio_clicked(); void on_cb_device_currentIndexChanged(int index); void on_slider_volume_valueChanged(int value); void on_volumeChanged(int value); - void on_audioReadyRead(); - void on_deviceReadyWrite(); + void on_audioPackReceived(AudioPack); signals: void volumeChanged(int value); - void readyWrite(); }; diff --git a/src/UDPReceiver/videoframereceiver.cpp b/src/UDPReceiver/videoframereceiver.cpp new file mode 100644 index 0000000..133bd2b --- /dev/null +++ b/src/UDPReceiver/videoframereceiver.cpp @@ -0,0 +1,105 @@ +#include "videoframereceiver.h" + +VideoFrameReceiver::VideoFrameReceiver() +{ + +} + +VideoFrameReceiver::~VideoFrameReceiver() +{ + video_socket->close(); + delete video_socket; +} + +void VideoFrameReceiver::run() +{ + groupAddress = QHostAddress("239.0.0.1"); + video_port = 8888; + + video_socket = new QUdpSocket; + video_socket->bind(QHostAddress::AnyIPv4, video_port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress); // 绑定组播地址端口 + video_socket->joinMulticastGroup(groupAddress); // 添加到组播,绑定到读套接字上 + video_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 1920 * 1080 * 16); // 缓冲区最大存储 4 张 1080p 位图 + + /* + * 此处必须指定连接方式为 Qt::DirectConnection,否则 SLOT 函数会在主线程内被执行,而不是子线程 + */ + connect(video_socket, SIGNAL(readyRead()), this, SLOT(on_videoReadyRead()), Qt::DirectConnection); + exec(); +} + +void VideoFrameReceiver::on_videoReadyRead() +{ + qint32 res; + + while(video_socket->hasPendingDatagrams()) + { + QByteArray byteArray; + byteArray.resize(static_cast(video_socket->pendingDatagramSize())); + res = video_socket->readDatagram(byteArray.data(), video_socket->pendingDatagramSize()); + if(res < 0) + { + qDebug() << "video_socket: Read Datagram Failed!"; + return; + } + + // 接收到停止信号则清空画面 + if(QString(byteArray) == "Stop") + { + emit videoFrameReceived(QImage()); + return; + } + + static quint32 num = 1; + static quint32 size = 0; + static VideoPack vp; + + PackageHeader *packageHead = (PackageHeader *)byteArray.data(); + if(packageHead->uDataPackageCurrIndex == 0) + { + // qDebug() << "New Image Arrived!"; + num = 1; + size = 0; + memset(&vp, 0, sizeof(vp)); + return; + } + + num++; + size += packageHead->uTransPackageSize - packageHead->uTransPackageHdrSize; + + if(size > packageHead->uDataSize) + { + // qDebug() << "image too big 1"; + num = 1; + size = 0; + memset(&vp, 0, sizeof(vp)); + return; + } + + if(packageHead->uDataPackageOffset > 1920 * 1080 * 4) + { + // qDebug() << "image too big 2"; + num = 1; + size = 0; + memset(&vp, 0, sizeof(vp)); + return; + } + + memcpy(vp.data + packageHead->uDataPackageOffset, byteArray.data() + packageHead->uTransPackageHdrSize, + packageHead->uTransPackageSize - packageHead->uTransPackageHdrSize); + + if((packageHead->uDataPackageCurrIndex == packageHead->uDataPackageNum) && (size == packageHead->uDataSize)) + { + vp.len = packageHead->uDataSize; + QImage image; + if(image.loadFromData(vp.data, vp.len, "JPEG")) + { + emit videoFrameReceived(image); + } + + num = 1; + size = 0; + memset(&vp, 0, sizeof(vp)); + } + } +} diff --git a/src/UDPReceiver/videoframereceiver.h b/src/UDPReceiver/videoframereceiver.h new file mode 100644 index 0000000..1792504 --- /dev/null +++ b/src/UDPReceiver/videoframereceiver.h @@ -0,0 +1,48 @@ +#ifndef VIDEOFRAMERECEIVER_H +#define VIDEOFRAMERECEIVER_H + +#include +#include +#include + +class VideoFrameReceiver : public QThread +{ + Q_OBJECT + +public: + VideoFrameReceiver(); + ~VideoFrameReceiver(); + +protected: + void run() override; + +private: + QUdpSocket *video_socket; + QHostAddress groupAddress; + quint16 video_port; + + struct PackageHeader + { + quint32 uTransPackageHdrSize; + quint32 uTransPackageSize; + quint32 uDataSize; + quint32 uDataPackageNum; + quint32 uDataPackageCurrIndex; + quint32 uDataPackageOffset; + }; + + struct VideoPack + { + uchar data[1920 * 1080 * 4]; + int len; + }; + +private slots: + void on_videoReadyRead(); + +signals: + void videoFrameReceived(QImage); + +}; + +#endif // VIDEOFRAMERECEIVER_H