LG发现了一个偶现的录制问题,为了上线必须解决。这里记录一下问题解决的过程。
问题的现象是这样的,偶然出现录制的视频会出现无法播放的问题,而且那个视频有些手机能播,有些不能播,很诡异。
由于使用的是MediaPlayer播放出现问题,正好在我自己编译的Pixel 3(Android 11)上也无法播放,那就在上面调试查看出错的地方吧。找到报错的地方后,就可以定位到是解析MP4文件中具体哪块数据的时候出现的问题。然后再去看生成这块数据的代码,阅读理解相关逻辑,看看哪里出错了。暂时就想到这,开始行动。
由于MP4数据还是很多的,要一个个排查不现实。所以就根据报错的日志来定位问题。
$ 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
通过在AOSP源码搜索b/23680780
定位到源文件位置在/home/kevin/ExtraSpace/aosp/frameworks/av/media/libstagefright/Utils.cpp
这里面有好几处都打印了这个日志,那就没什么好说的,把全部都设置上断点
然后调试。点击播放有问题的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
这里面对比发现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) lengthSizeMinusOne; //(第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
定位代码问题还是花了不少时间。具体源码分析这里就不展开了,参考意义不大。过程概括起来就以下这些步骤
阅读相关代码,理解numOfArrays是如何生成并写入到MP4中的,发现这块并没有什么问题
怀疑是数据问题。让测试复现,拿到原始数据,发现生成的MP4可以播放。(测试用了差不多2天才复现,辛苦了)
怀疑是内存相关的错误。(刚开始没想到这个,后来边阅读源码边思考,终于想到了可能是这个问题)
于是先尝试使用valgrind做检查,发现读取未初始化过的变量,并且这块就是hvcc生成赋值的地方!!!
上面的圈起来的变量和另外一个变量(没截图),没有初始化。(其实这里圈起来的最后一个变量错了,但是不是问题的原因)
应该就是这里了,但是是什么样的值才会导致问题呢?仔细阅读了代码,并测试验证后。确定是uint8 m_spsCount = 255(0xff)的时候会出现问题
花时间比较多,原因还是因为没能第一时间想到可能是读取未初始化变量的问题,经验不足导致。不过后面排查完其它错误之后终于发现了真正的错误原因。
通过这个问题的解决,并结合以往解决偶现问题的经验。偶现的问题,要么就是某些特殊输入导致,要么就是进程在偶现问题的那个时刻的状态(也就是依赖的相关变量)有问题,导致程序并没按照看起来的那样执行。
总的来说遇到问题,不断的排除可能的原因,那离真正的原因就不远了。