使用RTP包荷载AAC码流数据

本文介绍了RTP协议在实时音视频通话中的应用,特别是如何使用RTP荷载AAC码流数据。通过JRTPLIB库发送AAC数据,并利用VLC进行播放。同时,详细解析了RTP协议头的各个字段,以及AAC的ADTS帧格式。最后,提供了示例代码展示如何将AAC数据封装到RTP包中进行传输。
摘要由CSDN通过智能技术生成

一. 前言

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

二. RTP协议介绍

在实时音视频通话中,我们通常使用 UDP 作为传输层协议,使用 RTP 协议包荷载音视频数据,RTP(Real-time Transport Protocol)是一种在 Internet 上传输多媒体数据的应用层协议,它通常建立在 UDP 之上(也可以建立在 TCP 上)。UDP 协议没有序号等信息,而 RTP 协议可以补充许多音视频传输必要的信息,让音视频数据到达对端后可以重新组合完整,RTP 本身只保证实时数据的传输,并不能提供可靠传输保证,也没有流量控制,拥塞控制机制,它通常与 RTCP 配合使用以提供这些服务。

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

Version:RTP 协议版本号

P:填充标识,如果该位为 1,说明该 RTP 包末尾包含了一个或多个填充字节,最后一个字节的值表示填充的字节数(包含最后一个字节本身),一般在一些需要固定块大小的加密算法中才需要填充

X:扩展标识,如果该位为 1,说明有扩展头部信息(Extension header)

CC:CSRC Count,共享媒体源个数,一般用于混音和混屏中,例如某个音频流是混合了其它音频后的数据,那么其它音频源就是该音频源的 CSRC

M:Mark 标记位,对于不同的负载类型有不同含义,例如使用 RTP 荷载 H264 码流时,如果某个帧分成多个包进行传输,可以使用该位标记是否为帧的最后一个包

PT:Payload Type,负载包类型,接收端可以根据该信息查找相应的解码器进行解码,Payload Type 值对应的编解码类型参考该文档

Sequence number:序列号,每个 RTP 包序号递增加一,接收端根据序列号可以判断传输是否丢包,序列号初始值是随机的

Timestamp:相对时间戳信息,反映 RTP 数据包数据采样时间,一个帧的数据可能被分成多个 RTP 包发送,同一个帧的时间戳是相同的,不同帧的时间戳是不相同的,该值初始值是随机的,单位的含义与数据采样频率有关

SSRC:媒体源的标识,不同的 SSRC 标识不同的媒体源,例如不同用户的音频就属于不同的媒体源,具有不同的 SSRC

CSRC identifiers:共享媒体源列表,表示对 RTP 包内载荷起作用的媒体源,参见 CC 解释,CSRC 最多 15 个

Profile:RTP 扩展头部有两种类型,one-byte header 和 two-byte header,当 Profile = 0xBEDE 时表示使用 one-byte header,Profile = 0x1000 时表示使用 two-byte header,扩展头部个数由 Extension header length 决定

Extension header length:表示后面的 Extension header 共有几个字节,长度以 4 字节为单位,例如 length = 3 表示 Extension header 一共占 3*4=12 个字节

Extension header:具体的扩展头部,由 ID,L,data 组成,可以是 one-byte header 或者 two-byte header 组织方式

one-byte header 格式如下,它由 ID,L,data 三部分组成。ID 和 L 分别占 4 bit,加起来等于 one-byte,ID 表示扩展头部 ID 标记,L 表示 extension data 所占字节数 -1,例如 L = 0 时实际 data 占一个字节,由于头部需要按 4 字节对齐,因此中间补充了 padding 数据,最后一个 extension header data 占 4 字节。

two-byte header 格式如下,它也是由 ID, L, data 三部分组成,不同之处在于 two-byte header 中 ID,L 各占一字节,而 L 表示 extension data 所占的字节数(不同于 one-byte header 需要减一)。

常见的 header extension ID 类型如下,关于具体某个扩展头部的含义可以参考 RFC 或者 webrtc 文档。

enum RTPExtensionType : int {
  kRtpExtensionNone,
  kRtpExtensionTransmissionTimeOffset,
  kRtpExtensionAudioLevel,
  kRtpExtensionInbandComfortNoise,
  kRtpExtensionAbsoluteSendTime,
  kRtpExtensionAbsoluteCaptureTime,
  kRtpExtensionVideoRotation,
  kRtpExtensionTransportSequenceNumber,
  kRtpExtensionTransportSequenceNumber02,
  kRtpExtensionPlayoutDelay,
  kRtpExtensionVideoContentType,
  kRtpExtensionVideoTiming,
  kRtpExtensionFrameMarking,
  kRtpExtensionRtpStreamId,
  kRtpExtensionRepairedRtpStreamId,
  kRtpExtensionMid,
  kRtpExtensionGenericFrameDescriptor00,
  kRtpExtensionGenericFrameDescriptor = kRtpExtensionGenericFrameDescriptor00,
  kRtpExtensionGenericFrameDescriptor02,
  kRtpExtensionColorSpace,
  kRtpExtensionNumberOfExtensions  // Must be the last entity in the enum.
};

如下是 Wireshark 抓取某路流的一个 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 是由头部(固定头部+可变头部)和数据组成,帧头部结构和字段含义如下。

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

 本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值