zeerd's blog         Search     Categories     Tags     Feed

闲来生雅趣,无事乐逍遥。对窗相望雪,一盏茶香飘。

H.264数据的FU切分与恢复

#H.264 @Ethernet


Contents:

在传输负载H.264的流媒体时,无论是使用AVTP协议还是RTP协议,都会面临一个数据分包的问题。 这是由于以太网的PDU的大小存在上限,也就是MTU。这个数值通常是1500个字节。

为了解决这个问题,H.264引入了Fragmented Unit的概念。

如果H.264的媒体文件是以“字节流”的形式保存的, 我们可以看到,大概率,其每一个视频帧都是以标准“NALU”的类型存储的。

这时候,我们就需要将NALU的类型格式转换为FU-A的类型格式。

FU-A的数据格式与常用的NALU类型的数据格式非常类似。差别仅仅在类型字段。

NALU类型数据的类型字段是一个字节。FU-A类型数据的的类型字段是两个字节。 分别如下图所示:

+---------------+    +---------------++---------------+
|0|1|2|3|4|5|6|7|    |0|1|2|3|4|5|6|7||0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+    +-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+
|F|NRI|  Type   |    |F|NRI|  Type1  ||S|E|R|  Type2  |
+---------------+    +---------------++---------------+

上图的左侧部分为NALU的类型字段, 右侧部分为FU-A的类型字段(前8个Bites为FU indicator、后8个Bits为FU header)。

FU-A的F和NRI字段与NALU的意义相同。 Type1固定为28。 Type2为这包数据在使用非FU模式的时候的Type值(可以简单地认为就是NALU的Type值)。 S表示这是一个NAL的起始帧,E表示这是一个NAL的终止帧。R固定为0,是保留位。

因此,将一个标准的NALU的NAL转换为FU类型的方式非常简单。

对于发送端来说, 只要将一个字节的类型字段拆分成两个字节,并按着MTU的限制将NAL Data拆分成多段。

digraph G {
    node [shape=record];
    graph [ splines = line ];
    SA  [label="{NALU Type | {<f> F |<n> NRI |<t> Type } }| {<d1> NALU Data 1}| {<dx> NALU Data ...}| {<dn> NALU Data N}"];
    FU  [label="{<i> N}| {FU-A Indicator | {<f> F |<n> NRI | Type(28) } } | {FU-A Header | {<s> S(1) | E(0) | R(0) | <t> Type } }| {<p> FU-A Payload}"];
    FUx [label="{<i> N + ...} | {FU-A Indicator | {<f> F |<n> NRI | Type(28) } } | {FU-A Header | {S(0) | E(0) | R(0) | <t> Type } } | {<p> FU-A Payload}"];
    FUn [label="{<i> N + n} | {FU-A Indicator | {<f> F |<n> NRI | Type(28) } } | {FU-A Header | {S(0) |<e> E(1) | R(0) | <t> Type } } | {<p> FU-A Payload}"];
    FU:i -> FUx:i;
    FUx:i -> FUn:i;
    SA:f -> FU:f [color="DodgerBlue" ];
    SA:f -> FUx:f [color="DodgerBlue" ];
    SA:f -> FUn:f [color="DodgerBlue" ];
    SA:n -> FU:n [color="Blue" ];
    SA:n -> FUx:n [color="Blue" ];
    SA:n -> FUn:n [color="Blue" ];
    SA:t -> FU:t [color="Green" ];
    SA:t -> FUx:t [color="Green" ];
    SA:t -> FUn:t [color="Green" ];
    SA:d1 -> FU:p [label="第一段" color="Red" ];
    SA:dx -> FUx:p [label="第...段" color="Red" ];
    SA:dn -> FUn:p [label="最后一段" color="Red" ];
}

注意图中S和E的数值变化。它们标识了这一组FUs的起始。

在接收端,数据恢复的过程与之相反:

digraph G {
    node [shape=record];
    graph [splines=line];
    SA  [label="{NALU Type | {<f> F |<n> NRI |<t> Type } }| {<d1> NALU Data 1} | {<dx> NALU Data ...} | {<dn> NALU Data N}"];
    FU  [label="{<i> N} | {FU-A Indicator | {<f> F |<n> NRI | Type(28) } } | {FU-A Header | {S(1) | E(0) | R(0) | <t> Type } } | {<p> FU-A Payload}"];
    FUx [label="{<i> N + ...} | {FU-A Indicator | {<f> F |<n> NRI | Type(28) } } | {FU-A Header | {S(0) | E(0) | R(0) | <t> Type } } | {<p> FU-A Payload}"];
    FUn [label="{<i> N + n} | {FU-A Indicator | {<f> F |<n> NRI | Type(28) } } | {FU-A Header | {S(0) | E(1) | R(0) | <t> Type } } | {<p> FU-A Payload}"];
    FU:i -> FUx:i [label="下一条数据" color="DodgerBlue" ];
    FUx:i -> FUn:i [label="下一条数据" color="DodgerBlue" ];
    FU:f -> SA:f [color="Blue" ];
    FU:n -> SA:n [color="Blue" ];
    FU:t -> SA:t [color="Blue" ];
    FU:p -> SA:d1 [color="Red" ];
    FUx:p -> SA:dx [color="Red" ];
    FUn:p -> SA:dn [color="Red" ];
}

注1:NAL是Network Abstraction Layer,即网络抽象层的缩写。NALU 即 NAL Unit。 在H.264中,一个NAL基本上等同于一个数据帧。

注2:MTU是Maximum Transmission Unit,即最大传输单元的缩写。 以太网的MTU通常为1500字节。

注3:FU是Fragmentation Unit的缩写。 在H.264中,一个FU是某个NAL的一部分,且不能是完整的一个NAL (即一个NAL至少需要切成两个FUs,否则就不能使用FU的概念)。