当前位置: 首页 > news >正文

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

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

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

目录

  • 🎄一、概述
  • 🎄二、多播的概念
  • 🎄三、实现步骤、实现细节
  • 🎄四、多播传输AAC格式的RTP包的实现源码
  • 🎄五、总结


在这里插入图片描述

前面系列文章回顾:
【音视频 | 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多播地址,见下图:

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

// 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套接字socketint 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套接字socketint 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、绑定多播组端口 bindif (bind(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)perror("bind error" );// 4、加入多播组 239.0.1.1struct 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

/*** @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

/*** @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:06static 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 3bitreturn 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:39readLen = 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_rtp_aac_main.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 9832static 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);// aacAACReaderInfo_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包的步骤和细节,最后提供了实现的源代码,帮助读者学习理解。

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

http://www.dtcms.com/a/270727.html

相关文章:

  • mac m1安装大模型工具vllm
  • kotlin学习,val使用get()的问题
  • mysql 安装实战
  • Claude Code 开发使用技巧
  • User手机上如何抓取界面的布局uiautomatorviewer
  • Linux的`if test`和`if [ ]中括号`的取反语法比较 笔记250709
  • Unity Demo-3DFarm详解-其二
  • 以太坊智能合约核心技术解析与应用实践
  • LLaMA-Omni 深度解析:打开通往无缝人机语音交互的大门
  • HCIP 认证可以做什么?如何选择合适的职业路径?
  • C++11 future、promise实现原理
  • AI生成交互式数据图表
  • 【c++八股文】Day5:const和constexpr,define
  • sql查询davinci看板数据
  • 【一起来学AI大模型】PyTorch DataLoader 实战指南
  • 极简相册管理ios app Tech Support
  • ARM汇编编程(AArch64架构)课程 - 第7章:SIMD与浮点运算
  • 2025杰理蓝牙芯片:各系列芯片特点及市场分析
  • 【手写 new 操作符实现 - 深入理解 JavaScript 对象创建机制】
  • 【Linux】权限的概念及理解
  • VR/AR在HMI中的创新应用:远程协作与维修的沉浸式体验
  • 类和对象拓展——日期类
  • 【实习篇】之Http头部字段之Disposition介绍
  • 使用 Docker 搭建 Rust Web 应用开发环境——AI教你学Docker
  • VR重现红军过雪山:一场穿越时空的精神洗礼​
  • MySQL 09 普通索引和唯一索引
  • MySQL 间隙锁
  • pytorch 自动微分
  • 半导体晶圆检测的基本知识
  • EGARCH