百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

实测 | i.MX8MP 基于HTTP网页服务器和UDP上位机的MJPG码流传输

liebian365 2024-12-31 12:46 26 浏览 0 评论


作者|donatello1996

来源 | 电子发烧友

题图|飞凌嵌入式

本文采用的硬件板卡为飞凌嵌入式OKMX8MP-C开发板,系统版本Linux5.4.70+Qt5.15.0,主要介绍基于HTTP网页服务器和UDP上位机的MJPG码流传输。

MJPG格式作为一种持续传输的视频码流,在远程监控领域中应用较广,而实现这种远程监控的第三方应用最常见的有两种:浏览器HTTP网页、UDP上位机。

两者各有优势,对比鲜明,其中:

  • UDP上位机:传输效率高,上位机编写方便。
  • HTTP网页方式:客户端无需安装上位机,只需要一个浏览器应用即可;客户端访问服务器支持跨平台支持,无论是电脑、平板、手机,还是Linux系统、Windows系统及安卓系统都可以,只要有浏览器应用都可访问,而UDP上位机则受限于目标平台,不易移植。

这两种应用各有优缺点,对于嵌入式开发者来说,两者都必须掌握。

一、HTTP网页服务器

先说下HTTP网页服务器获取MJPG码流的代码,首先是OKMX8MP-C在开发板端建立TCP服务器:

int TCP_Server_Found(socklen_t* socket_found , char* ip , int port)
{
    struct sockaddr_in servaddr;
    socklen_t addrsize = sizeof(struct sockaddr);
    bzero(&servaddr , sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(ip);
    servaddr.sin_port = htons(port);
    int ret;
    IF( (*socket_found = socket(AF_INET , SOCK_STREAM , 0)) == -1)
        {
            printf("Create socket error: %s (errno :%d)\n",strerror(errno),errno);
            return -1;
        }
    int on = 1;
    if(setsockopt(*socket_found , SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
        printf("setsockopt error\n");
    }
    ret = bind(*socket_found , (struct sockaddr *)&servaddr , addrsize);
    if(ret == -1)
    {
            printf("Tcp bind faiLED!\n");
            return -1;
    }
    if(listen(*socket_found , 5) == -1)
    {
            printf("Listen failed!\n");
            return -1;
    }
    return 0;
}

其中setsockopt()函数是可选的,一般只用于规避socket()函数的建立错误。

建立了TCP服务器后,返回的socklen_t型实参在后面的HTTP网页服务器中需要用到。

HTTP网页服务器所属的TCP操作是需要另起轮询线程来让客户端进行accept()握手操作的,accept()之前的listen()倒是只需要执行一次即可,accept()握手操作和recv()接收操作需要创建一个死循环线程:

pthread_create(&tid_tcp_web_recv , NULL , Thread_TCP_Web_Recv , NULL);
void * Thread_TCP_Web_Recv(void *arg)
{
。。。
while(1)
{
            fd_socket_conn = accept(socket_web_server , (struct sockaddr *)&sockaddr_in_conn , &addrsize);
           printf("fd_socket_conn = accept()\n");
    。。。
    recv(fd_socket_conn , recvbuf , 1000 , 0);
}
。。。
}

MJPG帧可以使用Grab操作获取,获取到的MJPG帧需要在TCP线程中读,在Grab操作线程中写,这种被多个线程访问的资源需要加锁防止读写冲突,即资源被Grab操作写入时,需要上锁,不允许其它线程访问,操作完成时需要解锁,允许其它线程访问:

 pthread_mutex_lock(&pmt);
    pic_tmpbuffer = pic.tmpbuffer;
    pic.tmpbytesused = buff.bytesused;
    pic_tmpbytesused = pic.tmpbytesused;
    pthread_cond_broadcast(&pct);
    pthread_mutex_unlock(&pmt);

线程互斥锁使用之前需要初始化:

pthread_mutex_t pmt;
pthread_cond_t pct;
int main(int argc, char* argv[])
{
...
TCP_Server_Found(&socket_web_server , (char*)argv[2] , PORT_TCP);
pthread_mutex_init(&pmt , NULL);
    pthread_create(&tid_tcp_web_recv , NULL , Thread_TCP_Web_Recv , NULL);
    pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send , NULL);
...
    while(1)
    {
        V4l2_Grab_Mjpeg(false , MJPEG_FILE_NAME);
...
    }
...
}

然后是发送的细节,发送图片文件之前,需要先发送HTTP标准头,这个相当于给发送图片或者其它类型的流数据铺路:

#define STD_HEADER "Connection: close\r\n" \
    "Server: MJPG-Streamer/0.2\r\n" \
    "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" \
    "Pragma: no-cache\r\n" \
    "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n"
#define BOUNDARY "boundarydonotcross"
    printf("preparing header\n");
    sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
            "Access-Control-Allow-Origin: *\r\n" \
            STD_HEADER \
            "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \
            "\r\n" \
            "--" BOUNDARY "\r\n");
    if(write(fd, buffer, strlen(buffer)) < 0)
    {
        free(frame);
        return;
    }

发送完HTTP标准头之后,就需要发送内容头(Content-Type),这处的Content-Type为image/jpeg,同样,HTTP标准协议里面image支持的类型远不止jpeg一种,发送完内容头之后就是正文和boundary结尾,这样帧完整的HTTP头发送到指定的TCP GET地址,就会在浏览器中显示刚刚发送的图片:

 sprintf(buffer, "Content-Type: image/jpeg\r\n" \
                "Content-Length: %d\r\n" \
                "X-Timestamp: %d.%06d\r\n" \
                "\r\n", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);
        printf("sending intemdiate header\n");
        if(write(fd, buffer, strlen(buffer)) < 0)
            break;
        printf("sending frame\n");
        if(write(fd, frame, frame_size) < 0)
            break;
        printf("sending boundary\n");
        sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
        if(write(fd, buffer, strlen(buffer)) < 0)
            break;

另外需要说明的是,TCP服务器线程在发送MJPEG流的时候是死循环发送的,因此TCP客户端在发送完GET指令之后,就会收到TCP服务器循环发送的图像缓存,TCP客户端会陷入忙等待状态无法再对外发送任何GET或者POST指令,从客户端使用者角度来看的效果就是网页一直在等待。


?

二、UDP上位机

UDP发送操作,同样需要先建立UDP Socket:

int UDP_Send_Found(socklen_t* socket_found , struct sockaddr_in *addr , char* ip , int port)
{
    *socket_found = socket(AF_INET, SOCK_DGRAM, 0);
    if(*socket_found == (~0))
    {
        printf("Create udp send socket failed!\n");
        return -1;
    }
    addr->sin_family = AF_INET;
    addr->sin_addr.s_addr = inet_addr(ip);
    addr->sin_port = htons(port);
    memset(addr->sin_zero, 0, 8);
    return 0;
}

而UDP文件发送则要比HTTP发送简单得多,只需要将文件切片,每一片为固定长度的UDP帧长度,逐帧发送即可:

while(fend > 0)
{
memset(picture.data , 0 , sizeof(picture.data));
fread(picture.data , UDP_FRAME_LEN , 1, fp);
if(fend >= UDP_FRAME_LEN)
{
picture.length = UDP_FRAME_LEN;
picture.fin = 0;
}
else
{
picture.length = fend;
picture.fin = 1;
}
//printf("sendbytes = %d \n",sendbytes);
sendbytes = sendto(socket_send, (char *)&picture, sizeof(struct Package), 0, (struct sockaddr*)&addr,addr_len);
if(sendbytes == -1)
{
printf("Send Picture Failed!d\n");
return -1;
}
else
{
fend -= UDP_FRAME_LEN;
}
}

------------------------------

作者简介:

donatello1996,某大型企业资深嵌入式工程师,电子发烧友论坛技术大牛,同时也是飞凌嵌入式多年铁粉,曾基于飞凌多款板卡产出过优质测评文章或使用心得。本期三篇文章为donatello1996在使用OKMX8MP-C开发板过程中精心产出的干货,在此对donatello1996表示感谢。

相关推荐

go语言也可以做gui,go-fltk让你做出c++级别的桌面应用

大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...

旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗

这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...

codeblocks和VS2019下的fltk使用中文

在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...

FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库

FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...

中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux

IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...

Linux 5.13内核有望合并对苹果M1处理器支持的初步代码

预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...

Ubuntu系统下COM口测试教程(ubuntu port)

1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...

湖北嵌入式软件工程师培训怎么选,让自己脱颖而出

很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...

新阁上位机开发---10年工程师的Modbus总结

前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...

创建你的第一个可运行的嵌入式Linux系统-5

@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...

如何在Linux下给zigbee CC2530实现上位机

0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...

Python实现串口助手 - 03串口功能实现

 串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...

为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口

UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...

同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理

串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...

嵌入式linux为什么可以通过PC上的串口去执行命令?

1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...

取消回复欢迎 发表评论: