查看: 2677|回复: 8

[主题月] NXP主题月——基于MJPEG流传输的网页DEMO

[复制链接]
  • TA的每日心情
    开心
    2018-4-20 15:04
  • 签到天数: 8 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    49

    主题

    188

    帖子

    1

    金牌会员

    Rank: 6Rank: 6

    积分
    3257
    最后登录
    2023-7-24
    发表于 2021-12-13 14:53:27 | 显示全部楼层 |阅读模式
    基于MJPEG流传输的网页DEMO

    使用硬件:
    -IMX8开发板
    -USB UVC直播摄像头(分辨率2K*30FPS)

    IMX8开发板主控选取IMX8家族里面性能偏中上的NXP i.MX 8M Plus,第三方开发板为OKMX8MP-C开发板,CPU为四核A53,内存4G,我自己准备一个64G的TF卡做系统:
    44.JPG



    44.JPG


    46.jpg





    45.jpg

    开发板通过USB3.0接口连接一个2K30FPS的高清直播摄像头,是免驱的,即插即用,即对/dev/videox设备进行操作,内容与之前的智能家居系统大致相同,主要是对传输格式和效率做了大幅度优化。智能家居Demo帖子地址:
    https://www.nxpic.org.cn/module/forum/thread-622291-1-1.html


    建立TCP Socket的步骤相同,可增加可选内容,
    setsockopt(*socket_found , SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)
    防止建立socket的时候提示报错:
    1. int TCP_Server_Found(socklen_t* socket_found , char* ip , int port)
    2. {
    3.     struct sockaddr_in servaddr;
    4.     socklen_t addrsize = sizeof(struct sockaddr);

    5.     bzero(&servaddr , sizeof(servaddr));
    6.     servaddr.sin_family = AF_INET;
    7.     servaddr.sin_addr.s_addr = inet_addr(ip);
    8.     servaddr.sin_port = htons(port);

    9.     int ret;
    10.     if( (*socket_found = socket(AF_INET , SOCK_STREAM , 0)) == -1)
    11.         {
    12.             printf("Create socket error: %s (errno :%d)\n",strerror(errno),errno);
    13.             return -1;
    14.         }

    15.     int on = 1;
    16.     if(setsockopt(*socket_found , SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    17.     {
    18.         printf("setsockopt error\n");
    19.     }
    20.    
    21.     ret = bind(*socket_found , (struct sockaddr *)&servaddr , addrsize);
    22.     if(ret == -1)
    23.     {
    24.             printf("Tcp bind failed!\n");
    25.             return -1;
    26.     }

    27.     if(listen(*socket_found , 5) == -1)
    28.     {
    29.             printf("Listen failed!\n");
    30.             return -1;
    31.     }
    32.     return 0;
    33. }
    复制代码


    初始化fmt结构体时,需要将格式设置为MJPEG,不可以设置为相近的MJPG,这两个宏的数值是不同的,分辨率设置为2K即2560*1440:
    1.     #define  IMAGEWIDTH    2560
    2. #define  IMAGEHEIGHT   1440

    3. struct v4l2_format fmt;
    4. // 3、Setting output parameter.
    5.     fmt.type = V4L2_CAP_VIDEO_CAPTURE;
    6.     fmt.fmt.pix.width = IMAGEWIDTH;
    7.     fmt.fmt.pix.height = IMAGEHEIGHT;
    8.     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    9.     if (ioctl (fd_video, VIDIOC_S_FMT, &fmt) == -1)
    10.     {
    11.         perror("ERROR camera VIDIOC_S_FMT Failed.");
    12.         return -1;
    13.     }
    复制代码


    初始化MMAP用的tmpbuffer时,不使用V4L2常用的四路缓存机制,只MAP第一个通道的缓存,减少MMAP数据拷贝时的时间损耗:
    1.    struct v4l2_buffer v4l2_buf;
    2.     v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    3.     v4l2_buf.memory = V4L2_MEMORY_MMAP;
    4.     //for(i = 0; i < 4; i++)
    5.     //{
    6.         v4l2_buf.index = 0;
    7.         if(ioctl(fd_video, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
    8.         {
    9.             perror("Unable to query buffer.");
    10.             return -1;
    11.         }

    12.         pic.tmpbuffer = (unsigned char*)mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, fd_video, v4l2_buf.m.offset);
    13.         if(pic.tmpbuffer == MAP_FAILED)
    14.         {
    15.              perror("Unable to map buffer.");
    16.              return -1;
    17.         }
    18.         if(ioctl(fd_video, VIDIOC_QBUF, &v4l2_buf) < 0)
    19.         {
    20.             perror("Unable to queue buffer.");
    21.             return -1;
    22.         }
    23.     //}
    复制代码


    对缓存进行Grab操作时,添加线程互斥锁防止缓存数据被同时读写时不同步,产生画面撕裂的结果,若不增加互斥锁,帧数能更高,但是画面闪动撕裂情况非常严重,加了互斥锁之后就没有闪动或者撕裂了:
    1.     pthread_mutex_lock(&pmt);
    2.    
    3.     pic_tmpbuffer = pic.tmpbuffer;
    4.     pic.tmpbytesused = buff.bytesused;
    5.     pic_tmpbytesused = pic.tmpbytesused;

    6.     pthread_cond_broadcast(&pct);
    7.     pthread_mutex_unlock(&pmt);
    复制代码


    线程互斥锁使用之前需要初始化:
    1. pthread_mutex_t pmt;
    2. pthread_cond_t pct;

    3. int main(int argc, char* argv[])
    4. {
    5. ...
    6. TCP_Server_Found(&socket_web_server , (char*)argv[2] , PORT_TCP);
    7. pthread_mutex_init(&pmt , NULL);

    8.     pthread_create(&tid_tcp_web_recv , NULL , Thread_TCP_Web_Recv , NULL);

    9.     pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send , NULL);
    10. ...
    11.     while(1)
    12.     {
    13.         V4l2_Grab_Mjpeg(false , MJPEG_FILE_NAME);
    14. ...
    15.     }
    16. ...
    17. }
    复制代码

    然后是发送的细节,发送图片文件之前,需要先发送HTTP标准头,这个相当于给发送图片或者其它类型的流数据铺路:
    1. #define STD_HEADER "Connection: close\r\n" \
    2.     "Server: MJPG-Streamer/0.2\r\n" \
    3.     "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" \
    4.     "Pragma: no-cache\r\n" \
    5.     "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n"

    6. #define BOUNDARY "boundarydonotcross"

    7.     printf("preparing header\n");
    8.     sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
    9.             "Access-Control-Allow-Origin: *\r\n" \
    10.             STD_HEADER \
    11.             "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \
    12.             "\r\n" \
    13.             "--" BOUNDARY "\r\n");

    14.     if(write(fd, buffer, strlen(buffer)) < 0)
    15.     {
    16.         free(frame);
    17.         return;
    18.     }
    复制代码


    发送完HTTP标准头之后,就需要发送内容头(Content-Type),这处的Content-Type为image/jpeg,同样,HTTP标准协议里面image支持的类型远不止jpeg一种,还有gif png等,发送完内容头之后就是正文和boundary结尾,这样帧完整的HTTP头发送到指定的TCP GET地址,就会在浏览器中显示刚刚发送的图片:
    1.         sprintf(buffer, "Content-Type: image/jpeg\r\n" \
    2.                 "Content-Length: %d\r\n" \
    3.                 "X-Timestamp: %d.%06d\r\n" \
    4.                 "\r\n", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);

    5.         printf("sending intemdiate header\n");
    6.         if(write(fd, buffer, strlen(buffer)) < 0)
    7.             break;

    8.         printf("sending frame\n");
    9.         if(write(fd, frame, frame_size) < 0)
    10.             break;

    11.         printf("sending boundary\n");
    12.         sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
    13.         if(write(fd, buffer, strlen(buffer)) < 0)
    14.             break;
    复制代码

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


    启动程序,第一个IP参数为TCP服务器IP即板子的IP,因为HTTP网页服务器是建立在板子上面的;
    第二个IP参数为目标发送IP,实际上可以省略,因为TCP服务器的连接是不关心客户端IP的,只要是同一网段就能通信;
    端口号50011;

    1. make -j4
    2. ./udpimage 192.168.1.2 192.168.1.7
    复制代码



    47.JPG

    47.JPG

    Screenshot_2021-12-16-23-53-51-913_org.mozilla.firefox.png


    今天心情不错
    回复

    使用道具 举报

  • TA的每日心情
    开心
    3 天前
  • 签到天数: 1503 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    97

    主题

    4691

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    10088
    最后登录
    2025-7-29
    发表于 2021-12-13 15:04:04 | 显示全部楼层
    亲 内容呢~~
    该会员没有填写今日想说内容.
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2018-4-20 15:04
  • 签到天数: 8 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    49

    主题

    188

    帖子

    1

    金牌会员

    Rank: 6Rank: 6

    积分
    3257
    最后登录
    2023-7-24
     楼主| 发表于 2021-12-13 15:07:52 | 显示全部楼层

    在编辑呢,急啥啊老铁
    今天心情不错
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3899

    主题

    7512

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39622
    最后登录
    2025-7-31
    发表于 2021-12-13 15:58:19 | 显示全部楼层
    donatello1996 发表于 2021-12-13 15:07
    在编辑呢,急啥啊老铁

    看来网友还是期待的
    qiandao qiandao
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 20:46
  • 签到天数: 1853 天

    连续签到: 4 天

    [LV.Master]伴坛终老

    203

    主题

    3万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    112646
    最后登录
    2025-7-31
    发表于 2021-12-14 09:29:55 | 显示全部楼层
    传说中的主频高达1.8GHz的plus
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    3 天前
  • 签到天数: 1503 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    97

    主题

    4691

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    10088
    最后登录
    2025-7-29
    发表于 2021-12-14 14:02:52 | 显示全部楼层
    这帖子瞬间高大上了
    攀不起了
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    昨天 15:18
  • 签到天数: 1703 天

    连续签到: 120 天

    [LV.Master]伴坛终老

    23

    主题

    1万

    帖子

    1

    金牌会员

    Rank: 6Rank: 6

    积分
    15820
    最后登录
    2025-7-31
    发表于 2021-12-14 15:25:26 | 显示全部楼层
    8MP老牛了, 比日天还牛, 2.3T的算力. 另外楼主会介绍下用uuu烧写内核不?
    跟着日天混,三天饱九顿!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2018-4-20 15:04
  • 签到天数: 8 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    49

    主题

    188

    帖子

    1

    金牌会员

    Rank: 6Rank: 6

    积分
    3257
    最后登录
    2023-7-24
     楼主| 发表于 2021-12-14 16:14:58 | 显示全部楼层
    sumoon_yao 发表于 2021-12-14 15:25
    8MP老牛了, 比日天还牛, 2.3T的算力. 另外楼主会介绍下用uuu烧写内核不?

    飞凌提供的uuu烧写镜像是内核+dtbs+文件系统一体的,需要在烧录完毕之后自行覆盖
    今天心情不错
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3899

    主题

    7512

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39622
    最后登录
    2025-7-31
    发表于 2021-12-16 10:36:45 | 显示全部楼层
    有没有视频或者图片展示
    qiandao qiandao
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-8-1 00:00 , Processed in 0.102285 second(s), 28 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表