使用RTP包荷载AAC码流数据

news2024/10/7 20:29:34

目录

一. 前言

二. RTP协议介绍

三. AAC介绍

1. AAC格式

2. ADTS

四. RTP与AAC的结合

五. 代码实战

六. 效果展示


一. 前言

        音视频通话中我们通常使用 RTP 协议包荷载音视频码率数据,例如麦克风采集输入数据后编码成帧,再将帧数据放入 RTP 协议包发送到流媒体服务器,本文介绍 RTP 如何荷载 AAC 码流数据,使用 JRTPLIB 进行发送,VLC 进行播放。

二. RTP协议介绍

        参考这篇博客。

三. AAC介绍

1. AAC格式

AAC 有两种格式:ADIF,ADTS。

ADIF(Audio Data Interchange Format),音频数据交换格式,这种格式的特点是只在文件头部存储用于音频解码播放的头信息(例如采样率,通道数等),它的解码播放必须从文件头部开始,一般用于存储在本地磁盘中播放。

ADTS(Audio Data Transport Stream),音频数据传输流,这种格式的特点是可以将数据看做一个个的音频帧,而每帧都存储了用于音频解码播放的头信息(例如采样率,通道数等),即可以从任何帧位置解码播放,更适用于流媒体传输。

2. ADTS

ADTS 格式的 AAC 码流是由一个个的 ADTS Frame 组成的,结构如下。

其中每个 ADTS Frame 是由头部(固定头部+可变头部)和数据组成,帧头部结构和字段含义如下。

序号字段名称长度 (bits)说明
1Syncword12ADTS Frame 头部的第一个字段,12bit 都为1
2MPEG version1

0 表示 MPEG-4

1 表示 MPEG-2

3Layer2always 0
4Protection Absent1

是否存在 CRC 校验,0 表示存在 CRC 校验字段,1 表示不存在 CRC 校验字段

5Profile2

0 表示 AAC Main

1 表示 AAC LC

2 表示 AAC SSR

6MPEG-4 Sampling Frequence Index4

采样率,0 表示 96000Hz,4 表示 44100Hz,11 表示 8000Hz

详见此处

7Private Stream1编码时将该值设为 0,解码时忽略
8MPEG-4 Channel Configuration3通道数
9Originality1编码时将该值设置为 0,解码时忽略
10Home1编码时将该值设为 0,解码时忽略
11Copyrighted Stream1编码时将该值设为 0,解码时忽略
12Copyrighted Start1编码时将该值设为 0,解码时忽略
13Frame Length13ADTS 帧长度,包括头部所占的长度
14Buffer Fullness11值为 0x7FF 时表示动态码率
15Number of AAC Frames2值为 ADTS 帧里的 AAC 帧数量减一,为了兼容性一般一个 ADTS 帧包含一个 AAC 帧
16CRC16CRC 校验码

该网站提供了一个解析 AAC ADTS Frame Header 的工具,你可以输入头部 7 或 9 个字节的数据,点击 Submit 就能看到头部各字段对应的含义。

        如下是我们以二进制格式打开某个 aac 文件后展示的内容,可以看到第一个 ADTS Frame 开头 12bits 的 syncword 全为 1,之后继续解析头部可以获得帧长度,第二个 ADTS Frame 开头 12bits 的 syncword 也是全为 1。

 

四. RTP与AAC的结合

        如果使用 RTP 包荷载视频帧数据,由于视频帧数据较大,可能需要多个 RTP 包承载一个视频帧,而音频帧一般较小,一般只用一个 RTP 包也可以承载。RTP 承载 AAC 码流的 ADTS 帧数据示意图如下。

        首先在 RTP Payload 前面需要先加 4 个字节的荷载标识,payload[0] = 0x00,payload[1] = 0x10,payload[2] = (frameLength & 0x1FE0) >> 5,payload[3] = (frameLength & 0x1F) << 3。

        接下来将 ADTS Frame Data 拷贝到 RTP Payload[4] 开始的位置,注意 ADTS Frame Header 无需拷贝。

五. 代码实战

jrtp_aac.cpp

#include <jrtplib3/rtpsession.h>
#include <jrtplib3/rtplibraryversion.h>
#include <jrtplib3/rtpudpv4transmitter.h>
#include <jrtplib3/rtpsessionparams.h>
#include <jrtplib3/rtppacket.h>
#include <jrtplib3/rtperrors.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include "aac/aac.h"

using namespace std;
using namespace jrtplib;

const string SSRC = "10001";
const string AAC_FILE_PATH = "movie_file/lantingxv.aac";
const int MTU_SIZE = 1500;
const int MAX_RTP_PACKET_LENGTH = 1360;
const int AAC_PAYLOAD_TYPE = 97;
const int AAC_SAMPLE_NUM_PER_FRAME = 1024;

static void checkerror(int rtperr) {
    if (rtperr < 0) {
        std::cout << "ERROR: " << RTPGetErrorString(rtperr) << std::endl;
        exit(-1);
    }
}

int main(int argc, char** argv) {

    FILE* faac = fopen(AAC_FILE_PATH.c_str(), "rb");
    if (faac == NULL) {
        std::cout << "打开aac文件失败" << std::endl;
        exit(-1);
    }

    AdtsFrame* aframe = AllocAdtsFrame();
    int size = GetAdtsFrame(faac, aframe);
    if (size <= 0) {
        exit(0);
    }
    int frequence = GetFrequenceFromIndex(aframe->frequenceIdx);
    int frameRate = frequence / AAC_SAMPLE_NUM_PER_FRAME;
    uint32_t timestampInc = frequence / frameRate;
    fseek(faac, 0, SEEK_SET);

    // 获取本地用于发送的端口以及对端的IP和端口
    uint16_t localport;
    std::cout << "Enter local port(even): ";
	std::cin >> localport;
 
    std::string ipstr;
	std::cout << "Enter the destination IP address: ";
	std::cin >> ipstr;
	uint32_t destip = inet_addr(ipstr.c_str());
	if (destip == INADDR_NONE) {
		std::cerr << "Bad IP address specified" << std::endl;
		return -1;
	}
    destip = ntohl(destip);
 
    uint16_t destport;
	std::cout << "Enter the destination port: ";
	std::cin >> destport;

    // 设置RTP属性
    RTPUDPv4TransmissionParams tranparams;
    tranparams.SetPortbase(localport);
 
    RTPSessionParams sessparams;
    sessparams.SetOwnTimestampUnit(1.0/frequence);
 
    RTPSession sess;
    int status = sess.Create(sessparams, &tranparams);
    checkerror(status);
 
    RTPIPv4Address destAddr(destip, destport);
    status = sess.AddDestination(destAddr);
	checkerror(status);

    sess.SetDefaultPayloadType(AAC_PAYLOAD_TYPE);
    sess.SetDefaultMark(true);
    sess.SetDefaultTimestampIncrement(timestampInc);

    RTPTime sendDelay(0, 1000000/frameRate);
    uint8_t sendbuf[MTU_SIZE] = { 0 };

    while (true) {
        if (feof(faac)) {
            fseek(faac, 0, SEEK_SET);
        }
        int size = GetAdtsFrame(faac, aframe);
        if (size == 0) {
            continue;
        } else if (size < 0) {
            exit(0);
        } else {
            std::cout << "Adts Frame, profile: " << (int) aframe->profile << ", frequenceIdx: " << (int) aframe->frequenceIdx
                      << ", frameLength: " << aframe->frameLength << ", headerLen: " << aframe->headerLen << ", bodyLen: " << aframe->bodyLen << std::endl;

            if (size <= MAX_RTP_PACKET_LENGTH) {
                memset(sendbuf, 0, MTU_SIZE);
                sendbuf[0] = 0x00;
                sendbuf[1] = 0x10;
                sendbuf[2] = (aframe->frameLength & 0x1FE0) >> 5;
                sendbuf[3] = (aframe->frameLength & 0x1F) << 3;
                memcpy(sendbuf+4, aframe->body, aframe->bodyLen);
                sess.SendPacket((void*) sendbuf, aframe->bodyLen+4, AAC_PAYLOAD_TYPE, true, timestampInc);
            } else {
                std::cout << "frame size too large, just ignore it" << std::endl;
            }
            RTPTime::Wait(sendDelay);
        }
    }
    FreeAdtsFrame(aframe);
    if (faac) {
        fclose(faac);
        faac = NULL;
    }
    sess.BYEDestroy(RTPTime(3, 0), 0, 0);

    return 0;
}

aac.cpp

#include <jrtplib3/rtpsession.h>
#include <jrtplib3/rtplibraryversion.h>
#include <jrtplib3/rtpudpv4transmitter.h>
#include <jrtplib3/rtpsessionparams.h>
#include <jrtplib3/rtppacket.h>
#include <jrtplib3/rtperrors.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include "aac/aac.h"

using namespace std;
using namespace jrtplib;

const string SSRC = "10001";
const string AAC_FILE_PATH = "movie_file/lantingxv.aac";
const int MTU_SIZE = 1500;
const int MAX_RTP_PACKET_LENGTH = 1360;
const int AAC_PAYLOAD_TYPE = 97;
const int AAC_SAMPLE_NUM_PER_FRAME = 1024;

static void checkerror(int rtperr) {
    if (rtperr < 0) {
        std::cout << "ERROR: " << RTPGetErrorString(rtperr) << std::endl;
        exit(-1);
    }
}

int main(int argc, char** argv) {

    FILE* faac = fopen(AAC_FILE_PATH.c_str(), "rb");
    if (faac == NULL) {
        std::cout << "打开aac文件失败" << std::endl;
        exit(-1);
    }

    AdtsFrame* aframe = AllocAdtsFrame();
    int size = GetAdtsFrame(faac, aframe);
    if (size <= 0) {
        exit(0);
    }
    int frequence = GetFrequenceFromIndex(aframe->frequenceIdx);
    int frameRate = frequence / AAC_SAMPLE_NUM_PER_FRAME;
    uint32_t timestampInc = frequence / frameRate;
    fseek(faac, 0, SEEK_SET);

    // 获取本地用于发送的端口以及对端的IP和端口
    uint16_t localport;
    std::cout << "Enter local port(even): ";
	std::cin >> localport;
 
    std::string ipstr;
	std::cout << "Enter the destination IP address: ";
	std::cin >> ipstr;
	uint32_t destip = inet_addr(ipstr.c_str());
	if (destip == INADDR_NONE) {
		std::cerr << "Bad IP address specified" << std::endl;
		return -1;
	}
    destip = ntohl(destip);
 
    uint16_t destport;
	std::cout << "Enter the destination port: ";
	std::cin >> destport;

    // 设置RTP属性
    RTPUDPv4TransmissionParams tranparams;
    tranparams.SetPortbase(localport);
 
    RTPSessionParams sessparams;
    sessparams.SetOwnTimestampUnit(1.0/frequence);
 
    RTPSession sess;
    int status = sess.Create(sessparams, &tranparams);
    checkerror(status);
 
    RTPIPv4Address destAddr(destip, destport);
    status = sess.AddDestination(destAddr);
	checkerror(status);

    sess.SetDefaultPayloadType(AAC_PAYLOAD_TYPE);
    sess.SetDefaultMark(true);
    sess.SetDefaultTimestampIncrement(timestampInc);

    RTPTime sendDelay(0, 1000000/frameRate);
    uint8_t sendbuf[MTU_SIZE] = { 0 };

    while (true) {
        if (feof(faac)) {
            fseek(faac, 0, SEEK_SET);
        }
        int size = GetAdtsFrame(faac, aframe);
        if (size == 0) {
            continue;
        } else if (size < 0) {
            exit(0);
        } else {
            std::cout << "Adts Frame, profile: " << (int) aframe->profile << ", frequenceIdx: " << (int) aframe->frequenceIdx
                      << ", frameLength: " << aframe->frameLength << ", headerLen: " << aframe->headerLen << ", bodyLen: " << aframe->bodyLen << std::endl;

            if (size <= MAX_RTP_PACKET_LENGTH) {
                memset(sendbuf, 0, MTU_SIZE);
                sendbuf[0] = 0x00;
                sendbuf[1] = 0x10;
                sendbuf[2] = (aframe->frameLength & 0x1FE0) >> 5;
                sendbuf[3] = (aframe->frameLength & 0x1F) << 3;
                memcpy(sendbuf+4, aframe->body, aframe->bodyLen);
                sess.SendPacket((void*) sendbuf, aframe->bodyLen+4, AAC_PAYLOAD_TYPE, true, timestampInc);
            } else {
                std::cout << "frame size too large, just ignore it" << std::endl;
            }
            RTPTime::Wait(sendDelay);
        }
    }
    FreeAdtsFrame(aframe);
    if (faac) {
        fclose(faac);
        faac = NULL;
    }
    sess.BYEDestroy(RTPTime(3, 0), 0, 0);

    return 0;
}

aac.h

#pragma once

#include <iostream>

struct AdtsFrame {
    bool crcProtectionAbsent;
    uint8_t profile;
    uint8_t frequenceIdx;
    uint16_t frameLength;

    uint8_t* buf;
    uint32_t maxSize;
    uint32_t len;
    uint8_t* header;
    uint32_t headerLen;
    uint8_t* body;
    uint32_t bodyLen;
};

int GetAdtsFrame(FILE* f, AdtsFrame* aframe);
AdtsFrame* AllocAdtsFrame();
AdtsFrame* AllocAdtsFrame(uint32_t bufferSize);
void FreeAdtsFrame(AdtsFrame* aframe);
int GetFrequenceFromIndex(uint8_t idx);

编译:g++ jrtp_aac.cpp aac/aac.cpp -ljrtp -o jrtp_aac

六. 效果展示

        jrtp_aac 程序启动后,设置本端使用的发送端口以及对端地址后,进程就开始发包了,我们使用 VLC 设置 sdp 信息开始接收流并播放。

m=audio 10004 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/44100/2
a=fmtp:97 streamtype=5; profile-level-id=15; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3;
c=IN IP4 127.0.0.1

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

B站:以SLO为核心的可用性观测与质量运营

UGeek大咖说是优维科技为技术爱好者研讨云原生技术演进趋势而创办的系列活动&#xff0c;邀请一线互联网大厂的核心骨干主讲&#xff0c;分享原厂实践。本年度主题为可观测&#xff0c;我们希望通过一场场有趣、有料、有深度的活动&#xff0c;让运维圈的小伙伴聚集在一起&…

Java知识点--反射(上)

Java知识点--反射&#xff08;上&#xff09;&#x1f356;一、为什么需要反射1️⃣在特定情境中传统方法的不足2️⃣为了不修改原码引出反射&#x1f357;二、反射机制1️⃣Java反射机制2️⃣Java 反射机制原理示意图3️⃣Java 反射机制可以完成4️⃣反射相关的主要类5️⃣反射…

java计算机毕业设计ssm在线学习资源管理系统t4ko5(附源码、数据库)

java计算机毕业设计ssm在线学习资源管理系统t4ko5&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java丹徒高级中学校车预约9poqj

大四计算机专业的同学们即将面临大学4年的最后一次考验--毕业设计。通过完成毕业设计来对过去4年的大学学习生活做一个总结&#xff0c;也是检验我们学习成果的一种方式&#xff0c;毕业设计作品也是我们将来面试找工作的一个敲门砖。 选题前先看看自己掌握哪些技术点、擅长哪…

Denoising Diffusion Probabilistic Models

目录概要前向过程nice property逆向过程参数推导简化参考资料概要 Denoising Diffusion Probabilistic Model(DDPM)是一个生成模型&#xff0c;给定一个目标分布&#xff0c;学习模型以便可以从目标分布中采样。 使用马尔科夫链建模。输入是噪声&#xff0c;通过神经网络逐步去…

伟大的缝纫师—typedef

伟大的缝纫师—typedef一.历史的误会—也许应该是typerename二.typedef和#define的区别一.历史的误会—也许应该是typerename 为什么这样说呢&#xff1f;因为typedf其实就是一个重命名关键字&#xff0c;看示例 这里我觉得unsigned int太长了&#xff0c;我将它改名为u_int&am…

bat批处理文件的注释,和常用简单命令

参考&#xff1a;https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/windows-commands&#xff0c;https://blog.csdn.net/wuhenyouyuyouyu/article/details/120736519? 当前路径&#xff1a;%~dp0 这个参数只能在bat文件中在正常使用&…

Unity脚本(2) --- 脚本生命周期以及脚本的控制台调试

1.什么是脚本生命周期&#xff1f; 首先什么是脚本 --- 脚本的本质其实就是类&#xff0c;而脚本生命周期其实就是脚本对应的那个类从开始工作到最后销毁这么一个周期 &#xff08;或者说是Unity脚本从唤醒到销毁的过程&#xff09; &#xff08;消息&#xff0c;必然事件&am…

[附源码]Node.js计算机毕业设计儿童成长记录与分享系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

【Vue实践】尚硅谷张天禹Vue学习笔记(更新至第86课)-20221126~20221212

004_尚硅谷Vue技术_搭建Vue开发环境 搭建Vue.js devtools 允许访问文件网址 https://blog.csdn.net/sunhl951/article/details/80185628 阻止 vue 在启动时生成生产提示。 Vue.config.productionTip false 目测没有用 https://blog.csdn.net/DIUDIUjiang/article/details/…

这些车企在企业微信里,装上高速的“组织引擎”

“这真是一场惊险之旅。” 今年7月&#xff0c;胡先生一家疾驶在若羌县罗布泊镇国道上&#xff0c;迎面突然冲出一辆大型货车……为了避让&#xff0c;胡太太驾驶的极氪001撞上了路边的石墩&#xff0c;两个轮胎直接报废。 在人迹罕至的无人区&#xff0c;保险公司鞭长莫及&a…

C++ 基础篇之如何进行数据封装

&#x1f4d2;博客主页&#xff1a; ​​开心档博客主页​​ &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐留言&#x1f4dd; &#x1f4cc;本文由开心档原创&#xff01; &#x1f4c6;51CTO首发时间&#xff1a;&#x1f334;2022年12月12日&#x1f334; ✉…

rocketmq源码-producer启动流程

前言 DefaultMQProducer producer new DefaultMQProducer("please_rename_unique_group_name"); producer.setNamesrvAddr("127.0.0.1:9876");producer.setNamesrvAddr("127.0.0.1:9876");producer.start();创建、启动producer的逻辑&#xff…

[附源码]Node.js计算机毕业设计电影推荐网站Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

对长尾识别任务中解耦方法的改进

来源&#xff1a;投稿 作者&#xff1a;TransforMe 编辑&#xff1a;学姐 贡献 在长尾识别任务上&#xff0c;解耦&#xff08;二阶段&#xff09;的方法取得了巨大的进步&#xff0c;详情参考https://blog.csdn.net/weixin_41246832/article/details/115718084。本文详细分析…

Android实现SSH Client

本文实现的是如何使用JSCH在Android上实现一个简易版本的ssh client&#xff0c;来远程执行ssh命令。 1、启动ssh服务&#xff0c;本文以mac为例。 打开设置-->共享-->选择远程登录 2、检验ssh server是否开启成功。 打开shell ssh dongxuliip 输入dongxuli账户的密码&…

P3884 [JLOI2009]二叉树问题——树化图Floyd+dfs

[JLOI2009]二叉树问题 题目描述 如下图所示的一棵二叉树的深度、宽度及结点间距离分别为&#xff1a; 深度&#xff1a;444宽度&#xff1a;444结点 8 和 6 之间的距离&#xff1a;888结点 7 和 6 之间的距离&#xff1a;333 其中宽度表示二叉树上同一层最多的结点个数&…

[附源码]Python计算机毕业设计Django高校体育场馆管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

web前端期末大作业——基于HTML+CSS+JavaScript蓝色的远程监控设备系统后台管理界面模板

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

6-1和6-2矩阵键盘

江科大自动化单片机学习记录使用到的设备以及软件今天的学习内容弱上拉和强下拉代码LCD1602.cMatrixKey.c生成.h文件调用主函数main总结记录学习单片机的过程学习内容的视频链接:江科大自化协:51单片机入门教程-2020版,程序全程纯手打 使用到的设备以及软件 普中科技的嵌入式…