透明mp4
透明视频
使用案例
- 陌陌
陌陌的资源目录 sdcard/immomo/gift_video_effect/..
开源
- Mp4Composer-android
https://github.com/MasayukiSuda/Mp4Composer-android,基于 MediaPlayer>
- ExoPlayerFilter
https://github.com/lvpengwei/ExoPlayerFilter
remix 用的这个,基于 ExoPlayer
VAP 是企鹅电竞开发,用于播放特效动画的实现方案。具有高压缩率、硬件解码等优点。同时支持 iOS,Android,Web 平台。; 而且 VAP 还能在动画中融入自定义的属性(比如用户名称, 头像)
- Android 播放透明视频
https://blog.csdn.net/ywl5320/article/details/108812241
注意
- 视频编码手机默认的 MediaPlayer 兼容有很大问题
- 集成了 ExoPlayer 后会增大 APK 体积大概 0.4M
兼容问题
- 视频编码问题。采用系统 MediaPlayer 播放的话,设计采用 MPEG-2 编码的格式的 mp4 文件,在有些手机是不支持的,大部分手机用 H.264 都支持(mpeg-2 比 h.264 文件要小很多)
- 视频尺寸问题。oppo a37 播不了长尺寸视频,替换 exoplayer 后 oppo a37 能播短尺寸 +h.264 的,长尺寸和 mpeg-2 的都播不了;如果 mp4 文件的尺寸太大 (1440x1560),会出现有声音没画面的现象,改短一点尺寸 (1440x1280) 就可以了(如 vivo x5 pro)
- GalaxyA10 系统播放器不支持 h.264 的视频,mpeg-2 也不支持
读取 alpha 通道有问题
存在的问题:安卓版本读取 alpha 通道有问题
正确的是黑色部分应该全透明;纯白→纯黑 0%透明度→100%透明度
- 以
超级跑车-陌陌1440_1280.mp4
为例 - Android:
- iOS:
- 一箭穿心
https://oss.mashichat.com/img/gift/50VB33Q4YKL3P2N6.mp4
Ref
- 企鹅电竞动效揭秘
http://dopro.io/egame-animate.html - 精准光影动效解决之道 - 透明视频
http://dopro.io/animation-solution-alpha-video.html
VAP
https://github.com/Tencent/vap
相比 SVGA 的效率提升有多少
1
2
3
4
5
6
7
8
1 原理:
svga 原理与lottie相识,都是矢量动画方案,都是使用CPU进行绘制(开启硬件加速也只是canvas的硬件加速),从实现原理上来说,支持的特效样式有限,而且复杂动画性能低下(这个只测试了lottie,结论不一定适用svga)。
2 单帧解码时间对比:
矢量动画使用CPU做复杂动画与GPU播放视频(vap实质是视频方案)单帧耗时对比,CPU可能需要10+ms,动画越复杂时间越长(当然简单的矢量动画效率会更高,但限定在简单的动画效果,这个只测试了lottie,结论不一定适用svga);而视频解码1-3ms,无论内容多复杂,视频解码时间基本不会有太大波动。
3 实现效果上对比:
矢量动画方案天生决定某些特效无法支持,或者说代价异常高(比如复杂的粒子特效)。而vap实质是视频方案,理论上所有的视觉特效都能支持。
Vap 坑
VAP 播放 Assets 目录下的视频一定的几率失败
问题描述
- 在 Assets 目录下的同一个视频文件反复播放,间隔 100 毫秒(不同视频文件连续播放也会,即使增加播放时间间隔到 2s),有一定的概率会出现:MediaExtractor exception:Failed to instantiate extractor
除了 Failed to instantiate extractor ,其他异常也很多。大部分没问题,偶尔就会出现异常
运行环境
- Android 11 Google Pixel 3,但是其他所有手机也会这样,不同的系统版本都会
相关日志
1
2
3
4
5
6
7
8
9
10
11
12
2021-04-29 11:37:21.635 24084-26873/? E/AnimPlayer.HardDecoder: MediaExtractor exception e=java.io.IOException: Failed to instantiate extractor.
java.io.IOException: Failed to instantiate extractor.
at android.media.MediaExtractor.setDataSource(Native Method)
at com.tencent.qgame.animplayer.file.AssetsFileContainer.setDataSource(AssetsFileContainer.kt:42)
at com.tencent.qgame.animplayer.util.MediaUtil.getExtractor(MediaUtil.kt:40)
at com.tencent.qgame.animplayer.HardDecoder.startPlay(HardDecoder.kt:83)
at com.tencent.qgame.animplayer.HardDecoder.access$startPlay(HardDecoder.kt:28)
at com.tencent.qgame.animplayer.HardDecoder$start$1.run(HardDecoder.kt:44)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
播放失败的文件
file:///android_assets/pk/live_pk_begin.mp4
问题分析
- 1.通过 Vap 播放失败回调接口上报数据,并分析
QbStatService.onEvent(“vap_play_failed”, mAnimResPath, code, msg);
播放接口:fun startPlay(assetManager: AssetManager, assetsPath: String)
这里应该 MediaExtractor 的兼容问题,目前想到的解决方法是手动实现 mp4 封装,但工作量比较大,手动实现 mp4 解封装需要充分测试
解决方案
- 1.应用启动时将 vap 视频资源从 assets 目录拷贝到 sdcard
- 2.播放 vap 视频路径改为 fun startPlay(file: File) 的方式
个人猜想
- 1.存放在 assets 目录下的文件,在编译打包时会将文件压缩以降低安装包大小,使用或者读取数据时需要先解压。所以播放 sdcard 下的文件没问题。
Android 文件大小限制 在 Android 2.3 以前的任何压缩的资源的原始大小超过 1M 将不能从 APK 中读出,如果你使用 AssetManager 或 Resources classes 方法来获取 InputStream,将抛出 java.io.IOException 的异常如下 DEBUG/asset(1123): Data exceeds。
1
2
3
4
5
6
7
8
9
/* these formats are already compressed, or don't compress well */
static const char* kNoCompressExt[] = {
".jpg", ".jpeg", ".png", ".gif",
".wav", ".mp2", ".mp3", ".ogg", ".aac",
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
".amr", ".awb", ".wma", ".wmv"
};
从上面截图来看,我们已经是.mp4 文件后缀了,应该不在压缩的资源之列。那么问题来了,到底哪里出轨了?
- 2.有网友提到没有使用FileDescriptor,但是 Vap 播放 Assets 目录下的 vap 视频就是用的 FileDescriptor,问题也不在这里。
1
2
3
4
5
6
7
8
9
class AssetsFileContainer(assetManager: AssetManager, assetsPath: String): IFileContainer {
...
private val assetFd: AssetFileDescriptor = assetManager.openFd(assetsPath)
private val assetsInputStream: AssetManager.AssetInputStream =
assetManager.open(assetsPath, AssetManager.ACCESS_STREAMING) as AssetManager.AssetInputStream
...