感觉真是巧合,在整个工作像是restart一样的时候,又遇见了一个和系统相关的诡异bug。

几年前碰到一个AES加密算法在电信版本iPhone5还是iPhone5s上加密后的数据不对的bug,当时没有详细的去检查,所以这次遇到这个bug赶紧记录下来。不过,这次的bug,到底算不算bug,还真不好说。

Bug描述

部分用户反馈线上的某些视频下载完成后,在播放时提示“在此服务器上找不到所请求的URL”

因为负责这个App的同事都休假了,我临时来处理一下这个问题,感觉很明显,就是视频找不到。我粗略的看了一下,大概觉得很可能是资源没有下载下来,因为资源是有实效的,第一反应可能是用户下载的时候,资源过期了。我模拟了一下,发现不对。然后又回过头来看了一下用户反馈的视频,如果是资源过期或者找不到,App会很快提示,但是看视频发现明显有一个下载的过程,我基本可以断定,用户肯定是完成下载了。

梳理代码的时候发现,有一处删除视频的代码,我就想肯定是这里了!前后理了一下逻辑,发现App里需要剥离视频中的音频,如果操作失败的话会直接把视频删掉,那多半是这里导致的问题了。我想可能是用户下载文件不对,没有下载完,然后我就故意找了一个没下载完的视频来复现,结果发现完全没问题。因为视频是流媒体,即是没有下载完,也是可以播放,而且也可以提取音频,这很尴尬。

到这里基本就走入死胡同了。通过用户反馈,有99%的把握可以肯定是程序在播放视频前把视频删掉了,但是测试发现,这部分全是系统API,而且连未下载完成的视频都可以处理,那就只可能是用户下载的根本不是一个正常的视频。虽然这样说,但是心里还是很不确定。毕竟经历过硬件差异带来的bug,这个问题我隐隐约约觉得很可能也是系统、机型导致的,其实在着手处理之前就向产品提到了这个疑惑。但是Bug反馈的是某些视频,某些机型,并不是很明确。

好在这个App留了一些日志反馈,所以就新打了一个包,加上日志,给出问题的用户使用,然后收集日志。我尽可能地加上了各种日志。今天收到日志后,猛的发现就是在不可能出错的地方出错了,在初始化AVAssetReader时出错了,提示“这项操作无法完成”。很懵逼啊,这算什么提示。虽然这个提示作用不大,但基本可以肯定是系统的问题了,当时是觉得大概是某些机型在某些系统下对部分视频的兼容性有问题,这就很难处理了。不过反过来一想,如果只是部分视频有问题的话,那可以找到这部分有问题的视频,重新编码一下,也是可以解决的。这时,也收集到了部分出错用户的手机型号是iPhone 6s,系统是10.1,我怀疑是6s机型的问题,所以就找测试的同事想法在6s下测试。后来同事在6s 10.3的系统下没有复现,那基本就可以肯定是10.3之前的系统了。这个时候,还在考虑用真机复现,因为我觉得多半是机型+系统的问题,但是想了想还是在模拟器上跑一下试试,毕竟10.1的系统很难找到。

结果,模拟器一下载下来,app一跑,发现所有的视频都无法播放。根本不是一两个视频的问题,所有的视频在iOS 10.1下都无法正常播放,无法正常提取音轨。那就很明显了,并不是机型问题,是系统版本的问题。这时虽然定位bug了,但是基本上是无法解决的状态,大概两个方案,一用户升级系统,二用ffmpeg来处理、播放视频,成本都很高。

不过,这里还是有疑惑没有解开,既然高版本系统可以播放,视频本身也没有什么大问题,那到底哪里出现了问题?我还是想到了视频本身,之前提到找出问题视频重新编码,现在所有视频都有问题,那重编码有用没有?因为比较着急,直接就用系统的QuickTime Player来重转码,结果只能转为mov,这时想到了ffmpeg,又用ffmpeg把mov转换为mp4了,然后这个mp4居然可以提取音频,可以播放,这就更肯定是系统API对视频的兼容性问题了。哪问题到底在哪里呢?

只能用ffprobe来看看两个视频的差异了,其实在之前说起视频编码的时候,安卓的同事发来一个wiki,是现在视频编码参数,里面提到了两个音轨,一个aac一个mp3,当时没有注意,但是用ffprobe查看之后,这两个视频的主要差异就是不能播放的视频是两个音轨,可以播放的视频是一个音轨。现在就肯定是音轨的问题了。

至此,问题基本算是确认了。但是回过头来一想,走了很多弯路。首先,用户反馈的问题不是很明确,实际是所有视频不能播放,当然这个不怪用户,因为用户没法区分一些细节,假如能确定是所有视频无法播放,那问题就更突出,明确了。其次,没有及时在相似系统版本下复现,主要还是没有对应版本的真机。

总结来看的话,这个bug反应了诸多问题。

  1. 没有对错误进行有效处理

这是导致这个bug的根本,最终能够通过日志定位这个bug就是我专门将之前忽略的error加到日志里了。大部分iOS开发者估计都会忽略系统API的一些error,因为感觉不太可能出错,而如果代码里能够重视这个error,并且反馈到App流程当中,那么这个bug就很容易确定原因了。

  1. 对于bug的描述不精确

用户反馈的问题多多少少总会有一些出入,简单来说就是所谓的行话,用户很难用行话精确的去描述问题,而且产品人员在收集问题的时候也没有去深入的问用户。这就要求我们能够在用户反馈问题的同时收集一些机型、版本、系统等等附加信息,越多越好。另外,App也要为收集用户信息提供便利,比如通过用户名,定位用户的系统信息(这个稍稍有些敏感)。

  1. 没有处理好异常行为

还是要回到代码上来,其实,第一点应该算是这一条的一个特例。不管是错误也好,警告也好,在代码里都要充分的重视,要对异常进行完善的处理,特别是在提示的时候附加尽可能多的信息。

  1. 没有在尽可能相似的环境下尝试复现

大概是因为我先入为主的偏见,总觉得是机型的问题,所以没有第一时间在模拟器里复现。一旦遇到bug,应该首先在完全相似的环境下复现,如果没有,那至少也要在模拟器里复现,然后逐渐的放宽环境去复现。

最后回到bug本身,我尝试用ffmpeg合成了多个视频之后,发现AVAssetReader是支持视频中包含多个音轨的,但是如果在10.1的系统下,有一个音轨是mp3,那就会出错了。如果视频只包含一个mp3音轨,视频是没有声音的。所以,我猜测是因为mp3编码是有专利的,因为专利的问题,iOS自带的API是无法解码mp3,最终导致了AVAssetReader出错。在10.3及11.0以后,苹果大概是更新了API或者也是作为bug修复加强了对包含mp3音轨视频的兼容性。在完全弄清楚原因之后,解决方案可以是将线上的视频中的mp3音轨删掉,因为这个音轨本来也是为安卓准备的,iOS并不需要。相对于之前提到的解决方案,这个方案大概是影响最小,成本最小的方案了。

所以,这到底算不算是bug呢?