音视频开发RTP基于tcp的RTP传输服务器
Edmend Zhang基于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); struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000); FILE* fp = fopen(H264_FILE_NAME, "rb"); if (!fp) { printf("读取 %s 失败\n", H264_FILE_NAME); return; } rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0, 0, 0, 0x88923423);
printf("start play\n");
while (true) { 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(20); } 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; int sendByte = 0; int ret;
naluType = frame[0]; printf("%s frameSize=%d \n", __FUNCTION__, frameSize);
if (frameSize <= RTP_MAX_PKT_SIZE) {
memcpy(rtpPacket->payload, frame, frameSize); ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, frameSize,0x00); if(ret < 0) return -1;
rtpPacket->rtpHeader.seq++; sendByte += ret; if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) {
}
} else {
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; else if (remainPktSize == 0 && i == pktNum - 1) rtpPacket->payload[1] |= 0x40;
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;
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; 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; rtpPacket->payload[3] = (frameSize & 0x1F) << 3;
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++;
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; tempBuf[2] = (uint8_t)(((rtpSize) & 0xFF00) >> 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
|