云计算百科
云计算领域专业知识百科平台

【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭 🤣本文内容🤣:🍭介绍怎么实现最简单的传输H264的RTSP服务器🍭 😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭 ⏰发布时间⏰: 2025-06-30

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、实现步骤、实现细节
    • ✨2.1、使用RTSP服务的TCP套接字并监听
    • ✨2.2、接收客户端请求并处理RTSP命令
    • ✨2.3、读取H264并发送
  • 🎄三、传输H264的RTSP服务器的实现源码
  • 🎄四、总结

在这里插入图片描述

RTSP从零实践系列文章回顾: 【RTSP从零实践】1、根据RTSP协议实现一个RTSP服务 【RTSP从零实践】2、使用RTP协议封装并传输H264 【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器 【RTSP从零实践】4、使用RTP协议封装并传输AAC

在这里插入图片描述

🎄一、概述

这篇文章主要就是介绍怎么通过RTSP协议通信,将H264视频数据封装成RTP包并发送的,目的是写一个最简单的RTSP服务器,熟悉RTSP协议处理、RTP协议封包。

包含了下面知识点: 1、RTSP协议的内容; 2、实现TCP协议实现RTSP; 3、RTP协议; 4、怎么实现RTP协议;

这些知识点都可以从上面 【RTSP从零实践】系列文章学习到,这篇文章不再赘述,只介绍步骤和代码。


在这里插入图片描述

🎄二、实现步骤、实现细节

这个小写介绍怎么实现的步骤、细节,下个小节会提供源码,可以结合着源码看帮助理解消化。

✨2.1、使用RTSP服务的TCP套接字并监听

RTSP协议是基于TCP作为传输层协议去实现的。

// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
return 1;
}

// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
{
perror("setsockopt");
return 1;
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(RTSP_PORT);

// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("bind failed");
return 1;
}

// 开始监听
if (listen(server_fd, MAX_CLIENTS) < 0)
{
perror("listen");
return 1;
}


✨2.2、接收客户端请求并处理RTSP命令

RTSP通信不直接传输音视频码流,会先进行一些命令通信,所以第二个步骤就是处理这些命令:

char response[1024] = {0}; // 构造响应
if (strcmp(method, "OPTIONS") == 0)
{
rtsp_handle_OPTION(response, cseq);
}
else if (strcmp(method, "DESCRIBE") == 0)
{
rtsp_handle_DESCRIBE(response, cseq);
}
else if (strcmp(method, "SETUP") == 0)
{
rtsp_handle_SETUP(response, cseq, rtpPort);
}
else if (strcmp(method, "PLAY") == 0)
{
rtsp_handle_PLAY(response, cseq);
bSendFlag = RTP_PLAY;
}
else if (strcmp(method, "TEARDOWN") == 0)
{
rtsp_handle_TEARDOWN(response, cseq);
bSendFlag = RTP_STOP;
}
else
{
snprintf(response, sizeof(response),
"RTSP/1.0 501 Not Implemented\\r\\nCSeq: %d\\r\\n\\r\\n", cseq);
}


✨2.3、读取H264并发送

处理完RTSP命令后,就需要读取并发送RTP包了。

// h264
H264ReaderInfo_t h264Info;
if (H264_FileOpen(H264_FILE_NAME, &h264Info) < 0)
{
printf("failed to open %s\\n", H264_FILE_NAME);
return NULL;
}

H264Frame_t h264Frame;
h264Frame.pFrameBuf = (unsigned char *)malloc(1920 * 1080 * 4);

while (pRtpSend->bPlayFlag)
{
if (!H264_IsEndOfFile(&h264Info))
{
h264Frame.isLastFrame = 0;
H264_GetFrame(&h264Frame, &h264Info);

if (h264Frame.pFrameBuf != NULL)
{
if (h264Frame.isLastFrame) // 最后一帧,移到开头重新读
{
printf("warning SeekFile 1\\n");
H264_SeekFile(&h264Info);
}

// printf("rtpSendH264Frame, frameNum=%d, time=%u\\n", h264Info.frameNum, rtpPacket->rtpHeader.timestamp);
rtpSendH264Frame(rtp_send_fd, cli_ip, rtpPort, rtpPacket,
h264Frame.pFrameBuf + h264Frame.startcode_len,
h264Frame.frame_len h264Frame.startcode_len);

rtpPacket->rtpHeader.timestamp += 90000 / FPS; // RTP 传输视频每秒 90k HZ
usleep(1000 * 1000 / FPS);
}
}
else
{
printf("warning need SeekFile 1\\n");
}
}


在这里插入图片描述

🎄三、传输H264的RTSP服务器的实现源码

总共有5个源代码文件,具体如下:

1、H264Reader.h

/**
* @file H264Reader.h
* @author https://blog.csdn.net/wkd_007
* @brief
* @version 0.1
* @date 2025-06-24
*
* @copyright Copyright (c) 2025
*
*/

#ifndef __H264_READER_H__
#define __H264_READER_H__

#include <stdio.h>

#define MAX_STARTCODE_LEN (4)

typedef enum
{
FALSE,
TRUE,
} BOOL;

typedef enum
{
H264_NALU_TYPE_SLICE = 1,
H264_NALU_TYPE_DPA = 2,
H264_NALU_TYPE_DPB = 3,
H264_NALU_TYPE_DPC = 4,
H264_NALU_TYPE_IDR = 5,
H264_NALU_TYPE_SEI = 6,
H264_NALU_TYPE_SPS = 7,
H264_NALU_TYPE_PPS = 8,
H264_NALU_TYPE_AUD = 9,
H264_NALU_TYPE_EOSEQ = 10,
H264_NALU_TYPE_EOSTREAM = 11,
H264_NALU_TYPE_FILL = 12,
} H264NaluType;

typedef enum
{
H264_NALU_PRIORITY_DISPOSABLE = 0,
H264_NALU_PRIRITY_LOW = 1,
H264_NALU_PRIORITY_HIGH = 2,
H264_NALU_PRIORITY_HIGHEST = 3
} H264NaluPriority;

typedef struct
{
int startcode_len; //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
int forbidden_bit; //! should be always FALSE
int nal_reference_idc; //! H264_NALU_PRIORITY_xxxx
int nal_unit_type; //! H264_NALU_TYPE_xxxx
BOOL isLastFrame; //!
int frame_len; //!
unsigned char *pFrameBuf; //!
} H264Frame_t;

typedef struct H264ReaderInfo_s
{
FILE *pFileFd;
int frameNum;
} H264ReaderInfo_t;

int H264_FileOpen(char *fileName, H264ReaderInfo_t *pH264Info);
int H264_FileClose(H264ReaderInfo_t *pH264Info);
int H264_GetFrame(H264Frame_t *pH264Frame, H264ReaderInfo_t *pH264Info);
BOOL H264_IsEndOfFile(const H264ReaderInfo_t *pH264Info);
void H264_SeekFile(H264ReaderInfo_t *pH264Info);

#endif // __H264_READER_H__

2、H264Reader.c

/**
* @file H264Reader.c
* @author https://blog.csdn.net/wkd_007
* @brief
* @version 0.1
* @date 2025-06-30
*
* @copyright Copyright (c) 2025
*
*/

#include "H264Reader.h"
#include <stdlib.h>

#define MAX_FRAME_LEN (1920 * 1080 * 1.5) // 一帧数据最大字节数

static BOOL findStartCode_001(unsigned char *Buf)
{
// printf("[%d %d %d]\\n", Buf[0], Buf[1], Buf[2]);
return (Buf[0] == 0 && Buf[1] == 0 && Buf[2] == 1); // 0x000001?
}

static BOOL findStartCode_0001(unsigned char *Buf)
{
// printf("[%d %d %d %d]\\n", Buf[0], Buf[1], Buf[2], Buf[3]);
return (Buf[0] == 0 && Buf[1] == 0 && Buf[2] == 0 && Buf[3] == 1); // 0x00000001?
}

int H264_FileOpen(char *fileName, H264ReaderInfo_t *pH264Info)
{
pH264Info->pFileFd = fopen(fileName, "rb+");
if (pH264Info->pFileFd == NULL)
{
printf("[%s %d]Open file error\\n", __FILE__, __LINE__);
return 1;
}
pH264Info->frameNum = 0;
return 0;
}

int H264_FileClose(H264ReaderInfo_t *pH264Info)
{
if (pH264Info->pFileFd != NULL)
{
fclose(pH264Info->pFileFd);
pH264Info->pFileFd = NULL;
}
return 0;
}

BOOL H264_IsEndOfFile(const H264ReaderInfo_t *pH264Info)
{
return feof(pH264Info->pFileFd);
}

void H264_SeekFile(H264ReaderInfo_t *pH264Info)
{
fseek(pH264Info->pFileFd, 0, SEEK_SET);
pH264Info->frameNum = 0;
}

/**
* @brief 获取一阵h264视频帧
*
* @param pH264Frame :输出参数,使用后 pH264Frame->pFrameBuf 需要free
* @param pH264Info :输入参数
* @return int
*/

int H264_GetFrame(H264Frame_t *pH264Frame, H264ReaderInfo_t *pH264Info)
{
int rewind = 0;
if (pH264Info->pFileFd == NULL)
{
printf("[%s %d]pFileFd error\\n", __FILE__, __LINE__);
return 1;
}

// 1.读取帧数据
// unsigned char *pFrame = (unsigned char *)malloc(MAX_FRAME_LEN);
unsigned char *pFrame = pH264Frame->pFrameBuf;
int readLen = fread(pFrame, 1, MAX_FRAME_LEN, pH264Info->pFileFd);
if (readLen <= 0)
{
printf("[%s %d]fread error\\n", __FILE__, __LINE__);
// free(pFrame);
return 1;
}

// 2.查找当前帧开始码
int i = 0;
for (; i < readLen MAX_STARTCODE_LEN; i++)
{
if (!findStartCode_0001(&pFrame[i]))
{
if (!findStartCode_001(&pFrame[i]))
{
continue;
}
else
{
pH264Frame->startcode_len = 3;
break;
}
}
else
{
pH264Frame->startcode_len = 4;
break;
}
}
if (i != 0) // 不是帧开头,偏移到帧开头重新读
{
printf("[%s %d]startcode error, i=%d\\n", __FILE__, __LINE__, i);
// free(pFrame);
rewind = ((readLen i));
fseek(pH264Info->pFileFd, rewind, SEEK_CUR);
return 1;
}

// 3.查找下一帧开始码
i += MAX_STARTCODE_LEN;
for (; i < readLen MAX_STARTCODE_LEN; i++)
{
if (!findStartCode_0001(&pFrame[i]))
{
if (!findStartCode_001(&pFrame[i]))
{
continue;
}
else
{
break;
}
}
else
{
break;
}
}
if (i == (readLen MAX_STARTCODE_LEN))
{
if (!feof(pH264Info->pFileFd))
{
printf("[%s %d]MAX_FRAME_LEN too small\\n", __FILE__, __LINE__);
// free(pFrame);
return 1;
}
else
{
pH264Frame->isLastFrame = TRUE;
}
}

// 4.填数据
pH264Frame->forbidden_bit = pFrame[pH264Frame->startcode_len] & 0x80; // 1 bit
pH264Frame->nal_reference_idc = pFrame[pH264Frame->startcode_len] & 0x60; // 2 bit
pH264Frame->nal_unit_type = pFrame[pH264Frame->startcode_len] & 0x1f; // 5 bit, naluType 是开始码后一个字节的最后 5 位
// pH264Frame->pFrameBuf = pFrame;
pH264Frame->frame_len = i;

// 5.文件读取指针偏移到下一帧位置
rewind = ((readLen i));
fseek(pH264Info->pFileFd, rewind, SEEK_CUR);

pH264Info->frameNum++;

return pH264Frame->frame_len;
}

3、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, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);

#endif //_RTP_H_

4、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, 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;
}

5、rtsp_h264_main.c

/**
* @file rtsp_h264_main.c
* @author https://blog.csdn.net/wkd_007
* @brief
* @version 0.1
* @date 2025-06-30
*
* @copyright Copyright (c) 2025
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "rtp.h"
#include "H264Reader.h"

#define H264_FILE_NAME "test.h264"
#define FPS 25

#define RTSP_PORT 8554
#define RTP_PORT 55666
#define MAX_CLIENTS 5
#define SESSION_ID 10086001
#define SESSION_TIMEOUT 60

typedef struct
{
int rtpSendFd;
int rtpPort;
int bPlayFlag; // 播放标志
char *cliIp;
} RTP_Send_t;

typedef enum
{
RTP_NULL,
RTP_PLAY,
RTP_PLAYING,
RTP_STOP,
} RTP_PLAY_STATE;

static int createUdpSocket()
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return 1;

int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));

return fd;
}

static int rtpSendH264Frame(int socket, 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;
}

void *sendRtp(void *arg)
{
RTP_Send_t *pRtpSend = (RTP_Send_t *)arg;
int rtp_send_fd = pRtpSend->rtpSendFd;
int rtpPort = pRtpSend->rtpPort;
char *cli_ip = pRtpSend->cliIp;

struct RtpPacket *rtpPacket = (struct RtpPacket *)malloc(sizeof(struct RtpPacket) + (1920 * 1080 * 4));

rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,
0, 0, 0x88923423);

// h264
H264ReaderInfo_t h264Info;
if (H264_FileOpen(H264_FILE_NAME, &h264Info) < 0)
{
printf("failed to open %s\\n", H264_FILE_NAME);
return NULL;
}

H264Frame_t h264Frame;
h264Frame.pFrameBuf = (unsigned char *)malloc(1920 * 1080 * 4);

while (pRtpSend->bPlayFlag)
{
if (!H264_IsEndOfFile(&h264Info))
{
h264Frame.isLastFrame = 0;
H264_GetFrame(&h264Frame, &h264Info);

if (h264Frame.pFrameBuf != NULL)
{
if (h264Frame.isLastFrame) // 最后一帧,移到开头重新读
{
printf("warning SeekFile 1\\n");
H264_SeekFile(&h264Info);
}

// printf("rtpSendH264Frame, frameNum=%d, time=%u\\n", h264Info.frameNum, rtpPacket->rtpHeader.timestamp);
rtpSendH264Frame(rtp_send_fd, cli_ip, rtpPort, rtpPacket,
h264Frame.pFrameBuf + h264Frame.startcode_len,
h264Frame.frame_len h264Frame.startcode_len);

rtpPacket->rtpHeader.timestamp += 90000 / FPS; // RTP 传输视频每秒 90k HZ
usleep(1000 * 1000 / FPS);
}
}
else
{
printf("warning need SeekFile 1\\n");
}
}

free(h264Frame.pFrameBuf);
free(rtpPacket);
H264_FileClose(&h264Info);
}

// 解析RTSP请求
static void rtsp_request_parse(char *buffer, char *method, char *url, int *cseq, int *pRtpPort)
{
char *line = strtok(buffer, "\\r\\n");
sscanf(line, "%s %s RTSP/1.0", method, url);

while ((line = strtok(NULL, "\\r\\n")) != NULL)
{
if (strncmp(line, "CSeq:", 5) == 0)
{
sscanf(line, "CSeq: %d", cseq);
}

char *pCliPort = strstr(line, "client_port=");
if (pCliPort != NULL)
{
int rtcpPort = 0;
sscanf(pCliPort, "client_port=%d-%d", pRtpPort, &rtcpPort);
// printf("rtpPort: %d-%d\\n",*pRtpPort, rtcpPort);
}
}
}

// 生成SDP描述
const char *generate_sdp()
{
return "v=0\\r\\n"
"o=- 0 0 IN IP4 0.0.0.0\\r\\n"
"s=Example Stream\\r\\n"
"t=0 0\\r\\n"
"m=video 0 RTP/AVP 96\\r\\n"
"a=rtpmap:96 H264/90000\\r\\n"
"a=control:streamid=0\\r\\n";
}

void rtsp_handle_OPTION(char *response, int cseq)
{
sprintf(response,
"RTSP/1.0 200 OK\\r\\n"
"CSeq: %d\\r\\n"
"Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN\\r\\n\\r\\n",
cseq);
}

static void rtsp_handle_DESCRIBE(char *response, int cseq)
{
sprintf(response,
"RTSP/1.0 200 OK\\r\\n"
"CSeq: %d\\r\\n"
"Content-Type: application/sdp\\r\\n"
"Content-Length: %zu\\r\\n\\r\\n%s",
cseq, strlen(generate_sdp()), generate_sdp());
}

static void rtsp_handle_SETUP(char *response, int cseq, int rtpPort)
{
sprintf(response,
"RTSP/1.0 200 OK\\r\\n"
"CSeq: %d\\r\\n"
"Session: %u; timeout=%d\\r\\n"
"Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\\r\\n\\r\\n",
cseq, SESSION_ID, SESSION_TIMEOUT, rtpPort, rtpPort + 1, RTP_PORT, RTP_PORT + 1);
}

static void rtsp_handle_PLAY(char *response, int cseq)
{
sprintf(response,
"RTSP/1.0 200 OK\\r\\n"
"CSeq: %d\\r\\n"
"Session: %u; timeout=%d\\r\\n"
"Range: npt=0.000-\\r\\n\\r\\n",
cseq, SESSION_ID, SESSION_TIMEOUT);
}

static void rtsp_handle_TEARDOWN(char *response, int cseq)
{
sprintf(response,
"RTSP/1.0 200 OK\\r\\n"
"CSeq: %d\\r\\n"
"Session: %d; timeout=%d\\r\\n\\r\\n",
cseq, SESSION_ID, SESSION_TIMEOUT);
}

// 处理客户端连接
int handle_client(int cli_fd, int rtp_send_fd, char *cli_ip)
{
int client_sock = cli_fd;
char buffer[1024] = {0};
int cseq = 0;
int rtpPort = 0;
unsigned char bSendFlag = RTP_NULL;
RTP_Send_t rtpSend;
pthread_t thread_id;

while (1)
{
memset(buffer, 0, sizeof(buffer));
int len = read(client_sock, buffer, sizeof(buffer) 1);
if (len <= 0)
break;

printf("C->S [%s]\\n\\n", buffer);

char method[16] = {0};
char url[128] = {0};
rtsp_request_parse(buffer, method, url, &cseq, &rtpPort);

char response[1024] = {0}; // 构造响应
if (strcmp(method, "OPTIONS") == 0)
{
rtsp_handle_OPTION(response, cseq);
}
else if (strcmp(method, "DESCRIBE") == 0)
{
rtsp_handle_DESCRIBE(response, cseq);
}
else if (strcmp(method, "SETUP") == 0)
{
rtsp_handle_SETUP(response, cseq, rtpPort);
}
else if (strcmp(method, "PLAY") == 0)
{
rtsp_handle_PLAY(response, cseq);
bSendFlag = RTP_PLAY;
}
else if (strcmp(method, "TEARDOWN") == 0)
{
rtsp_handle_TEARDOWN(response, cseq);
bSendFlag = RTP_STOP;
}
else
{
snprintf(response, sizeof(response),
"RTSP/1.0 501 Not Implemented\\r\\nCSeq: %d\\r\\n\\r\\n", cseq);
}

write(client_sock, response, strlen(response));
printf("S->C [%s]\\n\\n", response);

if (bSendFlag == RTP_PLAY) // PLAY
{
rtpSend.rtpSendFd = rtp_send_fd;
rtpSend.rtpPort = rtpPort;
rtpSend.cliIp = cli_ip;
rtpSend.bPlayFlag = 1;

// 这里不使用线程的话,会一直无法处理 client_sock 发过来的 OPTION 消息,导致播放出问题
if (pthread_create(&thread_id, NULL, (void *)sendRtp, (void *)&rtpSend) < 0)
{
perror("pthread_create");
}
bSendFlag = RTP_PLAYING;
}

if (bSendFlag == RTP_STOP) // TEARDOWN
{
rtpSend.bPlayFlag = 0;
pthread_join(thread_id); // 等待线程结束
bSendFlag = RTP_NULL;
break;
}
}

printf("close ip=[%s] fd=[%d]\\n", cli_ip, client_sock);
close(client_sock);
return 0;
}

int main()
{
int server_fd, client_fd;
struct sockaddr_in address;
int opt = 1;
socklen_t addrlen = sizeof(address);

// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
return 1;
}

// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
{
perror("setsockopt");
return 1;
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(RTSP_PORT);

// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("bind failed");
return 1;
}

// 开始监听
if (listen(server_fd, MAX_CLIENTS) < 0)
{
perror("listen");
return 1;
}

// 用于发送 rtp 包的udp套接字
int rtp_send_fd = createUdpSocket();
if (rtp_send_fd < 0)
{
printf("failed to create socket\\n");
return 1;
}
address.sin_port = htons(RTP_PORT);
if (bind(rtp_send_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("rtp_send_fd bind failed");
return 1;
}

printf("RTSP Server listening on port %d\\n", RTSP_PORT);

// 主循环接受连接,目前处理一个客户端
while (1)
{
char cli_ip[40] = {0};
if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0)
{
perror("accept");
return 1;
}

strncpy(cli_ip, inet_ntoa(address.sin_addr), sizeof(cli_ip));
printf("handle cliend [%s]\\n", cli_ip);

handle_client(client_fd, rtp_send_fd, cli_ip);
}

return 0;
}


将上面代码保存在同一个目录后,并且在同目录里放一个.h264文件,然后运行 gcc *.c -lpthread 编译,再执行./a.out运行程序,下面是我运行的过程: 在这里插入图片描述


在这里插入图片描述

🎄四、总结

本文介绍了实现介绍怎么通过RTSP协议通信,将H264视频数据封装成RTP包并发送的,也提供了实现源码和运行结果,可以帮助读者快速了解怎样实现一个最简单的RTSP服务器。

在这里插入图片描述 如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

参考: https://blog.csdn.net/huabiaochen/article/details/104557971

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!