#include "videowin.h" #include "gqt.h" #include "crc.h" #include #include #include #include #include #include #include #include #include #include using namespace std; VideoWin *VideoWin::newIns(QByteArray &name, QWidget *parent) { if(name.isEmpty()) return 0; char errbuf[PCAP_ERRBUF_SIZE]{'\0'}; auto pcapRe = pcap_open_live(name.data(), 65536, PCAP_OPENFLAG_PROMISCUOUS, 50, errbuf); if(pcapRe == 0) { QMessageBox::critical(parent, "Error", QString("打开网卡失败")+errbuf); return 0; } auto pcapSend = pcap_open_live(name.data(), 65536, 0, 50, errbuf); if(pcapSend == 0) { QMessageBox::critical(parent, "Error", QString("打开网卡失败")+errbuf); return 0; } return new VideoWin(pcapRe, pcapSend, parent); } VideoWin::VideoWin(pcap_t *pcapRece, pcap_t *pcapSend, QWidget *parent) : BaseWin{parent} { setWindowModality(Qt::WindowModal); setAttribute(Qt::WA_DeleteOnClose); setWindowTitle("视频传输"); resize(800, 600); auto vBox = new QVBoxLayout(center); vBox->setContentsMargins(0,0,0,0); vBox->setSpacing(3); vBox->addLayout(addBtns(new QHBoxLayout())); auto hBox = new HBox(vBox); fdWidth = new QSpinBox; fdWidth->setRange(1, 9999); fdWidth->setValue(640); hBox->addWidget(fdWidth); hBox->addWidget(new QLabel("x")); fdHeight = new QSpinBox; fdHeight->setRange(1, 9999); fdHeight->setValue(360); hBox->addWidget(fdHeight); hBox->addWidget(new QLabel("截图间隔(ms)")); fdMsecCapScr = new QSpinBox; fdMsecCapScr->setRange(1, 1000); fdMsecCapScr->setValue(17); hBox->addWidget(fdMsecCapScr); hBox->addSpacing(20); fdShowImg = new QRadioButton("关闭显示"); fdShowImg->setChecked(showImg); hBox->addWidget(fdShowImg); hBox->addSpacing(20); hBox->addWidget(new QLabel("接收线程优先级")); fdYxj = new QSpinBox; fdYxj->setRange(0, 7); fdYxj->setValue(6); connect(fdYxj, (void(QSpinBox::*)(int))&QSpinBox::valueChanged, [=](int value) { if(thdRece) thdRece->setPriority(QThread::Priority(value)); }); hBox->addWidget(fdYxj); hBox->addSpacing(20); auto fdStart = new QRadioButton("发送"); connect(fdStart, &QRadioButton::toggled, this, [this](bool checked) { if(! checked) return; screen = QGuiApplication::primaryScreen(); if(timerId==0) timerId = startTimer(fdMsecCapScr->value()); }); hBox->addWidget(fdStart); hBox->addSpacing(20); fdEnd = new QRadioButton("停止"); connect(fdEnd, &QRadioButton::toggled, this, [this](bool checked) { if(! checked) return; if(timerId) { killTimer(timerId); timerId = 0; } }); hBox->addWidget(fdEnd); hBox->addSpacing(20); fdInfo = new QLabel; fdInfo->setStyleSheet("QLabel{color: #f22;}"); hBox->addWidget(fdInfo); hBox->addStretch(); hBox = new HBox(vBox); hBox->addWidget(new QLabel("接收:")); hBox->addSpacing(20); hBox->addWidget(new QLabel("丢包次数: ")); hBox->addWidget(fdLostTimes = new QLabel); fdLostTimes->setMinimumWidth(40); hBox->addWidget(new QLabel("丢包数: ")); hBox->addWidget(fdLostPkts = new QLabel); fdLostPkts->setMinimumWidth(40); hBox->addStretch(); auto fdCanvas = new Canvas; vBox->addWidget(fdCanvas, 1); connect(fdShowImg, &QRadioButton::toggled, [=](bool checked) { qDebug()<<"fdShowImg"<showImg = checked; if(! checked) { fdCanvas->img = QImage(); fdCanvas->update(); } }); thdRece = new VideoRecThread(pcapRece); connect(thdRece, &QThread::finished, this, [this] { thdRece = 0; }); connect(thdRece, &VideoRecThread::onMsg, fdCanvas, [this, fdCanvas](QImage img, int lostTimes, int lostPkts) { if(! showImg) return; fdCanvas->img = img; fdCanvas->update(); if(lostPkts==0) return; this->lostTimes += lostTimes; this->lostPkts += lostPkts; fdLostTimes->setText(QString::number(this->lostTimes)); fdLostPkts->setText(QString::number(this->lostPkts)); }); thdRece->start(QThread::Priority(fdYxj->value()));//HighestPriority //InheritPriority //TimeCriticalPriority sendThd = new VideoSendThread(pcapSend); connect(sendThd, &QThread::finished, this, [this] { fdEnd->setChecked(true); sendThd = 0; }); connect(sendThd, &VideoSendThread::onErr, this, [this](QString err) { fdEnd->setChecked(true); QMessageBox::critical(this, "Error", err); }); sendThd->start(); } void VideoWin::timerEvent(QTimerEvent *event) { if(event->timerId()!=timerId) BaseWin::timerEvent(event); else { int width = fdWidth->value(); int height = fdHeight->value(); if(width < 32 || height < 32) return; auto devicePixelRatio = screen->devicePixelRatio(); auto pixmap = screen->grabWindow(0, 0, 0, width/devicePixelRatio, height/devicePixelRatio); auto img = pixmap.toImage(); img.convertTo(QImage::Format_RGB888); { std::lock_guard lock(sendThd->mtx); if(sendThd->imgs.size()>2) return; sendThd->imgs.append(img); } auto now_epoch = chrono::duration_cast(chrono::steady_clock::now().time_since_epoch()).count(); int dur = now_epoch - last_epoch; last_epoch = now_epoch; fdInfo->setText(info = "帧间隔: "+QString::number(dur)+" 帧缓存: "+QString::number(sendThd ? sendThd->imgs.size() : 0)); } } VideoSendThread::VideoSendThread(pcap_t *pcap) : pcap(pcap) { connect(this, &QThread::finished, this, &QThread::deleteLater); } #define MAX_ONCE 1482 void VideoSendThread::run() { QImage img; quint32 idx = 0; while(status!=2) { { std::lock_guard lock(mtx); img = imgs.isEmpty() ? QImage() : imgs.takeFirst(); } if(img.isNull()) { QThread::msleep(15); continue; } QByteArray bytes; bytes.append("\x55\x55\x9a\x3d"); //前导, 版本号, 服务类型 bytes.append("\0\x4", 2); //数据长度 bytes.append(4, '\xff'); //目的地址 bytes.append(4, 0); //源地址 bytes.append("\x35\0\xc\xbb", 4); //内存指针 bytes.append(2, 0); //应答填充项 auto crc32 = crc32_calc((uint8_t*)bytes.data()+2, bytes.length()-2); bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32); bytes.append(img.width()>>8).append(img.width()).append(img.height()>>8).append(img.height()); crc32 = crc32_calc((uint8_t*)bytes.data()+bytes.length()-4, 4); bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32); //发送帧开始指令包 auto queue = pcap_sendqueue_alloc(img.width()*img.height()*4); { struct pcap_pkthdr pktheader; pktheader.len = pktheader.caplen = bytes.size(); if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) { onErr(QString("添加开始失败: ")+pcap_geterr(pcap)); goto end; } auto lineLen = img.width() * 3; int once; if(lineLen > MAX_ONCE) { int cnt = (lineLen + MAX_ONCE - 1) / MAX_ONCE; once = (lineLen + cnt - 1) / cnt; } else once = lineLen; auto bytesPerLine = img.bytesPerLine(); auto bits = img.constBits(); idx = 0; //按行发送图像数据包 for(int i=0; i once) dataLen = once; dataLen += 4; bytes.clear(); bytes.append("\x55\x55\x1\x33"); //前导, 版本号, 服务类型 bytes.append(dataLen>>8).append(dataLen); //数据长度 bytes.append(4, '\xff'); //目的地址 bytes.append(4, 0); //源地址 bytes.append(i>>8).append(i).append(j>>8).append(j); //内存指针 bytes.append(2, 0); //应答填充项 crc32 = crc32_calc((uint8_t*)bytes.data()+2, bytes.length()-2); bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32); bytes.append(idx>>24).append(idx>>16).append(idx>>8).append(idx);//包序号 idx++; bytes.append((const char*)bits + i * bytesPerLine + j, dataLen - 4); crc32 = crc32_calc((uint8_t*)bytes.data()+bytes.length()-dataLen, dataLen); bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32); pktheader.len = pktheader.caplen = bytes.size(); if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) { onErr(QString("添加数据失败: ")+pcap_geterr(pcap)); goto end; } } bytes.clear(); bytes.append("\x55\x55\x9a\x3d"); //前导, 版本号, 服务类型 bytes.append(2, 0); //数据长度 bytes.append(4, '\xff'); //目的地址 bytes.append(4, 0); //源地址 bytes.append("\x35\0\xc\xcc", 4); //内存指针 bytes.append(2, 0); //应答填充项 crc32 = crc32_calc((uint8_t*)bytes.data()+2, bytes.length()-2); bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32); pktheader.len = pktheader.caplen = bytes.size(); //发送帧结束指令包 if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) { onErr(QString("添加结束失败: ")+pcap_geterr(pcap)); goto end; } u_int res; if((res = pcap_sendqueue_transmit(pcap, queue, 0)) < queue->len) { onErr(QString::asprintf("发送包出错: %s. 仅发了 %d / %d bytes", pcap_geterr(pcap), res, queue->len)); goto end; } } end: pcap_sendqueue_destroy(queue); } } /* Callback function invoked by libpcap for every incoming packet */ //void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { //} VideoRecThread::VideoRecThread(pcap_t *pcap) : pcap(pcap) { connect(this, &QThread::finished, this, &QThread::deleteLater); } void VideoRecThread::run() { pcap_pkthdr *header; const u_char *data; QImage img; int res; int lastIdx = -1; int lostTimes{0}, lostPkts{0}; while((res = pcap_next_ex(pcap, &header, &data)) >= 0) { if(status==2) return; if(status==1 || res == 0 || !showImg) continue; //超时 if(header->caplen<24) continue; if(data[0]!=0x55 || data[1]!=0x55) continue; if(data[2]==0x9a && data[3]==0x3d) { if(data[17]==0xBB) { img = QImage(((int)data[24] << 8) + data[25], ((int)data[26] << 8) + data[27], QImage::Format_RGB888); lastIdx = -1; lostTimes = 0; lostPkts = 0; } else if(data[17]==0xCC) emit onMsg(img, lostTimes, lostPkts); continue; } if(data[2]!=0x1 || data[3]!=0x33) continue; int len = ((int)data[4] << 8) + data[5]; int row = ((int)data[14] << 8) + data[15]; int col = ((int)data[16] << 8) + data[17]; quint32 idx = ((quint32)data[24] << 24) + ((quint32)data[25] << 16) + ((quint32)data[26] << 8) + data[27]; //auto epoch = chrono::duration_cast(chrono::steady_clock::now().time_since_epoch()).count(); memcpy(img.bits()+row*img.bytesPerLine()+col, &data[28], len-4); int diff = idx - lastIdx - 1; lastIdx = idx; if(diff > 0) { //qDebug()<<"丢包"<