将H264码流打包成RTP包

简介: 将H264码流打包成RTP包

H.264相关知识

  • H.264由一个一个的NALU(Network Abstraction Layer Units)组成,每个NALU之间使用00 00 00 01或00 00 01分隔开
  • 每个NALU的第一次字节都有特殊的含义,其内容如下
描述
bit[7] 必须为0
bit[5-6] 标记NALU的重要性
bit[0-4] NALU单元的类型
  • 对于H.264格式,暂时了解这一点就可以了,如果想深入了解请自行搜索查找相关知识。

H.264的RTP打包方式

单NALU打包

  • 一个RTP包包含一个完整的NALU,这种方式最为简单,没啥好说的

分片打包

  • 每个RTP包都有大小限制的,因为RTP一般都是使用UDP发送,UDP没有流量控制,所以要限制每一次发送的大小,所以如果一个NALU的太大,就需要分成多个RTP包发送。
  • 我们知道,RTP包的格式是RTP头部+RTP载荷
  • RTP头部是固定的,那么只能在RTP载荷中去添加额外信息来说明这个RTP包是表示同一个NALU
  • 如果是分片打包的话,那么在RTP载荷开始有两个字节的信息,然后再是NALU的内容
  • 第一个字节位FU Indicator,其格式如下

  • 高三位:与NALU第一个字节的高三位相同
  • Type:28,表示该RTP包一个分片,为什么是28?因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲
  • 第二个字节位FU Header,其格式如下

  • S:标记该分片打包的第一个RTP包
  • E:比较该分片打包的最后一个RTP包
  • Type:NALU的Type

聚合打包[本文暂不介绍]

  • 对于较小的NALU,一个RTP包可包含多个完整的NALU

H.264 RTP包的时间戳计算

  • RTP包的时间戳起始值是随机的
  • 时间戳单位:时间戳计算的单位不是秒之类的单位,而是由采样频率所代替的单位,这样做的目的就是 为了是时间戳单位更为精准。
  • 一般视频采样频率是90K,帧率为25,频率为90K表示一秒90000点来表示,帧率为25,那么一帧就是1/25秒,所以一帧有90000*(1/25)=3600个点来表示,因此每一帧数据的时间增量为3600

源码

  • "test/002/test_rtp.c"
  • "test/002/test_rtp.h"
  • "test/002/test_rtp_h264.c.h"

额外所需文件

  • config.sdp
  • test.h264

测试

  • 编译:gcc test_rtp.c test_rtp_h264.c
  • 运行:./a.out test.h264
  • 打开“VLC”播放器,选择“媒体”->“打开文件”,选择“config.sdp”,开始播放画面

源码讲解

// 申请一块内存用于存储rtp包数据,初始化rtp包头
struct RTP_PACKET_S * rtpPacket = (RTP_PACKET_S *)malloc(500000); 
rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VERSION, RTP_PAYLOAD_TYPE_H264, 0, 0, 0, 0x88923423);
// 创建套接字
rtpServerSockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 打开h.264文件
int fd = open(argv[1], O_RDONLY);
// 申请内存用于读取h.264文件内容
uint8_t * frame = (uint8_t*)malloc(500000);
while(1)
{
  // 读取h.264文件
  readSize = read(fd, frame, 500000);
  // 找到第一个NALU数据,不包含00 00 01/00 00 00 01分隔
  getFirstNaluFrame(frame, 500000, &offset, &naluLen);
  // 处理并发送NALU数据
  rtpSendH264Frame(rtpServerSockfd, CLIENT_IP, CLIENT_PORT, rtpPacket, frame + offset, naluLen);
  // 移动文件读写位置到下一个NALU起始处
  lseek(fd, fd_Lseek, SEEK_SET );
  // 发送完一包NALU后,根据帧率计算延时
  usleep(1000*1000/FPS);
}
// rtpSendH264Frame函数分析
static int rtpSendH264Frame(int rtpSockfd, char * ip, int16_t port, struct RTP_PACKET_S * rtpPacket, uint8_t * naluFrame, int naluLen)
{
  // 1.单NALU打包
    // 填充payload
    memcpy(rtpPacket->rtpPayload, naluFrame, naluLen);
    // udp发送
    sendto(rtpSockfd, (void*)rtpPacket, naluLen + RTP_HEADER_SIZE, 0, (struct sockaddr *)&addr, sizeof(addr));
  // 2.分片打包
    // 计算完整包个数和最后一包剩余字节数
    int packetNum = naluLen / RTP_MAX_PKT_SIZE; // 有几个完整的包
    int lastRemainSize = naluLen % RTP_MAX_PKT_SIZE; // 最后一包剩余的字节数
    for(int i = 0; i < packetNum; i++) // 发送所有整包
    {
      // 分片发送是payoad前需加2字节的数据,具体见"4.将H264码流打包成RTP包.md"
      rtpPacket->rtpPayload[0]
      rtpPacket->rtpPayload[1]
      // 填充payload
      memcpy(rtpPacket->rtpPayload+2, naluFrame+pos, RTP_MAX_PKT_SIZE);
      // udp发送
      sendto(rtpSockfd, rtpPacket, RTP_HEADER_SIZE+RTP_MAX_PKT_SIZE+2, 0, (struct sockaddr *)&addr, sizeof(addr));
    }
    if(lastRemainSize > 0) // 发送剩余的数据
    {
      // 分片发送是payoad前需加2字节的数据,具体见"4.将H264码流打包成RTP包.md"
      rtpPacket->rtpPayload[0]
      rtpPacket->rtpPayload[1]
      // 填充payload
      memcpy(rtpPacket->rtpPayload+2, naluFrame+pos, lastRemainSize+2);
      // udp发送
      sendto(rtpSockfd, (void*)rtpPacket, RTP_HEADER_SIZE+lastRemainSize+2, 0, (struct sockaddr *)&addr, sizeof(addr));
    }
}
目录
相关文章
|
Web App开发 编解码 安全
【WebRTC 入门教程】全面解析WebRTC:从底层原理到Qt和FFmpeg的集成应用
【WebRTC 入门教程】全面解析WebRTC:从底层原理到Qt和FFmpeg的集成应用
5418 2
|
Web App开发 应用服务中间件 Go
尝鲜:如何搭建一个简单的webrtc服务器
前几天我一朋友问我有关webrtc的事,简单了解了下相关知识,搭建了一个webrtc的服务,以及经历的各种踩坑事件,感觉踩坑主要是Python、Node、OpenSSL等版本问题和证书问题导致。本来以为很简单的搭建,但在搭建的过程中遇到各种阻碍,写一篇文章梳理一下。
12290 0
|
10月前
|
编解码 监控 网络协议
如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频
本文详细介绍了如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频。内容涵盖环境搭建、编码配置、服务器端与客户端实现等方面,适合视频监控系统和直播平台等应用场景。通过具体命令和示例代码,帮助读者快速上手并实现目标。
2332 6
|
Web App开发 编解码 网络协议
WebRTC SDP 详解和剖析
WebRTC 技术体系中,SDP 是看起来简单却坑非常多的点,就像直播中的时间戳几乎占据了 80% 的问题,SDP 也是问题频发的点。这篇文章详细分享了 SDP 的关键点,容易出问题的点,是非常实用的满满的干货。
WebRTC SDP 详解和剖析
|
10月前
|
SQL 算法 Java
快出数量级的性能是怎样炼成的
我们通过使用开源 SPL 重写了多个金融行业的 SQL 任务,实现了显著的性能提升,如保险公司团保明细单查询提速 2000+ 倍、银行 POS 机交易报表提速 30+ 倍等。这些优化的核心在于使用了更低复杂度的算法,而非依赖硬件加速。SPL 基于离散数据集理论,提供了丰富的高性能算法,使得复杂任务的优化成为可能。更多案例和详细技术解析可参见乾学院的相关课程和图书。
|
前端开发 数据可视化 JavaScript
探索前端可视化开发:低代码平台原理与实践
【4月更文挑战第6天】本文探讨了低代码平台在前端开发中的应用,包括模型驱动和组件化开发原理,以及自动化代码生成和部署的优势。低代码平台能提高开发效率,降低技术门槛,并灵活适应变更,同时保证应用的一致性。实践中,需明确适用场景,选择合适平台,并培养团队低代码技能。通过与现有技术栈融合及持续优化,低代码平台能推动业务创新和数字化转型,开发者应积极探索其在实际项目中的应用。
349 0
|
12月前
|
安全 Linux
Linux中ldd命令的依赖复制技巧
`ldd`命令的依赖复制技巧在特定的场合下非常有用,但它也需要细心的处理和充分的测试,以确保在新环境中的稳定运行。此外,这种做法虽然方便,但在长期维护和安全更新方面可能会带来额外的负担。
285 0
在Linux中,如何杀死一个进程?如果无法正常终止应如何操作?
在Linux中,如何杀死一个进程?如果无法正常终止应如何操作?
|
消息中间件 设计模式 Java
聊聊 Kafka: Consumer 源码解析之 Rebalance 机制
聊聊 Kafka: Consumer 源码解析之 Rebalance 机制
766 0
|
机器学习/深度学习 存储 自然语言处理
FunASR
【6月更文挑战第14天】
1710 4