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(经常)
相同的代码.结果:ConsumedBytes> 0且GotPicture = 0
我认为这是正常的
当我找到一对新的SPS/PPS时,我使用此数据包的有效负载及其大小更新extradata和extrada_size.
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_bit和end_bit是0,那么只写有效载荷(跳过标识片段第一有效负荷字节)到缓冲器.
如果start_bit为0且end_bit为1,则表示它是最后一个片段,您只需将其有效负载(跳过标识该片段的第一个字节)写入缓冲区,现在您已重建IDR.
如果你需要一些代码,请在评论中提问,我会发布它,但我认为这很清楚怎么做... =)
关于解码
我今天想到你为什么在解码IDR时遇到错误(我认为你已经重建好了).您是如何构建AVC解码器配置记录的?您使用的lib是否具有自动化功能?如果没有,你还没有听说过,继续阅读......
指定AVCDCR允许解码器快速解析解码H264(AVC)视频流所需的所有数据.数据如下:
所有这些数据都在SDP的RTSP会话中在以下字段中发送:profile-level-id和sprop-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构建它...