目录
一. 前言
音视频通话中我们通常使用 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