目录
一. 前言
二. 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