【GB28181】RTSP服务器传输AAC音频

概述

实现一个简单的RTSP服务器,主要用于从本地AAC文件读取音频数据,然后通过RTP协议实时传输AAC音频流。整体结构和H264视频流服务器结构相似

ADTS头部

结构体分析

该结构体主要用于描述ADTS头部,该头部信息位于每个AAC音频帧之前,其中包含了音频帧的同步信息、长度、采样率等重要参数

  • syncword: 同步字 (0xFFF),标志 ADTS 帧的开始。
  • aacFrameLength: ADTS 帧长度 (包括头部和 AAC 原始数据),这是确定每个 AAC 帧大小的关键信息。
  • samplingFreqIndex: 采样率索引,对应实际的采样率 (如 44100Hz, 48000Hz)。
  • channelCfg: 声道配置,表示音频声道数 (如单声道、立体声)
struct AdtsHeader {unsigned int syncword;  //12 bit 同步字 '1111 1111 1111',一个ADTS帧的开始uint8_t id;        //1 bit 0代表MPEG-4, 1代表MPEG-2。uint8_t layer;     //2 bit 必须为0uint8_t protectionAbsent;  //1 bit 1代表没有CRC,0代表有CRCuint8_t profile;           //1 bit AAC级别(MPEG-2 AAC中定义了3种profile,MPEG-4 AAC中定义了6种profile)uint8_t samplingFreqIndex; //4 bit 采样率uint8_t privateBit;        //1bit 编码时设置为0,解码时忽略uint8_t channelCfg;        //3 bit 声道数量uint8_t originalCopy;      //1bit 编码时设置为0,解码时忽略uint8_t home;               //1 bit 编码时设置为0,解码时忽略uint8_t copyrightIdentificationBit;   //1 bit 编码时设置为0,解码时忽略uint8_t copyrightIdentificationStart; //1 bit 编码时设置为0,解码时忽略unsigned int aacFrameLength;               //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流unsigned int adtsBufferFullness;           //11 bit 缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。这个在使用音频编码的时候需要注意。/* number_of_raw_data_blocks_in_frame* 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧* 所以说number_of_raw_data_blocks_in_frame == 0* 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)*/uint8_t numberOfRawDataBlockInFrame; //2 bit
};

解析函数分析

主要用于解析ADTS头部,从字节流中提取ADTS头部信息,然后填充到AdtsHeader结构体中

  • 同步字校验: 首先检查前两个字节是否为 ADTS 同步字 (0xFFF),这是判断是否为有效 ADTS 头部的首要条件
  • 位域提取: 使用位运算从 ADTS 头部字节中提取各个字段的值,例如 AAC 级别、采样率索引、声道配置、ADTS 帧长度等,并将解析出的值存储到 AdtsHeader 结构体中
  • 错误处理: 如果同步字校验失败,则认为 ADTS 头部解析失败,返回错误代码
static int parseAdtsHeader(uint8_t* in, struct AdtsHeader* res) {static int frame_number = 0;memset(res, 0, sizeof(*res));if ((in[0] == 0xFF) && ((in[1] & 0xF0) == 0xF0)){res->id = ((uint8_t)in[1] & 0x08) >> 3;//第二个字节与0x08与运算之后,获得第13位bit对应的值res->layer = ((uint8_t)in[1] & 0x06) >> 1;//第二个字节与0x06与运算之后,右移1位,获得第14,15位两个bit对应的值res->protectionAbsent = (uint8_t)in[1] & 0x01;res->profile = ((uint8_t)in[2] & 0xc0) >> 6;res->samplingFreqIndex = ((uint8_t)in[2] & 0x3c) >> 2;res->privateBit = ((uint8_t)in[2] & 0x02) >> 1;res->channelCfg = ((((uint8_t)in[2] & 0x01) << 2) | (((unsigned int)in[3] & 0xc0) >> 6));res->originalCopy = ((uint8_t)in[3] & 0x20) >> 5;res->home = ((uint8_t)in[3] & 0x10) >> 4;res->copyrightIdentificationBit = ((uint8_t)in[3] & 0x08) >> 3;res->copyrightIdentificationStart = (uint8_t)in[3] & 0x04 >> 2;res->aacFrameLength = (((((unsigned int)in[3]) & 0x03) << 11) |(((unsigned int)in[4] & 0xFF) << 3) |((unsigned int)in[5] & 0xE0) >> 5);res->adtsBufferFullness = (((unsigned int)in[5] & 0x1f) << 6 |((unsigned int)in[6] & 0xfc) >> 2);res->numberOfRawDataBlockInFrame = ((uint8_t)in[6] & 0x03);return 0;}else{printf("failed to parse adts header\n");return -1;}
}

RTP AAC帧发送函数

负责将 AAC 音频帧封装成 RTP 包并通过 UDP 发送,其中AAC的RTP负载格式使用的是AU头部;整体逻辑是先设置通用的AU头部,然后将负载加入到RTP负载空间中,通过RTP发送,然后更新序列号和时间戳

代码分析

主要流程总结

  • 从AAC音频文件中读取数据,然后解析ADTS的头部
  • RTP数据包封装:将音频数据封装进一个RTP包中,然后通过网络进行发送
  • 控制帧率,确保音频数据按照正确的速度进行播放 
 //开始播放,发送RTP包if (!strcmp(method, "PLAY")) {struct AdtsHeader adtsHeader; // 存储ADTS头部信息struct RtpPacket* rtpPacket;  // RTP包uint8_t* frame; //存储文件中读取AAC音频数据int ret;FILE* fp = fopen(AAC_FILE_NAME, "rb");if (!fp) {printf("读取 %s 失败\n", AAC_FILE_NAME);break;}frame = (uint8_t*)malloc(5000);rtpPacket = (struct RtpPacket*)malloc(5000);// 初始化RTP包头信息(一般是版本负载类型等)rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_AAC, 1, 0, 0, 0x32411);while (true){// 读取前7个字节存储到frame中,也就是ADTS头部数据ret = fread(frame, 1, 7, fp);if (ret <= 0){printf("fread err\n");break;}printf("fread ret=%d \n",ret);// 解析ADTS头部信息if (parseAdtsHeader(frame, &adtsHeader) < 0){printf("parseAdtsHeader err\n");break;}// 读取完剩下的AAC音频数据ret = fread(frame, 1, adtsHeader.aacFrameLength - 7, fp);if (ret <= 0){printf("fread err\n");break;}rtpSendAACFrame(serverRtpSockfd, clientIP, clientRtpPort,rtpPacket, frame, adtsHeader.aacFrameLength - 7);Sleep(1);//usleep(23223);//1000/43.06 * 1000}free(frame);free(rtpPacket);break;}memset(method, 0, sizeof(method) / sizeof(char));memset(url, 0, sizeof(url) / sizeof(char));CSeq = 0;}

RTSP处理AAC音频SDP

将handleCmd_DESCRIBE修改为生成音频流的SDP描述

  • m=audio 0 RTP/AVP 97: 媒体类型改为 audio,payload type 设置为 97 (动态 payload type)。
  • a=rtpmap:97 mpeg4-generic/44100/2: RTP map 属性设置为 mpeg4-generic 音频,采样率 44100Hz,2 声道 (立体声)。
  • a=fmtp:97 ... config=1210;: 关键的 FMTP 属性,包含 AAC 音频流的格式特定参数,特别是 config=1210,这是 AudioSpecificConfig (音频特定配置),以十六进制表示,解码器需要这个配置信息才能正确解码 AAC 音频。 0x1210 代表 44100Hz 采样率和 2 声道

代码分析

核心流程分为三步

  • 解析RTSP的URL,从URL中提取本地的IP地址
  • 生成SDP数据,构建描述流媒体信息的SDP字符串
  • 构建RTSP响应,将生成的SDP数据与RTSP响应拼接在一起返回给客户端
static int handleCmd_DESCRIBE(char* result, int cseq, char* url) {char sdp[500];char localIp[100];// 从RTSP流地址中获取IP地址sscanf(url, "rtsp://%[^:]:", localIp);// 构建SDP数据sprintf(sdp, "v=0\r\n""o=- 9%ld 1 IN IP4 %s\r\n""t=0 0\r\n""a=control:*\r\n""m=audio 0 RTP/AVP 97\r\n""a=rtpmap:97 mpeg4-generic/44100/2\r\n""a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1210;\r\n"//"a=fmtp:97 SizeLength=13;\r\n""a=control:track0\r\n",time(NULL), localIp);sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n""Content-Base: %s\r\n""Content-type: application/sdp\r\n""Content-length: %d\r\n\r\n""%s",cseq,url,strlen(sdp),sdp);return 0;
}

构建SDP的内容分析

  • v=0: 版本号。
  • o=- 9%ld 1 IN IP4 %s: 发送者和会话标识。time(NULL) 提供当前时间戳,localIp 则是从 URL 中提取的本地 IP 地址。
  • t=0 0: 会话的开始和结束时间,这里设置为 0 0,表示该会话没有特定的开始或结束时间。
  • a=control:: 控制指令,指定 *,表示所有的流都可以被控制。
  • m=audio 0 RTP/AVP 97: 描述音频媒体流,使用 RTP 协议,流的媒体类型是音频,负载类型(Payload Type)为 97。
  • a=rtpmap:97 mpeg4-generic/44100/2: 负载类型 97 的详细信息,表示使用 MPEG4 编码,采样率为 44100 Hz,声道数为 2。
  • a=fmtp:97 ...: 为负载类型 97 提供的格式特定参数,指定编码方式、数据结构等详细信息。
  • a=control:track0: 控制指令,指定音频轨道为 track0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/33644.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全反渗透 网络安全攻防渗透

网络渗透防范主要从两个方面来进行防范&#xff0c;一方面是从思想意识上进行防范&#xff0c;另一方面就是从技术方面来进行防范。 1.从思想意识上防范渗透 网络攻击与网络安全防御是正反两个方面&#xff0c;纵观容易出现网络安全事故或者事件的公司和个人&#xff0c;在这些…

2025-03-15 学习记录--C/C++-PTA 练习3-4 统计字符

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 练习3-4 统计字符 本题要求编写程序&#xff0c;输入10个字符&#xff0c;统计其中英文字母、空格或回车、…

11a-PPDU

## 前导码和信令 OFDM 物理层&#xff08;PHY&#xff09;的 PPDU&#xff08;物理层协议数据单元&#xff09;格式包含以下实体信息&#xff1a; - **PPDU 组成**&#xff1a;由 OFDM PHY preamble&#xff08;前导码&#xff0c;12 个符号&#xff09;、PHY header&#xff…

TF-IDF:文本挖掘中的关键词提取利器

引言 在自然语言处理&#xff08;NLP&#xff09;和文本挖掘中&#xff0c;TF-IDF是一种常用的技术&#xff0c;用于评估一个词在文档中的重要性。它不仅在信息检索领域广泛应用&#xff0c;还在文本分类、关键词提取等任务中发挥着重要作用。本文将详细介绍TF-IDF的原理…

[新能源]新能源汽车快充与慢充说明

接口示意图 慢充接口为交流充电口&#xff08;七孔&#xff09;&#xff0c;快充接口为直流充电口&#xff08;九孔&#xff09;。 引脚说明 上图给的是充电口的引脚图&#xff0c;充电枪的为镜像的。 慢充接口引脚说明 快充接口引脚说明 充电流程 慢充示意图 慢充&…

docker3-容器与镜像命令

前言 容器命令[部分] docker run –name“nginx-lb” 这个就是为容器起一个名称 以前是随机起的名称 docker run -d --name mynginx1 nginx:1.24.0 docker ps 这样就可以看到我们起的名字了 docker stop mynginx1 这个就可以停掉指定名字的容器了&#xff0c;但不是删除…

vue/react/vite前端项目打包的时候加上时间最简单版本,防止后端扯皮

如果你是vite项目&#xff0c;直接写一个vite的插件&#xff0c;通过这个插件可以动态注入环境变量&#xff0c;然后当打包的时候&#xff0c;自动注入这个时间到环境变量中&#xff0c;然后在项目中App.vue中或者Main.tsx中打印出来&#xff0c;这就知道是什么时候编译的项目了…

Linux中Gdb调试工具常用指令大全

1.gdb的安装 如果你是root用户直接用指令 &#xff1a;yum install gdb &#xff1b;如果你是普通用户用指令&#xff1a;sudo yum install gdb&#xff1b; 2.gdb调试前可以对你的makefile文件进行编写&#xff1a; 下面展示为11.c文件编写的makefile文件&#xff1a; code…

go 安装swagger

1、依赖安装&#xff1a; # 安装 swag 命令行工具 go install github.com/swaggo/swag/cmd/swaglatest# 安装 gin-swagger 和 swagger 文件的依赖 go get -u github.com/swaggo/gin-swagger go get -u github.com/swaggo/files 2、测试 cmd中输入&#xff1a; swag -v 如果…

数据库---sqlite3

数据库&#xff1a; 数据库文件与普通文件区别: 1.普通文件对数据管理(增删改查)效率低 2.数据库对数据管理效率高,使用方便 常用数据库: 1.关系型数据库: 将复杂的数据结构简化为二维表格形式 大型:Oracle、DB2 中型:MySql、SQLServer …

go的gmp

参考链接&#xff1a;https://www.bilibili.com/video/BV19r4y1w7Nx Golang的GMP调度模型(协程调度器)是其并发编程的核心。GMP代表Goroutine、Machine和Processor三个关键组成部分。Goroutine是Go语言中的轻量级线程&#xff0c;Machine是操作系统的线程&#xff0c;Processor…

标贝自动化数据标注平台推动AI数据训练革新

随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;数据标注作为AI模型训练的关键环节&#xff0c;其重要性日益凸显。传统的人工数据标注方式虽然能够提供高质量的标注数据&#xff0c;但存在效率低、成本高、一致性差等问题。为了解决这些问题&#xff0c;标…

从传统制动到线控制动:技术变革与挑战

随着汽车产业从传统机械时代迈向电动化、智能化时代&#xff0c;车辆底盘的“线控化”已经成为重要发展趋势。其中&#xff0c;线控制动系统&#xff08;Brake-by-Wire&#xff0c;简称BBW&#xff09;是该趋势的核心一环。传统的制动系统主要依赖真空助力或液压传动&#xff0…

Java---JavaSpringMVC解析(1)

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc)&#xff0c;但它通常被称为"Spring MVC" 1.MVC MVC是Model View Controller的缩写&#…

VSTO(C#)Excel开发8:打包发布安装卸载

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

地下停车场调频广播覆盖:破解地下车库无线广播收听孤岛,技术赋能地下停车场FM调频无线广播覆盖

地下停车场调频广播覆盖&#xff1a;破解地下车库无线广播收听孤岛&#xff0c;技术赋能地下停车场FM调频无线广播覆盖 北京海特伟业科技有限公司任洪卓于2025年3月14日发布 地下停车场调频广播覆盖系统建设背景 随着城市化进程的加速&#xff0c;地下停车场已成为现代建筑不…

kettle的转换中sql不按设计顺序执行原因分析与解决办法

1.问题描述 如图&#xff0c;通过箭头指定多个SQL脚本的先后顺序&#xff0c;实际各个sql没有阻塞&#xff0c;没有等待&#xff0c;几乎是并行&#xff0c;与预期不符。 2.原因 转换文件&#xff08;.ktr&#xff09; 用于控制数据的流量&#xff0c;比如表输入指向表输出节…

P1259 黑白棋子的移动【java】【AC代码】

有 2n 个棋子排成一行&#xff0c;开始为位置白子全部在左边&#xff0c;黑子全部在右边&#xff0c;如下图为 n5 的情况&#xff1a; 移动棋子的规则是&#xff1a;每次必须同时移动相邻的两个棋子&#xff0c;颜色不限&#xff0c;可以左移也可以右移到空位上去&#xff0c;但…

P6772 [NOI2020] 美食家

训练角度&#xff1a;图上的状态转移&#xff0c;倍增 → \rightarrow → 优化状态转移&#xff1b; ▍ 题意 精灵王国共有 n n n 座城市&#xff0c;城市从 1 1 1 到 n n n 编号&#xff0c;其中城市 i i i 的美食能为小 W 提供 c i c_i ci​ 的愉悦值。精灵王国的城市…

51c大模型~合集7

我自己的原文哦~ https://blog.51cto.com/whaosoft/11519481 #MTMamba 王座易位&#xff1f;香港科技大学MTMamba&#xff0c;超越 ViT与CNN&#xff01; 本文作者提出了MTMamba&#xff0c;一种新型的多任务架构&#xff0c;具有基于Mamba的解码器&#xff0c;在多任务场…