在线时间234 小时
UID3301905
注册时间2017-1-8
NXP金币164
TA的每日心情 | 开心 2018-4-20 15:04 |
---|
签到天数: 8 天 连续签到: 1 天 [LV.3]偶尔看看II
金牌会员
 
- 积分
- 3257
- 最后登录
- 2023-7-24
|
基于MJPEG流传输的网页DEMO
使用硬件:
-IMX8开发板
-USB UVC直播摄像头(分辨率2K*30FPS)
IMX8开发板主控选取IMX8家族里面性能偏中上的NXP i.MX 8M Plus,第三方开发板为OKMX8MP-C开发板,CPU为四核A53,内存4G,我自己准备一个64G的TF卡做系统:
开发板通过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的时候提示报错:
- 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;
- }
复制代码
初始化fmt结构体时,需要将格式设置为MJPEG,不可以设置为相近的MJPG,这两个宏的数值是不同的,分辨率设置为2K即2560*1440:
- #define IMAGEWIDTH 2560
- #define IMAGEHEIGHT 1440
- struct v4l2_format fmt;
- // 3、Setting output parameter.
- fmt.type = V4L2_CAP_VIDEO_CAPTURE;
- fmt.fmt.pix.width = IMAGEWIDTH;
- fmt.fmt.pix.height = IMAGEHEIGHT;
- fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
- if (ioctl (fd_video, VIDIOC_S_FMT, &fmt) == -1)
- {
- perror("ERROR camera VIDIOC_S_FMT Failed.");
- return -1;
- }
复制代码
初始化MMAP用的tmpbuffer时,不使用V4L2常用的四路缓存机制,只MAP第一个通道的缓存,减少MMAP数据拷贝时的时间损耗:
- struct v4l2_buffer v4l2_buf;
- v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- v4l2_buf.memory = V4L2_MEMORY_MMAP;
- //for(i = 0; i < 4; i++)
- //{
- v4l2_buf.index = 0;
- if(ioctl(fd_video, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
- {
- perror("Unable to query buffer.");
- return -1;
- }
- pic.tmpbuffer = (unsigned char*)mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, fd_video, v4l2_buf.m.offset);
- if(pic.tmpbuffer == MAP_FAILED)
- {
- perror("Unable to map buffer.");
- return -1;
- }
- if(ioctl(fd_video, VIDIOC_QBUF, &v4l2_buf) < 0)
- {
- perror("Unable to queue buffer.");
- return -1;
- }
- //}
复制代码
对缓存进行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一种,还有gif png等,发送完内容头之后就是正文和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指令,从客户端使用者角度来看的效果就是网页一直在等待。
启动程序,第一个IP参数为TCP服务器IP即板子的IP,因为HTTP网页服务器是建立在板子上面的;
第二个IP参数为目标发送IP,实际上可以省略,因为TCP服务器的连接是不关心客户端IP的,只要是同一网段就能通信;
端口号50011;
- make -j4
- ./udpimage 192.168.1.2 192.168.1.7
复制代码
|
|