【RTSP从零实践】9、多播传输AAC格式的RTP包(附带源码)

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

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

目录


前面系列文章回顾:
【音视频 | RTSP】RTSP协议详解 及 抓包例子解析(详细而不赘述)
【音视频 | RTSP】SDP(会话描述协议)详解 及 抓包例子分析
【音视频 | RTP】RTP协议详解(H.264的RTP封包格式、AAC的RTP封包格式)
【RTSP从零实践】1、根据RTSP协议实现一个RTSP服务
【RTSP从零实践】2、使用RTP协议封装并传输H264
【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器
【RTSP从零实践】4、使用RTP协议封装并传输AAC
【RTSP从零实践】5、实现最简单的传输AAC的RTSP服务器
【RTSP从零实践】6、实现最简单的同时传输H264、AAC的RTSP服务器
【RTSP从零实践】7、多播传输H264格式的RTP包(附带源码)
【RTSP从零实践】8、多播传输H264码流的RTSP服务器------最简单的实现例子(附带源码)

🎄一、概述

这篇文章介绍使用多播的方式传输AAC格式的RTP包,所有的知识点应该都在前面学习过了,本文会再将必要的知识点介绍一下,最后给出例子代码,对前面内容掌握了的读者可以直接跳转到代码去看就行了。

本文内容:

  • 1、多播的概念;
  • 2、实现传输AAC格式的RTP包的步骤、细节;
  • 3、实现传输AAC格式的RTP包源码。

🎄二、多播的概念

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

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

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

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

c 复制代码
// 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,让链路层接口接收该多播组的数据报,使用完需要离开多播组。下面是一个多播接收端代码:

c 复制代码
// 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;
}

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

多播传输H264格式的RTP包的步骤如下:

1、实现 AAC文件读取器。

2、实现 AAC 的 RTP 数据包封装。

3、实现多播发送RTP包。

4、写一个支持多播 sdp 文件。

前两点在文章 4、使用RTP协议封装并传输AAC 介绍得很详细,这里不再赘述。

实现多播发送RTP包,我们只需要在文章 4、使用RTP协议封装并传输AAC 的代码里将UDP的目的地址改为我们要使用的多播组地址即可,我们这里要使用的是"239.0.0.1"

多播 sdp 文件也有点区别,具体如下:

复制代码
a=type:broadcast
m=audio 9832 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/48000/2
a=fmtp:97 SizeLength=13;
c=IN IP4 239.0.0.1/255

a=type:broadcast:表示使用广播或多播类型;
m=audio 9832 RTP/AVP 97 :表示这是一个媒体描述,媒体类型是audio;接收媒体流的端口号是9832;传输协议是RTP/AVP(通过UDP传输);RTP负载类型为97。

a=rtpmap:97 mpeg4-generic/48000/2:RTP负载类型的具体编码是 mpeg4-generic(AAC属于mpeg4的),时钟频率为 48000Hz,2个声道。

a=fmtp:97 SizeLength=13:帧长用13个bit表示。

c=IN IP4 239.0.0.1/255:表示连接信息,网络类型为IN,表示Internet。地址类型为IP4,表示IPv4地址。接收端地址为239.0.0.1/255。


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

1、aacReader.h

c 复制代码
/**
 * @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__

2、aacReader.c

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

c 复制代码
#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

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_rtp_aac_main.c

c 复制代码
/**
 * @file multicast_rtp_aac_main.c
 * @author : https://blog.csdn.net/wkd_007
 * @brief
 * @version 0.1
 * @date 2025-07-08
 *
 * @copyright Copyright (c) 2025
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#include "rtp.h"
#include "aacReader.h"

#define AAC_FILE_NAME  "test.aac"
#define MULTICAST_IP   "239.0.0.1"
#define MULTICAST_PORT 9832

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

int main(int argc, char *argv[])
{
    int socket = createUdpSocket();
    if (socket < 0)
    {
        printf("failed to create socket\n");
        return -1;
    }

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

    while (1)
    {
        if (!AAC_IsEndOfFile(&aacInfo))
        {
            AACFrame_t aacFrame;
            memset(&aacFrame, 0, sizeof(aacFrame));
            AAC_GetADTSFrame(&aacFrame, &aacInfo);

            if (aacFrame.pFrameBuf != NULL)
            {
                // printf("rtpSendAACFrame\n");
                rtpSendAACFrame(socket, MULTICAST_IP, MULTICAST_PORT, 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;
                usleep(21 * 1000);
            }
            else
            {
                printf("warning SeekFile\n");
                AAC_SeekFile(&aacInfo);
            }
        }
    }

    free(rtpPacket);

    return 0;
}

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


🎄五、总结

本文介绍了多播的一些概念,以及多播传输AAC格式的RTP包的步骤和细节,最后提供了实现的源代码,帮助读者学习理解。

如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

相关推荐
wkd_0072 天前
【RTSP从零实践】8、多播传输H264码流的RTSP服务器——最简单的实现例子(附带源码)
rtp·rtsp·多播·rtsp多播服务器
wkd_0074 天前
【RTSP从零实践】6、实现最简单的同时传输H264、AAC的RTSP服务器
aac·h264·rtsp·rtsp服务器·rtsp传输h264·rtsp传输aac
大胡子大叔6 天前
webrtc-streamer视频流播放(rstp协议h264笔记)
笔记·webrtc·rtsp·webrtc-streamer
Little_Code7 天前
uniapp 使用ffmpeg播放rtsp
ffmpeg·uni-app·rtsp
wkd_00720 天前
【音视频 | RTP】RTP协议详解(H.264的RTP封包格式、AAC的RTP封包格式)
音视频·aac·h.264·rtp·rtp封包
allnlei1 个月前
RTP over TCP 模式
网络协议·rtp·rtsp
superconvert1 个月前
最快的流媒体服务器搭建 smart_rtmpd
http·webrtc·rtmp·h264·hls·无人直播·dash·rtsp·gb28181·srt·m3u8·vlc·sfu·obs·flv
邪恶的贝利亚1 个月前
万字详解RTR RTSP SDP RTCP
网络·sdp·rtsp·rtcp·rtr
慢一点会很快1 个月前
【音频编码格式】AAC详解
网络·音视频·aac