龙空技术网

使用RTP包荷载AAC码流数据

音视频开发老舅 132

前言:

目前兄弟们对“c语言rtp解析aac”大概比较看重,同学们都想要学习一些“c语言rtp解析aac”的相关资讯。那么小编也在网络上汇集了一些关于“c语言rtp解析aac””的相关资讯,希望你们能喜欢,姐妹们一起来学习一下吧!

一. 前言

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

二. RTP协议介绍

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

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。

领C++音视频开发学习资料→「链接」

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 97a=rtpmap:97 mpeg4-generic/44100/2a=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语言rtp解析aac