一、目标功能
1、可以让pc通过udp与FPGA之间双向传输视频/图片。
2、udp传图的数据不经过压缩按:R(8bit)、G(8bit)、B(8bit)一个像素点一个像素点传输。
3、每帧图片得有帧标记(类似vga的hs vs de)。
二、实现过程
1、实现计划使用opencv库,qt+opencv的环境搭建
错误1:opencv编译中ffmpeg插件问题。
? ? ? ? ? ?videocapture无法打开视频(时间最长,卡了4、5天)
错误2:版本问题(opencv版本和ffmpeg下载的版本不不对应会出现)
? ? ? ? ? ? Qt版本:mingw730 64bit ?opencv:4.1.0 ?64bit
2、发送程序调试
1、实现了udp通讯的搭建
???实现了opencv提取图像像素矩阵数据?
???实现了像素数据到udp数据类型的转换
? ? ??代码:
2、测试图片:(画图软件绘制的12x12的图)
?3、Mat类输出矩阵显示:
?4、Mat转换为数组输出:
?代码:(待补充)
5、发送较大的图片文件测试
? ? ?上面测试的小数据量的,但超过一个udp包(65535byte)就会出错
AbstractSocket::SocketError
?6、分包发送。图像数据分多次发送。
?代码:(待补充)
7、数据包的设置
????????图片和视频都能发送了。
? ? ? ? 需要用于区分图片数据的开头结尾。(设置安排如下)
Head+每包数据:
Head ??: 帧标志+图像长度
(例:一帧数据的第一个包,图像行长为12)
01 00 0C???(3byte)
??????????????????????????????????(当前帧第二包及之后)
??????????????????????????????????????00 00 0C????(3byte)
??????????????第1byte: 一帧第一个包的为1,其他包为0。
??????????????后2byte: 为图像的行长。
??????????????Data ??: 图像数据(len=包长)
??????????????????????数据格式:第一个像素点R(8bit)+G(8bit)+B(8bit)
??代码:(待补充)
注意:frame.cols为int型,要转换
? ? ? ? ? 且?int存储方式为高位在后低位在前!
?代码:(待补充)
?3、接收程序调试
? ? ?目标:数据接收并转为图片,并窗口显示
? ? ?涉及: 多包数据整合
? ? ? ? ? ? ? ? ?需要利用head标记区分图片数据接收开始结束。
? ? ? ? ? ? ? ? ?Data与head分离
? ? ? ? ? ? ? ? 数据类型的转换
1、测试1:单包发送情况
???发送数据:(用的上面12x12的图的数据)
?结果可行!
QT的debug输出:
?效果展示:
2、一帧数据分多包发送的情况
? ? ?用flag判断一幅图片开始
?网络助手手发数据测试:
?基本功能完成。
?4、其他功能
? ? ? ? ?在实际FPGA上板测试后,又添加了一些功能
?①保存接收到的图片(save文件里)
②程序运行日志添加(save文件里log.txt),可用于检查软件运行日志(检查错误)
③ 两个图片显示窗口需要清空按钮。
?④添加本机的接收端口设置(不设置为固定端口)。
⑤应为有接收丢包的情况,尝试了多线程处理(实现把发送放到了子线程处理)
5、目前问题
? ? ? ? 1、在测试时,使用自发自收,接收时还是有可能丢数据,发送不丢包
? ? ? ? ? ? ? 可能原因:收发数据要分不同线程?(没有专门学过qt c++,不了解)
? ? ?
?6、程序
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTextStream>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QString title ="FPGA UDP传图 本机端口:"+ ui->rxport->text();
setWindowTitle(title);
udpsocket=new QUdpSocket(this);
udpsocket->abort();
portStr = ui->rxport->text().toInt();
udpsocket->bind(QHostAddress::Any,portStr);//绑定当前网卡所有的ip,端口
timer=new QTimer(this);
connect(ui->ptn_quit,SIGNAL(clicked(bool)),this,SLOT(quit())); //退出按钮
connect(udpsocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
connect(ui->ptn_openfile,SIGNAL(clicked(bool)),this,SLOT(openfile()) );// 打开文件
connect(ui->ptn_send,SIGNAL(clicked(bool)),this,SLOT(ptn_send_clicked())); //发送按钮按下
connect(timer,SIGNAL(timeout()),this,SLOT(PlayAndSendData()));//按下按钮后
//当收到数据时readyRead()有效,进而触发跳转到UDPReceive()函数;
connect(udpsocket,SIGNAL(readyRead()),this,SLOT(UDPReceive()));
//新建文件夹保存接收到的图片及log
path=QDir::currentPath();
path=path.replace("/","\\")+"/save";
createFile(path,"log.txt");
}
Widget::~Widget()
{
delete ui;
}
void Widget::openfile()
{
QString path=QDir::currentPath();
path.replace("/","\\");
filename = QFileDialog::getOpenFileName(this,"选择需要播放的视频",path,
"allfiles(*.*);;"
"MP4(*.mp4);;"
"AVI(*.avi)");
}
// ========================== udp发送 ===========================================================
void Widget::SendData() // 每帧执行一次
{
cvtColor(frame,frame,CV_BGR2RGB);
//-------- 分包处理 -------
QByteArray sendbyte;
// 定义包头
QByteArray head;
head.append(1);//一帧第一包的标志
int l=frame.cols; //int的存储是低位在前!! frame.cols是int32(而QByteArray用char16?)
int h=frame.rows;
head.append(dec2hex2(l),2);//写入一行长度(.append为追加行长数据)(char型,直接添加丢失精度)(这里int转hex且取2byte)
head.append(dec2hex2(h),2);
qDebug()<<"第 "<<n<<"frame 帧";
qDebug()<<"head = "<<head.toHex();
qDebug()<<"The size of head is "<<head.size();
// 定义将要发送的数据
QByteArray sendbyte1;
sendbyte1.append((char*)frame.data,frame.rows*frame.cols*3);
QByteArray byte;
QBuffer buffer(&byte);//缓冲区绑定数据源
buffer.open(QIODevice::WriteOnly);
buffer.write(sendbyte1);
//每帧图像分多次发送,总大小为ByteLen,每次发送的大小为sendLen,比较可知是否完整发完本帧的数据。
int ByteLen = byte.size();
int sendLen = 0;
qDebug() << "byte.size :" << ByteLen;
while(ByteLen > sendLen )
{
// 填充要发送的数据
sendbyte.append (head);//加上每包数据的3byte
sendbyte.append (byte.mid(sendLen,ui->lineEdit_bao->text().toUInt())); // !!!后设置分包大小:frame.cols*3
//发送
int len = udpsocket->writeDatagram(sendbyte,sendbyte.size(),QHostAddress(ip),port);
sendLen += len-head.size();//并减去head的长度
qDebug() <<"本次发送出数据长度为 this time:" << len-head.size() << "已发送数据长度为 Has been sent:" << sendLen << " 此帧像素数据总长度为 The total length:" << ByteLen ;
sendbyte.clear();
udpsocket->flush();
head[0]=0x00;//一帧非第一包标志
}
byte.clear();
//QThread::msleep(delay);
}
// ===================================================================
void Widget::displayError(QAbstractSocket::SocketError)
{
qDebug()<<"Failed to open video";
QString string=udpsocket->errorString();
qDebug()<<string;
}
void Widget::quit()
{
stop = true;
}
//=========================================================
// int转为hex(但这里高位在后)
QByteArray Widget::dec2hex(int a)
{
QByteArray array;
array.resize(sizeof(int));
memcpy(array.data(), &a, sizeof(int));
return array;
}
//但是下位机通信经常采用大端模式,即高字节数据在前!!
QByteArray Widget::dec2hex2(int a)
{
QByteArray array;
// array[0] = (uchar)(a >> 24 & 0xff);
// array[1] = (uchar)(a >> 16 & 0xff);
array[0] = (uchar)(a>>8 & 0xff);
array[1] = (uchar)(a & 0xff);
return array;
}
//============================================ UDP接收图片 =============================================================================
void Widget::UDPReceive() //测试:广播(255.255.255.255)发
{
qDebug()<<"start udp rx";
QByteArray receivebyte;//本次data
//QByteArray datareg;//累积的data
// 让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据
receivebyte.resize(udpsocket->pendingDatagramSize());
// 接收数据报,存到datagram
qint64 len = udpsocket->readDatagram(receivebyte.data(), receivebyte.size());
if(len>0)//如果>0就接收处理
{
// -----------------------------
// 解析出每次的head
int8_t flag =(receivebyte.at(0));
qDebug()<<"帧标记 flag:"<<flag;
bool ok;
QByteArray headL ,headH;
headL.resize(0);
headH.resize(0);
headL.append(receivebyte.at(1));
headL.append(receivebyte.at(2));
headH.append(receivebyte.at(3));
headH.append(receivebyte.at(4));
// head[0]=0x00;
// head[1]=0xB4;
int L = headL.toHex().toInt(&ok, 16);
int H = headH.toHex().toInt(&ok, 16);
//qDebug()<<"ok:"<<ok;
//qDebug()<<"head="<<headL;
//qDebug()<<"head="<<headH;
qDebug()<<"L="<<L;
qDebug()<<"H="<<H;
// -------------------------
int sum = H*L*3;
//qDebug()<<"H*L*3 sum="<<sum;
if(flag==1)//如果接收到一帧开始
{
//清空
datareg.remove(0,datareg.size());
qDebug()<<"清空 head refrish:datareg.size="<<datareg.size();
//取出本包数据
//qDebug()<<"(本包数据:)"<<receivebyte.right(receivebyte.size()-5).toHex();//5跳过head
unsigned char *rxdata;
rxdata = reinterpret_cast<unsigned char*>(receivebyte.data())+5; //+5:跳过head
//如果数据只有一帧,显示
qDebug()<<"(第1包)receivebyte.size:"<<receivebyte.size();
if(sum==receivebyte.size()-5)
{
qDebug()<<"only 1(只有一包数据)";
//Mat img(h,l,rxdata);
// 图片显示准备
QImage image_rx(rxdata,L,H,QImage::Format_RGB888);
// 同时显示当前图片
ui->label_rx->setPixmap(QPixmap::fromImage(image_rx));
ui->label_rx->resize(image_rx.size());
// 图像保存
imageSave(image_rx );
//清空 再重新 累计data
datareg.remove(0,datareg.size());//head.resize(0);???
}
else//如果不够一帧,累计
{
datareg.append(receivebyte.right(receivebyte.size()-5));//只存图像到datareg
}
}
else//不是帧头,累积datar
{
datareg.append(receivebyte.right(receivebyte.size()-5));//只存图像到datareg
//qDebug()<<"本包数据:"<<receivebyte.right(receivebyte.size()-5).toHex();//5跳过head
unsigned char *rxdata;
rxdata = reinterpret_cast<unsigned char*>(datareg.data()); //无head
//如果是最后一包数据,显示并重置
qDebug()<<"本次接受数据长度 Length of data accepted this time:"<<receivebyte.size()-5<<"已接收总量 Total received:"<<datareg.size()<<"此帧像素数据总长度为 The total length:"<<sum;
//qDebug()<<"h*l*3 sum="<<sum<<endl;
if(sum==datareg.size())//或者可以多数据?xx
{
//Mat img(h,l,rxdata);
// 图片显示准备
QImage image_rx(rxdata,L,H,QImage::Format_RGB888);
// 同时显示当前图片
ui->label_rx->setPixmap(QPixmap::fromImage(image_rx));
ui->label_rx->resize(image_rx.size());
// 图像保存
imageSave(image_rx);
//清空
datareg.remove(0,datareg.size());
}
}
}
}
// 图像保存=============================
void Widget::imageSave(QImage image_rx )
{
static int i = 0;
QString imageName = path+(QString("/%1.png").arg(i));
image_rx.save(imageName);
i++;
qDebug() << "image save ok";
}
// 新建文件==============================
void Widget::createFile(QString filePath,QString fileName)
{
QDir tempDir;
//临时保存程序当前路径
QString currentDir = tempDir.currentPath();
//如果filePath路径不存在,创建它
if(!tempDir.exists(filePath))
{
qDebug()<<"不存在该路径 no path!"<<endl;
tempDir.mkpath(filePath);
}
QFile *tempFile = new QFile;
//将程序的执行路径设置到filePath下
tempDir.setCurrent(filePath);
qDebug()<<tempDir.currentPath();
//检查filePath路径下是否存在文件fileName,如果停止操作。
if(tempFile->exists(fileName))
{
qDebug()<<"文件存在 File exists";
return ;
}
//此时,路径下没有fileName文件,使用下面代码在当前路径下创建文件
tempFile->setFileName(fileName);
if(!tempFile->open(QIODevice::WriteOnly|QIODevice::Text))
{
qDebug()<<"Open fail";
}
tempFile->close();
//将程序当前路径设置为原来的路径
tempDir.setCurrent(currentDir);
qDebug()<<tempDir.currentPath();
}
void Widget::on_pushButton_rxset_clicked()
{
udpsocket->abort();
QString title ="FPGA UDP传图 本机端口:"+ ui->rxport->text();
setWindowTitle(title);
portStr = ui->rxport->text().toInt();
udpsocket->bind(QHostAddress::Any,portStr);//绑定当前网卡所有的ip,端口
}
void Widget::on_pushButton_clicked()
{
ui->label_rx->clear();
}
void Widget::on_pushButton_2_clicked()
{
ui->label->clear();
}
|