使用ffmpeg(libavcodec)通过RTP解码H264视频的问题

bbe*_*ben 9 c++ rtp h.264 libavcodec

我使用SDP的profile-level-id et sprop-parameter-set设置AvCodecContext的profile_idc,level_idc,extradata和extradata_size.

我将Coded Slice,SPS,PPS和NAL_IDR_SLICE数据包的解码分开:

在里面:

uint8_t start_sequence [] = {0,0,1}; int size = recv(id_de_la_socket,(char*)rtpReceive,65535,0);

编码切片:

char *z = new char[size-16+sizeof(start_sequence)];
    memcpy(z,&start_sequence,sizeof(start_sequence));
    memcpy(z+sizeof(start_sequence),rtpReceive+16,size-16);
    ConsumedBytes = avcodec_decode_video(codecContext,pFrame,&GotPicture,(uint8_t*)z,size-16+sizeof(start_sequence));
    delete z;
Run Code Online (Sandbox Code Playgroud)

结果:ConsumedBytes> 0且GotPicture> 0(经常)

SPS和PPS:

相同的代码.结果:ConsumedBytes> 0且GotPicture = 0

我认为这是正常的

当我找到一对新的SPS/PPS时,我使用此数据包的有效负载及其大小更新extradata和extrada_size.

NAL_IDR_SLICE:

Nal单元类型是28 => idr帧被分段为此我尝试了两种方法来解码

1)我在第一个片段(没有RTP头)前加上序列0x000001,并将其发送到avcodec_decode_video.然后我将剩下的片段发送到这个函数.

2)我将第一个片段(没有RTP头)加上序列0x000001的前缀,并将其余的片段连接到它.我把这个缓冲区发送给解码器.

在这两种情况下,我都没有错误(ConsumedBytes> 0)但我没有检测到任何帧(GotPicture = 0)......

问题是什么 ?

Cip*_*ipi 25

在RTP中,所有H264 I帧(IDR)通常都是碎片化的.当您收到RTP时,首先必须跳过标题(通常是前12个字节),然后转到NAL单元(第一个有效负载字节).如果NAL是28(1C)那么这意味着跟随有效载荷代表一个H264 IDR(I帧)片段,并且您需要收集所有这些片段以重建H264 IDR(I帧).

由于有限的MTU和更大的IDR,会发生碎片.一个片段可能如下所示:

START BIT = 1的片段:

First byte:  [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS] 
Second byte: [ START BIT | END BIT | RESERVED BIT | 5 NAL UNIT BITS] 
Other bytes: [... IDR FRAGMENT DATA...]
Run Code Online (Sandbox Code Playgroud)

其他片段:

First byte:  [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS]  
Other bytes: [... IDR FRAGMENT DATA...]
Run Code Online (Sandbox Code Playgroud)

要重建IDR,您必须收集此信息:

int fragment_type = Data[0] & 0x1F;
int nal_type = Data[1] & 0x1F;
int start_bit = Data[1] & 0x80;
int end_bit = Data[1] & 0x40;
Run Code Online (Sandbox Code Playgroud)

如果fragment_type == 28它后面的有效载荷是IDR的一个片段.start_bit设置下一个检查,如果是,则该片段是序列中的第一个.您可以通过从第一个有效负载字节获取前3位来重建IDR的NAL字节(3 NAL UNIT BITS),并将它们与来自第二个有效负载字节的最后5位组合,(5 NAL UNIT BITS)这样您就可以获得这样的字节[3 NAL UNIT BITS | 5 NAL UNIT BITS].然后将该NAL字节首先写入一个清除缓冲区,该缓冲区包含该片段中的所有其他后续字节.请记住跳过序列中的第一个字节,因为它不是IDR的一部分,而只是识别片段.

如果start_bitend_bit是0,那么只写有效载荷(跳过标识片段第一有效负荷字节)到缓冲器.

如果start_bit为0且end_bit为1,则表示它是最后一个片段,您只需将其有效负载(跳过标识该片段的第一个字节)写入缓冲区,现在您已重建IDR.

如果你需要一些代码,请在评论中提问,我会发布它,但我认为这很清楚怎么做... =)

关于解码

我今天想到你为什么在解码IDR时遇到错误(我认为你已经重建好了).您是如何构建AVC解码器配置记录的?您使用的lib是否具有自动化功能?如果没有,你还没有听说过,继续阅读......

指定AVCDCR允许解码器快速解析解码H264(AVC)视频流所需的所有数据.数据如下:

  • ProfileIDC
  • ProfileIOP
  • LevelIDC
  • SPS(序列参数集)
  • PPS(图片参数集)

所有这些数据都在SDP的RTSP会话中在以下字段中发送:profile-level-idsprop-parameter-sets.

解码PROFILE-LEVEL-ID

Prifile级别ID字符串分为3个子字符串,每个字符串长2个字符:

[PROFILE IDC][PROFILE IOP][LEVEL IDC]

每个子字符串代表base16中的一个字节!因此,如果Profile IDC为28,则表示它在base10中实际为40.稍后您将使用base10值来构造AVC解码器配置记录.

解码SPROP-PARAMETER-SETS

Sprops通常是2个字符串(可能更多),以逗号分隔,并且base64编码!你可以解码它们,但没有必要.你的工作就是将它们从base64字符串转换为字节数组供以后使用.现在你有2个字节的数组,第一个数组是SPS,第二个是PPS.

建立AVCDCR

现在,您已经拥有构建AVCDCR所需的一切,您可以从创建新的干净缓冲区开始,现在按照此处说明的顺序将这些内容写入其中:

1 - 具有值1并表示版本的字节

2 - 配置文件IDC字节

3 - Prifile IOP字节

4 - 级别IDC字节

5 - 值为0xFF的字节(谷歌AVC解码器配置记录,看看这是什么)

6 - 字节值0xE1

7 - SPS阵列长度的值短

8 - SPS字节数组

9 - 具有PPS阵列数量的字节(在sprop-parameter-set中可以有更多它们)

10 - 跟随PPS阵列的长度短

11 - PPS阵列

解码视频流

现在你有字节数组告诉解码器如何解码H264视频流.我相信你需要这个,如果你的lib不是自己从SDP构建它...