0%

QT下的udp视频传输系统

QT下的udp视频传输系统

前段时间工作著

本文只为记录思路,若想认真学会开发请参考 参考资料官方文档

@[toc]

总体框图

框图是从需求里面来的,那我自然是奔着视频流传输去的,但是在底层的硬件配置摄像头的过程中,我们可以通过IIC去重设一些参数,比如宽高啊,白平衡啊什么的.所以我在这里的设计是这样的.
框图
就是分两条线程去写,主线程ui,新建一条UDP线程,不然在视频流传输的时候程序会很容易crash.
然后由于我想在一个路由器中搞完,不用自己指定ip,所以获取一下ip地址这样子

帧结构(帧头)

帧结构最初可见参考资料,我增删了一些东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct ImageFrameHead{
int funCode; // 功能码
unsigned int uTransFrameHdrSize; //!sizeof(Head)
unsigned int uTransFrameSize; //!sizeof(Head) + Data Size

//数据帧变量
unsigned int uDataFrameSize; //数据帧的总大小
unsigned int uDataFrameTotal; //一帧数据被分成传输帧的个数
unsigned int uDataFrameCurr; //数据帧当前的帧号
unsigned int uDataInFrameOffset; //数据帧在整帧的偏移
};

struct CmdFrameHead{
unsigned int funCode; // 功能码
unsigned int w;
unsigned int h;
//备用变量
unsigned int ip_1;
unsigned int ip_2;
unsigned int ip_3;
unsigned int ip_4;
};

这里我们跟当初学UDP一样进行分片处理,然后用片偏移分段写入到图片的Buffer中,然后CmdFrameHead里面是可以动态配置摄像头的,但是因为当时赶工程,这里就用从机ip打回来了(文末有点说明),而且保持了命令帧跟视频帧头等长

所以整个帧就是帧头加数据组成,总的传输长度由TRANS_SIZE 控制

1
#define TRANS_SIZE 1024

类UdpThread

由于在这里需要再拉一条线程干活,新建一个UDPSocket,不断接收消息和接收视频流.那就干脆新建一个类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class UdpThread : public QThread
{
Q_OBJECT
public:
explicit UdpThread(QObject *parent = nullptr);
~UdpThread();
QUdpSocket *m_udpSocket;


protected:
virtual void run() ;

signals:
void sigRecvok(char *buf, int len);
void cmdRecvok(CmdFrameHead *buf);


public slots:
void slotRecv();
void slotChangewh(int w,int h);


private:
char *m_buf;
};

这里需要注册两个信号两个槽:

  1. 信号sigRecvok(char *buf, int len)记录已经传输完一帧
  2. 信号cmdRecvok(CmdFrameHead *buf) 下位机发来贺电
  3. 槽slotRecv(),在网卡上截获到UDP信息(即readyRead())
  4. 槽slotChangewh(),上位机希望改变宽高

另外需要注意的就是,在构折函数里面,要关掉UDPSocket和free掉m_buf(^_^我炸了)

槽slotRecv()

在这里我们需要对接收到的帧进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void UdpThread::slotRecv()
{
char *recvBuf = new char[TRANS_SIZE];
memset(recvBuf, 0, TRANS_SIZE);
while(m_udpSocket->hasPendingDatagrams())
{
memset(recvBuf, 0, TRANS_SIZE);
qint64 size = m_udpSocket->pendingDatagramSize();
// qDebug()<<"size"<<size;
m_udpSocket->readDatagram(recvBuf, TRANS_SIZE);
ImageFrameHead *mes = (ImageFrameHead *)recvBuf;
//qDebug()<<"mes"<<mes->funCode;
if(mes->funCode == 24){
memcpy(m_buf + mes->uDataInFrameOffset,(recvBuf+ sizeof(ImageFrameHead)),mes->uTransFrameSize);
if(mes ->uTransFrameSize <TRANS_SIZE-sizeof(ImageFrameHead)){
//qDebug() << "人傻了";
emit sigRecvok(m_buf, mes->uDataFrameSize);
}
// qDebug() << mes->funCode << mes->uTransFrameHdrSize
// << mes->uTransFrameSize
// << mes->uDataFrameSize << mes->uDataFrameTotal
// << mes->uDataFrameCurr << mes->uDataInFrameOffset;
}
else if (mes->funCode == 26)
emit cmdRecvok((CmdFrameHead *)mes);
// else
//qDebug() << mes->funCode <<
// mes->uTransFrameHdrSize << mes->uTransFrameSize
// << mes->uDataFrameSize << mes->uDataFrameTotal
// << mes->uDataFrameCurr << mes->uDataInFrameOffset;
}
delete[] recvBuf;
}

槽slotChangewh(int w,int h)

在这里由于重设了宽和高,所以我们需要重设m_buf的大小,这里乘3是因为我用的是RGB888,所以需要考虑3通道,如果是ycr422的话就不需要这么大了

1
2
3
4
5
6
void UdpThread::slotChangewh(int w,int h)
{
free(m_buf);
m_buf = new char[w*h*3];
memset(m_buf, 0, w*h*3);
}

图片显示

其实在上面注册完信号和槽之后思路就很清晰了,这里要处理的主要是底层摄像头传回来的byte数据我们要怎么”显示”.
因为不同于其他抽象层次的传输,我们传上来的数据是没有编码的.看见有用opencv来干的,但是想想又要编译感觉好麻烦,我就去翻了一下官方手册,才发现官方是提供方法编码的.
核心代码见:

1
2
3
4
5
6
7
8
9
10
11
12
void Udp_Widget::slotRecv(char* buf,int len)
{
frame_count++;
int w = ui->m_combo_width->currentText().toInt();
int h = ui->m_combo_height->currentText().toInt();
QImage *img = new QImage((uchar*)buf,w,h,QImage::Format_RGB888);
QPixmap pixmap;
pixmap.convertFromImage(*img);
ui->m_pixmap->setPixmap(pixmap);
ui->m_label_recv->setNum(frame_count);
emit sigwhChanged(w, h);
}

那顺便记录一下,QT还支持多少这些像素格式编码.哎啊自己看手册吧
format
这里也引来一个毛病,就是如果也只是在一个线程中来编码的话,有时候会因为fps过高而爆炸,正常做法是做一个队列,再动态新建线程来处理,但是 赶工程 然后当时也没这个需求就没做了:)

一些设计细节

  1. 绑定的端口是8080,用之前要改win的防火墙规则

  2. 用udp发送数据的方法是:

    1
    2
    m_udpThread->m_udpSocket->writeDatagram(m_sendBuf, TRANS_SIZE,
    QHostAddress::Broadcast,8080);
  3. 其实拿从机的ip地址不需要自己傻乎乎整个命令帧,在处理帧的时候就可以直接拿了

    1
    2
    3
    4
    5
    char buf[1024] = {0};
    QHostAddress addr; //对方的ip
    quint16 port; //对方的端口
    qint64 len;
    len = udpsocket->readDatagram(buf, sizeof(buf), &addr, &port);

这样做的原因是因为在zynq中的lwip也是我设计的,当时在做的时候不知道发点啥,就回传一下ip看看误码怎么样
4. 功能码funcode我的定义是这样的
- funcode = 24 从->主,视频帧
- funcode = 25 主->从,命令帧
- funcode = 26 从->主,回复帧
5. 做了个截图功能,会保存在当前目录下

成果

res
由于赶工原因,界面ui我们就不讲究了吧…

版本信息

设计环境 版本号
QT creator 4.10.1
QT(版本号) 5.13

结语

之前想着用mfc来写的,但是考虑到mfc那个socket是真的过时,就放弃了
这个因为不是开发重点(硬件那边的设计难多了…),所以这个就是一直保持 能用就行的状态.但是还是记录一下,需要源码的可以联系我.

我还单独写了收发端,有需要的朋友也可以联系我,那个做的就是为了模拟硬件环境的收发,因为当时联调BUG太多了,不知道是哪方出了问题

另 接fpga,qt,嵌入式的外包,有兴趣可联系.
如果你觉得有丶收获的话

参考资料

Qt5&OpenCV3 UDP协议实现实时视频传输与通信
Qt学习之路十二——利用UDP进行通信
Qt通过UDP传图片,实现自定义分包和组包
Qt5–UDP图片传输并显示