😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍多播传输H264格式的RTP包 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰: 2025-07-08
本文未经允许,不得转发!!!
前面系列文章回顾:
【音视频 | 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从零实践】6、实现最简单的同时传输H264、AAC的RTSP服务器
🎄一、概述
前面的系列文章学完之后,应该对 RTSP 的基础概念都了解到位了,怎么写一个服务器也清楚了,但是有时我们需要让我们的服务器支持多播。如果了解了 多播 的相关概念后,只需要把前面的几个例子稍加修改就可以改成支持多播的服务器了,下面看看怎么写一个多播传输H264RTP包的程序吧。
🎄二、多播的概念
关于多播的概念可以参考这篇文章:多播的概念、多播地址、UDP实现多播的C语言例子。下面只简单介绍一下多播。
IP 多播(也称多址广播或组播)技术,是允许一台主机 向 多台主机 发送消息的一种通信方式。单播只向单个IP接口发送数据,广播是向子网内所有IP接口发送数据,多播则介于两者之间,向一组IP接口发送数据。
多播地址:用来标识多播组,IPv4使用D类地址的某一个来表示一个多播组地址。IPv4的D类地址(从224.0.0.0
到239.255.255.255
)是IPv4多播地址,见下图:
多播发送端:用于发送多播数据报的程序,下面是一个简单的多播发送端例子的代码。基本上就是在发送数据包时,将目的地址设置为 多播地址。
// multicastCli.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main()
{
// 1、创建UDP套接字socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd<0)
perror("socket error" );
// 2、准备多播组地址和端口
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons (10086);
if (inet_pton(AF_INET, "239.0.1.1", &servaddr.sin_addr) <= 0)
perror("inet_pton error");
// 4、使用 sendto 发送多播组数据报
if(sendto(sockfd, "Hello,I am udp client", strlen("Hello,I am udp client"), 0, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
perror("sendto error" );
// 5、处理应答
char recvline[256];
int n = 0;
struct sockaddr_in tmpAddr;
bzero(&tmpAddr, sizeof(tmpAddr));
socklen_t addrLen=sizeof(tmpAddr);
while ( (n = recvfrom (sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&tmpAddr, &addrLen)) > 0)
{
recvline[n] = 0 ;/*null terminate */
printf("recvfrom ip=[%s], [%s]\n",inet_ntoa(tmpAddr.sin_addr), recvline);
bzero(&tmpAddr, sizeof(tmpAddr));
}
if (n < 0)
perror("read error" );
// 6、关闭
close(sockfd);
return 0;
}
多播接收端:接收端是使用UDP服务端代码修改,需要在交互数据之前,将套接字加入多播组239.0.1.1,让链路层接口接收该多播组的数据报,使用完需要离开多播组。下面是一个多播接收端代码:
// multicastSer.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main()
{
// 1、创建UDP套接字socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd<0)
perror("socket error" );
// 2、准备本地ip接口和多播组端口
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons (10086);
servaddr.sin_addr.s_addr = INADDR_ANY; // 指定ip地址为 INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任一网络接口上接受客户端的连接
// 3、绑定多播组端口 bind
if (bind(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
perror("bind error" );
// 4、加入多播组 239.0.1.1
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1"); // 多播组的IP地址
mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 加入的客服端主机IP地址
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
perror("setsockopt");
return -1;
}
// 5、使用 sendto、recvfrom 交互数据
printf("UdpSer sockfd=%d, start \n",sockfd);
char recvline[256];
while(1)
{
struct sockaddr_in cliaddr;
bzero(&cliaddr, sizeof(cliaddr));
socklen_t addrLen=sizeof(cliaddr);
int n = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&cliaddr, &addrLen);
if(n>0)
{
recvline[n] = 0 ;/*null terminate */
printf("recv sockfd=%d %d byte, [%s] addrLen=%d, cliIp=%s, cliPort=%d\n",
sockfd, n, recvline, addrLen, inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
sendto(sockfd, "Hello,I am udp server", strlen("Hello,I am udp server"), 0, (struct sockaddr*)&cliaddr, addrLen);
}
}
// 6、离开多播组
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq));
// 7、关闭
close(sockfd);
return 0;
}
🎄三、实现步骤、实现细节
多播传输H264格式的RTP包的步骤如下:
1、实现 H264文件读取器。
2、实现 H264 的 RTP 数据包封装。
3、实现多播发送RTP包。
4、写一个支持多播 sdp 文件。
前两点在文章 使用RTP协议封装并传输H264 介绍得很详细,这里不再赘述。
实现多播发送RTP包,我们只需要在 使用RTP协议封装并传输H264 的代码里将UDP的目的地址改为我们要使用的多播组地址即可,我们这里要使用的是"239.0.0.1"
。
多播 sdp 文件也有点区别,具体如下:
a=type:broadcast
m=video 9832 RTP/AVP 96
a=rtpmap:96 H264/90000
a=framerate:25
c=IN IP4 239.0.0.1/255
a=type:broadcast
:表示使用广播或多播类型;
m=video 9832 RTP/AVP 96
:表示这是一个媒体描述,媒体类型是video;接收媒体流的端口号是9832;传输协议是RTP/AVP(通过UDP传输);RTP负载类型为96。
a=rtpmap:96 H264/90000
:RTP负载类型的具体编码是 H264,时钟频率为 90000Hz。
a=framerate:25
:帧率为25。
c=IN IP4 239.0.0.1/255
:表示连接信息,网络类型为IN,表示Internet。地址类型为IP4,表示IPv4地址。接收端地址为239.0.0.1/255。
🎄四、多播传输H264格式的RTP包的实现源码
1、H264Reader.h
#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
#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);
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、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、multicast_rtp_h264_main
/**
* @file multicast_rtp_h264_main.c
* @author : https://blog.csdn.net/wkd_007
* @brief
* @version 0.1
* @date 2025-07-03
*
* @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 "H264Reader.h"
#define H264_FILE_NAME "test.h264"
#define MULTICAST_IP "239.0.0.1"
#define MULTICAST_PORT 9832
#define FPS 25
static 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 rtpSendH264Frame(int socket, char *ip, int16_t port,
struct RtpPacket *rtpPacket, uint8_t *frame, uint32_t frameSize)
{
uint8_t naluType; // nalu第一个字节
int sendBytes = 0;
int ret;
naluType = frame[0];
if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包场:单一NALU单元模式
{
/*
* 0 1 2 3 4 5 6 7 8 9
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |F|NRI| Type | a single NAL unit ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
memcpy(rtpPacket->payload, frame, frameSize);
ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize);
if (ret < 0)
return -1;
rtpPacket->rtpHeader.seq++;
sendBytes += ret;
if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳
goto out;
}
else // nalu长度大于最大包场:分片模式
{
/*
* 0 1 2
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | FU indicator | FU header | FU payload ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/*
* FU Indicator
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |F|NRI| Type |
* +---------------+
*/
/*
* FU Header
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |S|E|R| Type |
* +---------------+
*/
int pktNum = frameSize / RTP_MAX_PKT_SIZE; // 有几个完整的包
int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小
int i, pos = 1;
/* 发送完整的包 */
for (i = 0; i < pktNum; i++)
{
rtpPacket->payload[0] = (naluType & 0x60) | 28;
rtpPacket->payload[1] = naluType & 0x1F;
if (i == 0) // 第一包数据
rtpPacket->payload[1] |= 0x80; // start
else if (remainPktSize == 0 && i == pktNum - 1) // 最后一包数据
rtpPacket->payload[1] |= 0x40; // end
memcpy(rtpPacket->payload + 2, frame + pos, RTP_MAX_PKT_SIZE);
ret = rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE + 2);
if (ret < 0)
return -1;
rtpPacket->rtpHeader.seq++;
sendBytes += ret;
pos += RTP_MAX_PKT_SIZE;
}
/* 发送剩余的数据 */
if (remainPktSize > 0)
{
rtpPacket->payload[0] = (naluType & 0x60) | 28;
rtpPacket->payload[1] = naluType & 0x1F;
rtpPacket->payload[1] |= 0x40; // end
memcpy(rtpPacket->payload + 2, frame + pos, remainPktSize + 2);
ret = rtpSendPacket(socket, ip, port, rtpPacket, remainPktSize + 2);
if (ret < 0)
return -1;
rtpPacket->rtpHeader.seq++;
sendBytes += ret;
}
}
out:
return sendBytes;
}
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) + (1920 * 1080 * 4));
rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,
0, 0, 0x88923423);
// h264
H264ReaderInfo_t h264Info;
if (H264_FileOpen(H264_FILE_NAME, &h264Info) < 0)
{
printf("failed to open %s\n", H264_FILE_NAME);
return -1;
}
while (1)
{
if (!H264_IsEndOfFile(&h264Info))
{
H264Frame_t h264Frame;
memset(&h264Frame, 0, sizeof(h264Frame));
H264_GetFrame(&h264Frame, &h264Info);
if (h264Frame.pFrameBuf != NULL)
{
if (h264Frame.isLastFrame) // 最后一帧,移到开头重新读
{
printf("warning SeekFile 1\n");
H264_SeekFile(&h264Info);
}
// printf("rtpSendH264Frame\n");
rtpSendH264Frame(socket, MULTICAST_IP, MULTICAST_PORT, rtpPacket,
h264Frame.pFrameBuf + h264Frame.startcode_len,
h264Frame.frame_len - h264Frame.startcode_len);
free(h264Frame.pFrameBuf);
rtpPacket->rtpHeader.timestamp += 90000 / FPS; // RTP 传输视频每秒 90k HZ
usleep(1000 * 1000 / FPS);
}
}
}
free(rtpPacket);
return 0;
}
将上面代码保存在同一个目录后,并且在同目录里放一个.h264文件,然后运行 gcc *.c 编译,再执行./a.out运行程序,下面是我运行的过程:
🎄五、总结
本文介绍了多播的一些概念,以及多播传输H264格式的RTP包的步骤和细节,最后提供了实现的源代码,帮助读者学习理解。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁