一个偶现的MP4录制问题解决过程记录

文章目录

前言

LG发现了一个偶现的录制问题,为了上线必须解决。这里记录一下问题解决的过程。

问题的现象是这样的,偶然出现录制的视频会出现无法播放的问题,而且那个视频有些手机能播,有些不能播,很诡异。

解决思路

由于使用的是MediaPlayer播放出现问题,正好在我自己编译的Pixel 3(Android 11)上也无法播放,那就在上面调试查看出错的地方吧。找到报错的地方后,就可以定位到是解析MP4文件中具体哪块数据的时候出现的问题。然后再去看生成这块数据的代码,阅读理解相关逻辑,看看哪里出错了。暂时就想到这,开始行动。

1. 定位MP4中哪块数据出现问题

由于MP4数据还是很多的,要一个个排查不现实。所以就根据报错的日志来定位问题。

a. 查看adb错误日志

$ adb logcat *:E 

02-24 17:59:30.569   352   352 E Utils   : did not find width and/or height
02-24 17:59:30.570   352   352 E Utils   : did not find width and/or height
02-24 17:59:30.583   354  1906 E Utils   : b/23680780
02-24 17:59:30.584  1878  1896 E MediaPlayerNative: error (1, -22)
02-24 17:59:30.634  1878  1878 E MediaPlayer: Error (1,-22)

发现错误出现在 E Utils : b/23680780

根据进程号354,发现是mediaserver进程

$ adb shell ps | grep 354
media           354      1   93744  26112 binder_thread_read  0 S mediaserver

b. 定位AOSP报错的源码位置

通过在AOSP源码搜索b/23680780定位到源文件位置在/home/kevin/ExtraSpace/aosp/frameworks/av/media/libstagefright/Utils.cpp

这里面有好几处都打印了这个日志,那就没什么好说的,把全部都设置上断点

然后调试。点击播放有问题的MP4,发现断点停在解析hvcc的地方
一个偶现的MP4录制问题解决过程记录

c. 用gdb打印MP4中的hvcc

其实不用gdb打印,也可以通过MP4分析工具查看,只是我这里在调试,所以就直接用gdb输出了。至于MP4分析工具还是用挺多,我这里讲一下Linux上。推荐使用MediaParser,这个原来的在qt6上编译有问题,我修复了。需要的话自己下载编译就行了。

好了回归正题。为了后续方便,这里称呼:

good表示正常的mp4的hvcc,bad表示有问题的mp4的hvcc

将两个打印出来,方便后续对比。

(gdb) x/110x data
good hvcC

0xee100380:	0x01	0x01	0x40	0x00	0x00	0x00	0x80	0x00
0xee100388:	0x00	0x00	0x00	0x00	0x7b	0xf0	0x00	0xfc
														(numOfArrays==3)
0xee100390:	0xfd	0xf8	0xf8	0x00	0x00	0x0f	0x03	0x20
0xee100398:	0x00	0x01	0x00	0x17	0x40	0x01	0x0c	0x01

bad hvcC

0xea580e10:	0x01	0x00	0x01	0x03	0x00	0x00	0x00	0x18
0xea580e18:	0x00	0x10	0x00	0x00	0x2d	0x00	0x00	0x00
														(numOfArrays==255溢出)
0xea580e20:	0xff	0xff	0xff	0xff	0xff	0xff	0xff	0x20
0xea580e28:	0x00	0x01	0x00	0x18	0x40	0x01	0x0c	0x01

bad2 hvcC(这是后面又复现的有问题的MP4文件)
0xe6d40c10:	0x01	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xe6d40c18:	0x00	0x00	0x00	0x00	0x00	0x49	0x00	0xe7
														(numOfArrays==0)
0xe6d40c20:	0xff	0xa4	0x00	0x00	0x00	0x00	0x00	0x20
0xe6d40c28:	0x00	0x01	0x00	0x18	0x40	0x01	0x0c	0x01

d. 根据ISO/IEC 14496-15文档,阅读hvcc

这里面对比发现bad.mp4的hvcc的确有问题。

这是ISO文档中对应的数据组成

aligned(8) class HEVCDecoderConfigurationRecord { 
    unsigned int(8) configurationVersion = 1; 

    //1 byte (第2个byte)
    unsigned int(2) general_profile_space; 
    unsigned int(1) general_tier_flag; 
    unsigned int(5) general_profile_idc; 

    //4 bytes (第3~6个byte)
    unsigned int(32) general_profile_compatibility_flags; 

    //6 byte (第7~12个byte)
    unsigned int(48) general_constraint_indicator_flags; 

    //(第13个byte)
    unsigned int(8) general_level_idc; 

    //(第14~15个byte)
    bit(4) reserved = ‘1111’b; 
    unsigned int(12) min_spatial_segmentation_idc;

    //(第16个byte)
    bit(6) reserved = ‘111111’b; 
    unsigned int(2) parallelismType; 

    //(第17个byte)
    bit(6) reserved = ‘111111’b; 
    unsigned int(2) chroISO/IEC 23008-2 ma_format_idc; 

    //(第18个byte)
    bit(5) reserved = ‘11111’b; 
    unsigned int(3) bit_depth_luma_minus8; 

    //(第19个byte)
    bit(5) reserved = ‘11111’b; 
    unsigned int(3) bit_depth_chroma_minus8; 

    //(第20~21个byte)
    bit(16) avgFrameRate; 

    //(第22个byte)
    bit(2) constantFrameRate; 
    bit(3) numTemporalLayers; 
    bit(1) temporalIdNested; 
    unsigned int(2) lengthSi*usOne; 

    //(第23个byte)
    unsigned int(8) numOfArrays; 

    for (j=0; j < numOfArrays; j++) { 
        //1个byte
        bit(1) array_completeness; 
        unsigned int(1) reserved = 0; 
        unsigned int(6) NAL_unit_type; 

        //2个byte
        unsigned int(16) numNalus; 

        for (i=0; i< numNalus; i++) { 
            //2个byte
            unsigned int(16) nalUnitLength; 

            bit(8*nalUnitLength) nalUnit; 
        }
    } 
}

根据ISO文档,解析hvcc数据,发现bad在[general_profile_space, numOfArrays]之间是不正常的。

numOfArrays应该是3(vps,sps,pps,一共3个)

good

0xee101350:	0x01	0x01	0x40	0x00	0x00	0x00	0x80	0x00
0xee101358:	0x00	0x00	0x00	0x00	0x7b	0xf0	0x00	0xfc
													  (numOfArrays)(32,vps)
0xee101360:	0xfd	0xf8	0xf8	0x00	0x00	0x0f	0x03	0x20
           (numNalus == 1) (nalUnitLength==23)(nalUnit
0xee101368:	0x00	0x01	0x00	0x17	0x40	0x01	0x0c	0x01
0xee101370:	0xff	0xff	0x01	0x40	0x00	0x00	0x03	0x00
0xee101378:	0x80	0x00	0x00	0x03	0x00	0x00	0x03	0x00
								)(33,sps)  (numNalus == 1)(nalUnitLength==33)
0xee101380:	0x7b	0xac	0x09	0x21	0x00	0x01	0x00	0x21
			(nalUnit
0xee101388:	0x42	0x01	0x01	0x01	0x40	0x00	0x00	0x03
0xee101390:	0x00	0x80	0x00	0x00	0x03	0x00	0x00	0x03
0xee101398:	0x00	0x7b	0xa0	0x02	0x80	0x80	0x2d	0x16
0xee1013a0:	0x5a	0xe4	0xb2	0xb6	0x6b	0x95	0x44	0xd8
				) (34,pps)(numNalus == 1)(nalUnitLength==8)(nalUnit
0xee1013a8:	0x02	0x22	0x00	0x01	0x00	0x3d	0x44	0x01
														)
0xee1013b0:	0xc0	0xe3	0x0f	0x03	0x32	0x40


bad
				  (不正常的数据
0xea580a90:	0x01	0x00	0x01	0x03	0x00	0x00	0x00	0x18
0xea580a98:	0x00	0x10	0x00	0x00	0x2d	0x00	0x00	0x00
														 (numOfArrays))(32,vps)
0xea580aa0:	0xff	0xff	0xff	0xff	0xff	0xff	0xff	0x20
		   (numNalus==1)(nalUnitLength==24)(nalUnit
0xea580aa8:	0x00	0x01	0x00	0x18	0x40	0x01	0x0c	0x01
0xea580ab0:	0xff	0xff	0x01	0x60	0x00	0x00	0x03	0x00
0xea580ab8:	0x00	0x03	0x00	0x00	0x03	0x00	0x00	0x03
										)  (33,sps)(numNalus == 1)(nalUnitLength==41
0xea580ac0:	0x00	0x96	0xac	0x09	0x21	0x00	0x01	0x00
				) (nalUnit
0xea580ac8:	0x29	0x42	0x01	0x01	0x01	0x60	0x00	0x00
0xea580ad0:	0x03	0x00	0x00	0x03	0x00	0x00	0x03	0x00
0xea580ad8:	0x00	0x03	0x00	0x96	0xa0	0x05	0x02	0x01
0xea580ae0:	0x69	0x63	0x6b	0x92	0x4c	0x9a	0xe5	0x9c
0xea580ae8:	0x02	0x00	0x00	0x07	0xd2	0x00	0x00	0x9c
						) (34,pps) (numNalus==1)(nalUnitLength==7)(nalUnit
0xea580af0:	0x68	0x10	0x22	0x00	0x01	0x00	0x07	0x44
														)
0xea580af8:	0x01	0xe0	0x76	0xb0	0x26	0x40

2. 在代码中定位问题

定位代码问题还是花了不少时间。具体源码分析这里就不展开了,参考意义不大。过程概括起来就以下这些步骤

  1. 阅读相关代码,理解numOfArrays是如何生成并写入到MP4中的,发现这块并没有什么问题

  2. 怀疑是数据问题。让测试复现,拿到原始数据,发现生成的MP4可以播放。(测试用了差不多2天才复现,辛苦了)

  3. 怀疑是内存相关的错误。(刚开始没想到这个,后来边阅读源码边思考,终于想到了可能是这个问题)

    1. 读取未初始化过的变量
    2. 野指针/悬垂指针读写
    3. 错误的指针类型转换
    4. 从已分配内存块的尾部进行读/写(数组等类型读写越界)
    5. 不匹配地使用 malloc/new/new[] 和 free/delete/delete[]
    6. 等等
  4. 于是先尝试使用valgrind做检查,发现读取未初始化过的变量,并且这块就是hvcc生成赋值的地方!!!
    一个偶现的MP4录制问题解决过程记录

    上面的圈起来的变量和另外一个变量(没截图),没有初始化。(其实这里圈起来的最后一个变量错了,但是不是问题的原因)

  5. 应该就是这里了,但是是什么样的值才会导致问题呢?仔细阅读了代码,并测试验证后。确定是uint8 m_spsCount = 255(0xff)的时候会出现问题

总结

花时间比较多,原因还是因为没能第一时间想到可能是读取未初始化变量的问题,经验不足导致。不过后面排查完其它错误之后终于发现了真正的错误原因。

通过这个问题的解决,并结合以往解决偶现问题的经验。偶现的问题,要么就是某些特殊输入导致,要么就是进程在偶现问题的那个时刻的状态(也就是依赖的相关变量)有问题,导致程序并没按照看起来的那样执行。

总的来说遇到问题,不断的排除可能的原因,那离真正的原因就不远了。

上一篇:在.NET中使用API的方法


下一篇:linux 访问windows共享