#include "videowin.h" #include "gutil/qgui.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(10); fdNoReview = new QCheckBox("关闭显示"); fdNoReview->setChecked(noReview); hBox->addWidget(fdNoReview); hBox->addSpacing(10); auto fdUseOldProtocol = new QCheckBox("用旧协议"); connect(fdUseOldProtocol, &QRadioButton::toggled, [=](bool checked) { useOldProto = checked; if(thdSend) thdSend->useOldProto = checked; if(thdRece) thdRece->useOldProto = checked; }); hBox->addWidget(fdUseOldProtocol); hBox->addSpacing(10); 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(fdNoReview, &QRadioButton::toggled, [=](bool checked) { noReview = checked; if(thdRece) thdRece->noReview = checked; if(noReview) { fdCanvas->img = QImage(); fdCanvas->update(); } }); thdRece = new VideoRecThread(pcapRece); connect(thdRece, &QThread::finished, this, [this] { thdRece = 0; }); connect(thdRece, &VideoRecThread::onMsg, fdCanvas, [this, fdCanvas](const QByteArray chars) { if(noReview) return; auto data = (uchar *)chars.data(); if(data[16]==0x12) { imgLines.append(chars); int i = (data[20]<<8 | data[21]) + 1; if(i > imgHeight) imgHeight = i; i = (data[22]<<8 | data[23]) + (data[24]<<8 | data[25]); if(i > imgWidth) imgWidth = i; } else if(data[16]==0x11 && imgWidth && imgHeight) { //qDebug()<<"imgWidth"<img = img; fdCanvas->update(); } }); connect(thdRece, &VideoRecThread::onImg, fdCanvas, [this, fdCanvas](QImage img, int lostTimes, int lostPkts) { if(noReview) 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 thdSend = new VideoSendThread(pcapSend); connect(thdSend, &QThread::finished, this, [this] { fdEnd->setChecked(true); thdSend = 0; }); connect(thdSend, &VideoSendThread::onErr, this, [this](QString err) { fdEnd->setChecked(true); QMessageBox::critical(this, "Error", err); }); thdSend->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(thdSend->mtx); if(thdSend->imgs.size()>2) return; thdSend->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(thdSend ? thdSend->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; } auto queue = pcap_sendqueue_alloc(img.width()*img.height()*4); if(useOldProto) { QByteArray bytes; bytes.append("\x55\x55"); bytes.append("\x00\x11\x22\x33\x44\x55", 6); //目的地址 bytes.append("\x00\x11\x22\x33\x44\x55", 6); //源地址 bytes.append("\x10\x5A"); //协议标识 bytes.append("\x11\0\0\0", 4); //包类型标识 保留 网口标识 接收卡标识 bytes.append(frameIdx>>8).append(frameIdx);//帧循环计数 frameIdx++; bytes.append(2, 0); //帧保持计数 bytes.append("\x0\xff\x0\x0\x0", 5); //自动亮度模式 整屏亮度 实时参数模式 测试模式 保留 auto crc32 = crc32_calc((uint8_t*)bytes.data(), bytes.length()); bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32); //发送帧开始指令包 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(); //按行发送图像数据包 for(int i=0; i once) dataLen = once; bytes.clear(); bytes.append("\x55\x55"); bytes.append("\x00\x11\x22\x33\x44\x55", 6); //目的地址 bytes.append("\x00\x11\x22\x33\x44\x55", 6); //源地址 bytes.append("\x10\x5A"); //协议标识 bytes.append("\x12\0\0\0", 4); //包类型标识 保留 网口标识 接收卡标识 bytes.append(i>>8).append(i).append(j/3>>8).append(j/3); //行列起始位置 bytes.append(dataLen/3>>8).append(dataLen/3); //包像素计数 bytes.append((const char*)bits + i * bytesPerLine + j, dataLen); //图像数据 crc32 = crc32_calc((uint8_t*)bytes.data(), bytes.length()); 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; } } else { 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); //发送帧开始指令包 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 || noReview) continue; //超时 if(header->caplen<24) continue; if(useOldProto) { if(data[0]!=0x55 || data[1]!=0x55 || data[14]!=0x10 || data[15]!=0x5A) continue; emit onMsg(QByteArray((char*)data, header->caplen)); } else { 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 onImg(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()<<"丢包"<