文章目录
前言
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的地方
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. 在代码中定位问题
定位代码问题还是花了不少时间。具体源码分析这里就不展开了,参考意义不大。过程概括起来就以下这些步骤
-
阅读相关代码,理解numOfArrays是如何生成并写入到MP4中的,发现这块并没有什么问题
-
怀疑是数据问题。让测试复现,拿到原始数据,发现生成的MP4可以播放。(测试用了差不多2天才复现,辛苦了)
-
怀疑是内存相关的错误。(刚开始没想到这个,后来边阅读源码边思考,终于想到了可能是这个问题)
- 读取未初始化过的变量
- 野指针/悬垂指针读写
- 错误的指针类型转换
- 从已分配内存块的尾部进行读/写(数组等类型读写越界)
- 不匹配地使用 malloc/new/new[] 和 free/delete/delete[]
- 等等
-
于是先尝试使用valgrind做检查,发现读取未初始化过的变量,并且这块就是hvcc生成赋值的地方!!!
上面的圈起来的变量和另外一个变量(没截图),没有初始化。(其实这里圈起来的最后一个变量错了,但是不是问题的原因)
-
应该就是这里了,但是是什么样的值才会导致问题呢?仔细阅读了代码,并测试验证后。确定是uint8 m_spsCount = 255(0xff)的时候会出现问题
总结
花时间比较多,原因还是因为没能第一时间想到可能是读取未初始化变量的问题,经验不足导致。不过后面排查完其它错误之后终于发现了真正的错误原因。
通过这个问题的解决,并结合以往解决偶现问题的经验。偶现的问题,要么就是某些特殊输入导致,要么就是进程在偶现问题的那个时刻的状态(也就是依赖的相关变量)有问题,导致程序并没按照看起来的那样执行。
总的来说遇到问题,不断的排除可能的原因,那离真正的原因就不远了。