#include "videowin.h" #include "table.h" #include "gqt.h" #include "crc.h" #include #include #include #include #include #include #include #include #include #include using namespace std; QByteArray getNetDev(QWidget *parent) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_if_t *devs; if(pcap_findalldevs(&devs, errbuf) == -1) { QMessageBox::critical(parent, "Error", QString("寻找网卡失败")+errbuf); return QByteArray(); } auto table = new Table{ {"name", "网卡名称", 400}, {"desc", "网卡描述", 300}, {"loopback","Loopback"}, {"ip","IP"}, {"netmask","Netmask"}, {"broadaddr","Broad Addr"}, {"dstaddr","Dst Addr"} }; pcap_if_t *device = 0; for(pcap_if_t *dev = devs; dev; dev = dev->next) { pcap_addr_t *a; for(a=dev->addresses; a; a=a->next) { auto sa_family = a->addr->sa_family; if(sa_family==AF_INET) { auto rr = table->rowCount(); table->setRowCount(rr+1); table->setValue(rr, "name", dev->name); table->setValue(rr, "desc", dev->description); table->setValue(rr, "loopback", (dev->flags & PCAP_IF_LOOPBACK)?"True":"False"); auto ip = (u_char *) &((sockaddr_in *) a->addr)->sin_addr.s_addr; table->setValue(rr, "ip", QString::asprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])); if(a->netmask) { ip = (u_char *) &((sockaddr_in *) a->netmask)->sin_addr.s_addr; table->setValue(rr, "netmask", QString::asprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])); } if(a->broadaddr) { ip = (u_char *) &((sockaddr_in *) a->broadaddr)->sin_addr.s_addr; table->setValue(rr, "broadaddr", QString::asprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])); } if(a->dstaddr) { ip = (u_char *) &((sockaddr_in *) a->dstaddr)->sin_addr.s_addr; table->setValue(rr, "dstaddr", QString::asprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])); } if(device==0) device = dev; } } } pcap_freealldevs(devs); auto rowCnt = table->rowCount(); if(rowCnt==0) { table->deleteLater(); QMessageBox::critical(parent, "Error", "没找到 internet 设备"); return QByteArray(); } else if(rowCnt==1) { table->deleteLater(); return table->item(0, "name")->text().toLocal8Bit(); } else { auto dlg = new BaseDlg(parent); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle("选择网卡"); dlg->resize(900, 300); auto vBox = new QVBoxLayout(dlg->center); vBox->setContentsMargins(0,0,0,0); vBox->setSpacing(3); vBox->addSpacing(30); table->setDefs(); table->setSelectionMode(QAbstractItemView::SingleSelection); table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); table->selectRow(0); QByteArray name; dlg->connect(table, &Table::cellDoubleClicked, dlg, [dlg, table, &name](int row) { name = table->item(row, "name")->text().toLocal8Bit(); dlg->accept(); }); vBox->addWidget(table); auto hBox = new QHBoxLayout; hBox->addStretch(); auto btnOk = new QPushButton("确定"); btnOk->setMinimumWidth(80); dlg->connect(btnOk, &QPushButton::clicked, dlg, [dlg, table, &name] { auto sels = table->selectedRanges(); if(sels.isEmpty()) { QMessageBox::warning(dlg, "Warning", "请选择网卡"); return; } name = table->item(sels[0].topRow(), "name")->text().toLocal8Bit(); dlg->accept(); }); hBox->addWidget(btnOk); hBox->addStretch(); auto btnClose = new QPushButton("关闭"); btnClose->setMinimumWidth(80); dlg->connect(btnClose, &QPushButton::clicked, dlg, &QDialog::reject); hBox->addWidget(btnClose); hBox->addStretch(); vBox->addLayout(hBox); dlg->exec(); return name; } } VideoWin *VideoWin::newIns(QWidget *parent) { auto name = getNetDev(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->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(17); }); 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); 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) { 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::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) 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()<<"丢包"<