QT下的udp视频传输系统

QT下的udp视频传输系统

前段时间工作著

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

总体框图

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

帧结构(帧头)

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

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 控制

#define TRANS_SIZE 1024

类UdpThread

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

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()

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

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的话就不需要这么大了

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来干的,但是想想又要编译感觉好麻烦,我就去翻了一下官方手册,才发现官方是提供方法编码的.
核心代码见:

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发送数据的方法是:
    m_udpThread->m_udpSocket->writeDatagram(m_sendBuf, TRANS_SIZE,
                               QHostAddress::Broadcast,8080);
  1. 其实拿从机的ip地址不需要自己傻乎乎整个命令帧,在处理帧的时候就可以直接拿了
 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 creator4.10.1
QT(版本号)5.13

结语

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

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

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

参考资料

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

  • 6
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小何的芯像石头

谢谢你嘞,建议用用我的链接

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值