0. 概念
网络协议数据在视频播放器中的位置如下所示。
MPEG-TS封装格式数据打包为RTP/UDP协议然后发送出去的流程如下图所示。图中首先每7个MPEG-TS Packet打包为一个RTP,然后每个RTP再打包为一个UDP。其中打包RTP的方法就是在MPEG-TS数据前面加上RTP Header,而打包RTP的方法就是在RTP数据前面加上UDP Header。
基本概念就差不多这些,更多的请看我写的代码注释,以及本文参考链接。
1. 代码
extern "C"
{
#ifdef __cplusplus
#define __STDC_CONSTANT_MACROS
#endif
}
extern "C" {
#include <stdio.h>
#include <winsock2.h>
}
#pragma comment(lib, "ws2_32.lib")
#pragma pack(1)
typedef struct RTP_FIXED_HEADER
{
unsigned char csrc_len
: 4;
unsigned char extension
: 1;
unsigned char padding
: 1;
unsigned char version
: 2;
unsigned char payload
: 7;
unsigned char marker
: 1;
unsigned short seq_no
;
unsigned long timestamp
;
unsigned long ssrc
;
} RTP_FIXED_HEADER
;
typedef struct MPEGTS_FIXED_HEADER
{
unsigned sync_byte
: 8;
unsigned transport_error_indicator
: 1;
unsigned payload_unit_start_indicator
: 1;
unsigned transport_priority
: 1;
unsigned PID
: 13;
unsigned scrambling_control
: 2;
unsigned adaptation_field_exist
: 2;
unsigned continuity_counter
: 4;
} MPEGTS_FIXED_HEADER
;
int simplest_udp_parser(int port
)
{
WSADATA wsaData
;
WORD sockVersion
= MAKEWORD(2, 2);
int cnt
= 0;
FILE
* myout
= stdout;
FILE
* fp1
= fopen("output_dump.ts", "wb+");
if (WSAStartup(sockVersion
, &wsaData
) != 0) {
return 0;
}
SOCKET serSocket
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
if (serSocket
== INVALID_SOCKET
) {
printf("socket error !");
return 0;
}
sockaddr_in serAddr
;
serAddr
.sin_family
= AF_INET
;
serAddr
.sin_port
= htons(port
);
serAddr
.sin_addr
.S_un
.S_addr
= INADDR_ANY
;
if (bind(serSocket
, (sockaddr
*)&serAddr
, sizeof(serAddr
)) == SOCKET_ERROR
) {
printf("bind error !");
closesocket(serSocket
);
return 0;
}
sockaddr_in remoteAddr
;
int nAddrLen
= sizeof(remoteAddr
);
int parse_rtp
= 1;
int parse_mpegts
= 1;
printf("Listening on port %d\n", port
);
char recvData
[10000];
while (1) {
int pktsize
= recvfrom(serSocket
, recvData
, 10000, 0, (sockaddr
*)&remoteAddr
,
&nAddrLen
);
if (pktsize
> 0) {
if (parse_rtp
!= 0) {
char payload_str
[10] = { 0 };
RTP_FIXED_HEADER rtp_header
;
int rtp_header_size
= sizeof(RTP_FIXED_HEADER
);
memcpy((void*)&rtp_header
, recvData
, rtp_header_size
);
char payload
= rtp_header
.payload
;
switch (payload
) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18: sprintf(payload_str
, "Audio"); break;
case 31: sprintf(payload_str
, "H.261"); break;
case 32: sprintf(payload_str
, "MPV"); break;
case 33: sprintf(payload_str
, "MP2T"); break;
case 34: sprintf(payload_str
, "H.263"); break;
case 96: sprintf(payload_str
, "H.264"); break;
default:sprintf(payload_str
, "other"); break;
}
unsigned int timestamp
= ntohl(rtp_header
.timestamp
);
unsigned int seq_no
= ntohs(rtp_header
.seq_no
);
fprintf(myout
, "[RTP Pkt] ]| %5s| u| ]| ]|\n", cnt
,
payload_str
, timestamp
, seq_no
, pktsize
);
char* rtp_data
= recvData
+ rtp_header_size
;
int rtp_data_size
= pktsize
- rtp_header_size
;
fwrite(rtp_data
, rtp_data_size
, 1, fp1
);
if (parse_mpegts
!= 0 && payload
== 33) {
MPEGTS_FIXED_HEADER mpegts_header
;
for (int i
= 0; i
< rtp_data_size
; i
= i
+ 188) {
if (rtp_data
[i
] != 0x47)
break;
fprintf(myout
, " [MPEGTS Pkt]\n");
}
}
}
else {
fprintf(myout
, "[UDP Pkt] ]| ]|\n", cnt
, pktsize
);
fwrite(recvData
, pktsize
, 1, fp1
);
}
cnt
++;
}
}
closesocket(serSocket
);
WSACleanup();
fclose(fp1
);
return 0;
}
int main()
{
simplest_udp_parser(8880);
return 0;
}
先在visual studio编译运行,
然后把sintel.ts放在ffmpeg.exe同级目录,使用ffmpeg推流。 ffmpeg安装使用可以参考我的这篇文章: https://blog.csdn.net/Codeliang666/article/details/106924074
ffmpeg
-re
-i sintel
.ts
-f rtp_mpegts udp
://127.0.0.1:8880
如下会一边推流一边解析文件。
2. 参考链接
https://blog.csdn.net/leixiaohua1020/article/details/50535230https://blog.csdn.net/zhaoyaofeng/article/details/89420829https://blog.csdn.net/Kayson12345/article/details/81266587