403 lines
15 KiB
C++
403 lines
15 KiB
C++
#include "videowin.h"
|
|
#include "table.h"
|
|
#include "gqt.h"
|
|
#include "crc.h"
|
|
#include <QTextEdit>
|
|
#include <QPushButton>
|
|
#include <QMessageBox>
|
|
#include <QDateTime>
|
|
#include <QTimerEvent>
|
|
#include <QApplication>
|
|
#include <QDesktopWidget>
|
|
#include <QScreen>
|
|
#include <QDebug>
|
|
#include <QPainter>
|
|
|
|
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<std::mutex> lock(sendThd->mtx);
|
|
if(sendThd->imgs.size()>2) return;
|
|
sendThd->imgs.append(img);
|
|
}
|
|
auto now_epoch = chrono::duration_cast<chrono::milliseconds>(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<std::mutex> 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<img.height(); i++) for(int j=0; j<lineLen; j+=once) {
|
|
int dataLen = lineLen - j;
|
|
if(dataLen > 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::microseconds>(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()<<"丢包"<<diff;
|
|
lostTimes++;
|
|
lostPkts += diff;
|
|
}
|
|
}
|
|
emit onErr(pcap_geterr(pcap));
|
|
}
|
|
|
|
void Canvas::paintEvent(QPaintEvent *event) {
|
|
QPainter painter(this);
|
|
painter.drawImage(0, 0, img);
|
|
}
|