RTP H264随笔

简单分析H264 RTP payload

 

SDP 参数 
下面描述了如何在 SDP 中表示一个 H.264 流:
. "m=" 行中的媒体名必须是 "video"
. "a=rtpmap" 行中的编码名称必须是 "H264".
. "a=rtpmap" 行中的时钟频率必须是 90000.
. 其他参数都包括在 "a=fmtp" 行中.如:
m=video 49170 RTP/AVP 98
a=rtpmap:98 H264/90000
a=fmtp:98 profile-level-id=42A01E; packetization-mode=1; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==

下面介绍一些常用的参数.
packetization-mode: 表示支持的封包模式. 
当 packetization-mode 的值为 0 时或不存在时, 必须使用单一 NALU 单元模式.
当 packetization-mode 的值为 1 时必须使用非交错(non-interleaved)封包模式.

当 packetization-mode 的值为 2 时必须使用交错(interleaved)封包模式.

sprop-parameter-sets: SPS,PPS
这个参数可以用于传输 H.264 的序列参数集和图像参数 NAL 单元. 这个参数的值采用 Base64 进行编码. 不同的参数集间用","号隔开.


profile-level-id:
这个参数用于指示 H.264 流的 profile 类型和级别. 由 Base16(十六进制) 表示的 3 个字节. 第一个字节表示 H.264 的 Profile 类型, 第三个字节表示 H.264 的 Profile 级别:

max-mbps: 
这个参数的值是一个整型, 指出了每一秒最大的宏块处理速度.

 

 

RTP协议格式
        RTP报文由报文头和报文体组成,报文头格式如下图所示。

V:RTP协议的版本号,占2位,当前协议版本号为2。
P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头。
CC:CSRC计数器,占4位,指示CSRC 标识符的个数。
M:标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
PT:有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是           用来区分音频流和视频流的,这样便于客户端进行解析。
SN:序列号,占16位,发送方在每发送完一个RTP包后就将该域的值增加1,接收方可以由该域检测包的丢失及恢             复包序列。序列号的初始值是随机的。
timestamp:时间戳,占32位,记录了该包中数据的第一个字节的采样时刻。它是去除抖动和实现同步不可缺少的。
SSRC:同步源标识符,占32位,同步源就是指RTP包流的来源。在同一个RTP会话中不能有两个相同的SSRC值。                    该标识符是随机选取的 RFC1889推荐了MD5随机算法。
CSRC:特约信源标识符,每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中               的所有特约信源。

 

RTP负载为H.264定义了三种不同的基本的负载结构,接收端可能通过RTP负载的首字节来识别它们。这一个字节类似NALU头的格式,它的类型字段则指出了代表的是哪一种结构,这个字节的结构如下:

+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |
+---------------+
Type定义如下:
0     没有定义
1-23  NAL单元   单个NAL单元包.
24    STAP-A   单一时间的组合包
25    STAP-B   单一时间的组合包
26    MTAP16   多个时间的组合包
27    MTAP24   多个时间的组合包
28    FU-A     分片的单元
29    FU-B     分片的单元
30-31 没有定义

首字节的类型字段和H.264的NALU头中类型字段的区别是,当Type的值为24~31表示这是一个特别格式的NAL单元,而H.264中,只取1~23是有效的值。下面分别说明这三种负载结构。
 

一.Single NALU Packet(单一NAL单元模式)

    
    即一个RTP负载仅由首字节和一个NALU负载组成,对于小于1400字节的NALU便采用这种打包方案。这种情况下首字节类型字段和原始的H.264的NALU头类型字段是一样的。也就是说,在这种情况下RTP的负载是一个完整的NALU。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |                                               |
+-+-+-+-+-+-+-+-+                                               |
|                                                               |
|               Bytes 2..n of a single NAL unit                 |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

二. Aggregation Packet(组合封包模式)

    在一个RTP中封装多个NALU,对于较小的NALU可以采用这种打包方案,从而提高传输效率。即可能是由多个NALU组成一个RTP包。分别有4种组合方式,STAP-A、STAP-B、MTAP16和MTAP24。那么这里的RTP负载首字节类型值分别是24、25、26和27。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |                                               |
+-+-+-+-+-+-+-+-+                                               |
|                                                               |
|             one or more aggregation units                     |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

三.Fragmentation Units(分片封包模式FUs)


    一个NALU封装在多个RTP中,每个RTP负载由首字节(这里实际上是FU indicator,但是它和原首字节的结构一样,这里仍然称首字节)、FU header和NALU负载的一部分组成。对于大于1400字节的NALU便采用这种方案进行拆包处理。存在两种类型FU-A和FU-B,类型值分别是28和29。

FU-A类型如下图所示:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator  |   FU header   |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
|                                                               |
|                         FU payload                            |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

FU-B类型如下图所示

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator  |   FU header   |               DON             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
|                                                               |
|                         FU payload                            |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


与FU-A相比,FU-B多了一个DON(decoding order number),DON使用的是网络字节序。FU-B只能用于隔行扫描封包模式,不能用于其他方面。

FU indicator字节结构如下所示:

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

Type=28或29

FU header字节结构如下所示:

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

S(Start): 1 bit,当设置成1,该位指示分片NAL单元的开始。当随后的FU负载不是分片NAL单元的开始,该位设为0。
E(End): 1 bit,当设置成1, 该位指示分片NAL单元的结束,此时荷载的最后字节也是分片NAL单元的最后一个字节。当随后的FU荷载不是分片NAL单元的结束,该位设为0。
R(Reserved): 1 bit,保留位必须设置为0,且接收者必须忽略该位。

Type:与NALU头中的Type值相同

例:

0x7C85=01111100 10000101 (开始包)

0x7C05=01111100 00000101 (中间包)

0x7C45=01111100 01000101 (结束包)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
解包 RTP H.264 数据需要进行以下步骤: 1. 从 RTP 报文中提取出 H.264 数据包。 2. 解析 H.264 数据包,提取出 NAL 单元。 3. 将 NAL 单元按照 H.264 规范进行拼接,形成完整的 H.264 帧。 4. 对 H.264 帧进行解码,获取视频帧。 下面是一个基于 C++ 和 FFmpeg 库的简单示例代码: ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> } #define MAX_RTP_PKT_LENGTH 1360 int main(int argc, char* argv[]) { AVFormatContext* fmt_ctx = NULL; AVCodecContext* codec_ctx = NULL; AVCodec* codec = NULL; AVPacket pkt; AVFrame* frame = NULL; uint8_t* frame_buf = NULL; int frame_size = 0; int got_frame = 0; int ret = 0; int i; av_register_all(); // 打开 RTP 输入文件 if (avformat_open_input(&fmt_ctx, "rtp://127.0.0.1:1234", NULL, NULL) != 0) { printf("Could not open input file.\n"); return -1; } // 查找视频流 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { printf("Could not find stream information.\n"); return -1; } int video_stream_index = -1; AVStream* video_stream = NULL; for (i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; video_stream = fmt_ctx->streams[i]; break; } } if (video_stream_index == -1) { printf("Could not find video stream.\n"); return -1; } // 查找视频解码器 codec = avcodec_find_decoder(video_stream->codecpar->codec_id); if (codec == NULL) { printf("Could not find codec.\n"); return -1; } // 初始化视频解码器上下文 codec_ctx = avcodec_alloc_context3(codec); if (codec_ctx == NULL) { printf("Could not allocate codec context.\n"); return -1; } if (avcodec_parameters_to_context(codec_ctx, video_stream->codecpar) < 0) { printf("Could not copy codec parameters.\n"); return -1; } if (avcodec_open2(codec_ctx, codec, NULL) < 0) { printf("Could not open codec.\n"); return -1; } // 解码 RTP 数据包 av_init_packet(&pkt); pkt.data = (uint8_t*)malloc(MAX_RTP_PKT_LENGTH); while (1) { // 从 RTP 文件中读取数据包 ret = av_read_frame(fmt_ctx, &pkt); if (ret < 0) { break; } // 跳过 RTP 头 uint8_t* rtp_data = pkt.data + 12; // 提取 H.264 NAL 单元 while (pkt.size > 0) { int nal_start = 0, nal_end = 0; for (i = 0; i < pkt.size - 4; i++) { if (rtp_data[i] == 0x00 && rtp_data[i + 1] == 0x00 && rtp_data[i + 2] == 0x00 && rtp_data[i + 3] == 0x01) { if (nal_start != 0) { nal_end = i - 1; break; } else { nal_start = i + 4; } } } if (nal_end == 0) { nal_end = pkt.size - 1; } // 拼接 NAL 单元 int nal_size = nal_end - nal_start + 1; if (frame_buf == NULL) { frame_buf = (uint8_t*)malloc(nal_size); } else { frame_buf = (uint8_t*)realloc(frame_buf, frame_size + nal_size); } memcpy(frame_buf + frame_size, rtp_data + nal_start, nal_size); frame_size += nal_size; // 解码 H.264 帧 while (frame_size > 0) { ret = avcodec_decode_video2(codec_ctx, frame, &got_frame, &pkt); if (ret < 0) { printf("Error decoding frame.\n"); break; } if (got_frame) { // 处理解码后的视频帧 printf("Decoded frame: %dx%d\n", codec_ctx->width, codec_ctx->height); } // 移动指针 int consumed = av_parser_parse2(codec_ctx->parser, codec_ctx, &pkt.data, &pkt.size, rtp_data + nal_end + 1, pkt.size - nal_size - (rtp_data + nal_end + 1 - pkt.data), pkt.pts, pkt.dts, pkt.pos); frame_size -= nal_size + consumed; rtp_data += nal_size + consumed; } // 移动指针 pkt.size -= nal_size; } av_packet_unref(&pkt); } // 释放资源 if (codec_ctx != NULL) { avcodec_close(codec_ctx); avcodec_free_context(&codec_ctx); } if (fmt_ctx != NULL) { avformat_close_input(&fmt_ctx); } if (frame_buf != NULL) { free(frame_buf); } return 0; } ``` 这里使用了 FFmpeg 库中的函数进行 RTP 数据包的解析和 H.264 数据的解码,具体流程请参考代码注释。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值