云计算百科
云计算领域专业知识百科平台

【RTSP从零实践】6、实现最简单的同时传输H264、AAC的RTSP服务器

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭 🤣本文内容🤣:🍭介绍实现最简单的传输H264、AAC的RTSP服务器 🍭 😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭 ⏰发布时间⏰: 2025-07-02

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、实现RTSP服务
    • ✨2.1 创建socket套接字
    • ✨2.2 处理RTSP命令
  • 🎄三、读取H264封装成RTP包并发送
    • ✨3.1 实现H.264文件读取器
    • ✨3.2 H264的RTP头(RTP Header)
    • ✨3.3 H264 的 RTP负载(RTP Payload)
  • 🎄四、读取AAC封装成RTP包并发送
    • ✨4.1 实现AAC文件读取器
    • ✨4.2 AAC的RTP头(RTP Header)
    • ✨4.3 AAC 的 RTP负载(RTP Payload)
  • 🎄五、同时发送H264、AAC的RTSP服务器实现源码
  • 🎄六、总结

在这里插入图片描述

前面系列文章回顾: 【音视频 | RTSP】RTSP协议详解 及 抓包例子解析(详细而不赘述) 【音视频 | RTSP】SDP(会话描述协议)详解 及 抓包例子分析 【音视频 | RTP】RTP协议详解(H.264的RTP封包格式、AAC的RTP封包格式) 【RTSP从零实践】1、根据RTSP协议实现一个RTSP服务 【RTSP从零实践】2、使用RTP协议封装并传输H264 【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器 【RTSP从零实践】4、使用RTP协议封装并传输AAC 【RTSP从零实践】5、实现最简单的传输AAC的RTSP服务器

在这里插入图片描述

🎄一、概述

这篇文章介绍如何实现最简单一个最简单的RTSP服务器,可以同时传输H264数据、AAC数据。

如果学习过前面系列文章,你应该也能够自己实现一个同时传输音视频的RTSP服务器了。其实就是将前面的所有知识点融合到一起,你可以尝试自己先实现一下,主要有下面这些知识点:

  • 1、一个RTSP服务相关知识,包括了 创建TCP套接字,了解SDP协议,处理RTSP客户端的命令 等;
  • 2、读取H264封装成RTP包并发送的相关知识,包括 了解H264、了解RTP协议怎么封装H264 等;
  • 3、读取AAC封装成RTP包并发送的相关知识,包括 了解AAC、了解RTP协议怎么封装AAC 等;

这些知识都可以从上面系列文章学习到,下面开始介绍一些必要的实现步骤。


在这里插入图片描述

🎄二、实现RTSP服务

RTSP服务是使用TCP作为传输层协议的,而本文的RTP包是使用UDP,所以实现这个RTSP服务需要创建3个套接字,然后是处理客户端的RTSP命令。

✨2.1 创建socket套接字

程序开始创建一个TCP套接字用来作为RTSP服务端,使用的是8554端口;然后创建2个UDP套接字分别用来发送H264的RTP包 和 AAC的RTP包。

// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
return 1;
}

// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
{
perror("setsockopt");
return 1;
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(RTSP_PORT);

// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("bind failed");
return 1;
}

// 开始监听
if (listen(server_fd, MAX_CLIENTS) < 0)
{
perror("listen");
return 1;
}

// 用于发送 h264 rtp 包的udp套接字
int rtp_h264_fd = createUdpSocket();
if (rtp_h264_fd < 0)
{
printf("failed to create socket\\n");
return 1;
}
address.sin_port = htons(RTP_PORT);
if (bind(rtp_h264_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("rtp_h264_fd bind failed");
return 1;
}

// 用于发送 aac rtp 包的udp套接字
int rtp_aac_fd = createUdpSocket();
if (rtp_aac_fd < 0)
{
printf("failed to create socket\\n");
return 1;
}
address.sin_port = htons(RTP_PORT + 2);
if (bind(rtp_aac_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("rtp_aac_fd bind failed");
return 1;
}


✨2.2 处理RTSP命令

处理RTSP命令时,首先需要注意的是处理 DESCRIBE 命令时,返回的sdp信息需要包含2个媒体信息,如下图:

在这里插入图片描述

其次,是处理 SETUP 命令时,需要保存客户端的2个RTP数据包接收端口,具体如下:

char response[1024] = {0}; // 构造响应
if (strcmp(method, "OPTIONS") == 0)
{
rtsp_handle_OPTION(response, cseq);
}
else if (strcmp(method, "DESCRIBE") == 0)
{
rtsp_handle_DESCRIBE(response, cseq);
}
else if (strcmp(method, "SETUP") == 0)
{
// printf("url:[%s]\\n", url);
int streamid = 0;
if (NULL != strstr(url, "streamid=1"))
{
streamid = 1;
rtpAACPort = rtpPort;
}
else
{
rtpH264Port = rtpPort;
}
rtsp_handle_SETUP(response, cseq, rtpPort, streamid);
}
else if (strcmp(method, "PLAY") == 0)
{
rtsp_handle_PLAY(response, cseq);
bSendFlag = RTP_PLAY;
}
else if (strcmp(method, "TEARDOWN") == 0)
{
rtsp_handle_TEARDOWN(response, cseq);
bSendFlag = RTP_STOP;
}
else
{
snprintf(response, sizeof(response),
"RTSP/1.0 501 Not Implemented\\r\\nCSeq: %d\\r\\n\\r\\n", cseq);
}


在这里插入图片描述

🎄三、读取H264封装成RTP包并发送

RTSP服务创建后,就可以开始发流了。这里先介绍H264的发流过程。

首先,要读取H264文件;然后,封装RTP包头;最后封装RTP负载。

✨3.1 实现H.264文件读取器

在这里插入图片描述

H.264文件保存了h264编码的视频帧,每个视频帧之间以开始码00 00 01或00 00 00 01分隔开。我们可以用下面代码判断是否为开始码。 在这里插入图片描述

在两个开始码之间的就是视频帧数据。h264视频帧数据的第一个字节是一个NAL头,内容如下图: 在这里插入图片描述 可以用下面代码读取NAL头: 在这里插入图片描述


✨3.2 H264的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头这里涉及到一个 时间戳怎么计算 的问题,需要注意的是,这个时间戳是一个 时钟频率 为单位的,而不是具体的时间(秒、毫秒等)。

一般情况下,H264的时钟频率为90000Hz,假设帧率为25,那么每一帧的 时间间隔 就是1/25秒,每一帧的 时钟增量 就是(90000/25=3600)。那时间戳怎么算呢?举个例子,如果帧率为25的H264视频,第一帧的RTP时间戳为0的话,那么第二帧的RTP时间戳就是 3600,第三帧的RTP时间戳就是 7200,依次类推,后一帧的RTP时间戳在前一帧的RTP时间戳的值加上一个时钟增量。


✨3.3 H264 的 RTP负载(RTP Payload)

H264 的 RTP负载需要介绍两种方式,第一种是 单个NAL单元封包(Single NAL Unit Packet);第二种是 分片单元(Fragmentation Unit) 。如果H264的视频帧NALU(NAL Unit)总字节数小于 MTU(网络最大传输单元1500字节),就可以使用第一种方式,因为有一些TCP/UDP头数据,所以一般判断小于1400字节,就采用 单个NAL单元封包(Single NAL Unit Packet),否则使用分片单元(Fragmentation Unit)的方式封装RTP包。

单个NAL单元封包 的RTP负载结构如下图,相当于直接将整个NAL Unit 填入RTP负载即可: 在这里插入图片描述

分片单元的RTP负载方式也有两种,本文介绍的是FU-A的方式,RTP负载最开始由三部分组成:第一个字节是FU indicator,第二个字节是FU header,第三个字节开始就是NAL单元去掉NAL头之后的数据: 在这里插入图片描述

  • FU indicator:FU indicator的大小是一个字节,格式如下,跟NAL头的格式一样,但作为 分片RTP封包 ,并不能直接将H264的NAL头直接填上去。 F:一般为0。为0表示此NAL单元不应包含bit错误或语法违规;为1表示此NAL单元可能包含bit错误或语法违规; NRI:直接将H264NAL头的NRI值填入即可; Type:FU-A格式的封包填28,FU-B格式的封包填29。

    +—————+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |F|NRI| Type |
    +—————+

  • FU header :FU header的大小也是一个字节,格式如下: S:start,NALU拆分多个分包后,第一个发送的分包,此bit位置1,其他分包为0; E:end,NALU拆分多个分包后,最后一个发送的分包,此bit位置1,其他分包为0; R:保留位,必须等于0; Type:将H264的NAL头的负载类型Type直接填入。

    +—————+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |S|E|R| Type |
    +—————+


在这里插入图片描述

🎄四、读取AAC封装成RTP包并发送

✨4.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文件结构不断读取音频帧数据了。


✨4.2 AAC的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的时间戳计算很重要,我一开始没懂时间戳的概念,导致播放的声音断断续续的。


✨4.3 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头之后的数据 了。


在这里插入图片描述

🎄五、同时发送H264、AAC的RTSP服务器实现源码

代码: 1、H264Reader.h

/**
* @file H264Reader.h
* @author https://blog.csdn.net/wkd_007
* @brief
* @version 0.1
* @date 2025-06-24
*
* @copyright Copyright (c) 2025
*
*/

#ifndef __H264_READER_H__
#define __H264_READER_H__

#include <stdio.h>

#define MAX_STARTCODE_LEN (4)

typedef enum
{
FALSE,
TRUE,
} BOOL;

typedef enum
{
H264_NALU_TYPE_SLICE = 1,
H264_NALU_TYPE_DPA = 2,
H264_NALU_TYPE_DPB = 3,
H264_NALU_TYPE_DPC = 4,
H264_NALU_TYPE_IDR = 5,
H264_NALU_TYPE_SEI = 6,
H264_NALU_TYPE_SPS = 7,
H264_NALU_TYPE_PPS = 8,
H264_NALU_TYPE_AUD = 9,
H264_NALU_TYPE_EOSEQ = 10,
H264_NALU_TYPE_EOSTREAM = 11,
H264_NALU_TYPE_FILL = 12,
} H264NaluType;

typedef enum
{
H264_NALU_PRIORITY_DISPOSABLE = 0,
H264_NALU_PRIRITY_LOW = 1,
H264_NALU_PRIORITY_HIGH = 2,
H264_NALU_PRIORITY_HIGHEST = 3
} H264NaluPriority;

typedef struct
{
int startcode_len; //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
int forbidden_bit; //! should be always FALSE
int nal_reference_idc; //! H264_NALU_PRIORITY_xxxx
int nal_unit_type; //! H264_NALU_TYPE_xxxx
BOOL isLastFrame; //!
int frame_len; //!
unsigned char *pFrameBuf; //!
} H264Frame_t;

typedef struct H264ReaderInfo_s
{
FILE *pFileFd;
int frameNum;
} H264ReaderInfo_t;

int H264_FileOpen(char *fileName, H264ReaderInfo_t *pH264Info);
int H264_FileClose(H264ReaderInfo_t *pH264Info);
int H264_GetFrame(H264Frame_t *pH264Frame, H264ReaderInfo_t *pH264Info);
BOOL H264_IsEndOfFile(const H264ReaderInfo_t *pH264Info);
void H264_SeekFile(H264ReaderInfo_t *pH264Info);

#endif // __H264_READER_H__

2、H264Reader.c

/**
* @file H264Reader.c
* @author https://blog.csdn.net/wkd_007
* @brief
* @version 0.1
* @date 2025-06-30
*
* @copyright Copyright (c) 2025
*
*/

#include "H264Reader.h"
#include <stdlib.h>

#define MAX_FRAME_LEN (1920 * 1080 * 1.5) // 一帧数据最大字节数

static BOOL findStartCode_001(unsigned char *Buf)
{
// printf("[%d %d %d]\\n", Buf[0], Buf[1], Buf[2]);
return (Buf[0] == 0 && Buf[1] == 0 && Buf[2] == 1); // 0x000001 ?
}

static BOOL findStartCode_0001(unsigned char *Buf)
{
// printf("[%d %d %d %d]\\n", Buf[0], Buf[1], Buf[2], Buf[3]);
return (Buf[0] == 0 && Buf[1] == 0 && Buf[2] == 0 && Buf[3] == 1); // 0x00000001 ?
}

int H264_FileOpen(char *fileName, H264ReaderInfo_t *pH264Info)
{
pH264Info->pFileFd = fopen(fileName, "rb+");
if (pH264Info->pFileFd == NULL)
{
printf("[%s %d]Open file error\\n", __FILE__, __LINE__);
return 1;
}
pH264Info->frameNum = 0;
return 0;
}

int H264_FileClose(H264ReaderInfo_t *pH264Info)
{
if (pH264Info->pFileFd != NULL)
{
fclose(pH264Info->pFileFd);
pH264Info->pFileFd = NULL;
}
return 0;
}

BOOL H264_IsEndOfFile(const H264ReaderInfo_t *pH264Info)
{
return feof(pH264Info->pFileFd);
}

void H264_SeekFile(H264ReaderInfo_t *pH264Info)
{
fseek(pH264Info->pFileFd, 0, SEEK_SET);
pH264Info->frameNum = 0;
}

/**
* @brief 获取一阵h264视频帧
*
* @param pH264Frame :输出参数,使用后 pH264Frame->pFrameBuf 需要free
* @param pH264Info :输入参数
* @return int
*/

int H264_GetFrame(H264Frame_t *pH264Frame, H264ReaderInfo_t *pH264Info)
{
int rewind = 0;
if (pH264Info->pFileFd == NULL)
{
printf("[%s %d]pFileFd error\\n", __FILE__, __LINE__);
return 1;
}

// 1.读取帧数据
// unsigned char *pFrame = (unsigned char *)malloc(MAX_FRAME_LEN);
unsigned char *pFrame = pH264Frame->pFrameBuf;
int readLen = fread(pFrame, 1, MAX_FRAME_LEN, pH264Info->pFileFd);
if (readLen <= 0)
{
printf("[%s %d]fread error\\n", __FILE__, __LINE__);
// free(pFrame);
return 1;
}

// 2.查找当前帧开始码
int i = 0;
for (; i < readLen MAX_STARTCODE_LEN; i++)
{
if (!findStartCode_0001(&pFrame[i]))
{
if (!findStartCode_001(&pFrame[i]))
{
continue;
}
else
{
pH264Frame->startcode_len = 3;
break;
}
}
else
{
pH264Frame->startcode_len = 4;
break;
}
}
if (i != 0) // 不是帧开头,偏移到帧开头重新读
{
printf("[%s %d]startcode error, i=%d\\n", __FILE__, __LINE__, i);
// free(pFrame);
rewind = ((readLen i));
fseek(pH264Info->pFileFd, rewind, SEEK_CUR);
return 1;
}

// 3.查找下一帧开始码
i += MAX_STARTCODE_LEN;
for (; i < readLen MAX_STARTCODE_LEN; i++)
{
if (!findStartCode_0001(&pFrame[i]))
{
if (!findStartCode_001(&pFrame[i]))
{
continue;
}
else
{
break;
}
}
else
{
break;
}
}
if (i == (readLen MAX_STARTCODE_LEN))
{
if (!feof(pH264Info->pFileFd))
{
printf("[%s %d]MAX_FRAME_LEN too small\\n", __FILE__, __LINE__);
// free(pFrame);
return 1;
}
else
{
pH264Frame->isLastFrame = TRUE;
}
}

// 4.填数据
pH264Frame->forbidden_bit = pFrame[pH264Frame->startcode_len] & 0x80; // 1 bit
pH264Frame->nal_reference_idc = pFrame[pH264Frame->startcode_len] & 0x60; // 2 bit
pH264Frame->nal_unit_type = pFrame[pH264Frame->startcode_len] & 0x1f; // 5 bit, naluType 是开始码后一个字节的最后 5 位
// pH264Frame->pFrameBuf = pFrame;
pH264Frame->frame_len = i;

// 5.文件读取指针偏移到下一帧位置
rewind = ((readLen i));
fseek(pH264Info->pFileFd, rewind, SEEK_CUR);

pH264Info->frameNum++;

return pH264Frame->frame_len;
}

3、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__

4、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:06

static 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 3bit
return 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<readLenMAX_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 = ((readLeni));
fseek (pAACInfo->pFileFd, rewind, SEEK_CUR);
return 1;
}

// 3.读取ADTS帧数据 2025-05-22 21:44:39
readLen = fread(pFrame+ADTS_HEADER_LEN, 1, sizeADTS_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;
}

5、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_

6、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;
}


在这里插入图片描述

🎄六、总结

文章介绍如何实现最简单一个最简单的RTSP服务器,可以同时传输H264数据、AAC数据。 在这里插入图片描述 如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【RTSP从零实践】6、实现最简单的同时传输H264、AAC的RTSP服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!