本文记录了UDP-RTP网络协议解析,网络协议数据在视频播放器中的位置如下所示
原理
MPEG-TS封装格式数据打包为RTP/UDP协议然后发送出去的流程如下图所示。图中首先每7个MPEG-TS Packet打包为一个RTP,然后每个RTP再打包为一个UDP。其中打包RTP的方法就是在MPEG-TS数据前面加上RTP Header,而打包UDP的方法就是在RTP数据前面加上UDP Header。
本程序的流程和上述发送MPEG-TS的流程正好是相反的,通过Socket编程收取UDP包,解析其中的RTP包的信息,然后再解析RTP包中MPEG-TS Packet的信息。
代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <error.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <arpa/inet.h>
#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "xohnffmpeg", format, ##__VA_ARGS__)
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, "xohnffmpeg", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...) printf("xohnffmpeg" format "\n", ##__VA_ARGS__)
#define LOGI(format, ...) printf("xohnffmpeg " format "\n", ##__VA_ARGS__)
#endif
#pragma pack(1)
/*
* VLC
* [memo] FFmpeg stream Command:
* ffmpeg -re -i sintel.ts -vcodec copy -acodec copy -f mpegts udp://192.168.8.108:8880
* ffmpeg -re -i sintel.ts -vcodec copy -acodec copy -f rtp_mpegts rtp://192.168.8.108:8880
*/
typedef struct RTP_FIXED_HEADER{
/* byte 0 */
unsigned char csrc_len:4; /* expect 0 */
unsigned char extension:1; /* expect 1 */
unsigned char padding:1; /* expect 0 */
unsigned char version:2; /* expect 2 */
/* byte 1 */
unsigned char payload:7;
unsigned char marker:1; /* expect 1 */
/* bytes 2, 3 */
unsigned short seq_no;
/* bytes 4-7 */
unsigned int timestamp;
/* bytes 8-11 */
unsigned int ssrc; /* stream number is used here. */
} 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;
/**
* Analysis UDP-RTP stream
* @param jstr_url Location of output st file.
*/
extern "C" JNIEXPORT jint JNICALL
Java_com_xohn_ffmpeg_AVUtils_udpRtpParser(
JNIEnv *env,jobject /* this */,
jstring jstr_url) {
const char *str = env->GetStringUTFChars(jstr_url, NULL);
FILE *fp1 = fopen(str,"wb+");
env->ReleaseStringUTFChars(jstr_url,str);
int st = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET, SOCK_STREAM, 0
if (st == -1){
LOGE("socket failed:%s\n", strerror(errno));
return EXIT_FAILURE;
}
int on = 0;
if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1){
LOGE("setsockopt failed:%s\n", strerror(errno));
close(st);
return EXIT_FAILURE;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8880);
//INADDR_ANY表示这个服务器上的所有Ip地址。一台服务器可以有多个ip地址。将socket绑定到这个机器的所有ip地址上
addr.sin_addr.s_addr = htonl(INADDR_ANY); //inet_addr("192.168.8.170"); //htonl(INADDR_ANY);
//将ip地址与server程序绑定
if (bind(st, (struct sockaddr*)&addr, sizeof(addr)) == -1){
LOGE("bind fail:%s\n", strerror(errno));
close(st);
return EXIT_FAILURE;
}
sockaddr_in remoteAddr;
socklen_t nAddrLen = sizeof(remoteAddr);
memset(&remoteAddr, 0, sizeof(remoteAddr));
int parse_rtp = 1;
int parse_mpegts = 1;
int cnt = 0;
char recvData[10000];
while (1){
//LOGI("while before recvfrom\n");
int pktsize = recvfrom(st, recvData, 10000, 0, (sockaddr *)&remoteAddr, &nAddrLen);
if (pktsize > 0){
//LOGI("Addr:%s\r\n",inet_ntoa(remoteAddr.sin_addr));
//LOGI("packet size:%d\r\n",pktsize);
//Parse RTP
if(parse_rtp != 0){
char payload_str[10] = {0};
RTP_FIXED_HEADER rtp_header;
int rtp_header_size = sizeof(RTP_FIXED_HEADER); //12个字节
//RTP Header
memcpy((void *)&rtp_header,recvData,rtp_header_size);
//RFC3551
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);
LOGI("[RTP Pkt] %5d| %5s| %10u| %5d| %5d|\n",cnt,payload_str,timestamp,seq_no,pktsize);
//RTP Data
char *rtp_data = recvData+rtp_header_size;
int rtp_data_size = pktsize-rtp_header_size;
fwrite(rtp_data,rtp_data_size,1,fp1);
//Parse MPEGTS
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;
//MPEGTS Header
//memcpy((void *)&mpegts_header,rtp_data+i,sizeof(MPEGTS_FIXED_HEADER));
LOGI(" [MPEGTS Pkt]\n");
}
}
}else{
LOGI("[UDP Pkt] %5d| %5d|\n",cnt,pktsize);
fwrite(recvData,pktsize,1,fp1);
}
cnt++;
}else if (pktsize == 0){//如果客户端关闭连接,server端接收时,返回0
LOGI("receive close\n");
break;
}else{
LOGI("receive fail %s\n", strerror(errno));
break;
}
}
close(st);
getchar();
fclose(fp1);
}
函数调用
AVUtils.getInstance().udpRtpParser(ROOT+"output_sintel.ts");
本程序执行后需要电脑端推流,输出为UDP/RTP/MPEG-TS的解析结果,并且生成一个output_sintel.ts文件。
首先查看手机端IP地址,具体在 “设置-系统-关于手机-状态信息-IP地址”(不同手机目录可能不一样),我测试手机为192.168.8.108,端口用8880。
电脑端用VLC工具推流(具体可以百度去官网下载,免费的),“媒体-流-添加(选择推流文件)-下面选串流-选择RTP/MPEG TS-添加-输入IP地址和端口号”
注:本来打算用ffmpeg推流,但是没成功,后期如果有需要在试吧。但用这个可以看到推流文件的音视频信息
ffmpeg -re -i sintel.ts -vcodec copy -acodec copy -f mpegts udp://192.168.8.108:8880
配置文件新加了一个,具体配置选择如下:
设置好保存,点流就推送了
Socket接收到UDP包并且解析的数据结果如下图,并且在输入目录下生成output_sintel.ts文件
参考文章:
雷神文章 https://blog.csdn.net/leixiaohua1020/article/details/50535230
项目git地址:
https://gitee.com/xohn/FFmpeg.git 中的FFMpeg1 - xohn_avutils_udp-rtp.cpp