😁博客主頁😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客內容🤑:🍭嵌入式開發、Linux、C語言、C++、數據結構、音視頻🍭
🤣本文內容🤣:🍭介紹怎么使用RTP協議封裝并傳輸AAC🍭
😎金句分享😎:🍭你不能選擇最好的,但最好的會來選擇你——泰戈爾🍭
?發布時間?: 2025-07-01 18:43:18
本文未經允許,不得轉發!!!
目錄
- 🎄一、概述
- 🎄二、實現步驟、實現細節
- ?2.1、實現AAC文件讀取器
- ?2.2、實現 AAC 的 RTP 數據包封裝
- 🎈2.2.1、RTP頭(RTP Header)
- 🎈2.2.2、AAC 的 RTP負載(RTP Payload)
- ?2.3、SDP協議介紹
- 🎄三、RTP協議封裝并傳輸AAC的實現源碼
- 🎄四、總結
🎄一、概述
前面文章介紹了怎么實現RTSP協議、RTP協議傳輸H264等:
【RTSP從零實踐】1、根據RTSP協議實現一個RTSP服務
【RTSP從零實踐】2、使用RTP協議封裝并傳輸H264
【RTSP從零實踐】3、實現最簡單的傳輸H264的RTSP服務器
這篇文章介紹使用RTP協議封裝并傳輸AAC。
首先,需要對 AAC文件結構 有了解,我們要從一個AAC文件讀取音頻幀,再進行RTP封包發送。本文會介紹需要用到的AAC相關知識,需要了解更詳細的可以參考以前文章:AAC格式音頻文件解析。
然后,需要對 RTP包結構 有個了解,本文介紹的RTP包由2部分組成:RTP頭、RTP負載。需要了解RTP頭數據結構、RTP負載數據結構,下個小節會介紹怎樣用代碼實現。對RTP數據包格式需要了解更多的可以參考這篇文章:RTP協議詳解 。
最后,需要對 SDP協議 有了解,后面代碼運行后,需要使用vlc打開.sdp
文件來驗證功能,所以需要知道一個sdp文件的內容是什么意思,這個將會在2.3小節介紹,如果需要了解更多sdp協議知識,可以看這篇文章:SDP(會話描述協議)詳解 及 抓包例子分析。
🎄二、實現步驟、實現細節
這個小寫介紹一些基礎知識點,下個小節會提供源碼,可以結合著源碼看幫助理解消化。
?2.1、實現AAC文件讀取器
.aac
文件保存了AAC編碼的音頻幀,在ADTS格式的aac文件中,每個音頻幀都包含了一個ADTS header
和AAC ES
。而ADTS header
占了7個字節,且最開始表示同步碼(syncword)的12bit的所有的bit位都是1,總是0xFFF,代表一個ADTS幀的開始,作為分界符,用于同步每幀起始位置。在可變頭部有表示aac幀長的aac_frame_length
,占13bit。我們可以用下面代碼來查找同步碼并獲取幀長。
清楚上述知識后,我們就可以從aac文件結構不斷讀取音頻幀數據了。
?2.2、實現 AAC 的 RTP 數據包封裝
本文介紹的RTP數據包主要由2部分組成,即 RTP頭、RTP負載。
🎈2.2.1、RTP頭(RTP Header)
上圖是RTP頭的結構圖,包含了12個字節的內容,可以用代碼定義成如下結構體:
struct RtpHeader
{/* byte 0 */uint8_t csrcLen:4;uint8_t extension:1;uint8_t padding:1;uint8_t version:2;/* byte 1 */uint8_t payloadType:7;uint8_t marker:1;/* bytes 2,3 */uint16_t seq;/* bytes 4-7 */uint32_t timestamp;/* bytes 8-11 */uint32_t ssrc;
};
RTP頭這里涉及到一個 時間戳怎么計算 的問題,需要注意的是,這個時間戳是一個 時鐘頻率 為單位的,而不是具體的時間(秒、毫秒等)。
一般情況下,AAC每個1024個采樣為一幀。假設AAC的時鐘頻率為48000Hz,所以一秒就有 48000 / 1024 = 47幀,那么每一幀的 時間間隔 就是1/47秒,每一幀的 時鐘增量 就是(48000 / 47 = 1021)。
那時間戳怎么算呢?舉個例子,以上面計算的數據,第一幀的RTP時間戳為0的話,那么第二幀的RTP時間戳就是 1021,第三幀的RTP時間戳就是 (1021+1021),依次類推,后一幀的RTP時間戳在前一幀的RTP時間戳的值加上一個時鐘增量。
注意:RTP的時間戳計算很重要,我一開始沒懂時間戳的概念,導致播放的聲音斷斷續續的。
🎈2.2.2、AAC 的 RTP負載(RTP Payload)
RTP負載常用的有兩種方式,第一種是 單個NAL單元封包(Single NAL Unit Packet
);第二種是 分片單元(Fragmentation Unit
) 。因為一幀ADTS幀一般小于 MTU(網絡最大傳輸單元1500字節),所以對于AAC的RTP封包只需要采用 單個NAL單元封包(Single NAL Unit Packet
) 即可。
但并不是直接將 ADTS 幀去掉ADTS頭之后的數據 作為RTP負載,AAC的RTP負載最開始有4個字節,其中2個字節表示AU頭長度(AU-headers-length),13bit的AU size;3bit的AU-Index(-delta) field。如下圖:
所以,AAC的RTP負載的一個字節為0x00,第二個字節為0x10,第三個字節和第四個字節保存AAC Data的大小,最多只能保存13bit(也就是說,第三個字節保存數據大小的高八位,第四個字節的高5位保存數據大小的低5位)。
參考下列代碼:
rtpPacket->payload[0] = 0x00;
rtpPacket->payload[1] = 0x10;
rtpPacket->payload[2] = (frameSize & 0x1FE0) >> 5; // 高8位
rtpPacket->payload[3] = (frameSize & 0x1F) << 3; // 低5位
這4個字節之后就是 ADTS 幀去掉ADTS頭之后的數據 了。
?2.3、SDP協議介紹
本文是使用VLC打開.sdp
文件來驗證功能的,所以也需要知道SDP協議的一些知識,下面是本文使用的sdp文件:
m=audio 9832 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/48000/2
a=fmtp:97 SizeLength=13;
c=IN IP4 192.168.2.180
-
m=audio 9832 RTP/AVP 97
:表示這是一個媒體描述,媒體類型是audio;接收媒體流的端口號是9832;傳輸協議是RTP/AVP
(通過UDP傳輸);RTP負載類型為97。 -
a=rtpmap:97 mpeg4-generic/48000/2
:RTP負載類型的具體編碼是 mpeg4-generic(AAC屬于mpeg4的),時鐘頻率為 48000Hz,2個聲道。 -
a=fmtp:97 SizeLength=13;
:幀長用13個bit表示。 -
c=IN IP4 192.168.2.180
:表示連接信息,網絡類型為IN,表示Internet。地址類型為IP4,表示IPv4地址。接收端地址為192.168.2.180。
🎄三、RTP協議封裝并傳輸AAC的實現源碼
總共有5個源代碼文件,1個sdp文件,具體如下:
1、aacReader.h
/*** @file aacReader.h* @author : https://blog.csdn.net/wkd_007* @brief * @version 0.1* @date 2025-06-30* * @copyright Copyright (c) 2025* */
#ifndef __AAC_READER_H__
#define __AAC_READER_H__#include <stdio.h>#define ADTS_HEADER_LEN (7)typedef struct
{int frame_len; //! unsigned char *pFrameBuf; //!
} AACFrame_t;typedef struct AACReaderInfo_s
{FILE *pFileFd;
}AACReaderInfo_t;int AAC_FileOpen(char *fileName, AACReaderInfo_t *pAACInfo);
int AAC_FileClose(AACReaderInfo_t *pAACInfo);
int AAC_GetADTSFrame(AACFrame_t *pAACFrame, const AACReaderInfo_t *pAACInfo);
int AAC_IsEndOfFile(const AACReaderInfo_t *pAACInfo);
void AAC_SeekFile(const AACReaderInfo_t *pAACInfo);#endif // __AAC_READER_H__
2、aacReader.c
/*** @file aacReader.c* @author : https://blog.csdn.net/wkd_007* @brief * @version 0.1* @date 2025-06-30* * @copyright Copyright (c) 2025* */
#include <stdlib.h>
#include <string.h>
#include "aacReader.h"#define MAX_FRAME_LEN (1024*1024) // 一幀數據最大字節數
#define MAX_SYNCCODE_LEN (3) // 同步碼字節個數 2025-05-21 17:45:06static int findSyncCode_0xFFF(unsigned char *Buf, int *size)
{if((Buf[0] == 0xff) && ((Buf[1] & 0xf0) == 0xf0) )//0xFF F,前12bit都為1 2025-05-21 17:46:57{*size |= ((Buf[3] & 0x03) <<11); //high 2 bit*size |= Buf[4]<<3; //middle 8 bit*size |= ((Buf[5] & 0xe0)>>5); //low 3bitreturn 1;}return 0;
}int AAC_FileOpen(char *fileName, AACReaderInfo_t *pAACInfo)
{pAACInfo->pFileFd = fopen(fileName, "rb+");if (pAACInfo->pFileFd==NULL){printf("[%s %d]Open file error\n",__FILE__,__LINE__);return -1;}return 0;
}int AAC_FileClose(AACReaderInfo_t *pAACInfo)
{if (pAACInfo->pFileFd != NULL) {fclose(pAACInfo->pFileFd);pAACInfo->pFileFd = NULL;}return 0;
}int AAC_IsEndOfFile(const AACReaderInfo_t *pAACInfo)
{return feof(pAACInfo->pFileFd);
}void AAC_SeekFile(const AACReaderInfo_t *pAACInfo)
{fseek(pAACInfo->pFileFd,0,SEEK_SET);
}/*** @brief * * @param pAACFrame :輸出參數,使用后 pAACInfo->pFrameBuf 需要free* @param pAACInfo * @return int */
int AAC_GetADTSFrame(AACFrame_t *pAACFrame, const AACReaderInfo_t *pAACInfo)
{int rewind = 0;if (pAACInfo->pFileFd==NULL){printf("[%s %d]pFileFd error\n",__FILE__,__LINE__);return -1;}// 1.先讀取ADTS幀頭(7個字節)unsigned char* pFrame = (unsigned char*)malloc(MAX_FRAME_LEN);int readLen = fread(pFrame, 1, ADTS_HEADER_LEN, pAACInfo->pFileFd);if(readLen <= 0){printf("[%s %d]fread error readLen=%d\n",__FILE__,__LINE__,readLen);free(pFrame);return -1;}// 2.查找當前幀同步碼,獲取幀長度int i=0;int size = 0;for(; i<readLen-MAX_SYNCCODE_LEN; i++){if(!findSyncCode_0xFFF(&pFrame[i], &size)){continue;}else{break;}}if(i!=0) // 不是幀開頭,偏移到幀開頭重新讀{printf("[%s %d]synccode error, i=%d\n",__FILE__,__LINE__,i);free(pFrame);rewind = (-(readLen-i));fseek (pAACInfo->pFileFd, rewind, SEEK_CUR);return -1;}// 3.讀取ADTS幀數據 2025-05-22 21:44:39readLen = fread(pFrame+ADTS_HEADER_LEN, 1, size-ADTS_HEADER_LEN, pAACInfo->pFileFd);if(readLen <= 0){printf("[%s %d]fread error\n",__FILE__,__LINE__);free(pFrame);return -1;}// 4.填數據pAACFrame->frame_len = size;pAACFrame->pFrameBuf = pFrame;return pAACFrame->frame_len;
}
3、rtp.h
#ifndef _RTP_H_
#define _RTP_H_
#include <stdint.h>#define RTP_VESION 2#define RTP_PAYLOAD_TYPE_H264 96
#define RTP_PAYLOAD_TYPE_AAC 97#define RTP_HEADER_SIZE 12
#define RTP_MAX_PKT_SIZE 1400/*** 0 1 2 3* 7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* |V=2|P|X| CC |M| PT | sequence number |* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* | timestamp |* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* | synchronization source (SSRC) identifier |* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+* | contributing source (CSRC) identifiers |* : .... :* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+**/
struct RtpHeader
{/* byte 0 */uint8_t csrcLen:4;uint8_t extension:1;uint8_t padding:1;uint8_t version:2;/* byte 1 */uint8_t payloadType:7;uint8_t marker:1;/* bytes 2,3 */uint16_t seq;/* bytes 4-7 */uint32_t timestamp;/* bytes 8-11 */uint32_t ssrc;
};struct RtpPacket
{struct RtpHeader rtpHeader;uint8_t payload[0];
};void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc);
int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);#endif //_RTP_H_
4、rtp.c
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "rtp.h"void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc)
{rtpPacket->rtpHeader.csrcLen = csrcLen;rtpPacket->rtpHeader.extension = extension;rtpPacket->rtpHeader.padding = padding;rtpPacket->rtpHeader.version = version;rtpPacket->rtpHeader.payloadType = payloadType;rtpPacket->rtpHeader.marker = marker;rtpPacket->rtpHeader.seq = seq;rtpPacket->rtpHeader.timestamp = timestamp;rtpPacket->rtpHeader.ssrc = ssrc;
}int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize)
{struct sockaddr_in addr;int ret;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);ret = sendto(socket, (void*)rtpPacket, dataSize+RTP_HEADER_SIZE, 0,(struct sockaddr*)&addr, sizeof(addr));rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);return ret;
}
5、rtp_aac_main.c
/*** @file rtp_aac_main.c* @author : https://blog.csdn.net/wkd_007* @brief * @version 0.1* @date 2025-06-30* * @copyright Copyright (c) 2025* */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>#include "rtp.h"
#include "aacReader.h"#define AAC_FILE_NAME "test.aac"
#define CLIENT_IP "192.168.2.180" // 運行vlc打開sdp文件的電腦IP
#define CLIENT_PORT 9832static int createUdpSocket()
{int fd = socket(AF_INET, SOCK_DGRAM, 0);if (fd < 0)return -1;int on = 1;setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));return fd;
}static int rtpSendAACFrame(int socket, char *ip, int16_t port,struct RtpPacket *rtpPacket, uint8_t *frame, uint32_t frameSize)
{int ret;rtpPacket->payload[0] = 0x00;rtpPacket->payload[1] = 0x10;rtpPacket->payload[2] = (frameSize & 0x1FE0) >> 5; // 高8位rtpPacket->payload[3] = (frameSize & 0x1F) << 3; // 低5位memcpy(rtpPacket->payload + 4, frame, frameSize);ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize + 4);if (ret < 0){printf("failed to send rtp packet\n");return -1;}rtpPacket->rtpHeader.seq++;return 0;
}int main(int argc, char *argv[])
{int socket = createUdpSocket();if (socket < 0){printf("failed to create socket\n");return -1;}struct RtpPacket *rtpPacket = (struct RtpPacket *)malloc(sizeof(struct RtpPacket) + 1500);rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_AAC, 1, 0, 0, 0x32411);// aacAACReaderInfo_t aacInfo;if (AAC_FileOpen(AAC_FILE_NAME, &aacInfo) < 0){printf("failed to open %s\n", AAC_FILE_NAME);return -1;}while (1){if (!AAC_IsEndOfFile(&aacInfo)){AACFrame_t aacFrame;memset(&aacFrame, 0, sizeof(aacFrame));AAC_GetADTSFrame(&aacFrame, &aacInfo);if (aacFrame.pFrameBuf != NULL){// printf("rtpSendAACFrame\n");rtpSendAACFrame(socket, CLIENT_IP, CLIENT_PORT, rtpPacket,aacFrame.pFrameBuf + ADTS_HEADER_LEN, aacFrame.frame_len - ADTS_HEADER_LEN);free(aacFrame.pFrameBuf);/** 如果采樣頻率是48000* 一般AAC每個1024個采樣為一幀* 所以一秒就有 48000 / 1024 = 47幀* 時間增量就是 48000 / 47 = 1021* 一幀的時間為 1000ms / 47 = 21ms*/rtpPacket->rtpHeader.timestamp += 1021;usleep(21 * 1000);}else{printf("warning SeekFile\n");AAC_SeekFile(&aacInfo);}}}free(rtpPacket);return 0;
}
6、rtp_aac.sdp
m=audio 9832 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/48000/2
a=fmtp:97 SizeLength=13;
c=IN IP4 192.168.2.180
將上面代碼保存在同一個目錄后,并且在同目錄里放一個.aac文件,然后運行 gcc *.c
編譯,再執行./a.out
運行程序,下面是我運行的過程:
🎄四、總結
本文介紹了實現 使用RTP協議封裝并傳輸 AAC 的一些步驟和細節,介紹了RTP封包格式,sdp相關知識等,也提供了實現源碼和運行結果,可以幫助讀者快速了解RTP協議怎么傳輸AAC數據的。
如果文章有幫助的話,點贊👍、收藏?,支持一波,謝謝 😁😁😁
參考:
https://blog.csdn.net/huabiaochen/article/details/104576088