前言
本文從零基礎一步步實現ONVIF協議、RTSP/RTP協議獲取IPC實時視頻流、FFMPEG解碼。開發環境爲WIN7 32位 + VS2010。
最終成功獲取浩雲、海康、大華的IPC實時視頻流。
若是要了解本文更多細節,或者用本文做設計指導,那最好把文中提到的鏈接都打開,與本文對照着看。
前期準備
1.準備一個ONVIF服務器
既然開發的是客戶端,那必須要有服務端了。我這裏大把的IPC,好幾個品牌的,就隨便拿了一個。
若是沒有IPC,卻是能夠用 VLC media player 搭建一下。或者其餘播放器也能夠。這個網上不少資料。
2.準備一個ONVIF 測試工具
這個工具在ONVIF的官網上能夠找到:ONVIF Device Test Tool 。
3.準備解碼器相關資料及資源
收到視頻流後,須要解碼。能夠用ffmpeg,也能夠用其餘解碼庫。這個是後話了,等ONVIF搞定以後再搞解碼也不遲。推薦連接:
http://wenku.baidu.com/view/f8c94355c281e53a5802ffe4.html?re=view
(Windows下使用MinGW編譯ffmpeg與x265)
4.準備資料
ONVIF協議書必看,ONVIF官網天然是不能少的。其餘資料推薦幾個連接:
http://www.cuplayer.com/player/PlayerCode/camera/2015/1119/2156.html
http://blog.csdn.net/gubenpeiyuan/article/details/25618177#
http://blog.csdn.net/saloon_yuan/article/details/24901597
http://www.onvif.org/onvif/ver20/util/operationIndex.html
5.準備抓包工具IPAnalyse
關係到網絡通訊的有個IP抓包工具能讓你省去不少麻煩,也能讓你清楚的看到協議的細節。IPAnalyse很容易在網上能夠下載。
測試ONVIF
看《ONVIF協議書》估計不少人都會雲裏霧裏,實在搞不懂的話,那就接上IPC,打開 Test Tool,測試一下各項功能。Test Tool裏能夠看到整個協議的工做細節,每一步作什麼,發了什麼報文,收了什麼報文,均可以看到。


若是你沒有IPC,那用VLC理論上也能夠,不過我沒測試過。兩個VLC(一個做爲服務器一個做爲客戶端)加上IP抓包工具,這個我用過也能夠看到協議細節。不過從抓包工具裏看到的只是一段段的報文,沒有步驟說明,不如Test tool來得明瞭。
固然,若是你看懂了ONVIF協議,那就沒必要搞這些。
Soap及開發框架生成
本人一開始並無聽過soap,只好自已查資料去了,這裏也不班門弄斧。這個開發框架生成網上大把大把的資料,但系都很差使啊。每一個人的開發環境都有一點點差異,以致於不少文章都不能從頭到尾的跟着走一次。惟有看比較多的文章再總結一下,才能本身生成一個框架。因此我這裏也很少說了,推薦連接:
E.http://blog.csdn.net/saloon_yuan/article/details/24901597css
固然也能夠不用框架。如你所見,ONVIF的實現最終不過是發送報文和接收報文,用到的功能很少的話徹底能夠本身手動發報文過去,再解析接收到的報文。
後面也會說到不用框架來發現設備。html
ONVIF設備發現、設備搜索
設備發現的過程:客戶端發送報文到239.255.255.250的3702端口(ONVIF協議規定),服務器收到報文後再向客戶端返回一個報文,客戶端收到這個報文而後解析出Xaddrs,這就完成了一次設備發現。
理論上只要報文能正確收發就能夠發現設備。不過,用soap框架搜索設備的時候,多網卡、跨網段等複雜網絡會出現搜索不到的問題。這時候就須要路由支持才行(網上說能夠加入相關的路由協議)。
爲了解決這個問題,本身又寫了一個非SOAP框架的設備發現函數。兩個發現函數請看下面代碼。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
int UserInitSoap(
struct soap *soap,
struct SOAP_ENV__Header *header)
{
if(soap == NULL){
return NULL;
}
soap_set_namespaces( soap, namespaces);
int timeout =
5 ;
soap->recv_timeout = timeout ;
soap->send_timeout = timeout ;
soap->connect_timeout = timeout ;
char buf[
128];
GUID guid;
if(CoCreateGuid(&guid)==S_OK){
_snprintf_s(buf,
sizeof(buf)
,
"urn:uuid:%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X"
, guid.Data1 , guid.Data2 , guid.Data3
, guid.Data4[
0], guid.Data4[
1]
, guid.Data4[
2], guid.Data4[
3], guid.Data4[
4],guid.Data4[
5]
, guid.Data4[
6], guid.Data4[
7] );
}
else{
_snprintf_s(buf,
sizeof(buf),
"a5e4fffc-ebb3-4e9e-913e-7eecdf0b05e8");
}
soap_default_SOAP_ENV__Header(soap, header);
header->wsa__MessageID =(
char *)soap_malloc(soap,
128);
memset(header->wsa__MessageID,
0,
128);
memcpy(header->wsa__MessageID, buf,
strlen(buf));
header->wsa__Action =
"http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
header->wsa__To =
"urn:schemas-xmlsoap-org:ws:2005:04:discovery";
soap->header = header;
return 0 ;
}
SOAP裏全部要申請的空間請用帶有soap_XXX()的,不然不能經過soap_del(soap);來釋放全部空間。這會形成內存泄漏。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
/*
SOAP釋放
*/
int UserReleaseSoap(struct soap *soap)
{
//soap_free(soap);
soap_destroy(soap);
soap_end(soap);
soap_done(soap);
soap_del(soap);
/*
The gSOAP engine uses a memory management method to allocate
and deallocate memory.
The deallocation
is performed
with soap_destroy() followed
by soap_end().
However,
when you compile
with -DDEBUG
or -DSOAP_MEM_DEBUG
then no memory
is released
until soap_done()
is invoked. This ensures that the gSOAP engine
can track all malloced data to verify leaks
and double frees
in debug mode.
Use -DSOAP_DEBUG to use the normal debugging facilities without memory debugging.
Note that some compilers have DEBUG enabled
in the debug configuration, so
this behavior
should be expected
unless you compile
in release config
*/
return 0 ;
}
如下這裏有用到類,其中IP爲要發現設備IP,XAddrs爲設備的端地址。要複製這段代碼的,請自行修改。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
int GetVideo::FindONVIFservers()
{
this->error =
0 ;
memset(
this->error_info ,
0,
1024);
memset(
this->error_info_,
0,
1024);
int FoundDevNo =
0;
int retval = SOAP_OK;
struct __wsdd__ProbeMatches resp;
struct SOAP_ENV__Header header;
struct soap* soap;
soap = soap_new();
UserInitSoap(soap,&header);
const char *soap_endpoint =
"soap.udp://239.255.255.250:3702/";
wsdd__ProbeType req;
wsdd__ScopesType sScope;
soap_default_wsdd__ScopesType(soap, &sScope);
sScope.__item =
"";
soap_default_wsdd__ProbeType(soap, &req);
req.Scopes = &sScope;
req.Types =
"";
int time=
10;
do{
retval = soap_send___wsdd__Probe(soap, soap_endpoint, NULL, &req);
Sleep(
100);
}
while(retval != SOAP_OK && time--);
while (retval == SOAP_OK)
{
retval = soap_recv___wsdd__ProbeMatches(soap, &resp);
if (retval == SOAP_OK) {
if (!soap->error){
FoundDevNo ++;
if (resp.wsdd__ProbeMatches->ProbeMatch != NULL &&
resp.wsdd__ProbeMatches->ProbeMatch->XAddrs != NULL
){
char *http = strstr(resp.wsdd__ProbeMatches->ProbeMatch->XAddrs,
this->IP );
if(http!=NULL){
memcpy(
this->XAddrs,
"http://",
7);
for(
int t=
0;t<
255-
7;t++){
if(http[t]==
' ' || http[t]==
'\n' ||
http[t]==
'\r'|| http[t]==
'\0' ){
retval =
1234 ;
break;
}
this->XAddrs[t+
7] = http[t] ;
}
}
}
}
else{
retval = soap->error;
this->error = soap->error ;
const char *tmp = *soap_faultcode(soap) ;
if(tmp)
memcpy(error_info , tmp , strlen(tmp ));
const char *tmp_ = *soap_faultstring(soap) ;
if(tmp_)
memcpy(error_info_, tmp_, strlen(tmp_));
}
}
else if (soap->error){
if (FoundDevNo){
soap->error =
0 ;
retval =
0;
}
else {
retval = soap->error;
}
break;
}
#ifdef DEBUG_INFOPRINT
if (retval == SOAP_OK) {
if (!soap->error){
if (resp.wsdd__ProbeMatches->ProbeMatch != NULL && resp.wsdd__ProbeMatches->ProbeMatch->XAddrs != NULL){
printf(
"****** No %d Devices Information ******\n", FoundDevNo );
printf(
"Device Service Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
printf(
"Device EP Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
printf(
"Device Type : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
printf(
"Device Metadata Version : %d\r\n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
}
}
else{
printf(
"[%d]: recv soap error :%d, %s, %s\n", __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
}
}
else if (soap->error){
if (FoundDevNo){
printf(
"Search end! Find %d Device! \n", FoundDevNo);
}
else {
printf(
"No Device found!\n");
}
break;
}
#endif
}
if(retval!=
1234){
this->error = NO_DEVICE ;
memcpy(
this->error_info_,
"ONVIF: this device NOT found\n",
128);
char tmp[
128];
sprintf_s(tmp,
128,
"ONVIF: found %d devices \n",FoundDevNo);
memcpy(
this->error_info, tmp,
128);
}
Sleep(
10);
UserReleaseSoap(soap);
return retval;
}
以上代碼實現了整個設備發現的過程,最終獲得設備的XAddrs。流程爲:設置命名空間 >> 超時設置 >> 生成GUID >> 填充header >> 設置搜索範圍 >> 發送報文 >> 接收並解析報文 。
非SOAP框架 設備發現
如下代碼也實現了事個發現過程,用到幾個類變量:this->IP是要發現的設備IP,this->Port是其端口號,this->LocalIP是用來保存與設備IP同一網段的本地IP地址,this->FIND_BASE_UDP_PORT是用於鏈接設備的本地端口號(若是此端口號被佔用,會自動加2查找下一個)。
關於char *cxml 報文,最直接的方法是從Test tool裏複製過來,或者從抓包工具裏複製出來,固然也能夠複製本文的。
流程爲:輸入要發現的設備IP - 判斷本地是否添加了此IP網段 - 測試設備是否能夠鏈接 - 綁定本地IP - 準備報文 - 發送報文 - 接收報文 - 解析報文 。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
bool GetLocalIPs(
char ips[][
32],
int maxCnt,
int *cnt)
{
WSADATA wsaData;
int ret=WSAStartup(
0x0202,&wsaData);
if (ret!=
0){
return false; }
char hostname[
256];
ret=gethostname(hostname,
sizeof(hostname));
if (ret==SOCKET_ERROR){
return false; }
HOSTENT* host=gethostbyname(hostname);
if (host==NULL){
return false; }
*cnt=host->h_length<maxCnt? host->h_length:maxCnt;
for (
int i=
0;i<*cnt;i++) {
in_addr* addr =(in_addr*)*host->h_addr_list;
strcpy_s(ips[i],
16, inet_ntoa(addr[i]));
}
WSACleanup();
return true;
}
int TcpOnlineTest(
char *IP,
int Port)
{
WSADATA wsdata;
WSAStartup(
0x0202,&wsdata);
SOCKET PtcpFD=socket(AF_INET,SOCK_STREAM,
0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr(IP);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(Port);
int error = -
1;
int len =
sizeof(
int);
timeval tm;
fd_set
set;
unsigned
long ul =
1;
ioctlsocket(PtcpFD, FIONBIO, &ul);
int err =connect(PtcpFD, (SOCKADDR*)&addrSrv,
sizeof(SOCKADDR));
if(err==-
1){
tm.tv_sec =
3;
tm.tv_usec =
0;
FD_ZERO(&
set);
FD_SET(PtcpFD, &
set);
if(
select(PtcpFD+
1, NULL, &
set, NULL, &tm) >
0){
getsockopt(PtcpFD, SOL_SOCKET, SO_ERROR, (
char *)&error, &len);
if(error ==
0){ err =
0; }
else { err = -
1; }
}
else { err = -
1; }
}
if(err==
0){
ul =
0;
ioctlsocket(PtcpFD, FIONBIO, &ul);
char buf[
16]={
0};
err = send(PtcpFD, buf,
16,
0);
if(err!=
16){
err = -
1 ;
}
else{ err =
0 ; }
}
closesocket(PtcpFD);
WSACleanup();
return err;
}
int UdpConnect(
char *IP,
int UdpPort,
int *UdpFD)
{
WSADATA wsdata;
WSAStartup(
0x0202,&wsdata);
sockaddr_in serv;
serv.sin_family = AF_INET ;
serv.sin_addr.s_addr= inet_addr(IP) ;
serv.sin_port = htons(UdpPort) ;
*UdpFD = socket(AF_INET,SOCK_DGRAM,
0);
if(!(*UdpFD)){
return -
1; }
bool optval =
0 ;
int res =
0;
int nNetTimeout=
100;
res|= setsockopt(*UdpFD,SOL_SOCKET,SO_RCVTIMEO,(
char *)&nNetTimeout,
sizeof(
int));
int nBuf=
100*
1024;
res|= setsockopt(*UdpFD,SOL_SOCKET,SO_SNDBUF,(
const char*)&nBuf,
sizeof(
int));
res|= setsockopt(*UdpFD,SOL_SOCKET,SO_RCVBUF,(
const char*)&nBuf,
sizeof(
int));
if(res){
return -
1; }
res =bind(*UdpFD,(sockaddr *)&serv,
sizeof(sockaddr_in));
if(res==-
1){
int errorcode =::GetLastError();
return -
2;
}
return 0 ;
}
int UdpClose(
int UdpFD)
{
closesocket(UdpFD);
WSACleanup();
return 0 ;
}
int UdpSend(
int UdpFD,
char*msg,
int len)
{
int ret =send(UdpFD, msg, len,
0 );
return ret ;
}
int UdpSendto(
int UdpFD,
char *msg,
int len,
char *toIP,
int toPort)
{
int tolen =
sizeof(
struct sockaddr_in);
sockaddr_in to;
to.sin_family = AF_INET ;
to.sin_addr.s_addr = inet_addr(toIP) ;
to.sin_port = htons(toPort) ;
int ret =sendto(UdpFD, msg, len,
0, (
const sockaddr *)&to, tolen);
return ret ;
}
int UdpRecvfrom(
int UdpFD,
char *fromIP,
char *buf,
int size)
{
sockaddr_in afrom ;
afrom.sin_family = AF_INET ;
afrom.sin_addr.s_addr = INADDR_BROADCAST;
int fromlength =
sizeof(SOCKADDR);
memset(buf,
0, size);
int res =recvfrom(UdpFD,buf,size,
0,(
struct sockaddr FAR *)&afrom,(
int FAR *)&fromlength);
if( res &&
(afrom.sin_addr.S_un.S_addr != inet_addr(fromIP))
){
res =
0 ;
}
return res ;
}
int GetDiscoveryParam(
char *buf,
int size,
char *MessageID,
char *nonSoap_XAddrs,
int xaddr_len)
{
const char *type =
"NetworkVideoTransmitter";
const char *Scopes =
"www.onvif.org";
int isUse_Type =
0 ;
int isUse_Scopes =
0 ;
if(isUse_Type){
char *find_type =strstr(buf, type);
if(find_type==NULL){
return -
1 ;
}
}
if(isUse_Type){
char *find_Scopes =strstr(buf, Scopes);
if(find_Scopes==NULL){
return -
1 ;
}
}
char *find_uuid =strstr(buf, MessageID);
if(find_uuid==NULL){
return -
1 ;
}
const char *wsddXAddrs_b =
"XAddrs>";
const char *wsddXAddrs_e =
"</";
char *find_XAddrs_b =strstr(buf, wsddXAddrs_b);
char *find_XAddrs_e =strstr(find_XAddrs_b, wsddXAddrs_e);
if(find_XAddrs_b==NULL || wsddXAddrs_e==NULL){
return -
1 ;
}
int len_b = strlen(wsddXAddrs_b);
int len = find_XAddrs_e -find_XAddrs_b -len_b;
char *tmp =
new char[len+
1];
memset(tmp,
0, len+
1);
for(
int i=
0;i<len;i++){
if(find_XAddrs_b[len_b+i]!=
' '){
tmp[i] = find_XAddrs_b[len_b+i] ;
}
else{
len = i ;
break;
}
}
if(len>xaddr_len){
return len ;
}
memset(nonSoap_XAddrs,
0, xaddr_len);
memcpy(nonSoap_XAddrs, tmp, len);
delete tmp ;
return 0 ;
}
int GetVideo::FindONVIFservers_nonSoap()
{
this->error =
0 ;
memset(
this->error_info_,
'\0',
1024);
memset(
this->error_info,
'\0',
1024);
int a,b,c,d;
char IPD[
32];
sscanf_s(
this->IP,
"%d.%d.%d.%d",&a,&b,&c,&d);
sprintf_s(IPD,
32,
"%d.%d.%d.",a,b,c);
int isAddIPD =
0 ;
char lips[
64][
32];
char *UseIP;
int cnt =
0 ;
GetLocalIPs(lips,
64, &cnt);
for(
int i=
0;i<cnt;i++){
isAddIPD =
0 ;
UseIP = strstr(lips[i], IPD);
if(UseIP!=NULL){
isAddIPD =
1 ;
memcpy(
this->LocalIP, UseIP,
64);
break;
}
}
if(isAddIPD==
0){
this->error = NOTADDIP ;
memcpy(
this->error_info_,
"沒有添加該網段",
32);
memcpy(
this->error_info,
"請先添加該網段",
32);
return -
4;
}
int isConnecting = TcpOnlineTest(
this->IP,
this->Port);
if(isConnecting==-
1){
this->error = NOCONNECT ;
memcpy(
this->error_info_,
"該IP/Port沒法鏈接,請檢查是否填寫正確",
128);
memcpy(
this->error_info,
"ONVIF:Discovery IP/Port error\n",
128);
return -
1;
}
int Dis_UdpFD =
0 ;
int UsePort =
this->FIND_BASE_UDP_PORT ;
int res =
0;
for(
int i=
0;i<
512;i++){
res =UdpConnect(UseIP, UsePort, &Dis_UdpFD);
if(res==-
1){
this->error = NOCONNECT ;
memcpy(
this->error_info_,
"該IP沒法建立UDP鏈接",
64);
memcpy(
this->error_info,
"ONVIF:Discovery UDP SOCKET ERROR(-1)\n",
128);
UdpClose(Dis_UdpFD);
return -
1;
}
else if(res==-
2){
UsePort+=
2;
UdpClose(Dis_UdpFD);
Sleep(
10);
}
else if(res==
0){
break;
}
}
if(res==-
2){
this->error = NOCONNECT ;
memcpy(
this->error_info_,
"Can't bind UDP PORT 5340-5596.(used by others now)",
128);
memcpy(
this->error_info,
"ONVIF:Discovery UDP bind ERROR\n",
128);
return -
1;
}
char *cxml = {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope x"
"mlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\" x"
"mlns:SOAP-ENC=\"http://www.w3.org/2003/05/soap-encoding\" x"
"mlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" x"
"mlns:xsd=\"http://www.w3.org/2001/XMLSchema\" x"
"mlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" x"
"mlns:wsdd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" x"
"mlns:chan=\"http://schemas.microsoft.com/ws/2005/02/duplex\" x"
"mlns:wsa5=\"http://www.w3.org/2005/08/addressing\" x"
"mlns:c14n=\"http://www.w3.org/2001/10/xml-exc-c14n#\" x"
"mlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" x"
"mlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\" x"
"mlns:wsc=\"http://schemas.xmlsoap.org/ws/2005/02/sc\" x"
"mlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" x"
"mlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" x"
"mlns:xmime=\"http://tempuri.org/xmime.xsd\" x"
"mlns:xop=\"http://www.w3.org/2004/08/xop/include\" x"
"mlns:tt=\"http://www.onvif.org/ver10/schema\" x"
"mlns:wsrfbf=\"http://docs.oasis-open.org/wsrf/bf-2\" x"
"mlns:wstop=\"http://docs.oasis-open.org/wsn/t-1\" x"
"mlns:wsrfr=\"http://docs.oasis-open.org/wsrf/r-2\" x"
"mlns:tad=\"http://www.onvif.org/ver10/analyticsdevice/wsdl\" x"
"mlns:tan=\"http://www.onvif.org/ver20/analytics/wsdl\" x"
"mlns:tdn=\"http://www.onvif.org/ver10/network/wsdl\" x"
"mlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" x"
"mlns:tev=\"http://www.onvif.org/ver10/events/wsdl\" x"
"mlns:wsnt=\"http://docs.oasis-open.org/wsn/b-2\" x"
"mlns:timg=\"http://www.onvif.org/ver20/imaging/wsdl\" x"
"mlns:tls=\"http://www.onvif.org/ver10/display/wsdl\" x"
"mlns:tmd=\"http://www.onvif.org/ver10/deviceIO/wsdl\" x"
"mlns:tptz=\"http://www.onvif.org/ver20/ptz/wsdl\" x"
"mlns:trc=\"http://www.onvif.org/ver10/recording/wsdl\" x"
"mlns:trp=\"http://www.onvif.org/ver10/replay/wsdl\" x"
"mlns:trt=\"http://www.onvif.org/ver10/media/wsdl\" x"
"mlns:trv=\"http://www.onvif.org/ver10/receiver/wsdl\" x"
"mlns:tse=\"http://www.onvif.org/ver10/search/wsdl\">"
"<SOAP-ENV:Header><wsa:MessageID>urn:uuid:9D7A37A4-DBFE-4bdd-A79C-74998B7A375D</wsa:MessageID>"
"<wsa:To SOAP-ENV:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
"<wsa:Action SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
"</SOAP-ENV:Header><SOAP-ENV:Body><wsdd:Probe><wsdd:Types></wsdd:Types><wsdd:Scopes></wsdd:Scopes>"
"</wsdd:Probe></SOAP-ENV:Body></SOAP-ENV:Envelope>" };
char MessageID[
128];
GUID guid;
if(CoCreateGuid(&guid)==S_OK){
_snprintf_s(MessageID,
sizeof(MessageID)
,
"urn:uuid:%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X"
, guid.Data1 , guid.Data2 , guid.Data3
, guid.Data4[
0], guid.Data4[
1]
, guid.Data4[
2], guid.Data4[
3], guid.Data4[
4],guid.Data4[
5]
, guid.Data4[
6], guid.Data4[
7] );
}
else{
_snprintf_s(MessageID,
sizeof(MessageID),
"a5e4fffc-ebb3-4e9e-913e-7eecdf0b05e8");
}
int xml_len = strlen(cxml);
char *xml =
new char[xml_len+
2] ;
memset(xml,
0, xml_len+
2);
memcpy(xml, cxml, xml_len);
char *tmp_xml = strstr(xml,
"urn:uuid:");
int idlen = strlen(MessageID);
for(
int i=
0;i<idlen;i++){
tmp_xml[i] = MessageID[i] ;
}
res =UdpSendto(Dis_UdpFD, xml, xml_len,
"239.255.255.250",
3702);
if(res!=xml_len){
this->error = -
1 ;
char tmp[
128];
sprintf_s(tmp,
128,
"UDP send(%d)!=res(%d) ERROR",xml_len,res);
memcpy(
this->error_info_, tmp,
128);
memcpy(
this->error_info,
"ONVIF:Discovery UDP send ERROR\n",
128);
UdpClose(Dis_UdpFD);
delete xml;
return -
1;
}
char buf[
4096] ;
int size =
4096 ;
for(
int j=
0,res=
0; res==
0 && j<
100; j++){
res =UdpRecvfrom(Dis_UdpFD,
this->IP, buf+res, size-res);
Sleep(
10);
}
if(res==-
1){
this->error = -
1 ;
char tmp[
128];
sprintf_s(tmp,
128,
"UDP recv(%d) ERROR",res);
memcpy(
this->error_info_, tmp,
128);
memcpy(
this->error_info,
"ONVIF:Discovery UDP recv ERROR\n",
128);
UdpClose(Dis_UdpFD);
delete xml;
return -
1;
}
int resGDP =GetDiscoveryParam(buf, res, MessageID,
this->XAddrs,
256);
if(resGDP>
0){
this->error = -
1 ;
char tmp[
128];
sprintf_s(tmp,
128,
"GetVideo: XAddrs buf overflow(256<%d) ERROR",res);
memcpy(
this->error_info_, tmp,
128);
memcpy(
this->error_info,
"GetVideo: 請增大XAddrs空間\n",
128);
UdpClose(Dis_UdpFD);
delete xml;
return -
1;
}
if(resGDP==-
1){
this->error = NO_DEVICE ;
memcpy(
this->error_info_,
"ONVIF: this device NOT found\n",
128);
memcpy(
this->error_info,
"ONVIF:non Soap Discovery NO Device\n",
128);
UdpClose(Dis_UdpFD);
delete xml;
return -
1;
}
UdpClose(Dis_UdpFD);
delete xml;
return 0 ;
}
其實設備發現,就是要獲得一個XAddrs。同時看一下設備是否在線。若是你確認設備在線,並已知XAddrs,那不用發現這一步了。直接能夠進行下面的操做。
ONVIF獲取設備能力
先看代碼
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
void UserGetCapabilities(
struct soap *soap ,
struct __wsdd__ProbeMatches *resp,
class _tds__GetCapabilities *capa_req,
class _tds__GetCapabilitiesResponse *capa_resp,
char *ONVIF_USER,
char *ONVIF_PASSWORD)
{
capa_req->Category = (
enum tt__CapabilityCategory *)soap_malloc(soap,
sizeof(
int));
capa_req->__sizeCategory =
1;
capa_resp->Capabilities = (
class tt__Capabilities*)soap_malloc(soap,
sizeof(
class tt__Capabilities)) ;
*(capa_req->Category) = (
enum tt__CapabilityCategory)(tt__CapabilityCategory__All);
if(soap->error){
return; }
COnvifDigest cTokenDigest(ONVIF_USER, ONVIF_PASSWORD);
cTokenDigest.Authentication(soap,TRUE,
"user");
if(soap->error){
return; }
int result = soap_call___tds__GetCapabilities(soap, resp->wsdd__ProbeMatches->ProbeMatch->XAddrs, NULL, capa_req, capa_resp);
}
小白都會問:這段代碼怎麼得來的呢?請看解析。
設備發現以後是能力獲取,這能夠從ONVIF官方資料得知。
能力獲取是用GetCapabilities()函數,這能夠從ONVIF官網的操做說明看到,也就是上那個D網址。
那怎麼知道soap的前綴呢,soap_call___tds__XXX也不是亂加的。最保守是在soapClient.cpp裏Ctrl+F查找GetCapabilities關鍵字,就能夠找到相應的函數。到你熟悉以後,你會發現tds是GetCapabilities()的命名空間,其餘函數有其餘的命名空間,代替一下就能夠。
關於函數調用以前,輸入輸出變量是什麼意思,能夠看ONVIF官網的函數說明。
ONVIF鑑權
鑑權能夠用函數:
soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);
這個函數要用到openssl庫,要本身編譯加進去,有點麻煩。
這裏的鑑權也就是用戶名、密碼、隨機碼/時間碼的base64加密,本身也可能實現。因此我選擇了不用openssl庫的方法。推薦連接:
http://blog.csdn.net/pony_maggie/article/details/8588888
http://blog.csdn.net/qiaowei0/article/details/42024703
(後面的連接,一開始能夠用,後來設備多了狀況複雜了就彷佛有點問題,我修改過,但忘記改了哪裏,代碼就不放上來了 so sorry)
ONVIF流程
能夠實現能力獲取這一步,就已經能夠觸類旁通完成全部的ONVIF操做了。下面看看總體流程:
發現設備 >> 獲取能力 >> 獲取媒體信息 >> 獲取視頻編碼配置 >> 設置視頻編碼配置 >> 獲取URI >> ONVIF完成 -> RTSP播放 -> 解碼
RTSP/RTP協議實現
增長準備資料,推薦如下連接:
http://blog.csdn.net/chenyanxu/article/details/2728427
http://www.cnblogs.com/lidabo/p/3701068.html
http://blog.csdn.net/baby313/article/details/7353605
http://www.faqs.org/rfcs/
http://blog.csdn.net/gavinr/article/details/7035966
http://blog.csdn.net/nkmnkm/article/category/1066093/2
http://blog.csdn.net/cjj198561/article/details/30248963
https://my.oschina.net/u/1431835/blog/393315
都是關於RTSP及RTP包及H264等相關資料
RTSP通訊
理論自已看上面的連接,很少說,直接上代碼。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
int C_RTSP_SendCMD::OPTIONS()
{
memset(Authorization_Basic,
'\0',
128);
char *cmd =
new char[
1024];
memset(cmd,
0,
1024);
sprintf_s(cmd,
1024,
"OPTIONS %s RTSP/1.0\r\n"
"CSeq:1\r\n"
"User-Agent:HaoYun"
"\r\n"
"\r\n", C_RTSP_SendCMD::URI );
int sLenth =
strlen(cmd);
int res = send(tcpFD, cmd, sLenth,
0);
delete cmd ;
return res ;
}
這裏用到變量是:URI由ONVIF獲取的。tcpFD是socket值。由LocalIP與設備創建的一個tcp連接,注意RTSP協議規定端口爲554(有些也可能用8554)。Authorization_Basic是關於用戶名和密碼的一個basic64加密認證。
這裏要注意的是命令裏面不要有多餘的空格和多餘的轉義,不然不行。
發送完命令後,立刻接收服務器返回的數據,而後就能夠看到資料裏所說的過程了。例如:
OPTIONS rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 1 //每一個消息都有序號來標記,第一個包一般是option請求消息
User-Agent: HaoYun
服務器的迴應信息包括提供的一些方法,例如:
RTSP/1.0 200 OK
Server: HaoYun IPC
Cseq: 1 //每一個迴應消息的cseq數值和請求消息的cseq相對應
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, SCALE, GET_PARAMETER //服務器提供的可用的方法
再來一個要認證身份的例子
看下面代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
int C_RTSP_SendCMD::DESCRIBE()
{
char *cmd =
new char[
1024];
memset(cmd,
0,
1024);
sprintf_s(cmd,
1024,
"DESCRIBE %s RTSP/1.0\r\n"
"CSeq:2\r\n"
"Accept: application/sdp\r\n"
"User-Agent:HaoYun"
"\r\n"
"\r\n", C_RTSP_SendCMD::URI );
if(
this->Authorization_Basic[
1]!=
'\0'){
char *cmd_tmp =
strstr(cmd,
"HaoYun");
sprintf_s(cmd_tmp,
512,
"HaoYun\r\n"
"Authorization: Basic %s"
"\r\n"
"\r\n",
this->Authorization_Basic );
}
int sLenth =
strlen(cmd);
int res = send(tcpFD, cmd, sLenth,
0);
delete cmd ;
return res ;
}
這裏的Authorization_Basic是以下代碼算出的。其中base64_bits_to_64()網上好多都有就不復制上來了。SOAP裏也有,在那個鑑權函數裏面,修改一下soap_malloc()就能夠用了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
int C_RTSP_SendCMD::Calc_Authorization_Basic(
char *user,
char *passwd)
{
unsigned char *user_passwd =
new unsigned char[
600];
memset(user_passwd,
'\0',
600);
int u_len =
strlen(user);
int p_len =
strlen(passwd);
memcpy(user_passwd, user, u_len);
*(user_passwd+u_len) =
':';
memcpy(user_passwd+u_len+
1, passwd, p_len);
base64_bits_to_64((
unsigned char *)
this->Authorization_Basic, user_passwd, u_len+p_len+
1);
delete user_passwd;
return 0 ;
}
1.接收到DESCRIBE的sdp報文後,就在sdp裏解析出 control 和 transport 來。
2.接着就是用上面獲得的信息進行SETUP
3.接收到SETUP的返回信息後,解析出Session,每一個通話都有一個固定的Session。
4.用Session值發送PLAY命令,成功的話,就有視頻流了。這時候的流是RTP包(若是用RTP協議的話)。
5.還有一個關閉的命令TEARDOWN,在關閉的時候調用。
RTP協議實現接收
看完上面RTSP的連接後,就能夠看下面的代碼了。(若是一頭霧水,請對照着那兩個RTP網址來看,或對照着RTP協議也能夠)
理論不說,分析代碼去。
其中兩個結構體是關於RTP頭的,GetHeader()就是解析填充這兩個頭,代碼和協議有一一對應的關係。而GetNALUnit_OneFrame()是從視頻流中把一個個小RTP包拆解成NAL包,再一個個的合併成一個大幀,合併獲得的幀能夠送到解碼器解碼了。有些解碼器也許不須要合併幀這一步,直接把NAL流送過去就能夠解碼的。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
class CH264_RTP_UNPACK_2
{
#define RTP_VERSION 2
#define BUF_SIZE (1024 * 500)
typedef
struct
{
unsigned
char CC;
unsigned
char X;
unsigned
char P;
unsigned
char V;
unsigned
char PT;
unsigned
char M;
unsigned
short seq_number;
unsigned
int time_stamp;
unsigned
int SSRC;
unsigned
int size;
} RTP_Header_t;
typedef
struct
{
unsigned
char Type;
unsigned
char NRI;
unsigned
char F;
unsigned
char FUA_Type;
unsigned
char FUA_R;
unsigned
char FUA_E;
unsigned
char FUA_S;
unsigned
char isMajor578;
unsigned
int size;
} NAL_Header_t;
public:
RTP_Header_t m_RTP_Header;
NAL_Header_t m_NAL_Header;
unsigned
char *H264_OneFrame;
int H264_1f_size;
int pic_size3;
public:
CH264_RTP_UNPACK_2()
{
}
~CH264_RTP_UNPACK_2()
{
delete H264_OneFrame ;
}
void CH264_RTP_UNPACK_2_Init()
{
memset(&m_RTP_Header,
0,
sizeof(RTP_Header_t));
memset(&m_NAL_Header,
0,
sizeof(NAL_Header_t));
H264_OneFrame =
new unsigned
char[pic_size3];
memset(H264_OneFrame,
0, pic_size3);
H264_1f_size =
0 ;
}
int GetHeader(unsigned
char *RTP_PackegRawBuf,
int size)
{
if(size<
13){
return -
2; }
unsigned
int ba,bb,bc,bd;
m_RTP_Header.V = RTP_PackegRawBuf[
0] >>
6 ;
m_RTP_Header.P = (RTP_PackegRawBuf[
0] &
0x20)?
1:
0 ;
m_RTP_Header.X = (RTP_PackegRawBuf[
0] &
0x10)?
1:
0 ;
m_RTP_Header.CC = RTP_PackegRawBuf[
0] &
0x0f ;
m_RTP_Header.M = RTP_PackegRawBuf[
1] >>
7 ;
m_RTP_Header.PT = RTP_PackegRawBuf[
1] &
0x7f ;
ba = RTP_PackegRawBuf[
2] ;
bb = RTP_PackegRawBuf[
3] ;
m_RTP_Header.seq_number = (ba<<
8) + bb ;
ba = RTP_PackegRawBuf[
4] ;
bb = RTP_PackegRawBuf[
5] ;
bc = RTP_PackegRawBuf[
6] ;
bd = RTP_PackegRawBuf[
7] ;
m_RTP_Header.time_stamp = (ba<<
24) +(bb<<
16) +(bc<<
8) +bd ;
ba = RTP_PackegRawBuf[
8] ;
bb = RTP_PackegRawBuf[
9] ;
bc = RTP_PackegRawBuf[
10] ;
bd = RTP_PackegRawBuf[
11] ;
m_RTP_Header.SSRC = (ba<<
24) +(bb<<
16) +(bc<<
8) +bd ;
m_RTP_Header.size =
12 + m_RTP_Header.CC ;
if(m_RTP_Header.V!=RTP_VERSION){
return -
2;
}
if(m_RTP_Header.X){
return -
2;
}
if(m_RTP_Header.PT !=
96){
return -
2;
}
int nal_h_offset =
12+m_RTP_Header.CC ;
m_NAL_Header.F = RTP_PackegRawBuf[nal_h_offset] >>
7;
m_NAL_Header.NRI = (RTP_PackegRawBuf[nal_h_offset] &
0x60)>>
5;
m_NAL_Header.Type = RTP_PackegRawBuf[nal_h_offset] &
0x1f;
m_NAL_Header.isMajor578 =
0 ;
if(m_NAL_Header.Type==
5 || m_NAL_Header.Type==
7 ||
m_NAL_Header.Type==
8 || m_NAL_Header.Type==
1 )
{
m_NAL_Header.isMajor578 =
1 ;
}
if(m_NAL_Header.Type>
23 && m_NAL_Header.Type!=
28){
return -
2;
}
if(m_NAL_Header.Type==
28){
m_NAL_Header.FUA_S = RTP_PackegRawBuf[nal_h_offset+
1] >>
7 ;
m_NAL_Header.FUA_E = (RTP_PackegRawBuf[nal_h_offset+
1] &
0x40)>>
6;
m_NAL_Header.FUA_R =
0 ;
m_NAL_Header.FUA_Type = RTP_PackegRawBuf[nal_h_offset+
1] &
0x1f ;
m_NAL_Header.size =
2 ;
}
else{
m_NAL_Header.FUA_S =
0 ;
m_NAL_Header.FUA_E =
0 ;
m_NAL_Header.FUA_R =
0 ;
m_NAL_Header.FUA_Type =
0 ;
m_NAL_Header.size =
1 ;
}
return 0;
}
int CheckLostMajorPackeg(
float *fLossRate)
{
bool isLost =
0;
static bool isFrame_loss[
256] = {
0} ;
static unsigned
char i =
0 ;
static unsigned
short seq_number_old =
0;
bool isMajor =
0 ;
unsigned
char Type[
2];
Type[
0] = m_NAL_Header.Type &
0x0f ;
Type[
1] = m_NAL_Header.FUA_Type &
0x0f ;
for(
int j=
0;j<
2 && isMajor==
0;j++){
switch(Type[j]){
case 5:
case 7:
case 8: isMajor =
1 ;
break;
default: isMajor =
0 ;
}
}
if(isMajor==
0){
seq_number_old = m_RTP_Header.seq_number ;
return 0;
}
isLost =
1 ;
if(m_RTP_Header.seq_number-seq_number_old ==
1){
isLost =
0 ;
}
if(seq_number_old==
0xffff && m_RTP_Header.seq_number==
0x0000){
isLost =
0 ;
}
float lossnum =
0.0 ;
isFrame_loss[i++] = isLost ;
for(
int j=
0;j<
256;j++){
if(isFrame_loss[j])
lossnum +=
1.0 ;
}
*fLossRate = lossnum/
256.0f ;
seq_number_old = m_RTP_Header.seq_number ;
return (
int)isLost;
}
int GetNALUnit_OneFrame(unsigned
char *RTP_PackegRawBuf,
int size)
{
int reValue =
0 ;
float fLossRate =
0.0;
static int isLossPackeg =
0 ;
if(RTP_PackegRawBuf==NULL || size<
13){
return -
1;}
GetHeader(RTP_PackegRawBuf,size);
unsigned
char *playload = RTP_PackegRawBuf ;
int playload_size =
0 ;
int padding_size =
0 ;
if(m_RTP_Header.P){
padding_size = RTP_PackegRawBuf[size-
1] ;
}
playload_size = size -(m_RTP_Header.size+m_NAL_Header.size) -padding_size +
1;
playload += m_RTP_Header.size + m_NAL_Header.size -
1;
if(m_NAL_Header.size==
1 && !m_NAL_Header.isMajor578){
playload -=
3 ;
playload_size +=
3 ;
playload[
0] =
0x00; playload[
1] =
0x00;
playload[
2] =
0x01;
}
else if(m_NAL_Header.size==
1 && m_NAL_Header.isMajor578){
playload -=
4 ;
playload_size +=
4 ;
playload[
0] =
0x00; playload[
1] =
0x00;
playload[
2] =
0x00; playload[
3] =
0x01;
}
else if(m_NAL_Header.size==
2){
if(m_NAL_Header.FUA_S){
playload -=
4 ;
playload_size +=
4 ;
playload[
4] = (playload[
3]&
0xe0) + (playload[
4]&
0x1f);
playload[
0] =
0x00; playload[
1] =
0x00;
playload[
2] =
0x00; playload[
3] =
0x01;
}
else{
playload +=
1 ;
playload_size -=
1 ;
}
}
memcpy(H264_OneFrame+H264_1f_size, playload, playload_size);
H264_1f_size += playload_size ;
if(m_RTP_Header.M){
if(H264_1f_size<pic_size3){
reValue =
1 ;
}
else{
memset(H264_OneFrame,
0, pic_size3);
H264_1f_size =
0 ;
}
}
return reValue ;
}
};
FFMPEG解碼實現
終於到最後一步了。請編譯ffmpeg與x264。網上雖然不少資料,但大多不行,要本身綜合各方資料。請看上面的推薦連接。也許編譯這一步會有點麻煩,耐心一點總能夠解決。
關於FFMPEG多線程問題:
這裏注意多線程問題,若是你用FFMPEG解多路視頻的話,編譯的時候請--enable-w32thread. 不然很差說,網上不少人都說多線程會出現問題,但也有人沒有問題。並且就算你enable了多線程,裏面有些函數也是不支持多線程的。好比說:
/** ...... * @warning This function is not thread safe! * * @note Always call this function before using decoding routines (such as ....... */
int avcodec_open2(AVCodecContext *avctx,
const AVCodec *codec, AVDictionary **options);
關於FFMPGE的例子:
官方的資料裏有PLAYER的例子,還有test文件夾下的decode例子。推薦一個文件api-h264-test.cpp,這個參考性很高。
民間例子更多了,好比《最簡單的基於FFmpeg的視頻播放器 Simplest FFmpeg Player》雷霄驊。
下面看一個由api-h264-test.cpp更裝過來的函數。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
int C_FFMPEG_Decode::onlyforfun_and_test()
{
AVFormatContext *pFormatCtx;
int i, videoindex;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
char filepath[]=
"test.264";
av_register_all();
pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx,filepath,
NULL,
NULL)!=
0){
printf(
"Couldn't open input stream.(沒法打開輸入流)\n");
return -
1;
}
if(avformat_find_stream_info(pFormatCtx,
NULL)<
0){
printf(
"Couldn't find stream information.(沒法獲取流信息)\n");
return -
1;
}
videoindex=-
1;
for(i=
0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
}
if(videoindex==-
1){
printf(
"Didn't find a video stream.(沒有找到視頻流)\n");
return -
1;
}
pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==
NULL)
{
printf(
"Codec not found.(沒有找到解碼器)\n");
return -
1;
}
if(avcodec_open2(pCodecCtx, pCodec,
NULL)<
0)
{
printf(
"Could not open codec.(沒法打開解碼器)\n");
return -
1;
}
pCodecCtx->time_base
.num =
1 ;
pCodecCtx->frame_number =
1 ;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->bit_rate =
0 ;
pCodecCtx->time_base
.den =
25 ;
pCodecCtx->width =
384 ;
pCodecCtx->height =
288 ;
AVFrame *pFrame,*pFrameYUV;
pFrame=av_frame_alloc();
pFrameYUV=av_frame_alloc();
uint8_t *out_buffer=(uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV444P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV444P, pCodecCtx->width, pCodecCtx->height);
int ret, got_picture;
AVPacket *packet=(AVPacket *)av_malloc(
sizeof(AVPacket));
printf(
"File Information(文件信息)---------------------\n");
av_dump_format(pFormatCtx,
0,filepath,
0);
printf(
"-------------------------------------------------\n");
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV444P, SWS_BICUBIC,
NULL,
NULL,
NULL);
for (;;) {
if(
1){
Sleep(
40);
if(av_read_frame(pFormatCtx, packet)>=
0){
if(packet->stream_index==videoindex){
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(ret <
0){
printf(
"Decode Error.(解碼錯誤)\n");
return -
1;
}
if(got_picture){
sws_scale(img_convert_ctx, (
const uint8_t*
const*)pFrame->data, pFrame->linesize,
0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
int rows = pCodecCtx->height ;
int cols = pCodecCtx->width ;
if(rows && cols){
}
}
}
av_free_packet(packet);
}
else{
break;
}
}
}
sws_freeContext(img_convert_ctx);
av_free(out_buffer);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
這裏須要注意的是,視頻的寬、高、幀率、輸入格式、輸出格式等。
細心者能夠看到,這裏能夠直接打開URL。是的,可是不能打開全部URL。若是本身實現RTSP協議,則能夠打開足夠多類型的URL。
若是是打開URL,那接收buffer要足夠大,否則會花屏、卡、跳幀等。
若是本身實現RTSP/RTP,那麼就要本身填充AVPacket了(AVPacket是FFMPEG中一個爲數很少的重要結構體!)。參考代碼以下:java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
packet
= av_packet_alloc();
av_init_packet(packet);
packet
->data = (uint8_t
*)av_malloc(width
*height
*3+64);
memset(packet
->data,
0, width
*height
*3+64);
packet
->size
= 0 ;
.....
packet
->size
= nal_size ;
memcpy(packet
->data, packet
->size,nal_data);
......
avcodec_decode_video2(
...)
.....
memset(packet
->data,
0, width
*height
*3+64);
packet
->size
= 0 ;
......
看看整體效果圖
c#
一直從網上索取資料,今天終於抽出時間寫一篇原創了
感謝以上提到的連接的做者
2016年11月11日 廣州
Newyanapi