前言:
目前兄弟们对“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