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

【RTSP从零实践】4、使用RTP协议封装并传输AAC

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍怎么使用RTP协议封装并传输AAC🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰: 2025-07-01 18:43:18

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

目录

  • 🎄一、概述
  • 🎄二、实现步骤、实现细节
    • ✨2.1、实现AAC文件读取器
    • ✨2.2、实现 AAC 的 RTP 数据包封装
      • 🎈2.2.1、RTP头(RTP Header)
      • 🎈2.2.2、AAC 的 RTP负载(RTP Payload)
    • ✨2.3、SDP协议介绍
  • 🎄三、RTP协议封装并传输AAC的实现源码
  • 🎄四、总结


在这里插入图片描述

在这里插入图片描述

🎄一、概述

前面文章介绍了怎么实现RTSP协议、RTP协议传输H264等:
【RTSP从零实践】1、根据RTSP协议实现一个RTSP服务
【RTSP从零实践】2、使用RTP协议封装并传输H264
【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器

这篇文章介绍使用RTP协议封装并传输AAC。

首先,需要对 AAC文件结构 有了解,我们要从一个AAC文件读取音频帧,再进行RTP封包发送。本文会介绍需要用到的AAC相关知识,需要了解更详细的可以参考以前文章:AAC格式音频文件解析。

然后,需要对 RTP包结构 有个了解,本文介绍的RTP包由2部分组成:RTP头、RTP负载。需要了解RTP头数据结构、RTP负载数据结构,下个小节会介绍怎样用代码实现。对RTP数据包格式需要了解更多的可以参考这篇文章:RTP协议详解 。

最后,需要对 SDP协议 有了解,后面代码运行后,需要使用vlc打开.sdp文件来验证功能,所以需要知道一个sdp文件的内容是什么意思,这个将会在2.3小节介绍,如果需要了解更多sdp协议知识,可以看这篇文章:SDP(会话描述协议)详解 及 抓包例子分析。


在这里插入图片描述

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

这个小写介绍一些基础知识点,下个小节会提供源码,可以结合着源码看帮助理解消化。

✨2.1、实现AAC文件读取器

在这里插入图片描述

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

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


✨2.2、实现 AAC 的 RTP 数据包封装

本文介绍的RTP数据包主要由2部分组成,即 RTP头、RTP负载。

🎈2.2.1、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的时间戳计算很重要,我一开始没懂时间戳的概念,导致播放的声音断断续续的。


🎈2.2.2、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头之后的数据 了。


✨2.3、SDP协议介绍

本文是使用VLC打开.sdp文件来验证功能的,所以也需要知道SDP协议的一些知识,下面是本文使用的sdp文件:

m=audio 9832 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/48000/2
a=fmtp:97 SizeLength=13;
c=IN IP4 192.168.2.180
  • 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 192.168.2.180:表示连接信息,网络类型为IN,表示Internet。地址类型为IP4,表示IPv4地址。接收端地址为192.168.2.180。


在这里插入图片描述

🎄三、RTP协议封装并传输AAC的实现源码

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

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、rtp_aac_main.c

/*** @file rtp_aac_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 <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 CLIENT_IP "192.168.2.180" // 运行vlc打开sdp文件的电脑IP
#define CLIENT_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, CLIENT_IP, CLIENT_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;
}

6、rtp_aac.sdp

m=audio 9832 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/48000/2
a=fmtp:97 SizeLength=13;
c=IN IP4 192.168.2.180

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


在这里插入图片描述

🎄四、总结

本文介绍了实现 使用RTP协议封装并传输 AAC 的一些步骤和细节,介绍了RTP封包格式,sdp相关知识等,也提供了实现源码和运行结果,可以帮助读者快速了解RTP协议怎么传输AAC数据的。

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

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

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

相关文章:

  • 学习threejs,使用自定义GLSL 着色器,生成艺术作品
  • 电机参数测量
  • 自由学习记录(66)
  • JT808教程:消息的结构
  • react中在Antd3.x版本中 Select框在单选时 选中框的高度调整
  • Qt 实现Opencv功能模块切换界面功能
  • 【算法】动态规划:python实现 1
  • TensorFlow内核剖析:分布式TensorFlow架构解析与实战指南
  • mini-electron使用方法
  • 内部类与Lambda的衍生关系(了解学习内部类,Lambda一篇即可)
  • C# WPF + Helix Toolkit 实战:用两种方式打造“六面异色立方体”
  • QNN SDK学习笔记
  • 二十八、【环境管理篇】灵活应对:多测试环境配置与切换
  • python开发|yaml用法知识介绍
  • STM32F4操作内部FLASH简洁版
  • 【代码审计】安全审核常见漏洞修复策略
  • 位运算经典题解
  • 启用不安全的HTTP方法
  • 图像处理专业书籍以及网络资源总结
  • Java编程之状态模式
  • 《UE5_C++多人TPS完整教程》学习笔记40 ——《P41 装备(武器)姿势(Equipped Pose)》
  • 基于Socketserver+ThreadPoolExecutor+Thread构造的TCP网络实时通信程序
  • mac重复文件清理,摄影师同款清理方案
  • flv.js视频/直播流测试demo
  • 2025 推理技术风向标:DeepSeek-R1 揭示大模型从 “记忆” 到 “思考” 的进化路径
  • 【linux】基础开发工具(1)
  • Flink Savepoints 总结
  • js代码09
  • Spring Boot WebSocket方案终极指南:Netty与官方Starter对比与实践
  • MFC的List Control自适应主界面大小