参考链接: 1. PES,TS,PS,RTP等流的打包格式解析之RTP流 https://blog.csdn.net/appledurian/article/details/73135343
2. RTP协议全解析(H264码流和PS流)https://blog.csdn.net/chen495810242/article/details/39207305
(重要)以下代码并未实测,除ts的发送外,其余都是伪代码(并且未搜集资料查询思路是否正确), 这边只为自己记录,参考请谨慎, 自己记录下而已。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h> static union { char c[]; unsigned long mylong; } endian_test = {{ 'l', '?', '?', 'b' } }; #define ENDIANNESS ((char)endian_test.mylong) #define PRINTF_DEBUG
#define TAB44 " " #define MAX_ARGS_FILEFORMAT 10
#define MAX_ARGS_FILEPATH 128
#define MAX_RTPURL_IP 128
#define MAX_ARGS_RTPURL 256
#define MTU 1400 #define DEFAULT_FILE_PATH "./videos/mux/ts_test.ts"
#define DEFAULT_FILE_FORMAT "ts"
#define DEFAULT_RTP_URL "rtp://127.0.0.1:8888" #define DEFAULT_ARGS {0, DEFAULT_FILE_PATH, DEFAULT_FILE_FORMAT, DEFAULT_RTP_URL} /* define4ts */
#define MAX_TS_PACKET_COUNT 7
#define TS_PACKET_LEN 188 /* define4ps */
#define SCODE_PS_END 0x000001B9
#define SCODE_PS_HEADER 0x000001BA
#define SCODE_PS_SYSTEM_HEADER 0x000001BB
#define SCODE_PS_SYSTEM_MAP_HEADER 0x000001BC /* define4mpeg2 */
typedef enum e_mpeg2_sc_type
{
E_SC_MPEG2_SEQ_HEADER = 0x000001B3,
E_SC_MPEG2_SEQ_PIC_EXTEN_HEADER = 0x000001B5,
E_SC_MPEG2_SEQ_END = 0x000001B7,
E_SC_MPEG2_GROUP_HEADER = 0x000001B8,
E_SC_MPEG2_PICTURE_HEADER = 0x00000100
} E_MPEG2_SC_TYPE; typedef enum e_rtp_playload_type
{
E_RTP_PLAYLOAD_TS = ,
E_RTP_PLAYLOAD_PS = ,
E_RTP_PLAYLOAD_MPEG4 = ,
E_RTP_PLAYLOAD_H264 = ,
} E_RTP_PLAYLOAD_TYPE; typedef struct t_args
{
unsigned short isLoop; unsigned char filePath[MAX_ARGS_FILEPATH+];
unsigned char fileFormat[MAX_ARGS_FILEFORMAT+];
unsigned char rtpUrl[MAX_ARGS_RTPURL+];
} T_ARGS; /******************************************************
个人理解
1. 位域内单字节的内存排布是定义的先后, 先定义的在内存的低地址;
2. 位域内单字节, 字节由高到低, 先定义的为高字节;
3. 因此对于小端(低地址放低字节).
******************************************************/
typedef struct t_rtp_header
{
#if 1 /* 小端, BIG_ENDIAN系统宏, 暂不知道怎么用 */
/* bytes 0 */
unsigned char csrc_len:;
unsigned char extension:;
unsigned char padding:;
unsigned char version:;
/* bytes 1*/
unsigned char playload:;
unsigned char marker:;
#else
/* bytes 0 */
unsigned char version:;
unsigned char padding:;
unsigned char extension:;
unsigned char csrc_len:;
/* bytes 1*/
unsigned char marker:;
unsigned char playload:;
#endif /* byte 2, 3 */
unsigned short seq_no;
/* bytess 4-7 */
unsigned int timestamp;
/* bytes 8-11 */
unsigned int ssrc;
} T_RTP_HEADER; /* gloabl data */
FILE *fp = NULL; struct sockaddr_in servAddr; T_ARGS defaultArgs = DEFAULT_ARGS; static void Usage(void)
{
fprintf(stderr, "usage: rtpserver [options]\n\n"
"Options:\n"
"-l | --stream_loop Read and send strame for loop\n"
"-i | --filepath File need to send\n"
"-f | --fileformat Container of file(support ts, ps, h264, mpeg2, flv)\n"
"-s | --rtpurl Rtp url include ip and port\n"
"-h | --help Print this message\n");
} /******************************************************************************
1. const char shortOpt[] = "li:f:s:h";
单个字符表示选项;
单个字符后接一个冒号, 表示后面必须跟一个参数. 参数紧跟选项后或者加一个空格;
单个字符后接两个冒号, 表示可有也可没有, 参数紧跟选项后, 不能加空格.
2. 参数的值赋给了optarg;
3. c = getopt_long(argc, argv, shortOpt, longOpt, NULL);
返回值为参数字符, 若全部解析完成则返回-1.
******************************************************************************/
static void ParseArgs(int argc, char *argv[], T_ARGS *args)
{
int c = ; const char shortOpt[] = "li:f:s:h";
const struct option longOpt[] = {
{"stream_loop", no_argument, NULL, 'l'},
{"filepath", required_argument, NULL, 'i'},
{"fileformat", required_argument, NULL, 'f'},
{"rtpurl", required_argument, NULL, 's'},
{"help", no_argument, NULL, 'h'},
{, , , }
}; for (;;)
{
c = getopt_long(argc, argv, shortOpt, longOpt, NULL); if (- == c)
{
break;
} switch (c)
{
case 'l':
args->isLoop = ; break; case 'i':
memcpy(args->filePath, optarg, strlen(optarg)); args->filePath[strlen(optarg)] = '\0'; break; case 'f':
if (( != strcmp(optarg, "ts"))
&& ( != strcmp(optarg, "ps")
&& ( != strcmp(optarg, "h264")
&& ( != strcmp(optarg, "mpeg2")
&& ( != strcmp(optarg, "flv"))
{
Usage(); exit();
} memcpy(args->fileFormat, optarg, strlen(optarg)); args->fileFormat[strlen(optarg)] = '\0'; break; case 's':
memcpy(args->rtpUrl, optarg, strlen(optarg)); args->rtpUrl[strlen(optarg)] = '\0'; break; default:
Usage(); exit();
}
}
} static void Parse_RtpUrl(unsigned char* const rtpUrl, unsigned char *urlIp, unsigned short *urlPort)
{
unsigned short port = ; unsigned char *url = NULL;
unsigned char *portStart = NULL; url = rtpUrl; url += strlen("rtp://"); portStart = strstr(url, ":"); port = atoi(portStart+); *urlPort = port; memcpy(urlIp, url, portStart-url);
} static unsigned long GetTickCount()
{
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (ts.tv_sec * + ts.tv_nsec / );
} static void Rtp_Header_Costruct(T_RTP_HEADER *rtpHeader, E_RTP_PLAYLOAD_TYPE playloadType)
{
static unsigned short seqNo = ; rtpHeader->version = ;
rtpHeader->padding = ;
rtpHeader->extension = ;
rtpHeader->csrc_len = ;
rtpHeader->marker = ;
rtpHeader->playload = playloadType;
rtpHeader->seq_no = seqNo++;
rtpHeader->timestamp = htonl(GetTickCount()*/);
rtpHeader->ssrc = htonl();
} static void Rtp_DealTs(int socketFd)
{
int readLen = ;
int bufCount = ;
int packetCount = ; unsigned char rtpBuf[MTU] = {}; memset(rtpBuf, 0x0, MTU); Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); bufCount = sizeof(T_RTP_HEADER); while ()
{
if (feof(fp))
{
if (defaultArgs.isLoop)
{
rewind(fp); packetCount = ; memset(rtpBuf, 0x0, MTU); Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); bufCount = sizeof(T_RTP_HEADER);
}
else
{
break;
}
} readLen = fread(rtpBuf+bufCount, , TS_PACKET_LEN, fp); packetCount++;
bufCount += readLen; if (packetCount>=MAX_TS_PACKET_COUNT)
{
sendto(socketFd, rtpBuf, bufCount, , (const struct sockaddr*)&servAddr, sizeof(servAddr)); packetCount = ; memset(rtpBuf, 0x0, MTU); Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); bufCount = sizeof(T_RTP_HEADER); usleep(); // 应根据帧率发送或者可实现rtcp来动态控制发送速度
}
}
} static void Rtp_DealPs(int socketFd)
{
int readLen = ; unsigned int startCode = ; while ()
{
if ( != fread(&startCode, , , fp))
{
break;
} switch (startCode)
{
case SCODE_PS_END:
break; case SCODE_PS_HEADER:
/* get and send, like psparse.c */ break; case SCODE_PS_SYSTEM_HEADER:
/* get and send, like psparse.c */ break; case SCODE_PS_SYSTEM_MAP_HEADER:
/* get and send, like psparse.c */ break; default:
/*
1. get and send, like psparse.c;
2. here data mybe>MTU, 分包, 每次发MTU, 直到全部完成;
3. rtp头上的marker标识了一帧的开始/结束, 分包的时候刚开始写0, 最后一包填1;
4. 未证实: 分包时rtp头的timestamp应该是不变的.
*/ break;
}
}
} /**************************************************************************************************************************
1. 组合封包模式
在NALU单元很小的时候, 可以将多个NALU封装到一个RTP包里面进行传输, 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24;
那么这里的类型值分别是24, 25, 26以及27;
我们主要介绍STAP-A, 其封包格式如下所示:
[RTP Header] [Nalu头, type: 24(一个字节78)] [Nalu1 len(2 bytes)] [Nalu1 data] [Nalu2 len(2 bytes)] [Nalu2 data] ...
2. 分片封包模式
当一个NALU长度超过了MTU, 就需要采用分片的方式进行RTP封包, 将一个NALU分到多个RTP包中进行传输;
存在两种分片类型FU-A和FU-B, 类型值分别是28和29.
RTP+FU-A分片封包的组合方式如下:
[RTP Header][FU indicator][FU header][payload]: 其中RTP Header占12字节, FU indicator和FU header各占1个字节; [FU indicator]有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
type=28表示FU-A分包 [FU header]的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S: 设置成1表示此FU-A分片包为NAL单元的起始包, 其他情况设置为0;
E:设置成1表示此FU-A分片为NAL单元的结束包, 其他情况设置为0;
R:保留位,必须为0;
Type: 为被分包的Nalu的type. 简单说就是加了一个字节描述分包的开始和结束.
*******************************************************************************************************************************/
static void Rtp_DealH264(int socketFd)
{
while (!feof(fp))
{
/*
1. get nalu data;
2. sps, pps等较小的, 可采用组合封包;
3. 帧数据大于MTU, 需分包. 如FU-A, 将帧拆分, 加上FU-A的格式, 再加上FTP的头发送出去;
4. 以上都可参照h264parse.c
*/
}
} static void Rtp_DealMpeg2(int socketFd)
{
while (!feof(fp))
{
/*
1. get data by startcode(seq, gop, pic...);
2. data_len<MTU, send;
3. data_len>MTU, 分包, 每次最大MTU;
4. 以上都可参照mpeg2parse.c
*/
}
} static void Rtp_DealFlv(int socketFd)
{
while (!feof(fp))
{
/*
1. get data by tag(script, video, audio...);
2. data_len<MTU, send;
3. data_len>MTU, 分包, 每次最大MTU;
4. 以上都可参照flvparse.c
*/
}
} /*
1. rtp client, send data to servAddr;
2. server can play used rtp://ip:port
*/
int main(int argc, char *argv[])
{
int socketFd = ; unsigned short serverPort = ; unsigned char serverIp[MAX_RTPURL_IP] = {}; ParseArgs(argc, argv, &defaultArgs); memset(serverIp, 0x0, MAX_RTPURL_IP); Parse_RtpUrl(defaultArgs.rtpUrl, serverIp, &serverPort); socketFd = socket(AF_INET, SOCK_DGRAM, );
if (socketFd < )
{
printf("%s\n", strerror(errno)); exit();
} memset(&servAddr, , sizeof(servAddr)); servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(serverPort);
servAddr.sin_addr.s_addr = inet_addr(serverIp); fp = fopen(defaultArgs.filePath, "r+");
if (!fp)
{
printf("%s\n", strerror(errno)); exit();
} if ( == strcmp(defaultArgs.fileFormat, "ts"))
{ Rtp_DealTs(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "ps"))
{
Rtp_DealPs(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "h264"))
{
Rtp_DealH264(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "mpeg2"))
{
Rtp_DealMpeg2(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "flv"))
{
Rtp_DealFlv(socketFd); fclose(fp);
} return ;
}
最后如果您觉得本篇对您有帮助,可以打赏下,谢谢!!!