基于tcp的RTP传输服务器

基于TCP的RTP传输服务器

流程

1,客户端请求RTSP的Describe请求时,RTSP服务器返回的SDP协议

2,客户端请求RTSP的Setup请求时,RTSP服务器不需要再对应创建RTP和RTCP的UDP连接通道,因为TCP版的RTP传输,客户端与服务器交互时,无论是RTSP信令还是RTP数据包或者是RTCP数据包,都是使用同一个tcp连接通道。

不过这个tcp连接通道在发送rtp数据包或者rtcp数据包时,需要加一些分隔字节。

3,客户端请求RTSP的Play请求时,RTSP服务器在对Play请求回复以后,还需要源源不断的同时向客户端发送音频流和视频流的RTP数据包。

与UDP 对比

  • UDP协议上的RTSP/RTP需要打开许多UDP端口,一个端口用于RTSP通信,n个端口用于RTP,n个端口用于RTCP
  • 中间网络路由器很容易就过滤或者忽略掉UDP数据包
  • UDP是不可靠传输协议,媒体包在因特网上传输时会面临着丢包

不需要再去创建UDP通道

CODE

//“m=” 出现新流 96代表H264 // 97代表音频流AAC

1
2
3
4
5
6
7
8
9
10
11
12
13
sprintf(sdp, "v=0\r\n"
"o=- 9%ld 1 IN IP4 %s\r\n"
"t=0 0\r\n"
"a=control:*\r\n"
"m=video 0 RTP/AVP/TCP 96\r\n"
"a=rtpmap:96 H264/90000\r\n"
"a=control:track0\r\n"
"m=audio 1 RTP/AVP/TCP 97\r\n"
"a=rtpmap:97 mpeg4-generic/44100/2\r\n"
"a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1210;\r\n"
"a=control:track1\r\n",

time(NULL), localIp);

sdp 返回到客户端后,客户端对每一路流都发起setup 发起的时候使用这里定义的名字 track0

doClient Function

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
std::thread t1([&]() {

int frameSize, startCode;
char* frame = (char*)malloc(500000);
// 创建RTP Packet
struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000);
FILE* fp = fopen(H264_FILE_NAME, "rb");
if (!fp) {
printf("读取 %s 失败\n", H264_FILE_NAME);
return;
}
//RTP_PAYLOAD_TYPE_H264 规定类型 0x88923423 SSRC指定
rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,
0, 0, 0x88923423);

printf("start play\n");

while (true) {
// 读取以nlu
frameSize = getFrameFromH264File(fp, frame, 500000);
if (frameSize < 0)
{
printf("读取%s结束,frameSize=%d \n", H264_FILE_NAME, frameSize);
break;
}

// 判断分隔符
if (startCode3(frame))
startCode = 3;
else
startCode = 4;

frameSize -= startCode;
rtpSendH264Frame(clientSockfd, rtpPacket, frame + startCode, frameSize);

rtpPacket->rtpHeader.timestamp += 90000 / 25;

//Sleep(40);//->30,20,需要处理NAL类型
Sleep(20);
//usleep(40000);//1000/25 * 1000
}
free(frame);
free(rtpPacket);

});

RTP的发送

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
static int rtpSendH264Frame(int clientSockfd,
struct RtpPacket* rtpPacket, char* frame, uint32_t frameSize)
{

uint8_t naluType; // nalu第一个字节
int sendByte = 0;
int ret;

naluType = frame[0];

printf("%s frameSize=%d \n", __FUNCTION__, frameSize);

if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包场:单一NALU单元模式
{

//* 0 1 2 3 4 5 6 7 8 9
//* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//* |F|NRI| Type | a single NAL unit ... |
//* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

memcpy(rtpPacket->payload, frame, frameSize);
// 主要区别 RTP 0X00
ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, frameSize,0x00);
if(ret < 0)
return -1;

rtpPacket->rtpHeader.seq++;
sendByte += ret;
if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳
{

}

}
else // nalu长度小于最大包:分片模式
{

//* 0 1 2
//* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
//* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//* | FU indicator | FU header | FU payload ... |
//* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+



//* FU Indicator
//* 0 1 2 3 4 5 6 7
//* +-+-+-+-+-+-+-+-+
//* |F|NRI| Type |
//* +---------------+



//* FU Header
//* 0 1 2 3 4 5 6 7
//* +-+-+-+-+-+-+-+-+
//* |S|E|R| Type |
//* +---------------+


int pktNum = frameSize / RTP_MAX_PKT_SIZE; // 有几个完整的包
int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小
int i, pos = 1;

// 发送完整的包
for (i = 0; i < pktNum; i++)
{
rtpPacket->payload[0] = (naluType & 0x60) | 28;
rtpPacket->payload[1] = naluType & 0x1F;

if (i == 0) //第一包数据
rtpPacket->payload[1] |= 0x80; // start
else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据
rtpPacket->payload[1] |= 0x40; // end

memcpy(rtpPacket->payload+2, frame+pos, RTP_MAX_PKT_SIZE);
ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, RTP_MAX_PKT_SIZE+2,0x00);
if(ret < 0)
return -1;

rtpPacket->rtpHeader.seq++;
sendByte += ret;
pos += RTP_MAX_PKT_SIZE;
}

// 发送剩余的数据
if (remainPktSize > 0)
{
rtpPacket->payload[0] = (naluType & 0x60) | 28;
rtpPacket->payload[1] = naluType & 0x1F;
rtpPacket->payload[1] |= 0x40; //end

memcpy(rtpPacket->payload+2, frame+pos, remainPktSize+2);
ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, remainPktSize+2, 0x00);
if(ret < 0)
return -1;

rtpPacket->rtpHeader.seq++;
sendByte += ret;
}
}


return sendByte;

}

rtpSendPacketOverTcp fuction

在发送之前 需要在RTP发送的字节之前加入四个字节

视频流和音频流都有两个通道 RTP和RTCP

如果不是I帧P帧的某一帧 则不能累加时间戳 否则会导致音视频传输不对等 如果累加了时间戳 会导致视频延时

tempBuf[0] = 0x24;//$ 分隔符
tempBuf[1] = channel;// 0x00; channel 一路流两个通道
tempBuf[2] = (uint8_t)(((rtpSize) & 0xFF00) >> 8);
tempBuf[3] = (uint8_t)((rtpSize) & 0xFF);
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
int rtpSendPacketOverTcp(int clientSockfd, struct RtpPacket* rtpPacket, uint32_t dataSize, char channel)
{

rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);
rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);
rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);

uint32_t rtpSize = RTP_HEADER_SIZE + dataSize;
char* tempBuf = (char *)malloc(4 + rtpSize);
tempBuf[0] = 0x24;//$
tempBuf[1] = channel;// 0x00;
tempBuf[2] = (uint8_t)(((rtpSize) & 0xFF00) >> 8);
tempBuf[3] = (uint8_t)((rtpSize) & 0xFF);
memcpy(tempBuf + 4, (char*)rtpPacket, rtpSize);

int ret = send(clientSockfd, tempBuf, 4 + rtpSize, 0);

rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);
rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);
rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);

free(tempBuf);
tempBuf = NULL;

return ret;
}

音频传输

ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, frameSize + 4,0x02);

这里的音频流是0X02 视频流和音频流共有两路 RTP和RTCP占用了01 因为这是RTP传输的第二路流 所以是02

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
static int rtpSendAACFrame(int clientSockfd,
struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize) {
int ret;

rtpPacket->payload[0] = 0x00;
rtpPacket->payload[1] = 0x10;
rtpPacket->payload[2] = (frameSize & 0x1FE0) >> 5; //高8位
rtpPacket->payload[3] = (frameSize & 0x1F) << 3; //低5位

memcpy(rtpPacket->payload + 4, frame, frameSize);


ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, frameSize + 4,0x02);

if (ret < 0)
{
printf("failed to send rtp packet\n");
return -1;
}

rtpPacket->rtpHeader.seq++;

/*
* 如果采样频率是44100
* 一般AAC每个1024个采样为一帧
* 所以一秒就有 44100 / 1024 = 43帧
* 时间增量就是 44100 / 43 = 1025
* 一帧的时间为 1 / 43 = 23ms
*/
rtpPacket->rtpHeader.timestamp += 1025;

return 0;
}

2 3 字节 做运算 存储

1
2
3
4
5
6
7
uint32_t rtpSize = RTP_HEADER_SIZE + dataSize;
char* tempBuf = (char *)malloc(4 + rtpSize);
tempBuf[0] = 0x24;//$ 分隔符
tempBuf[1] = channel;// 0x00; 通道
tempBuf[2] = (uint8_t)(((rtpSize) & 0xFF00) >> 8); //与运算后右移8位
tempBuf[3] = (uint8_t)((rtpSize) & 0xFF);
memcpy(tempBuf + 4, (char*)rtpPacket, rtpSize);

ffmpeg 命令行提取码流和音频文件

1
2
3
4
5
//ffmpeg命令行 从mp4视频文件提取h264 码流文件
ffmpeg -i test.mp4 -an -vcodec copy -f h264 test.h264

//ffmpeg命令行 从mp4视频文件提取aac 音频文件
ffmpeg -i test.mp4 -vn -acodec aac test.aac