卷起来了!如何设计一款优秀的音视频sdk

328 阅读6分钟

结合工作内容以及一些问题经验,给出一些建议。

均基于自己的理解和实践,可能有不对的地方,欢迎大家指正。

包体一定要尽可能的小

在移动互联网时代,App 的包体大小,对获取用户有着至关重要的作用,特别是在线下的推广活动中,包体太大,下载费时费流量,很多用户会失去耐心,从而直接导致 App 的推广效果大打折扣。

而 App 的包体大小,除了自身的代码量,也会受到第三方库的包体影响,因此,短视频 SDK 包体越小,对于减小 App 包体大小越有帮助,开发者才会越喜欢。

结合实际上工作的经验,有以下几点建议

  • 尽量使用系统原生的api,减少sdk引入第三库的成本,如果实在必须需要引入第三方库,那么尽量使用比较有名通用的库,比如网络,那就是okhttp,基本上层app本身也会使用到,但是也可能会存在版本兼容问题,最好是sdk的第三方库版本要低一点,这样子,就算上层的库版本高,也会兼容sdk的第三方库,反之,让上层app去升级库的版本,则会引入新的工作量
  • 良好的模块划分,每个功能独立化成一个包,给到上层足够的自由去选择集成。
  • 涉及UI相关的资源文件,远程加载,本地尽量不要包含图片,asset目录下的静态文件等等
  • 提供静/动态加载方案,这里是针对.so文件,so文件一般是比较大的,普遍在1~6M左右,那么如果客户要求总共是1M以下呢?那么显然就需要动态加载so文件,这样情况下,就需要采用动态加载方案,甚至连jar包都可以动态加载,对外一个代理调用壳即可。但这种情况下也是有局限性的,因为往往下载的文件是放在公网的服务器的,但是有些客户的环境比较特殊,比如iptv网络网络环境,无法访问公网环境,所以需要动态化配置,以静态加载方案兜底。

清晰 方便 开放性

  • 接口名定义清晰,减少对外接入成本,接口名应该等于功能含义
  • 凡可配置的参数,一律提供配置,比如分辨率帧率等等配置参数,凡可回调的数据,一律提供回调,凡运行过程中的状态,一律提供通知,尽量的可能更的细致化,上层可以有选择的进行做自己的业务逻辑。
  • 接口可重入性,乱序性,健壮性,如果由于上层的失误对接口有一些异常的调用导致的异常问题(比如调用了多次,时序不对等等),sdk应该保持最好的反馈给到上层,减少问题定位的难度以及复杂度。
  • 整体的实例最好使用单例模式,提供init和uninit,因为音视频场景上层可能会涉及到多个页面之间的切换,所以最好是公用一个实例进行处理。
  • 防止内存泄露,单例模式下,如果持有一个对象,那么这个对象是无法进行回收的,尽量持有和单例实例相同生命周期的对象,比如context,那就最好持有application的context,或者在sdk,unit的时候,销毁单例对象也是可以的。

功能最小化,UI逻辑分离

sdk的每个接口应该保持最小化的功能点,最小维度的可移植性。这个怎么理解呢?

举个例子,比如,实现一个播放H264码流的功能,然后提供对外接口,可能第一时间想到,那么提供一个接口就好了,直接去播放。

startPlay(Surfaceview view);

但是这个逻辑本身并没有最小功能化,因为本身里面有拉取资源,渲染两件事情,可能拉取资源里面还可以更加细致化的区分。

假如上层希望达到预加载的效果,一进去就能够直接播放,那么就必须需要预加载资源,然后进入播放页面之后,直接渲染就好,那很明显,sdk就需要将两个功能拆分开来,准备资源提供给到上层具体的回调接口,以及单独去渲染的接口,调整成下面的设计则更好。

prepare(Listener listener);
startRender(SurfaceView view);

遵循上面一点规则,UI逻辑分离就是很自然的事情了,方便上层去定制化自己的UI

保持输入和输出,不要自作主张

这么说更好理解一点,所有你在开发过程中的不确定的因素,不要去自作主张的去写默认值,或者你认为是一个正确的值,上层的业务千变万化,你觉得不确定的值,最好让上层传递进来。

比如,在播放全屏的情况下,需要获取对应的宽高,千万不要理所应当的认为就是屏幕的宽高,然后调用系统api去处理,这种情况下,你根据不知道上层到底是全屏,还是处理了刘海屏,还是有状态栏等等?所以有可能在不同的客户场景下,获取的不一致,那么肯定就会有问题了。

最好的就是根据上层传递进来的渲染View,获取到最外层的父布局的大小,拿到app真实显示全屏的值。

不要觉得让上层传入很多东西,感觉上层集成很麻烦,往往越通用的东西,刚开始使用起来越麻烦,但到后面之后,就会越来越方便,就像opengl一样,刚开始画一个三角形,可tm费劲了,各种函数,GLSL,着色器等等,但是这样子就高度定制通用化了,提供一个核心,让开发人员去自由实现。

性能稳定性,可靠性

  • crash率
  • cpu占用率,尽量使用硬解+软解结合。
  • 内存占用(内存泄露等问题)
  • 健全的文档,每次异常对应有个错误码,并有详细的解释,减少维护成本。
  • 规范的日志格式,如:<SDK TAG>:<Module TAG>:<Class TAG>:<Message>,可以快速方便地过滤出各个模块的运行状态

\