本帖最后由 飞凌-marketing 于 2022-1-27 11:00 编辑
6 K9 R( M3 l" d2 ^. A+ \. ?* `" J4 J! F1 t Y. |# \; J
![](https://www.forlinx.com/file.php?f=202201/f_d9ba74cd18d10f93a7282885b5984a0a&t=jpg&o=&s=&v=1641785943) 1 M# c4 `3 c7 s2 N; j; C
作者|donatello1996 来源 | 电子发烧友 题图|飞凌嵌入式 iMX8MPlus 核心板: https://www.forlinx.com/product/136.html
% D+ ~1 ^, K) V+ L) ?
0 G. K' \2 y7 a本文采用的硬件板卡为飞凌嵌入式OKMX8MP-C开发板,系统版本Linux5.4.70+Qt5.15.0,主要介绍基于HTTP网页服务器和UDP上位机的MJPG码流传输。 MJPG格式作为一种持续传输的视频码流,在远程监控领域中应用较广,而实现这种远程监控的第三方应用最常见的有两种:浏览器HTTP网页、UDP上位机。 2 Z3 R7 E' X( v1 ~1 F5 u2 ]
![](https://www.forlinx.com/file.php?f=202201/f_6bd70bd3c17f0577a1765f6dfeda97e9&t=jpg&o=&s=&v=1641785608) . m" d3 e! K0 k! O; W
两者各有优势,对比鲜明,其中: 这两种应用各有优缺点,对于嵌入式开发者来说,两者都必须掌握。
0 P& V( F: v) O- u6 K一、HTTP网页服务器+ H: }9 E! Y1 b# a5 ^
先说下HTTP网页服务器获取MJPG码流的代码,首先是OKMX8MP-C在开发板端建立TCP服务器: - int TCP_Server_Found(socklen_t* socket_found , char* ip , int port) i' F$ Z" L. @- @' r& U' N
- { x6 o% M( C- v! H6 R2 [
- struct sockaddr_in servaddr;6 D( K+ S2 N! f, m6 \; J
- socklen_t addrsize = sizeof(struct sockaddr);0 j4 t! l0 T9 @& {
- bzero(&servaddr , sizeof(servaddr));/ [( j5 D# U8 f5 l& x& Q2 G- T
- servaddr.sin_family = AF_INET;
+ j: u4 e1 j) V2 m - servaddr.sin_addr.s_addr = inet_addr(ip);
% S7 K7 {3 J6 }: b: v' u } - servaddr.sin_port = htons(port);. i Q7 e. b/ t4 S
- int ret;
) P, O5 _( H$ T) t* k6 l5 w - IF( (*socket_found = socket(AF_INET , SOCK_STREAM , 0)) == -1)
7 ^* U8 H, E/ G - {
7 s! s5 x( N. Z, U* s4 D' r4 O - printf("Create socket error: %s (errno :%d)\n",strerror(errno),errno);
4 R% f: F( _" n8 k' ]3 M - return -1;
+ [2 t$ ?6 j$ I% m! ^! M& L5 e - }4 d* r) D8 [& E
- int on = 1;8 \9 ?: z; ^, ~4 M5 C, z2 A, K
- if(setsockopt(*socket_found , SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
8 a. @* @+ f: l; I. V# w1 R% E - {
" v2 X' B% s7 x) m3 S - printf("setsockopt error\n"); o9 P# B# X4 s0 Q+ m: N; a7 w( @
- }
4 |, o3 y% ]& u- s8 e3 B. g: G. g - ret = bind(*socket_found , (struct sockaddr *)&servaddr , addrsize);: U, n# S& Z5 R* H
- if(ret == -1)% j9 R: A: a' {4 P* e1 f$ K- `
- {
y0 x+ H4 H' V9 D& @* F* x6 K - printf("Tcp bind faiLED!\n");: |* |1 j* \2 d- {/ I1 s
- return -1;1 a! g( C+ T% w f/ z- S: u( h) u+ U' L
- }" C) }$ ~' E7 ^5 Z
- if(listen(*socket_found , 5) == -1)* M, A6 n* X0 o0 |& M d
- {
1 C8 r- u) p& w; D( @( V, p! P2 U5 I3 g - printf("Listen failed!\n");
# D0 G9 r. c3 n$ ]1 V - return -1;7 H# d/ n6 \: M" t, G: o
- }( A' h; r" [8 A8 v( `$ ~5 G
- return 0;
7 F' _7 [1 [0 E5 u - }
复制代码其中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);" A( i% Z! w @3 L7 }. W
- void * Thread_TCP_Web_Recv(void *arg)* u- x/ k0 [. S6 l! D3 K9 N
- {0 n3 i' w% Z W9 P/ y) ]0 Q
- 。。。/ g% [+ `7 c! D# j4 ~
- while(1)8 }; h) H, c$ E2 O" b" N
- {
3 Y; J4 v8 \0 J7 T - fd_socket_conn = accept(socket_web_server , (struct sockaddr *)&sockaddr_in_conn , &addrsize);
" H3 ]9 M+ j: h0 O( M% x F/ t% i - printf("fd_socket_conn = accept()\n");
5 I8 a% h( t& c+ z- C$ j$ B - 。。。
5 V z# b" N5 f9 I - recv(fd_socket_conn , recvbuf , 1000 , 0);- n' p0 R( R- z6 D
- }
! }8 ~* I6 o$ s1 F' S8 A9 z/ y0 W - 。。。
3 a9 ?" F2 Q) y' G! Q) p% p - }
复制代码MJPG帧可以使用Grab操作获取,获取到的MJPG帧需要在TCP线程中读,在Grab操作线程中写,这种被多个线程访问的资源需要加锁防止读写冲突,即资源被Grab操作写入时,需要上锁,不允许其它线程访问,操作完成时需要解锁,允许其它线程访问: - pthread_mutex_lock(&pmt);, f5 N9 H5 b o3 j1 G# c% B
- pic_tmpbuffer = pic.tmpbuffer;( O4 C! E1 Q5 j
- pic.tmpbytesused = buff.bytesused;7 W2 H3 Y' \; o+ b' N8 c& O* s
- pic_tmpbytesused = pic.tmpbytesused;3 x0 `# k1 ]6 W# M
- pthread_cond_broadcast(&pct);) Z5 ^% e: A; _
- pthread_mutex_unlock(&pmt);
复制代码线程互斥锁使用之前需要初始化: - pthread_mutex_t pmt;
2 a# ^9 S9 g; K1 d! r, M - pthread_cond_t pct;( z. y2 ?4 i# O I, L
- int main(int argc, char* argv[])
+ v9 d! w$ V3 E - {- w" w. L, o8 F8 C6 @+ t
- .... P s( K0 I/ u9 ?
- TCP_Server_Found(&socket_web_server , (char*)argv[2] , PORT_TCP);4 F4 j. q9 `4 d2 f' H2 l
- pthread_mutex_init(&pmt , NULL);, `/ b3 ~$ i2 g9 Y; \8 e
- pthread_create(&tid_tcp_web_recv , NULL , Thread_TCP_Web_Recv , NULL);$ n; q/ @3 v1 C# A2 Q7 N# z! y
- pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send , NULL);8 M1 n% z& O* j* P
- ...
4 n c4 H/ E" H/ E& C9 d8 g - while(1)* ]1 h( E$ ]4 X& p" n
- {
9 P! w, q) N% R- p* l( A - V4l2_Grab_Mjpeg(false , MJPEG_FILE_NAME);
' H+ \- R, i: r5 @7 i - ...
% n Q6 Q1 I1 X% U, @$ F. C% m - }
% h2 G% @" N5 a5 |' r - ...
3 t) f2 m$ _) S4 b9 l/ j c - }
复制代码然后是发送的细节,发送图片文件之前,需要先发送HTTP标准头,这个相当于给发送图片或者其它类型的流数据铺路: - <p style="box-sizing: border-box; border: 0px; vertical-align: baseline; line-height: 26px;"><span style="text-indent: 32px;">; R. I' w$ T4 y4 X9 Q0 A
- </span></p><p style="box-sizing: border-box; border: 0px; vertical-align: baseline; line-height: 26px;"></p>
复制代码- #define STD_HEADER "Connection: close\r\n" \, I" o: M" ^+ w' D% d! k
- "Server: MJPG-Streamer/0.2\r\n" \% L/ s- T4 r) \, p; ~6 m
- "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" \* v6 J& t- P& H; I# p5 s+ r
- "Pragma: no-cache\r\n" \! I) {2 X2 S8 K- m6 G) r+ _; F9 p
- "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n"& G4 u& b M' l. V3 I( V. G% Z
- #define BOUNDARY "boundarydonotcross"
5 p: p+ B% J p) \- y* g7 A6 H& o - printf("preparing header\n");
2 V" d- ~9 U3 B. w1 Z. Z - sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
, h( j& D( J" v4 v! e - "Access-Control-Allow-Origin: *\r\n" \
9 W: m D+ Z/ H0 R( Q1 h - STD_HEADER \
: V; A- ^7 M% u* b: E, A - "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \; X# V! J4 }/ E- E5 D+ |& H+ ?$ `
- "\r\n" \
5 p, h1 Y6 h/ f - "--" BOUNDARY "\r\n");. r6 s! o" j% `, i' v! \3 G6 A" Y
- if(write(fd, buffer, strlen(buffer)) < 0)# o% S! K5 N: ^4 O9 S0 @4 Q
- {
5 f5 ?: R9 }0 _+ d7 S9 J; S - free(frame);
% h) H+ s8 ?* b5 T: ]* w - return;
: C5 o, T7 _: q% d: a$ u - }
复制代码发送完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" \# q" N2 m/ [. [8 o/ `) v/ w9 q
- "Content-Length: %d\r\n" \
% {- u0 @( E& b3 e- [ - "X-Timestamp: %d.%06d\r\n" \ R! ?: \1 X. m7 m/ K+ Q6 H
- "\r\n", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);1 k7 [, ]0 G9 U. z. o1 X' D
- printf("sending intemdiate header\n");
* L1 B% u6 J6 R7 W3 [% | - if(write(fd, buffer, strlen(buffer)) < 0)
7 x4 t+ y: l$ k) W- | - break;
, F3 I' K* l9 \7 K$ Z8 L+ U - printf("sending frame\n");
3 W; e0 r; r8 f2 x% G3 | - if(write(fd, frame, frame_size) < 0)! Y% I x8 E9 ]& h$ m
- break;: E% i8 W6 M( [2 \5 q
- printf("sending boundary\n");$ x8 q8 V. d2 f. [8 U9 @6 S' N
- sprintf(buffer, "\r\n--" BOUNDARY "\r\n");+ \. ]6 \1 @7 J Q2 C
- if(write(fd, buffer, strlen(buffer)) < 0), f8 [+ B5 L% H4 J! R
- 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指令,从客户端使用者角度来看的效果就是网页一直在等待。 ![](https://www.forlinx.com/file.php?f=202201/f_f75bc92d70e4ff993101f0ee6807cb42&t=jpg&o=&s=&v=1641785774)
" W# G! Z9 Y8 T4 D% x5 e2 S![](https://www.forlinx.com/file.php?f=202201/f_208cc80a9077dc3b320b3f38a3bdd556&t=jpg&o=&s=&v=1641785882)
! C3 {1 c# e4 L: F: Q6 N+ ~# J0 ?4 r二、UDP上位机UDP发送操作,同样需要先建立UDP Socket:
+ V c7 C( W' {$ |5 B" a- int UDP_Send_Found(socklen_t* socket_found , struct sockaddr_in *addr , char* ip , int port)
/ H4 ]; M. R' W; ^) u - {7 I- s2 e! K2 N1 K
- *socket_found = socket(AF_INET, SOCK_DGRAM, 0);
4 Q3 X4 \! ?! W8 k- u* i - if(*socket_found == (~0))) d0 c: A8 d* f* C1 F* N- f
- {6 |! t! k" o% g8 z
- printf("Create udp send socket failed!\n");" p7 w9 a9 e5 E7 Q
- return -1;5 G# x+ s9 s. V9 @- d% H
- }, m! ]4 m1 E8 u3 O+ G
- addr->sin_family = AF_INET;2 W# a+ W4 E* M2 K" k2 s6 S
- addr->sin_addr.s_addr = inet_addr(ip);
1 U, i4 l" l4 V5 f - addr->sin_port = htons(port);+ o+ w; E/ i% e+ S* {& V
- memset(addr->sin_zero, 0, 8);% S; @) I7 x6 B# P+ N `4 f
- return 0;" b8 [* ~5 {* k6 z' w r d
- }
复制代码 6 s: B/ [- s: m0 {0 |0 j1 D- i# ^5 x
; x) ^) t( E2 F x( @9 Z
而UDP文件发送则要比HTTP发送简单得多,只需要将文件切片,每一片为固定长度的UDP帧长度,逐帧发送即可:
$ x) ?% z, \- X6 b; r4 R
' G% m% f% H7 z3 q8 X1 u, W' B# b K3 W# X
- while(fend > 0)& h+ x: W* D( I# y1 O( A
- {
: m% c r( o9 g+ S7 K9 H - memset(picture.data , 0 , sizeof(picture.data));
# I- U5 j2 H; p/ W2 e6 g9 |3 X - fread(picture.data , UDP_FRAME_LEN , 1, fp);
! A0 q8 ]" Z s' `- c7 s( x7 t - if(fend >= UDP_FRAME_LEN)
* z- ?! Y9 I2 v1 w' S - {0 Y) R. n1 L6 n1 _
- picture.length = UDP_FRAME_LEN;7 r7 |) @- h! C' }4 G' B! X
- picture.fin = 0;
) H# m2 V' @9 \& C! w6 Z) e+ ~ - }/ M1 `0 c$ p& g. ]& p
- else
" u. M H+ H2 }: L - {
7 z9 [3 d: r) V" _$ h1 D( E - picture.length = fend;
- ^' D! C" f) a2 j6 x: W% @5 I7 F - picture.fin = 1;4 H0 a; c8 _3 G3 P4 K
- }
( \5 v/ E$ N8 v! z0 V% v - //printf("sendbytes = %d \n",sendbytes);. z# i1 F* Y$ D- t$ R
- sendbytes = sendto(socket_send, (char *)&picture, sizeof(struct Package), 0, (struct sockaddr*)&addr,addr_len);
" Z9 |' q( h( m" p* e - if(sendbytes == -1)
, f1 P: F- F7 k: Y+ ?4 s' P) @ - {, \* k) C( B( |" y9 E7 f
- printf("Send Picture Failed!d\n");2 x! c2 v% [2 u2 U5 S& a6 C
- return -1;
O% O. G h: Q; Y1 D# m - }
1 |% E$ F9 v4 h+ z - else
. u! @7 X2 z; Q; u+ q& s3 K) K - {
' y- d3 K# a5 { - fend -= UDP_FRAME_LEN;2 g+ c3 u w( P( Q
- }
9 ^, x4 E! c! C3 z& B1 a5 C - }
复制代码 ; Q# {, w6 \( l% ^8 ~3 ^+ T5 L
3 `. p5 T0 ? B4 E: M
![](https://www.forlinx.com/file.php?f=202201/f_3756769b714a9ade8206de49ef438432&t=jpg&o=&s=&v=1641785891)
- W5 t$ ~) u) v" w
+ v# C3 k0 ]) r; X8 t) piMX8MPlus 核心板: https://www.forlinx.com/product/136.html |