- 计算 AVCDecoderConfigurationRecord 得到 CodecPrivateData 数据(只有第一帧需要);
- 计算 NALUs 得到帧数据。
计算 AVCDecoderConfigurationRecord 得到 CodecPrivateData 数据
H.264 视频流的 CodecPrivateData 实际上就是 AVCDecoderConfigurationRecord 中 SequenceParameterSets(SPS)和 PictureParameterSets(PPS)使用 byte[] {00, 00, 01} 连接的字节数组。
注意!FLV 文件中第一个 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 总是 AVCDecoderConfigurationRecord(在 ISO/IEC 14496-15 中定义),解码的时候注意跳过这个 VIDOETAG。
AVCDecoderConfigurationRecord 结构的定义:
aligned(8) class AVCDecoderConfigurationRecord {
unsigned int(8) configurationVersion = 1; unsigned int(8) AVCProfileIndication; unsigned int(8) profile_compatibility; unsigned int(8) AVCLevelIndication; bit(6) reserved = ‘111111’b; unsigned int(2) lengthSizeMinusOne; bit(3) reserved = ‘111’b; unsigned int(5) numOfSequenceParameterSets; for (i=0; i< numOfSequenceParameterSets; i++) { unsigned int(16) sequenceParameterSetLength ; bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit; } unsigned int(8) numOfPictureParameterSets; for (i=0; i< numOfPictureParameterSets; i++) { unsigned int(16) pictureParameterSetLength; bit(8*pictureParameterSetLength) pictureParameterSetNALUnit; } }
下面蓝色的部分就是 FLV 文件中的 AVCDecoderConfigurationRecord 部分。
00000130h: 00 00 00 17 00 00 00 00 01 4D 40 15 FF E1 00 0A ; .........M@.?.
00000140h: 67 4D 40 15 96 53 01 00 4A 20 01 00 05 68 E9 23 ; gM@.朣..J ...h? 00000150h: 88 00 00 00 00 2A 08 00 00 52 00 00 00 00 00 00 ; ?...*...R......
根据 AVCDecoderConfigurationRecord 结构的定义:
- configurationVersion = 01
- AVCProfileIndication = 4D
- profile_compatibility = 40
- AVCLevelIndication = 15
- lengthSizeMinusOne = FF <- 非常重要,是 H.264 视频中 NALU 的长度,计算方法是 1 + (lengthSizeMinusOne & 3)
- numOfSequenceParameterSets = E1 <- SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F
- sequenceParameterSetLength = 00 0A <- SPS 的长度
- sequenceParameterSetNALUnits = 67 4D 40 15 96 53 01 00 4A 20 <- SPS
- numOfPictureParameterSets = 01 <- PPS 的个数
- pictureParameterSetLength = 00 05 <- PPS 的长度
- pictureParameterSetNALUnits = 68 E9 23 88 00 <- PPS
因此 CodecPrivateData 的字符串表示就是 000001674D4015965301004A2000000168E9238800
但是设置 MediaStreamAttributeKeys.CodecPrivateData 是没用的(只有 H.264 是这样,其他类型的视频流仍然需要设置),只有将 CodecPrivateData 写入 H.264 视频流第一帧数据的前面 Silverlight 才能正常解码。
也就是说,Silverlight 的 H.264 解码器会读取第一帧前面的 CodecPrivateData 数据来进行配置。
因为 CodecPrivateData 数据已经包含视频流的解码器参数(包括视频的宽高),所以就不需要设置 MediaStreamAttributeKeys.CodecPrivateData、MediaStreamAttributeKeys.Width 和 MediaStreamAttributeKeys.Height 了。
计算 NALU 得到帧数据
FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 不是原始视频帧数据,而是一个或更多个 NALU 数据片段。在这篇文章中,你认为 H.264 视频帧数据是由多个 NALU 组成的。当然实际上并不是这样,关于这部分的概念请自行 Google,本文将不做讨论。
下面是 FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 属性的数据(第一帧数据)。
- 红色的部分是 NALU 数据的长度,而红色部分的长度则由 lengthSizeMinusOne 决定。
- 蓝色的部分是 NALU 数据部分。
- 删除的部分是废弃的数据。
00000300h: 00 00 00 00 00 17 01 00 00 22 00 00 00 31 65 88 ; ........."...1e?
00000310h: 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 F8 45 FB ; €@.窌Sg刲.?鳨? 00000320h: F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 00 3F 2B ; ?q.づ,.......?+ 00000330h: 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 7A FE 00 ; [.WH)?.....衵? 00000340h: 00 00 38 65 01 22 22 01 00 17 B7 95 53 67 FF 84 ; ..8e.""...窌Sg? 00000350h: 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 2C 00 E8 ; l.?鳨.q.づ,.? 00000360h: F3 37 75 43 90 00 00 03 00 15 EF AA A8 53 86 01 ; ?uC?....铼⊿? 00000370h: DD 57 60 00 00 03 01 59 0C F4 3C 00 00 00 33 65 ; 軼`....Y.?...3e 00000380h: 00 90 88 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 ; .悎€@.窌Sg刲.? 00000390h: F8 45 FB F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 ; 鳨.q.づ,...... 000003a0h: 00 3F 2B 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 ; .?+[.WH)?.....? 000003b0h: 7A FE 00 00 00 38 65 00 D8 88 80 40 05 B7 95 53 ; z?..8e.貓€@.窌S 000003c0h: 67 FF 84 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 ; g刲.?鳨.q.づ 000003d0h: 2C 00 E8 F3 37 75 43 90 00 00 03 00 15 EF AA A8 ; ,.梵7uC?....铼? 000003e0h: 53 86 01 DD 57 60 00 00 03 01 59 0C F4 3C 00 00 ; S?軼`....Y.?.. 000003f0h: 00 F4 08 00 01 33 00 00 17 00 00 00 00 AF 01 27 ; .?..3.......?'
帧数据是将多个 NALU 使用 byte[] {00, 00, 01} 连接的字节数组。
byte[] = {
00,00,01,65,88,
80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB, F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B, 5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE,00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84,
6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8, F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01, DD,57,60,00,00,03,01,59,0C,F4,3C,00,00,01,65,
00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00, F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03, 00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0, 7A,FE,00,00,01,65,00,D8,88,80,40,05,B7,95,53,
67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5, 2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8, 53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C};
如果是第一帧数据,那么前面还要加上 CodecPrivateData 数据。
byte[] = {
00,00,01,67,4D,40,15,96,53,01,00,4A,20,
00,00,01,68,E9,23,88,00,
00,00,01,65,88,
80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB, F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B, 5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE,00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84,
6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8, F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01, DD,57,60,00,00,03,01,59,0C,F4,3C,00,00,01,65,
00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00, F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03, 00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0, 7A,FE,00,00,01,65,00,D8,88,80,40,05,B7,95,53,
67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5, 2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8, 53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C};