本帖最后由 飞凌-marketing 于 2022-1-27 11:00 编辑 8 `5 ]4 ?9 q4 V! k* {3 J# ^
3 d" ^( ]) i- G9 ?& g; s, q4 U

- Z4 V9 }- O5 t* L1 B作者|donatello1996 来源 | 电子发烧友 题图|飞凌嵌入式 iMX8MPlus 核心板: https://www.forlinx.com/product/136.html) E2 c' w: F- x4 s
& x. P$ z/ Y$ y- J本文采用的硬件板卡为飞凌嵌入式OKMX8MP-C开发板,系统版本Linux5.4.70+Qt5.15.0,主要介绍基于HTTP网页服务器和UDP上位机的MJPG码流传输。 MJPG格式作为一种持续传输的视频码流,在远程监控领域中应用较广,而实现这种远程监控的第三方应用最常见的有两种:浏览器HTTP网页、UDP上位机。 + W: Q% g: M% d S

% Z$ i' _( f- b+ n6 I% i7 w; L两者各有优势,对比鲜明,其中: 这两种应用各有优缺点,对于嵌入式开发者来说,两者都必须掌握。 5 A4 ^! Y" _ ~& _* m t* ~
一、HTTP网页服务器! B/ G+ r5 f6 Q$ ~* P
先说下HTTP网页服务器获取MJPG码流的代码,首先是OKMX8MP-C在开发板端建立TCP服务器: - int TCP_Server_Found(socklen_t* socket_found , char* ip , int port)
! C" D* v$ M9 K$ ^- N9 n( P - { T, M" y) ~ U2 }
- struct sockaddr_in servaddr;8 {+ J" i( O- V, n: T
- socklen_t addrsize = sizeof(struct sockaddr);
- j9 o9 E+ U- Z% m q7 ~ - bzero(&servaddr , sizeof(servaddr));' ^, m! o5 B' T ]- o+ k" s
- servaddr.sin_family = AF_INET;2 `" }- a! g' Z7 V, w, d
- servaddr.sin_addr.s_addr = inet_addr(ip);; u" L& J- ^, u) N
- servaddr.sin_port = htons(port);
$ N7 M1 c& U: Y! p6 V5 v% K* t- n - int ret;
" s) W; W4 N, d& e4 @' R4 X. W - IF( (*socket_found = socket(AF_INET , SOCK_STREAM , 0)) == -1)- V% U8 p& @+ m: ]
- {
$ S* o- ^/ {" c% U: S - printf("Create socket error: %s (errno :%d)\n",strerror(errno),errno);8 n. N* D/ W/ S
- return -1;: P9 y" @' K9 q* o! f* @8 ~
- }- s: o! T! q# K" ?
- int on = 1;
) E1 l# T+ V- u3 h' \ - if(setsockopt(*socket_found , SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
. T( |' O* a# R - {! m( ^7 ^5 g6 N
- printf("setsockopt error\n");) v( q$ o4 u& G" \, \& M1 o
- }
$ y. E/ p& C0 w - ret = bind(*socket_found , (struct sockaddr *)&servaddr , addrsize);
; \: @9 X& S3 | - if(ret == -1)
% K Q: O3 t, j+ K. g - {
) e+ R# R$ W" y# p. H$ u; J - printf("Tcp bind faiLED!\n");# h- O: A6 U! S( N/ K
- return -1;" H1 i" `: E2 e6 Y
- }
; @' C7 ~9 U ]- i0 `0 B8 H - if(listen(*socket_found , 5) == -1)3 r, r' n8 s/ @ a
- {& p! ?3 O! `0 t& |6 @/ h3 @
- printf("Listen failed!\n");, t5 m# }( T$ f" `( L0 X( I# y% ~
- return -1;) \3 E+ j) q9 ]- J
- }- V1 R3 W9 F. m
- return 0;
' } F6 T$ _8 e1 D: I/ `+ T - }
复制代码其中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);
9 `& n- q; o/ | J9 R2 C - void * Thread_TCP_Web_Recv(void *arg)
6 [5 A- h/ J7 O2 {" ^1 ^! \ - {
p5 W0 a: _, J% [2 O* H3 u! i - 。。。
' N8 N, e `$ m! P8 R$ A - while(1)
- S0 |- [- F% `" H2 D. d - {1 P" w( U6 h! w8 D
- fd_socket_conn = accept(socket_web_server , (struct sockaddr *)&sockaddr_in_conn , &addrsize);' ^- k+ l' C& ]3 v5 e. J2 ]
- printf("fd_socket_conn = accept()\n");8 l4 s$ e; A% H
- 。。。! H) l+ A* f+ r% `6 k0 s1 S, ]
- recv(fd_socket_conn , recvbuf , 1000 , 0);
( n* k; T3 c9 o W0 V - }; b# R' A1 ?0 N# [/ i) V# e
- 。。。2 ^' C: M) O" ?3 q2 }- s- |
- }
复制代码MJPG帧可以使用Grab操作获取,获取到的MJPG帧需要在TCP线程中读,在Grab操作线程中写,这种被多个线程访问的资源需要加锁防止读写冲突,即资源被Grab操作写入时,需要上锁,不允许其它线程访问,操作完成时需要解锁,允许其它线程访问: - pthread_mutex_lock(&pmt);
% b+ v1 D+ S: E - pic_tmpbuffer = pic.tmpbuffer;+ ^& S/ H5 e2 U! e' F
- pic.tmpbytesused = buff.bytesused;/ `( r( b4 ~. R0 D7 l; ~+ w5 C
- pic_tmpbytesused = pic.tmpbytesused;2 b$ `. |7 O$ b, C
- pthread_cond_broadcast(&pct); F& ? W% R% k0 |$ x) Z
- pthread_mutex_unlock(&pmt);
复制代码线程互斥锁使用之前需要初始化: - pthread_mutex_t pmt;
1 B, W( V& w k+ v - pthread_cond_t pct;
, |: v# ] N5 z5 H - int main(int argc, char* argv[])
3 W3 z, I, B4 |- G - {
; K/ @* s& v6 y) d2 l - ...* p9 w; u# O& y- ?) C
- TCP_Server_Found(&socket_web_server , (char*)argv[2] , PORT_TCP);' _+ s; y8 K' y% s1 A$ s1 H2 y
- pthread_mutex_init(&pmt , NULL);
) K+ _$ S" l/ y1 M - pthread_create(&tid_tcp_web_recv , NULL , Thread_TCP_Web_Recv , NULL);
$ ]/ w+ R. J: {1 U$ Z - pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send , NULL);, N# g& X% z0 q& ~/ Z
- ...
5 T8 f' m. [+ |9 Y1 J4 Q - while(1)
7 j+ {5 h4 \' {' C; J0 W5 D7 z - {9 Y _: L1 d0 L/ e
- V4l2_Grab_Mjpeg(false , MJPEG_FILE_NAME);
7 X9 {$ U; w. L% b# G7 v - ...
7 r9 F9 q' c" |$ c - }3 \3 O+ `' O: L0 ]/ z: l" S
- ...
: m8 _: T. X" r1 D: X - }
复制代码然后是发送的细节,发送图片文件之前,需要先发送HTTP标准头,这个相当于给发送图片或者其它类型的流数据铺路: - <p style="box-sizing: border-box; border: 0px; vertical-align: baseline; line-height: 26px;"><span style="text-indent: 32px;">
) i P2 A- B4 M; t h& [ - </span></p><p style="box-sizing: border-box; border: 0px; vertical-align: baseline; line-height: 26px;"></p>
复制代码- #define STD_HEADER "Connection: close\r\n" \# _8 C1 ~7 ?. b/ R1 @* D% E
- "Server: MJPG-Streamer/0.2\r\n" \
, |- u$ ?# v! F - "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" \
e" M- {: V" ~; E& r - "Pragma: no-cache\r\n" \
* B: k, J1 R0 x% T( ? - "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n"
7 N4 Q' J. y9 V6 ~: [8 D+ y! L+ [ - #define BOUNDARY "boundarydonotcross"$ N8 O/ q4 A5 O# V
- printf("preparing header\n");
4 C( N4 h$ r; s% a - sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
V6 z/ g6 y& q - "Access-Control-Allow-Origin: *\r\n" \
8 ?/ g3 }* G ^ - STD_HEADER \
v0 B( o0 V) \+ ?9 }" c$ @ - "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \) x3 f. i0 }7 k
- "\r\n" \/ J/ `& {3 g8 Y4 A+ M" G
- "--" BOUNDARY "\r\n");
( R" ~* t: P: z$ G - if(write(fd, buffer, strlen(buffer)) < 0)
( N% b. o( [9 m: J0 S+ C - {3 r5 F2 s+ b9 G1 z0 }/ s
- free(frame);6 o% D+ T8 M. I3 f7 X2 s* q# a. i9 B
- return;, a3 X" Q ~/ S
- }
复制代码发送完HTTP标准头之后,就需要发送内容头(Content-Type),这处的Content-Type为image/jpeg,同样,HTTP标准协议里面image支持的类型远不止jpeg一种,发送完内容头之后就是正文和boundary结尾,这样帧完整的HTTP头发送到指定的TCP GET地址,就会在浏览器中显示刚刚发送的图片: - <pre class="prettyprint lang-cpp" style="box-sizing: border-box; font-family: Monaco, Menlo, Consolas, "Courier New", monospace; font-size: 16px; white-space: pre-wrap; line-height: 1.38462; color: rgb(51, 51, 51); word-break: break-all; background-color: rgb(245, 245, 245); border: 0px; border-radius: 4px; vertical-align: baseline;"> sprintf(buffer, "Content-Type: image/jpeg\r\n" \
4 I9 M' E2 s( C9 o+ e2 r `' Q - "Content-Length: %d\r\n" \
% Q) @. ]/ g; r( O - "X-Timestamp: %d.%06d\r\n" \
2 |1 n7 A6 w4 | - "\r\n", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);
) W0 ?2 J7 J @* u9 U% k - printf("sending intemdiate header\n");. x7 X* C0 u4 m( R3 ~5 O
- if(write(fd, buffer, strlen(buffer)) < 0). P& g7 v. E7 b, p) Z
- break;! J7 }; d3 U- {
- printf("sending frame\n");
7 h; B" v w1 F; M t, w - if(write(fd, frame, frame_size) < 0)
C3 T; \2 V2 {: [ - break;
/ V: ]* C! X8 O3 _6 c- } - printf("sending boundary\n");
/ r% b. x3 J# n1 I3 X! A - sprintf(buffer, "\r\n--" BOUNDARY "\r\n");5 y0 Q9 d9 M% Y! q% D. a6 G+ K
- if(write(fd, buffer, strlen(buffer)) < 0)
0 \! d Y5 g7 X/ v4 m - break;</pre><p style="box-sizing: border-box; border: 0px; font-size: 16px; vertical-align: baseline; line-height: 26px; color: rgb(51, 51, 51); font-family: " helvetica="" neue",="" helvetica,="" tahoma,="" arial,="" "microsoft="" yahei",="" "hiragino="" sans="" gb",="" "wenquanyi="" micro="" hei",="" sans-serif;"=""></p>
复制代码另外需要说明的是,TCP服务器线程在发送MJPEG流的时候是死循环发送的,因此TCP客户端在发送完GET指令之后,就会收到TCP服务器循环发送的图像缓存,TCP客户端会陷入忙等待状态无法再对外发送任何GET或者POST指令,从客户端使用者角度来看的效果就是网页一直在等待。  4 M1 C+ n% P# L0 K# C
 , a' N- V3 `: q" W! {. E' S1 R8 u
二、UDP上位机UDP发送操作,同样需要先建立UDP Socket:
7 L& I) Z) |9 l: B3 P `) ^" w- int UDP_Send_Found(socklen_t* socket_found , struct sockaddr_in *addr , char* ip , int port)
5 Q3 i9 M. C4 i" v' K - {
8 ~* ~+ f o6 c# J) v - *socket_found = socket(AF_INET, SOCK_DGRAM, 0);& ?3 U. C) p2 f5 d
- if(*socket_found == (~0))$ H9 O$ v+ j% X( _
- {' S7 ]' V( n7 U1 @1 K
- printf("Create udp send socket failed!\n");8 w3 T1 i7 }: l" ^6 h: d
- return -1;9 j5 P/ B+ k7 }( b& ]! c
- }* y" G* h. h- ~; ^4 w
- addr->sin_family = AF_INET;
! |0 o/ o8 x9 Z: D* s - addr->sin_addr.s_addr = inet_addr(ip);
+ ~8 q5 H" F" } J - addr->sin_port = htons(port);- W0 v5 O1 ?0 {) c
- memset(addr->sin_zero, 0, 8);
7 y! l) a O$ f( Y - return 0;9 H4 a# C! V: ]0 {0 N# v- H% l
- }
复制代码 / F6 d o$ c0 G- p2 ?
3 I. B5 `& }, |5 l而UDP文件发送则要比HTTP发送简单得多,只需要将文件切片,每一片为固定长度的UDP帧长度,逐帧发送即可:
+ G6 x$ Q U6 z i! J( Z
6 S7 q0 y; S: [
" g Y% p2 b* J8 g6 b8 S- while(fend > 0)
9 W6 }( U5 o. f: t% C: f - {. D% ]5 P D6 f* K u/ F; d s5 N
- memset(picture.data , 0 , sizeof(picture.data));
4 r# F2 [7 p6 f - fread(picture.data , UDP_FRAME_LEN , 1, fp);
3 t& `2 M2 Q0 m' |. G - if(fend >= UDP_FRAME_LEN)
H+ A O8 [0 V9 u8 S - {9 j* a+ T) z1 E% q, T% r
- picture.length = UDP_FRAME_LEN;
5 w$ }/ ^8 X! y; i% c6 q8 `9 } - picture.fin = 0;% l/ K! v% n! C+ L& O3 v! u( H
- }4 [5 e5 D' k: {6 X" h! d7 l$ Y9 z/ P
- else, B/ ~/ B T& t/ t- p. q/ m
- {
J- `( L6 |$ [* i" \7 T - picture.length = fend;
+ f5 `- F# ~8 R* L# }; s& F5 O - picture.fin = 1;/ X/ @8 Y1 ]0 x2 Z4 z7 M4 {1 p# y
- }4 [0 t6 w7 W; {4 t8 `
- //printf("sendbytes = %d \n",sendbytes);+ B; V$ t2 a) B$ X1 `" y
- sendbytes = sendto(socket_send, (char *)&picture, sizeof(struct Package), 0, (struct sockaddr*)&addr,addr_len);% P" {: m8 d' Q# C5 F
- if(sendbytes == -1)
1 w4 o( t1 j: D) n5 Z- B - {0 Y' Y$ `0 |8 o+ Y# q
- printf("Send Picture Failed!d\n");( `4 ~$ K% [% [
- return -1;, I* @' G$ R* J
- }6 i! L$ F1 l! O3 K7 d4 E
- else
, K* B6 I8 D' Q7 c6 H/ P - {
) c6 N4 Z8 l8 J$ Y* }- M - fend -= UDP_FRAME_LEN;* W c% C8 T0 K' b
- }0 X- X/ q, i* ^: I: Y) |
- }
复制代码
% a! c4 T5 I% {! d
! W5 R2 I& U/ R) Z N$ Z# c & @0 g [5 U# X4 ^) }8 M5 J# A+ w
! Z* W3 f6 t! s! T5 G
iMX8MPlus 核心板: https://www.forlinx.com/product/136.html |