基于HTTP网页服务器和UDP上位机的MJPG码流传输

本文采用的硬件板卡为飞凌嵌入式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] , po
rt
_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)) 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; } }


QFN侧面为什么很难上锡,该如何解决?
软总线架构在实时多任务软件系统中的设计应用
机器视觉是机器人发展的重要方向,是提高机器人智能化水平的关键因素
针对校园的手机RF-SIM一卡通管理系统设计
气体分析设备是如何提高半导体产品质量的?
基于HTTP网页服务器和UDP上位机的MJPG码流传输
Thread Group公布2024年发展规划,将改善Mesh网络连接体验
端点管理会影响应用程序安全吗?
(WiFi干货)WiFi模块的TCP和UDP协议
产品系统可靠性的原理与执行
寄存器怎么赋初值啊?这电路怎么工作呢?
变革性的RFID解决方案可满足智能工厂、物流和医疗应用的需求
哪些因素影响着3D打印的成型速度?
高压变频器在钢铁烧结厂高压同步电机上的应用
多串口通信服务器
高速转换器中的PCB布局布线规则
设备控制器基本功能
Linux 编程之经典多级时间轮定时器(上)
国家标准《氢系统安全的基本要求》4月1日起实施
【世说芯品】赋能绿色未来 | 芯讯通助力能耗大户书写“绿色答卷”