mjpg-streamer
mjpg-streamer是一个开源的视频服务器,通过摄像头采集数据,放到内存中,再通过socket把视频数据发送出去,最终在web端显示视频数据。mjpg-streamer把采集数据、socket发送数据封装成了两个动态库,一个称作输入插件,一个称作输出插件。
使用mjpg-streamer的优势
如果我们想做一些跟视频传输相关的项目,完全可以利用mjpg-streamer作为视频数据来源,而不用再关心底层驱动如何实现,驱动视频数据如何读取。mjpg-streamer自带压缩算法,可以把采集的原始数据压缩成jpg格式的图像数据,从而方便传输。
本文就来简单的分析下,如何自己实现客户端程序。
mjpg-streamer输出流程
关于mjpg-streamer的流程介绍网上有很多,我们只是分析下它是如何输出数据的,因为只有知道如何输出数据,才能写对应的代码去接收数据。
主要代码集中在 httpd.c 文件中。
mjpg-streamer可以同时处理多个客户端(web)的连接,对于每个连接到服务器的客户端,mjpg-streamer都会创建单独的线程来处理对应的客户端。
下面开始圈重点。
客户端想要获取服务器的视频数据,先要向服务器发起请求,简单点理解,就是先得告诉服务器,是要获取视频流,还是获取一张图片。
于是,在成功连接服务器后,我们要做的第一步:
向服务器发送字符串 "GET /?action=stream"
为了保证数据的安全性,mjpg-streamer加上了用户名和密码,当然,是在启动服务器的时候,通过参数来决定是否要加验证。
从代码的 898 行可以看出,如果客户端发送的数据小于 2 个字节,就是跳出循环,代码继续向下走。所以得到了第二步:
向服务器发送任意小于两字节的字符串。
接着程序走到了 929 行,开始调用函数 send_stream。
send_streamer主要是向客户端返回数据,只要搞清楚返回哪些数据,那么我们的客户端程序基本就写出来了。
首先返回头部信息,这一部分没有什么有用的信息,所以我们直接接收后忽略就好。
接下来进入 376 行开始死循环。
分别向客户端发送了三个数据:
头部信息:包含了一帧数据的大小;
帧数据:我们真正想要得到的数据;
尾部信息。
虽然是分为三次发送,但是因为使用的是TCP协议,传输的过程中可能会分包或者粘包,所以接收数据的时候,并不是接收三次那么简单。有可能第一次收到的数据既包含了头部信息,又包含了帧数据(粘包);有可能第二次接收数据的时候,只收到了部分帧数据(分包)。
总结一下,如果想实现客户端,需要完成下面的步骤:
- 向服务器发送请求,即发送字符串“GET /?action=stream”;
- 向服务器发送任意两个字节的数据;
- 接收服务器返回的头部信息;
- 开始循环;
- 接收服务器返回的头部信息、帧数据、尾部信息。
循环接收的时候,最好能够根据头部信息和尾部信息来确定帧数据,做到万无一失。
最后附上C语言的实现代码(跟语言、平台没有关系)。
int main()
{int video_sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in video_addr;memset(&video_addr, 0, sizeof(video_addr));video_addr.sin_family = AF_INET;video_addr.sin_port = htons(8080);video_addr.sin_addr.s_addr = inet_addr("127.0.0.1");connect(video_sockfd, (struct sockaddr *)&video_addr, sizeof(video_addr));printf("connect to mjpg-streamer success\n");char *buf = (char *)malloc(102400);memset(buf, 0, 102400);strcpy(buf, "GET /?action=stream\n");send(video_sockfd, buf, strlen(buf), 0);send(video_sockfd, "f\n", 2, 0);memset(buf, 0, 102400);recv(video_sockfd, buf, BUFLEN, 0);int recv_size, pic_length = 0, p = 0;char *begin, *end;char cont_len[10] = {0};char *pic_data = (char *)malloc(102400);while (1){memset(buf, 0, 102400);recv_size = recv(video_sockfd, buf, 74, 0);if (strstr(buf, "Content-Type")){ begin = strstr(buf, "Content-Length");end = strstr(buf, "X-Timestamp");memcpy(cont_len, begin + 16, end - 2 - begin - 16);pic_length = atoi(cont_len);printf("recv head Content-Length = %d %d\n", atoi(cont_len), recv_size);memset(cont_len, 0, 10);}else {continue;}while (1){memset(buf, 0, 102400);recv_size = recv(video_sockfd, buf, pic_length, 0);if (recv_size == pic_length){memcpy(pic_data + p, buf, recv_size);p += recv_size;//处理图片数据p = 0;memset(pic_data, 0, 102400);pic_length = 0;break;}else{memcpy(pic_data + p, buf, recv_size);pic_length = pic_length - recv_size;p += recv_size;}}recv(video_sockfd, buf, 24, 0);}}
详细视频教程 智能WiFi摄像头项目实战? 扫码访问