将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));
    }
}
目录
相关文章
wireshark解析rtp协议,流媒体中的AMR/H263/H264包的方法
抓到完整的流媒体包之后,用wireshark打开,其中的包可能不会自动映射成RTP+AMR/H263/H264的包,做如下修改操作即可:1.  把UDP 包解析成RTP/RTCP包。选中UDP包,右键,选择Decode As,选RTP2.  把RTP Payload映射成实际的媒体格式。
2899 0
|
3月前
|
网络协议 API 网络安全
探讨TCP传输视频流并利用FFmpeg进行播放的过程
探讨TCP传输视频流并利用FFmpeg进行播放的过程
320 0
|
8月前
|
Web App开发
ZLMediaKit webrtc RTP包接受与重传
ZLMediaKit webrtc RTP包接受与重传
|
编解码
RTP 包格式 详细解析
H.264 视频 RTP 负载格式 1. 网络抽象层单元类型 (NALU) NALU 头由一个字节组成, 它的语法如下:       +---------------+      |0|1|2|3|4|5|6|7|      +-+-+-+-+-+-+-+-+      |F|NRI|  Type   |      +---------------+ F: 1 个比特.
1246 0
|
编解码 内存技术 容器
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
243 0
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
259 0
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
|
数据采集 传感器 编解码
【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频解码信息 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )
【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频解码信息 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )
235 0
|
数据采集 传感器 编解码
【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频采样数据 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )
【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频采样数据 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )
266 0
|
Web App开发 存储 缓存