【RTSP从零实践】11、多播同时传输H264、AAC码流的RTSP服务器——最简单的实现例子(附带源码)

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍多播同时传输H264、AAC码流的RTSP服务器 🍭
⏰发布时间⏰: 2025-07-10

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


在这里插入图片描述

RTSP系列文章回顾:
【音视频 | RTSP】RTSP协议详解 及 抓包例子解析(详细而不赘述)
【音视频 | RTSP】SDP(会话描述协议)详解 及 抓包例子分析
【音视频 | RTP】RTP协议详解(H.264的RTP封包格式、AAC的RTP封包格式)
【RTSP从零实践】01、根据RTSP协议实现一个RTSP服务
【RTSP从零实践】02、使用RTP协议封装并传输H264
【RTSP从零实践】03、实现最简单的传输H264的RTSP服务器
【RTSP从零实践】04、使用RTP协议封装并传输AAC
【RTSP从零实践】05、实现最简单的传输AAC的RTSP服务器
【RTSP从零实践】06、实现最简单的同时传输H264、AAC的RTSP服务器
【RTSP从零实践】07、多播传输H264格式的RTP包(附带源码)
【RTSP从零实践】08、多播传输H264码流的RTSP服务器——最简单的实现例子(附带源码)
【RTSP从零实践】09、多播传输AAC格式的RTP包(附带源码)
【RTSP从零实践】10、多播传输AAC码流的RTSP服务器——最简单的实现例子(附带源码)
【RTSP从零实践】11、多播同时传输H264、AAC码流的RTSP服务器——最简单的实现例子(附带源码)
【RTSP从零实践】12、TCP传输H264格式RTP包(RTP_over_TCP)的RTSP服务器(附带源码)
【RTSP从零实践】13、TCP传输AAC格式RTP包(RTP_over_TCP)的RTSP服务器(附带源码)
【RTSP从零实践】14、TCP传输H264格式、AAC格式RTP包(RTP_over_TCP)的RTSP服务器(附带源码)

在这里插入图片描述

🎄一、概述

关于RTSP服务器的知识点,在前面的文章已经介绍得非常完善了,这篇文章基本没什么新的知识,只是将前面的知识点结合起来,做一个多播发送H264、AAC码流的RTSP服务器。

本文的实现代码与文章 【RTSP从零实践】06、实现最简单的同时传输H264、AAC的RTSP服务器 非常相识,只是将原本单播发送的代码改为多播发送。读者可以比较学习。

本文内容安排如下:

  • 1、一个RTSP服务相关知识,包括了 多播概念、创建socket套接字、了解SDP协议、处理RTSP客户端的命令 等;
  • 2、读取H264封装成RTP包并发送的相关知识,包括 了解H264、了解RTP协议怎么封装H264 等;
  • 3、读取AAC封装成RTP包并发送的相关知识,包括 了解AAC、了解RTP协议怎么封装AAC 等;

第1个点比较重要,特别是处理RTSP命令时,需要处理多播。


在这里插入图片描述

🎄二、实现RTSP服务

✨2.1 多播的概念

关于多播的概念可以参考这篇文章:多播的概念、多播地址、UDP实现多播的C语言例子。下面只简单介绍一下多播。

IP 多播(也称多址广播或组播)技术,是允许一台主机 向 多台主机 发送消息的一种通信方式。单播只向单个IP接口发送数据,广播是向子网内所有IP接口发送数据,多播则介于两者之间,向一组IP接口发送数据。

多播地址:用来标识多播组,IPv4使用D类地址的某一个来表示一个多播组地址。IPv4的D类地址(从224.0.0.0239.255.255.255)是IPv4多播地址,见下图:

多播发送端:用于发送多播数据报的程序,下面是一个简单的多播发送端例子的代码。基本上就是在发送数据包时,将目的地址设置为 多播地址

// multicastCli.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main()
{
	// 1、创建UDP套接字socket
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd<0)
		perror("socket error" );
	
	// 2、准备多播组地址和端口
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons (10086);
	if (inet_pton(AF_INET, "239.0.1.1", &servaddr.sin_addr) <= 0)
		perror("inet_pton error");
		
	// 4、使用 sendto 发送多播组数据报
	if(sendto(sockfd, "Hello,I am udp client", strlen("Hello,I am udp client"), 0, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		perror("sendto error" );
	
	// 5、处理应答
	char recvline[256];
	int n = 0;
	struct sockaddr_in tmpAddr;
	bzero(&tmpAddr, sizeof(tmpAddr));
	socklen_t addrLen=sizeof(tmpAddr);
	while ( (n = recvfrom (sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&tmpAddr, &addrLen)) > 0)
	{
		recvline[n] = 0 ;/*null terminate */
		printf("recvfrom ip=[%s], [%s]\n",inet_ntoa(tmpAddr.sin_addr), recvline);
		bzero(&tmpAddr, sizeof(tmpAddr));
	}
	
	if (n < 0)
		perror("read error" );
	
	// 6、关闭
	close(sockfd);

	return 0;
}

多播接收端:接收端是使用UDP服务端代码修改,需要在交互数据之前,将套接字加入多播组239.0.1.1,让链路层接口接收该多播组的数据报,使用完需要离开多播组。下面是一个多播接收端代码:

// multicastSer.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main()
{
	// 1、创建UDP套接字socket
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd<0)
		perror("socket error" );
	
	// 2、准备本地ip接口和多播组端口
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons (10086);
	servaddr.sin_addr.s_addr = INADDR_ANY; // 指定ip地址为 INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任一网络接口上接受客户端的连接
	
	// 3、绑定多播组端口 bind
	if (bind(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		perror("bind error" );
	
	// 4、加入多播组 239.0.1.1
	struct ip_mreq mreq;
	mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);		// 加入的客服端主机IP地址
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)    {
        perror("setsockopt");
        return -1;
    }
	
	// 5、使用 sendto、recvfrom 交互数据
	printf("UdpSer sockfd=%d, start \n",sockfd);
	char recvline[256];
	while(1)
	{
		struct sockaddr_in cliaddr;
		bzero(&cliaddr, sizeof(cliaddr));
		socklen_t addrLen=sizeof(cliaddr);
		int n = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&cliaddr, &addrLen);
		if(n>0)
		{
			recvline[n] = 0 ;/*null terminate */
			printf("recv sockfd=%d %d byte, [%s] addrLen=%d, cliIp=%s, cliPort=%d\n",
				sockfd, n, recvline, addrLen, inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
			sendto(sockfd, "Hello,I am udp server", strlen("Hello,I am udp server"), 0, (struct sockaddr*)&cliaddr, addrLen);
		}
	}
	
	// 6、离开多播组
	setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq)); 
	
	// 7、关闭
	close(sockfd);

	return 0;
}

✨2.2 创建socket套接字

程序开始创建一个TCP套接字用来作为RTSP服务端,使用的是8554端口;然后创建2个UDP套接字分别用来发送H264的RTP包 和 AAC的RTP包。

需要注意的是,这两个 UDP套接字 不需要像单播发送那样去绑定端口。

// 创建套接字
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;
}

// 用于发送 h264 rtp 包的udp套接字,不需要绑定端口
int rtp_h264_fd = createUdpSocket();
if (rtp_h264_fd < 0)
{
    printf("failed to create socket\n");
    return -1;
}

// 用于发送 aac rtp 包的udp套接字,不需要绑定端口
int rtp_aac_fd = createUdpSocket();
if (rtp_aac_fd < 0)
{
    printf("failed to create socket\n");
    return -1;
}

✨2.3 处理RTSP命令

这个简单的RTSP服务器,只实现了如下5个命令的处理:

  • OPTION
    处理OPTION消息时,直接返回本服务器支持的方法即可,没什么特别的:
    在这里插入图片描述

  • DESCRIBE
    在这里插入图片描述

  • SETUP
    SETUP 命令会发两次过来,因为有两路媒体流:
    在这里插入图片描述
    在这里插入图片描述

  • PLAY
    在这里插入图片描述

  • TEARDOWN
    在这里插入图片描述


在这里插入图片描述

🎄三、读取H264封装成RTP包并发送

RTSP服务创建后,就可以开始发流了。这里先介绍H264的发流过程。

首先,要读取H264文件;然后,封装RTP包头;最后封装RTP负载。

✨3.1 实现H.264文件读取器

在这里插入图片描述

H.264文件保存了h264编码的视频帧,每个视频帧之间以开始码00 00 0100 00 00 01分隔开。我们可以用下面代码判断是否为开始码。
在这里插入图片描述

在两个开始码之间的就是视频帧数据。h264视频帧数据的第一个字节是一个NAL头,内容如下图:
在这里插入图片描述
可以用下面代码读取NAL头:
在这里插入图片描述


✨3.2 H264的RTP头(RTP Header)

在这里插入图片描述
上图是RTP头的结构图,包含了12个字节的内容,可以用代码定义成如下结构体:

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;
};

RTP头这里涉及到一个 时间戳怎么计算 的问题,需要注意的是,这个时间戳是一个 时钟频率 为单位的,而不是具体的时间(秒、毫秒等)。

一般情况下,H264的时钟频率为90000Hz,假设帧率为25,那么每一帧的 时间间隔 就是1/25秒,每一帧的 时钟增量 就是(90000/25=3600)。那时间戳怎么算呢?举个例子,如果帧率为25的H264视频,第一帧的RTP时间戳为0的话,那么第二帧的RTP时间戳就是 3600,第三帧的RTP时间戳就是 7200,依次类推,后一帧的RTP时间戳在前一帧的RTP时间戳的值加上一个时钟增量


✨3.3 H264 的 RTP负载(RTP Payload)

H264 的 RTP负载需要介绍两种方式,第一种是 单个NAL单元封包(Single NAL Unit Packet);第二种是 分片单元(Fragmentation Unit) 。如果H264的视频帧NALU(NAL Unit)总字节数小于 MTU(网络最大传输单元1500字节),就可以使用第一种方式,因为有一些TCP/UDP头数据,所以一般判断小于1400字节,就采用 单个NAL单元封包(Single NAL Unit Packet),否则使用分片单元(Fragmentation Unit)的方式封装RTP包。

单个NAL单元封包 的RTP负载结构如下图,相当于直接将整个NAL Unit 填入RTP负载即可:
在这里插入图片描述

分片单元的RTP负载方式也有两种,本文介绍的是FU-A的方式,RTP负载最开始由三部分组成:第一个字节是FU indicator,第二个字节是FU header,第三个字节开始就是NAL单元去掉NAL头之后的数据:
在这里插入图片描述

  • FU indicatorFU indicator的大小是一个字节,格式如下,跟NAL头的格式一样,但作为 分片RTP封包 ,并不能直接将H264的NAL头直接填上去。
    F:一般为0。为0表示此NAL单元不应包含bit错误或语法违规;为1表示此NAL单元可能包含bit错误或语法违规;
    NRI:直接将H264NAL头的NRI值填入即可;
    Type:FU-A格式的封包填28,FU-B格式的封包填29。

    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |F|NRI|  Type   |
    +---------------+
    
  • FU header FU header的大小也是一个字节,格式如下:
    S:start,NALU拆分多个分包后,第一个发送的分包,此bit位置1,其他分包为0;
    E:end,NALU拆分多个分包后,最后一个发送的分包,此bit位置1,其他分包为0;
    R:保留位,必须等于0;
    Type:将H264的NAL头的负载类型Type直接填入。

    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |S|E|R|  Type   |
    +---------------+
    

在这里插入图片描述

🎄四、读取AAC封装成RTP包并发送

✨4.1 实现AAC文件读取器

在这里插入图片描述

.aac文件保存了AAC编码的音频帧,在ADTS格式的aac文件中,每个音频帧都包含了一个ADTS headerAAC ES。而ADTS header占了7个字节,且最开始表示同步码(syncword)的12bit的所有的bit位都是1,总是0xFFF,代表一个ADTS帧的开始,作为分界符,用于同步每帧起始位置。在可变头部有表示aac帧长的aac_frame_length,占13bit。我们可以用下面代码来查找同步码并获取帧长。
在这里插入图片描述

清楚上述知识后,我们就可以从aac文件结构不断读取音频帧数据了。


✨4.2 AAC的RTP头(RTP Header)

在这里插入图片描述
上图是RTP头的结构图,包含了12个字节的内容,可以用代码定义成如下结构体:

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;
};

RTP头这里涉及到一个 时间戳怎么计算 的问题,需要注意的是,这个时间戳是一个 时钟频率 为单位的,而不是具体的时间(秒、毫秒等)。
一般情况下,AAC每个1024个采样为一帧。假设AAC的时钟频率为48000Hz,所以一秒就有 48000 / 1024 = 47帧,那么每一帧的 时间间隔 就是1/47秒,每一帧的 时钟增量 就是(48000 / 47 = 1021)。
那时间戳怎么算呢?举个例子,以上面计算的数据,第一帧的RTP时间戳为0的话,那么第二帧的RTP时间戳就是 1021,第三帧的RTP时间戳就是 (1021+1021),依次类推,后一帧的RTP时间戳在前一帧的RTP时间戳的值加上一个时钟增量

在这里插入图片描述
注意:RTP的时间戳计算很重要,我一开始没懂时间戳的概念,导致播放的声音断断续续的。


✨4.3 AAC 的 RTP负载(RTP Payload)

RTP负载常用的有两种方式,第一种是 单个NAL单元封包(Single NAL Unit Packet);第二种是 分片单元(Fragmentation Unit) 。因为一帧ADTS帧一般小于 MTU(网络最大传输单元1500字节),所以对于AAC的RTP封包只需要采用 单个NAL单元封包(Single NAL Unit Packet) 即可。

但并不是直接将 ADTS 帧去掉ADTS头之后的数据 作为RTP负载,AAC的RTP负载最开始有4个字节,其中2个字节表示AU头长度(AU-headers-length),13bit的AU size;3bit的AU-Index(-delta) field。如下图:

所以,AAC的RTP负载的一个字节为0x00,第二个字节为0x10,第三个字节和第四个字节保存AAC Data的大小,最多只能保存13bit(也就是说,第三个字节保存数据大小的高八位,第四个字节的高5位保存数据大小的低5位)。

参考下列代码:

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

这4个字节之后就是 ADTS 帧去掉ADTS头之后的数据 了。


在这里插入图片描述

🎄五、多播传输H264、AAC格式的RTP包的实现源码

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、aacReader.h

/**
 * @file aacReader.h
 * @author : https://blog.csdn.net/wkd_007
 * @brief 
 * @version 0.1
 * @date 2025-06-30
 * 
 * @copyright Copyright (c) 2025
 * 
 */
#ifndef	__AAC_READER_H__
#define __AAC_READER_H__

#include <stdio.h>

#define ADTS_HEADER_LEN	(7)

typedef struct
{
	int frame_len;                //! 
	unsigned char *pFrameBuf;     //! 
} AACFrame_t;

typedef struct AACReaderInfo_s
{
	FILE *pFileFd;
}AACReaderInfo_t;

int AAC_FileOpen(char *fileName, AACReaderInfo_t *pAACInfo);
int AAC_FileClose(AACReaderInfo_t *pAACInfo);
int AAC_GetADTSFrame(AACFrame_t *pAACFrame, const AACReaderInfo_t *pAACInfo);
int AAC_IsEndOfFile(const AACReaderInfo_t *pAACInfo);
void AAC_SeekFile(const AACReaderInfo_t *pAACInfo);

#endif 	// __AAC_READER_H__

4、aacReader.c

/**
 * @file aacReader.c
 * @author : https://blog.csdn.net/wkd_007
 * @brief 
 * @version 0.1
 * @date 2025-06-30
 * 
 * @copyright Copyright (c) 2025
 * 
 */
#include <stdlib.h>
#include <string.h>
#include "aacReader.h"

#define MAX_FRAME_LEN (1024*1024)	// 一帧数据最大字节数
#define MAX_SYNCCODE_LEN    (3)     // 同步码字节个数 2025-05-21 17:45:06

static int findSyncCode_0xFFF(unsigned char *Buf, int *size)
{
	if((Buf[0] == 0xff) && ((Buf[1] & 0xf0) == 0xf0) )//0xFF F,前12bit都为1 2025-05-21 17:46:57
    {
        *size |= ((Buf[3] & 0x03) <<11);     //high 2 bit
        *size |= Buf[4]<<3;                //middle 8 bit
        *size |= ((Buf[5] & 0xe0)>>5);        //low 3bit
        return 1;
    }
	return 0;
}

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

int AAC_FileClose(AACReaderInfo_t *pAACInfo)
{
	if (pAACInfo->pFileFd != NULL) {
		fclose(pAACInfo->pFileFd);
		pAACInfo->pFileFd = NULL;
	}
	return 0;
}

int AAC_IsEndOfFile(const AACReaderInfo_t *pAACInfo)
{
	return feof(pAACInfo->pFileFd);
}

void AAC_SeekFile(const AACReaderInfo_t *pAACInfo)
{
	fseek(pAACInfo->pFileFd,0,SEEK_SET);
}

/**
 * @brief 
 * 
 * @param pAACFrame :输出参数,使用后 pAACInfo->pFrameBuf 需要free
 * @param pAACInfo 
 * @return int 
 */
int AAC_GetADTSFrame(AACFrame_t *pAACFrame, const AACReaderInfo_t *pAACInfo)
{
    int rewind = 0;
	if (pAACInfo->pFileFd==NULL){
		printf("[%s %d]pFileFd error\n",__FILE__,__LINE__);
		return -1;
	}

    // 1.先读取ADTS帧头(7个字节)
	unsigned char* pFrame = (unsigned char*)malloc(MAX_FRAME_LEN);
	int readLen = fread(pFrame, 1, ADTS_HEADER_LEN, pAACInfo->pFileFd);
	if(readLen <= 0)
	{
		printf("[%s %d]fread error readLen=%d\n",__FILE__,__LINE__,readLen);
		free(pFrame);
		return -1;
	}

    // 2.查找当前帧同步码,获取帧长度
    int i=0;
    int size = 0;
	for(; i<readLen-MAX_SYNCCODE_LEN; i++)
	{
		if(!findSyncCode_0xFFF(&pFrame[i], &size))
		{
			continue;
		}
		else
		{
			break;
		}
	}
	if(i!=0)	// 不是帧开头,偏移到帧开头重新读
	{
		printf("[%s %d]synccode error, i=%d\n",__FILE__,__LINE__,i);
		free(pFrame);
		rewind = (-(readLen-i));
		fseek (pAACInfo->pFileFd, rewind, SEEK_CUR);
		return -1;
	}

    // 3.读取ADTS帧数据 2025-05-22 21:44:39
    readLen = fread(pFrame+ADTS_HEADER_LEN, 1, size-ADTS_HEADER_LEN, pAACInfo->pFileFd);
    if(readLen <= 0)
	{
		printf("[%s %d]fread error\n",__FILE__,__LINE__);
		free(pFrame);
		return -1;
	}

    // 4.填数据
    pAACFrame->frame_len = size;
    pAACFrame->pFrameBuf = pFrame;
	
	return pAACFrame->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、multicast_rtsp_h264_aac_main.c

/**
 * @file multicast_rtsp_h264_aac_main.c
 * @author : https://blog.csdn.net/wkd_007
 * @brief 
 * @version 0.1
 * @date 2025-07-10
 * 
 * @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"
#include "aacReader.h"

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

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

#define MULTICAST_IP    "239.0.0.1"
#define MULTICAST_PORT  55666

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;
}

static int rtpSendAACFrame(int socket, char *ip, int16_t port,
                           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 = rtpSendPacket(socket, ip, port, rtpPacket, frameSize + 4);
    if (ret < 0)
    {
        printf("failed to send rtp packet\n");
        return -1;
    }

    rtpPacket->rtpHeader.seq++;

    return 0;
}

void *sendRtpH264(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);
    return NULL;
}

void *sendRtpAAC(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) + 1500);

    rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_AAC, 1, 0, 0, 0x32411);

    // aac
    AACReaderInfo_t aacInfo;
    if (AAC_FileOpen(AAC_FILE_NAME, &aacInfo) < 0)
    {
        printf("failed to open %s\n", AAC_FILE_NAME);
        return NULL;
    }

    while (pRtpSend->bPlayFlag)
    {
        if (!AAC_IsEndOfFile(&aacInfo))
        {
            AACFrame_t aacFrame;
            memset(&aacFrame, 0, sizeof(aacFrame));
            AAC_GetADTSFrame(&aacFrame, &aacInfo);

            if (aacFrame.pFrameBuf != NULL)
            {
                // printf("rtpSendAACFrame\n");
                rtpSendAACFrame(rtp_send_fd, cli_ip, rtpPort, rtpPacket,
                                aacFrame.pFrameBuf + ADTS_HEADER_LEN, aacFrame.frame_len - ADTS_HEADER_LEN);
                free(aacFrame.pFrameBuf);

                /*
                 * 如果采样频率是48000
                 * 一般AAC每个1024个采样为一帧
                 * 所以一秒就有 48000 / 1024 = 47帧
                 * 时间增量就是 48000 / 47 = 1021
                 * 一帧的时间为 1000ms / 47 = 21ms
                 */
                rtpPacket->rtpHeader.timestamp += 1021;//1043;
                usleep(21 * 1000);
            }
            else
            {
                printf("warning SeekFile\n");
                AAC_SeekFile(&aacInfo);
            }
        }
    }

    free(rtpPacket);
    AAC_FileClose(&aacInfo);
    return NULL;
}

// 解析RTSP请求
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描述
char g_sdp[512] = {
    0,
};
const char *generate_sdp()
{
    memset(g_sdp, 0, sizeof(g_sdp));
    sprintf(g_sdp,
            "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"
            "a=type:broadcast\r\n"
            "a=rtcp-unicast: reflection\r\n"
            "c=IN IP4 %s/255\r\n"
            "m=video %d RTP/AVP 96\r\n"
            "a=rtpmap:96 H264/90000\r\n"
            "a=control:streamid=0\r\n"
            "m=audio %d RTP/AVP 97\r\n"
            "a=rtpmap:97 mpeg4-generic/48000/2\r\n"
            "a=fmtp:97 SizeLength=13;\r\n"
            "a=control:streamid=1\r\n",
            MULTICAST_IP, MULTICAST_PORT, MULTICAST_PORT + 2);
    return g_sdp;
}

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, char *url, int streamid)
{
    char localIp[32];
    sscanf(url, "rtsp://%[^:]:", localIp);

    int rtpDesPort = MULTICAST_PORT;
    if (streamid == 1)
    {
        rtpDesPort = MULTICAST_PORT + 2;
    }
    sprintf(response,
            "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Session: %u; timeout=%d\r\n"
            "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=255\r\n\r\n",
            cseq, SESSION_ID, SESSION_TIMEOUT, MULTICAST_IP, localIp, rtpDesPort, rtpDesPort + 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_h264_fd, int rtp_aac_fd, char *cli_ip)
{
    int           client_sock  = cli_fd;
    char          buffer[1024] = {0};
    int           cseq         = 0;
    int           rtpPort      = 0;
    int           rtpH264Port = 0, rtpAACPort = 0;
    unsigned char bSendFlag = RTP_NULL;
    RTP_Send_t    rtpSendH264, rtpSendAAC;
    pthread_t     h264Thread_id, aacThread_id;

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

        printf("C->S [\n%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)
        {
            // printf("url:[%s]\n", url);
            int streamid = 0;
            if (NULL != strstr(url, "streamid=1"))
            {
                streamid   = 1;
                rtpAACPort = rtpPort;
            }
            else
            {
                rtpH264Port = rtpPort;
            }
            rtsp_handle_SETUP(response, cseq, url, streamid);
        }
        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 [\n%s]\n\n", response);

        if (bSendFlag == RTP_PLAY) // PLAY
        {
            // 这里不使用线程的话,会一直无法处理 client_sock 发过来的 OPTION 消息,导致播放出问题
            rtpSendH264.rtpSendFd = rtp_h264_fd;
            rtpSendH264.rtpPort   = MULTICAST_PORT;
            rtpSendH264.cliIp     = MULTICAST_IP;
            rtpSendH264.bPlayFlag = 1;
            if (pthread_create(&h264Thread_id, NULL, (void *)sendRtpH264, (void *)&rtpSendH264) < 0)
            {
                perror("pthread_create");
            }

            rtpSendAAC.rtpSendFd = rtp_aac_fd;
            rtpSendAAC.rtpPort   = MULTICAST_PORT + 2;
            rtpSendAAC.cliIp     = MULTICAST_IP;
            rtpSendAAC.bPlayFlag = 1;
            if (pthread_create(&aacThread_id, NULL, (void *)sendRtpAAC, (void *)&rtpSendAAC) < 0)
            {
                perror("pthread_create");
            }

            bSendFlag = RTP_PLAYING;
        }

        if (bSendFlag == RTP_STOP) // TEARDOWN
        {
            rtpSendH264.bPlayFlag = 0;
            pthread_join(h264Thread_id); // 等待线程结束
            rtpSendAAC.bPlayFlag = 0;
            pthread_join(aacThread_id); // 等待线程结束
            bSendFlag = RTP_NULL;
            break;
        }
    }

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

int main(int argc, char *argv[])
{
    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;
    }

    // 用于发送 h264 rtp 包的udp套接字,不需要绑定端口
    int rtp_h264_fd = createUdpSocket();
    if (rtp_h264_fd < 0)
    {
        printf("failed to create socket\n");
        return -1;
    }

    // 用于发送 aac rtp 包的udp套接字,不需要绑定端口
    int rtp_aac_fd = createUdpSocket();
    if (rtp_aac_fd < 0)
    {
        printf("failed to create socket\n");
        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_h264_fd, rtp_aac_fd, cli_ip);
    }

    return 0;
}

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


🎄六、总结

本文介绍了多播的一些概念,以及多播传输H264、AAC码流的RTSP服务器实现的步骤和细节,最后提供了实现的源代码,帮助读者学习理解。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wkd_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值