【mjpg-streamer】编写客户端程序,实现视频数据采集

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协议,传输的过程中可能会分包或者粘包,所以接收数据的时候,并不是接收三次那么简单。有可能第一次收到的数据既包含了头部信息,又包含了帧数据(粘包);有可能第二次接收数据的时候,只收到了部分帧数据(分包)。

总结一下,如果想实现客户端,需要完成下面的步骤:

  1. 向服务器发送请求,即发送字符串“GET /?action=stream”;
  2. 向服务器发送任意两个字节的数据;
  3. 接收服务器返回的头部信息;
  4. 开始循环;
  5. 接收服务器返回的头部信息、帧数据、尾部信息。

循环接收的时候,最好能够根据头部信息和尾部信息来确定帧数据,做到万无一失。

最后附上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摄像头项目实战👇 扫码访问

在这里插入图片描述

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页