本帖最后由 猫先森大仙 于 2021-2-5 15:04 编辑
一、先来看看什么是mjpg-streamer视频流:
mjpg-streamer是一个开源的视频服务器,通过摄像头采集数据,放到内存中,再通过socket把视频数据发送出去,最终在web端显示视频数据。mjpg-streamer把采集数据、socket发送数据封装成了两个动态库,一个称作输入插件,一个称作输出插件。 如果我们想做一些跟视频传输相关的项目,完全可以利用mjpg-streamer作为视频数据来源,而不用再关心底层驱动如何实现,驱动视频数据如何读取。mjpg-streamer自带压缩算法,可以把采集的原始数据压缩成jpg格式的图像数据,从而方便传输。
以上是网上搜到的说法,不是很懂,但是说了两点①:通过SOCKET传输 ②:传输的是一幅幅mjpg图像。 1.、SOCKET 图1. 图2 理解这幅图,在tcp/ip协议中,tcp通过三次握手建立起一个tcp的链接就行,具体知识自行百度或者查资料。 对应到程序里面,IP就是你连接设备的网络地址,port就是这台设备上的哪个对象。好比送信,IP地址是你们小区的地址,PORT就是你家的门牌号。再来看程序里面,IP地址就是树莓派或者WiFi模块的地址,port就是他们创建对象的端口,比如视频流传输使用8080端口, 看上图2客户端,你得创建个socket类,然后连接模块,地址192.168.1.1,端口8080,然后连接成功就可以发送和接收数据了 2.mjpg协议 关于mjpg-streamer的流程介绍网上有很多,我们只是分析下它是如何输出数据的,因为只有知道如何输出数据,才能写对应的代码去接收数据。 主要代码集中在 httpd.c 文件中。 下面我们来看树莓派或WiFi模块中的代码: 客户端想要获取服务器的视频数据,先要向服务器发起请求,简单点理解,就是先得告诉服务器,是要获取视频流,还是获取一张图片。 ![](https://img.it610.com/image/info8/c38dfe7661f441fcbd562324b40fe4eb.jpg)
于是,在成功连接服务器后,我们要做的第一步:
向服务器发送字符串 "GET /?action=stream" 为了保证数据的安全性,mjpg-streamer加上了用户名和密码,当然,是在启动服务器的时候,通过参数来决定是否要加验证。 从代码的 898 行可以看出,如果客户端发送的数据小于 2 个字节,就是跳出循环,代码继续向下走。所以得到了第二步: 向服务器发送任意小于两字节的字符串。 接着程序走到了 929 行,开始调用函数 send_stream。 send_streamer主要是向客户端返回数据,只要搞清楚返回哪些数据,那么我们的客户端程序基本就写出来了。 ![](https://img.it610.com/image/info8/7eaa4730d02140d5a5f08ae46a5cd8b8.jpg)
首先返回头部信息,这一部分没有什么有用的信息,所以我们直接接收后忽略就好。接下来进入 376 行开始死循环。
![](https://img.it610.com/image/info8/ae061eb3d4514314b9d6b6143aa6fe8c.jpg)
分别向客户端发送了三个数据:
头部信息:包含了一帧数据的大小;
帧数据:我们真正想要得到的数据;
尾部信息。好了,一帧mjpg流数据大概是这样: “content-type”+“content-length”+0XFF +0xD8+byte1+byte2+.......+byteN+0xFF+0xd9; 其中图像数据为:0XFF +0xD8+byte1+byte2+.......+byteN+0xFF+0xd9;
虽然是分为三次发送,但是因为使用的是TCP协议,传输的过程中可能会分包或者粘包,所以接收数据的时候,并不是接收三次那么简单。有可能第一次收到的数据既包含了头部信息,又包含了帧数据(粘包);有可能第二次接收数据的时候,只收到了部分帧数据(分包)。 二、代码实现 总结一下,如果想实现客户端的视频采集,需要完成下面的步骤: 1.构造QTcpSocket: mysocket_video = new QTcpSocket; 2.连接服务器端 mysocket_video->connectToHost(ui->lineEdit_IP->text(),ui->lineEdit_Port->text().toInt());//连接服务器端 if(!mysocket_video->waitForConnected(30000)) { qDebug()<<tr("error!"); } else { qDebug()<<tr("success!");
}- 3.向服务器发送请求,即发送字符串“GET /?action=stream”,之后向服务器发送任意两个字节的数据,合在一起就是;
- QString req22= "GET /?action=stream\n\n"; //0x0a 0x0a mysocket_video->write(req22.toLatin1(),strlen(req22.toLatin1()));
- 4.利用定时器,循环接收服务器返回的图像数据;
- 构造函数中初始化定时器:
- timer_crema = new QTimer; timer_crema->start(20);
- 打开视频按钮,增加槽:
- connect(timer_crema,SIGNAL(timeout()),this,SLOT(socket_Read_data()));
- 接收图像流:
- QByteArray Buffer;//原始视频图片流缓冲
- Buffer=mysocket_video->readAll(); //即“content-type”+“content-length”+0XFF +0xD8+byte1+byte2+.......+byteN+0xFF+0xd9;
- 接收服务器返回的头部信息、帧数据、尾部信息。
循环接收的时候,最好能够根据头部信息和尾部信息来确定帧数据,做到万无一失。 5.利用头部信息、帧数据、尾部信息进行图像提取,这个就考验提取算法了 QByteArray jpg_buf;//采集到的图片数组 ,即0XFF +0xD8+byte1+byte2+.......+byteN+0xFF+0xd9; 6 .显示图像: PICTURE.loadFromData(jpg_buf);//图片数据向QPixmap PICTURE传递 ui->CREMA_Show->setPixmap(PICTURE);//显示 7.到此为止,一个图像显示上位机就好了,水平有限,适合初学者,大神轻喷。
这个程序对30万像素640*480的摄像头运行没问题,但是对1280*760这样的摄像头就会很卡,而且报错:Corrupt JPEG data: premature end of data segment,希望大神看看啥原因。
|