The purpose of this article : The purpose of this article is to implement a RTSP server that plays H.264. I believe that if you read the previous articles carefully, it is simply as easy as putting the previous things together.
So this article The article does not describe new knowledge, but just puts together the previous things, sorts out the ideas, and finally gives an example. The following begins to explain the operation process of this example I provide.
First, establish a socket
Once you enter the main function, you listen to the server tcp socket, bind the port number, and then start listening
Then establish udp sockets for RTP and RTCP separately, bind the ports
Then enter the loop to start the service
main()
{
/* 创建服务器tcp套接字,绑定端口,监听 */
serverSockfd = createTcpSocket();
bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT);
listen(serverSockfd, 10);
/* 建立用于RTP和RTCP的udp套接字,绑定好端口 */
serverRtpSockfd = createUdpSocket();
serverRtcpSockfd = createUdpSocket();
bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT);
bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT);
while(1)
{
...
}
}
Second, receive client connections
Receive the client in the while loop, and then call the doClient service
main()
{
...
while(1)
{
clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
doClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);
}
}
The above is actually the basic steps of a TCP server, nothing special
Let's take a look at the doClient function
3. Parsing commands
doClient is a while loop (this is a server that can only serve one client at a time), constantly receiving command parsing commands, and then calling the corresponding operations
doClient()
{
while(1)
{
recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
...
sscanf(line, "%s %s %s\r\n", method, url, version);
...
sscanf(line, "CSeq: %d\r\n", &cseq)
...
}
}
Four, processing requests
After parsing the client command, it will call the corresponding request, after processing, it will receive the print sBuf
, and then send it to the client
doClient()
{
while(1)
{
...
/* 处理请求 */
if(!strcmp(method, "OPTIONS"))
handleCmd_OPTIONS(sBuf, cseq);
else if(!strcmp(method, "DESCRIBE"))
handleCmd_DESCRIBE(sBuf, cseq, url);
else if(!strcmp(method, "SETUP"))
handleCmd_SETUP(sBuf, cseq, clientRtpPort);
else if(!strcmp(method, "PLAY"))
handleCmd_PLAY(sBuf, cseq);
/* 放回结果 */
send(clientSockfd, sBuf, strlen(sBuf), 0);
}
}
Let's take a look at the actions of each request
4.1 OPTIONS
Return to available methods
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;
}
4.2 DESCRIBE
Return sdp file information, this is a H.264 media description information, the details have been explained in the previous article, no more cumbersome here
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;
}
4.3 SETUP
SETUP process sends server RTP port and RTCP port
static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort,
int* localRtpSockfd, int* localRtcpSockfd)
{
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;
}
4.4 PLAY
After the PLAY operation reply, it will start sending RTP packets
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;
}
Five, H.264 RTP package transmission
Read a NALU from the H.264 file and send RTP packets to the client (destination IP, destination RTP port)
doClient()
{
while(1)
{
...
...
...
if(!strcmp(method, "PLAY"))
{
while(1)
{
/* 获取一帧 */
frameSize = getFrameFromH264File(fd, frame, 500000);
/* RTP打包发送 */
rtpSendH264Frame(localRtpSockfd, clientIP, clientRtpPort,
rtpPacket, frame+startCode, frameSize);
}
}
}
}
Let's take a look at the RTP packaging process. RTP packaging implements single NALU packaging and fragment packaging
static int rtpSendH264Frame(int socket, const char* ip, int16_t port,
struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize)
{
/* 如果包比较小,则采用单NALU打包 */
if (frameSize <= RTP_MAX_PKT_SIZE)
{
rtpSendPacket(socket, ip, port, rtpPacket, frameSize);
}
else //否则采用分片打包
{
for (i = 0; i < pktNum; i++)
{
/* 填充载荷的前两个字节 */
rtpPacket->payload[0] = (naluType & 0x60) | 28;
rtpPacket->payload[1] = naluType & 0x1F;
/* 发送RTP包 */
rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE+2);
}
}
}
Sixth, the source code
I am too lazy to build a Git warehouse, the source code is posted directly here
A total of three h264_rtsp_server.c
documents, rtp.c
, ,rtp.h
h264_rtsp_server.c
#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 "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;
else
return 0;
}
static inline int startCode4(char* buf)
{
if(buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 1)
return 1;
else
return 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; // start
else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据
rtpPacket->payload[1] |= 0x40; // end
memcpy(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; //end
memcpy(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;
else
startCode = 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;
}
rtp.c
#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, const 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;
}
rtp.h
#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, const char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);
#endif //_RTP_H_
Seven, test
Save the file down to h264_rtsp_server.c
three, rtp.c
, ,rtp.h
Compile # gcc h264_rtsp_server.c rtp.c
Run, the program will open test.h264
the video file by default , if you do not have a video source, you can get it from the example directory of RtspServer
# ./a.out
A url will be printed after running
rtsp://127.0.0.1:8554
Enter url in vlc, you can see the video