1、功能
实现了RTP OVER UDP:创建了TCP 套接字(socket),使用TCP协议来实现RTSP交互,为RTP和RTCP创建UDP 套接字(socket),利用UDP协议来发送RTP包。
2、参考
从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器:
https://blog.csdn.net/weixin_42462202/article/details/99111635
3、代码
具体见如下:
3.1、rtp.c
/** 作者:Frank*/#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <arpa/inet.h>#include \"rtp.h\"void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc){rtpPacket->rtpHeader.csrcLen = csrcLen;rtpPacket->rtpHeader.extension = extension;rtpPacket->rtpHeader.padding = padding;rtpPacket->rtpHeader.version = version;rtpPacket->rtpHeader.payloadType = payloadType;rtpPacket->rtpHeader.marker = marker;rtpPacket->rtpHeader.seq = seq;rtpPacket->rtpHeader.timestamp = timestamp;rtpPacket->rtpHeader.ssrc = ssrc;}int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize){struct sockaddr_in addr;int ret;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);ret = sendto(socket, (void*)rtpPacket, dataSize+RTP_HEADER_SIZE, 0,(struct sockaddr*)&addr, sizeof(addr));rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);return ret;}
View Code
3.2、rtp.h
/** 作者:Frank*/#ifndef _RTP_H_#define _RTP_H_#include <stdint.h>#define RTP_VESION 2#define RTP_PAYLOAD_TYPE_H264 96#define RTP_PAYLOAD_TYPE_AAC 97#define RTP_HEADER_SIZE 12#define RTP_MAX_PKT_SIZE 1400/*** 0 1 2 3* 7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* |V=2|P|X| CC |M| PT | sequence number |* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* | timestamp |* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* | synchronization source (SSRC) identifier |* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+* | contributing source (CSRC) identifiers |* : .... :* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+**/struct RtpHeader{/* byte 0 */uint8_t csrcLen:4;uint8_t extension:1;uint8_t padding:1;uint8_t version:2;/* byte 1 */uint8_t payloadType:7;uint8_t marker:1;/* bytes 2,3 */uint16_t seq;/* bytes 4-7 */uint32_t timestamp;/* bytes 8-11 */uint32_t ssrc;};struct RtpPacket{struct RtpHeader rtpHeader;uint8_t payload[0];};void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc);int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);#endif //_RTP_H_
View Code
3.3、h264_rtsp_server.c
/** 作者:Frank*/#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <time.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <assert.h>#include <unistd.h>#include \"rtp.h\"#define H264_FILE_NAME \"test.h264\"#define SERVER_PORT 8554#define SERVER_RTP_PORT 55532#define SERVER_RTCP_PORT 55533#define BUF_MAX_SIZE (1024*1024)static int createTcpSocket(){int sockfd;int on = 1;sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0)return -1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));return sockfd;}static int createUdpSocket(){int sockfd;int on = 1;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0)return -1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));return sockfd;}static int bindSocketAddr(int sockfd, const char* ip, int port){struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)return -1;return 0;}static int acceptClient(int sockfd, char* ip, int* port){int clientfd;socklen_t len = 0;struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));len = sizeof(addr);clientfd = accept(sockfd, (struct sockaddr *)&addr, &len);if(clientfd < 0)return -1;strcpy(ip, inet_ntoa(addr.sin_addr));*port = ntohs(addr.sin_port);return clientfd;}static inline int startCode3(char* buf){if(buf[0] == 0 && buf[1] == 0 && buf[2] == 1)return 1;elsereturn 0;}static inline int startCode4(char* buf){if(buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 1)return 1;elsereturn 0;}static char* findNextStartCode(char* buf, int len){int i;if(len < 3)return NULL;for(i = 0; i < len-3; ++i){if(startCode3(buf) || startCode4(buf))return buf;++buf;}if(startCode3(buf))return buf;return NULL;}static int getFrameFromH264File(int fd, char* frame, int size){int rSize, frameSize;char* nextStartCode;if(fd < 0)return fd;rSize = read(fd, frame, size);if(!startCode3(frame) && !startCode4(frame))return -1;nextStartCode = findNextStartCode(frame+3, rSize-3);if(!nextStartCode){//lseek(fd, 0, SEEK_SET);//frameSize = rSize;return -1;}else{frameSize = (nextStartCode-frame);lseek(fd, frameSize-rSize, SEEK_CUR);}return frameSize;}static int rtpSendH264Frame(int socket, const char* ip, int16_t port,struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize){uint8_t naluType; // nalu第一个字节int sendBytes = 0;int ret;naluType = frame[0];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);ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize);if(ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳goto out;}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; // startelse if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据rtpPacket->payload[1] |= 0x40; // endmemcpy(rtpPacket->payload+2, frame+pos, RTP_MAX_PKT_SIZE);ret = rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE+2);if(ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;pos += RTP_MAX_PKT_SIZE;}/* 发送剩余的数据 */if (remainPktSize > 0){rtpPacket->payload[0] = (naluType & 0x60) | 28;rtpPacket->payload[1] = naluType & 0x1F;rtpPacket->payload[1] |= 0x40; //endmemcpy(rtpPacket->payload+2, frame+pos, remainPktSize+2);ret = rtpSendPacket(socket, ip, port, rtpPacket, remainPktSize+2);if(ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;}}out:return sendBytes;}static char* getLineFromBuf(char* buf, char* line){while(*buf != \'\\n\'){*line = *buf;line++;buf++;}*line = \'\\n\';++line;*line = \'\\0\';++buf;return buf;}static int handleCmd_OPTIONS(char* result, int cseq){sprintf(result, \"RTSP/1.0 200 OK\\r\\n\"\"CSeq: %d\\r\\n\"\"Public: OPTIONS, DESCRIBE, SETUP, PLAY\\r\\n\"\"\\r\\n\",cseq);return 0;}static int handleCmd_DESCRIBE(char* result, int cseq, char* url){char sdp[500];char localIp[100];sscanf(url, \"rtsp://%[^:]:\", localIp);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 96\\r\\n\"\"a=rtpmap:96 H264/90000\\r\\n\"\"a=control:track0\\r\\n\",time(NULL), localIp);sprintf(result, \"RTSP/1.0 200 OK\\r\\nCSeq: %d\\r\\n\"\"Content-Base: %s\\r\\n\"\"Content-type: application/sdp\\r\\n\"\"Content-length: %d\\r\\n\\r\\n\"\"%s\",cseq,url,strlen(sdp),sdp);return 0;}static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort){sprintf(result, \"RTSP/1.0 200 OK\\r\\n\"\"CSeq: %d\\r\\n\"\"Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\\r\\n\"\"Session: 66334873\\r\\n\"\"\\r\\n\",cseq,clientRtpPort,clientRtpPort+1,SERVER_RTP_PORT,SERVER_RTCP_PORT);return 0;}static int handleCmd_PLAY(char* result, int cseq){sprintf(result, \"RTSP/1.0 200 OK\\r\\n\"\"CSeq: %d\\r\\n\"\"Range: npt=0.000-\\r\\n\"\"Session: 66334873; timeout=60\\r\\n\\r\\n\",cseq);return 0;}static void doClient(int clientSockfd, const char* clientIP, int clientPort,int serverRtpSockfd, int serverRtcpSockfd){char method[40];char url[100];char version[40];int cseq;int clientRtpPort, clientRtcpPort;char *bufPtr;char* rBuf = malloc(BUF_MAX_SIZE);char* sBuf = malloc(BUF_MAX_SIZE);char line[400];while(1){int recvLen;recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);if(recvLen <= 0)goto out;rBuf[recvLen] = \'\\0\';printf(\"---------------C->S--------------\\n\");printf(\"%s\", rBuf);/* 解析方法 */bufPtr = getLineFromBuf(rBuf, line);if(sscanf(line, \"%s %s %s\\r\\n\", method, url, version) != 3){printf(\"parse err\\n\");goto out;}/* 解析序列号 */bufPtr = getLineFromBuf(bufPtr, line);if(sscanf(line, \"CSeq: %d\\r\\n\", &cseq) != 1){printf(\"parse err\\n\");goto out;}/* 如果是SETUP,那么就再解析client_port */if(!strcmp(method, \"SETUP\")){while(1){bufPtr = getLineFromBuf(bufPtr, line);if(!strncmp(line, \"Transport:\", strlen(\"Transport:\"))){sscanf(line, \"Transport: RTP/AVP;unicast;client_port=%d-%d\\r\\n\",&clientRtpPort, &clientRtcpPort);break;}}}if(!strcmp(method, \"OPTIONS\")){if(handleCmd_OPTIONS(sBuf, cseq)){printf(\"failed to handle options\\n\");goto out;}}else if(!strcmp(method, \"DESCRIBE\")){if(handleCmd_DESCRIBE(sBuf, cseq, url)){printf(\"failed to handle describe\\n\");goto out;}}else if(!strcmp(method, \"SETUP\")){if(handleCmd_SETUP(sBuf, cseq, clientRtpPort)){printf(\"failed to handle setup\\n\");goto out;}}else if(!strcmp(method, \"PLAY\")){if(handleCmd_PLAY(sBuf, cseq)){printf(\"failed to handle play\\n\");goto out;}}else{goto out;}printf(\"---------------S->C--------------\\n\");printf(\"%s\", sBuf);send(clientSockfd, sBuf, strlen(sBuf), 0);/* 开始播放,发送RTP包 */if(!strcmp(method, \"PLAY\")){int frameSize, startCode;char* frame = malloc(500000);struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000);int fd = open(H264_FILE_NAME, O_RDONLY);assert(fd > 0);rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,0, 0, 0x88923423);printf(\"start play\\n\");printf(\"client ip:%s\\n\", clientIP);printf(\"client port:%d\\n\", clientRtpPort);while (1){frameSize = getFrameFromH264File(fd, frame, 500000);if(frameSize < 0){break;}if(startCode3(frame))startCode = 3;elsestartCode = 4;frameSize -= startCode;rtpSendH264Frame(serverRtpSockfd, clientIP, clientRtpPort,rtpPacket, frame+startCode, frameSize);rtpPacket->rtpHeader.timestamp += 90000/25;usleep(1000*1000/25);}free(frame);free(rtpPacket);goto out;}}out:printf(\"finish\\n\");close(clientSockfd);free(rBuf);free(sBuf);}int main(int argc, char* argv[]){int serverSockfd;int serverRtpSockfd, serverRtcpSockfd;serverSockfd = createTcpSocket();if(serverSockfd < 0){printf(\"failed to create tcp socket\\n\");return -1;}if(bindSocketAddr(serverSockfd, \"0.0.0.0\", SERVER_PORT) < 0){printf(\"failed to bind addr\\n\");return -1;}if(listen(serverSockfd, 10) < 0){printf(\"failed to listen\\n\");return -1;}serverRtpSockfd = createUdpSocket();serverRtcpSockfd = createUdpSocket();if(serverRtpSockfd < 0 || serverRtcpSockfd < 0){printf(\"failed to create udp socket\\n\");return -1;}if(bindSocketAddr(serverRtpSockfd, \"0.0.0.0\", SERVER_RTP_PORT) < 0 ||bindSocketAddr(serverRtcpSockfd, \"0.0.0.0\", SERVER_RTCP_PORT) < 0){printf(\"failed to bind addr\\n\");return -1;}printf(\"rtsp://127.0.0.1:%d\\n\", SERVER_PORT);while(1){int clientSockfd;char clientIp[40];int clientPort;clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);if(clientSockfd < 0){printf(\"failed to accept client\\n\");return -1;}printf(\"accept client;client ip:%s,client port:%d\\n\", clientIp, clientPort);doClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);}return 0;}
View Code
4、实现结果
编译过程:gcc -o rtp_h264 rtp_h264.c rtp.c
运行:./h264_rtsp_server
rtsp://127.0.0.1:8554accept client;client ip:127.0.0.1,client port:35172---------------C->S--------------OPTIONS rtsp://127.0.0.1:8554 RTSP/1.0CSeq: 2User-Agent: LibVLC/2.2.2 (LIVE555 Streaming Media v2016.02.09)---------------S->C--------------RTSP/1.0 200 OKCSeq: 2Public: OPTIONS, DESCRIBE, SETUP, PLAY---------------C->S--------------DESCRIBE rtsp://127.0.0.1:8554 RTSP/1.0CSeq: 3User-Agent: LibVLC/2.2.2 (LIVE555 Streaming Media v2016.02.09)Accept: application/sdp---------------S->C--------------RTSP/1.0 200 OKCSeq: 3Content-Base: rtsp://127.0.0.1:8554Content-type: application/sdpContent-length: 125v=0o=- 91630585458 1 IN IP4 127.0.0.1t=0 0a=control:*m=video 0 RTP/AVP 96a=rtpmap:96 H264/90000a=control:track0---------------C->S--------------SETUP rtsp://127.0.0.1:8554/track0 RTSP/1.0CSeq: 4User-Agent: LibVLC/2.2.2 (LIVE555 Streaming Media v2016.02.09)Transport: RTP/AVP;unicast;client_port=58866-58867---------------S->C--------------RTSP/1.0 200 OKCSeq: 4Transport: RTP/AVP;unicast;client_port=58866-58867;server_port=55532-55533Session: 66334873---------------C->S--------------PLAY rtsp://127.0.0.1:8554 RTSP/1.0CSeq: 5User-Agent: LibVLC/2.2.2 (LIVE555 Streaming Media v2016.02.09)Session: 66334873Range: npt=0.000----------------S->C--------------RTSP/1.0 200 OKCSeq: 5Range: npt=0.000-Session: 66334873; timeout=60start playclient ip:127.0.0.1client port:58866finish