目录

一. 前言

二. 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) 说明
1 Syncword 12 ADTS Frame 头部的第一个字段,12bit 都为1
2 MPEG version 1

0 表示 MPEG-4

1 表示 MPEG-2

3 Layer 2 always 0
4 Protection Absent 1

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

5 Profile 2

0 表示 AAC Main

1 表示 AAC LC

2 表示 AAC SSR

6 MPEG-4 Sampling Frequence Index 4

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

详见此处

7 Private Stream 1 编码时将该值设为 0,解码时忽略
8 MPEG-4 Channel Configuration 3 通道数
9 Originality 1 编码时将该值设置为 0,解码时忽略
10 Home 1 编码时将该值设为 0,解码时忽略
11 Copyrighted Stream 1 编码时将该值设为 0,解码时忽略
12 Copyrighted Start 1 编码时将该值设为 0,解码时忽略
13 Frame Length 13 ADTS 帧长度,包括头部所占的长度
14 Buffer Fullness 11 值为 0x7FF 时表示动态码率
15 Number of AAC Frames 2 值为 ADTS 帧里的 AAC 帧数量减一,为了兼容性一般一个 ADTS 帧包含一个 AAC 帧
16 CRC 16 CRC 校验码

该网站提供了一个解析 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

使用RTP包荷载AAC码流数据相关推荐

  1. 使用RTP包荷载H264码流数据

    目录 一. 前言 二. RTP协议介绍 三. H264码流结构介绍 四. RTP与H264的结合 1. 单一NALU模式 2. 组合包模式 3. 分片模式 五. 代码实战 六. 效果展示 一. 前言 ...

  2. 【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

    一 前言 最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大.那写这 ...

  3. FU-A分包方式,以及从RTP包里面得到H.264数据和AAC数据的方法

    From: http://www.cnweblog.com/fly2700/archive/2012/02/23/319718.html RFC3984是H.264的baseline码流在RTP方式下 ...

  4. <整理总结>H264/265码流数据包格式分析(带mp4v2封装H264/265为MP4的源码示例)

    H264/265码流数据包格式分析 前言: 一.H.264码流解析 I帧P帧B帧说明: 二.H.265码流解析 三.主要源码 前言: 最近在学习使用MP4v2将H264/H265码流以及AAC音频封装 ...

  5. NDK学习笔记:RtmpPusher之利用rtmpdump推h264/aac码流

    NDK学习笔记:RtmpPusher之利用rtmpdump推h264/aac码流 本篇将是 RtmpPusher 的最后一篇.在之前的3篇文章里,我们已经把原生的视频YUV格式编码成h264,把音频的 ...

  6. Qt结合FFmpeg转码码流数据(h264)生成不同视频格式(mp4、mov、flv、avi等)

    目录 1.转码流程分析 2.创建一个类专门用来转码 .h文件 构造函数 打开对应的码流数据 转码得到最终的封装格式 主函数测试 转码运行结果 1.转码流程分析 /*转码流程分析: * 1.注册组件 * ...

  7. 海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP)

    海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP) 问题分析 转码推rtmp PS流转封装 码云(Gitee)主页:https://gitee.com/banmaj ...

  8. android查看网页源码,流数据

    工具: 获取流数据 package com.glsite.htmlviewer;import java.io.ByteArrayOutputStream; import java.io.InputSt ...

  9. RTP协议全解(H264码流和PS流)

    1 视频编码的原理 1.1 一个图像或者一个视频序列进行压缩,产生码流. 对图像的处理即是:帧内预测编码 其预测值P,是由已编码的图像做参考,经运动补偿得到的.预测图像P和当前帧Fn相减,得到两图像的 ...

  10. 视音频数据处理入门:AAC音频码流解析

    ===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...

最新文章

  1. stm32车联网监控源码_物联网DIY,STM32配合ESP8266,APP控制LED,可以绑定天猫精灵...
  2. 从事数据科学前必须知道的五件事儿
  3. 个人信息泄露,背后竟有“内鬼”作祟,堵上网络安全漏洞
  4. Android监听后台状态,退出即杀死并显示退出提示框
  5. 在线即时通讯工具的网页即时聊天的html代码
  6. cocoscreator editbox 只允许数字_用Cocos做一个数字调节框
  7. div 高度等与html,html – 仅限CSS – 基于兄弟的div的高度
  8. 非常好的Java反射例子
  9. 体验VisualStudio 2013中的内存分析功能
  10. 重复类发展手法_正确护肤手法图解!
  11. 服务器进销财务管理系统,进销存财务管理系统
  12. 基于物品的协同过滤算法实现图书推荐系统
  13. 坚果pro官方固件_坚果Pro线刷包_坚果Pro刷机包_坚果Pro固件包_坚果Pro救砖包 - 线刷宝ROM中心...
  14. 【先锋】永洪科技何春涛:不忘初心,砥砺前行
  15. 计算机网络 第七章 网络安全
  16. Microsoft Edge 嗯...无法访问此页面解决办法
  17. Java 导出富文本到Word(包含图片)
  18. Vue前端实现微信扫码登录
  19. 概述-元数据是什么?
  20. 【JDBC上篇】什么是JDBC

热门文章

  1. 解决IE浏览器jQuery执行ajax不响应问题
  2. spi slaver接口的fpga实现
  3. Android屏幕亮度调节
  4. android 5.1一键root工具箱,最新的安卓5.1.1 ROOT教程(不需要刷第三方内核)
  5. C#替换Word中的文本内容
  6. 前端项目性能优化方案有哪些
  7. 一元三次方程求根公式及韦达定理
  8. sdcc 存储类型关键字
  9. Pylab Plotting
  10. 《西部世界》暗示了大数据人工智能什么