方案2综合评估后是改造风险最小的 。综合成本和风险考量 , 我们保守的采用了方案2 , 该方案是对裁剪区域进行坐标换算(如果用前置摄像头拍摄录制视频 , 会出现预览画面和录制的视频是镜像的问题 , 需要处理) 。当录制完视频后 , 生成了mp4文件 , 用MediaCodec对其编码 , 在编码阶段再利用OpenGL做内容区域的裁剪来实现 。但该方案又引发了如下挑战 。
(1)对焦问题
因我们对采集区域做了裁剪 , 引发了点触对焦问题 。比如用户点击了相机预览画面 , 正常情况下会触发相机的对焦动作 , 但是用户的点击区域只是预览画面的部分区域 , 这就导致了相机的对焦区域错乱 , 不能正常进行对焦 。后期经过问题排查 , 对点触区域再次进行相应的坐标变换 , 最终得到正确的对焦区域 。
(2)兼容适配
我们的视频录制利用MediaRecorder , 在获取配置信息时 , 由于Android碎片化问题 , 不同的设备支持的配置信息不同 , 所以就会出现设备适配问题 。
// VIVO Y66 模版拍摄时候 , 播放某些有问题的视频文件的同时去录制视频 , 会导致MediaServer挂掉的问题 // 发现将1080P尺寸的配置降低到720P即可避免此问题 // 但是720P尺寸的配置下 , 又存在绿边问题 , 因此再降到480 if(isVIVOY66() && mMediaServerDied) { return getCamcorderProfile(CamcorderProfile.QUALITY_480P); } //SM-C9000,在1280 x 720 分辨率时有一条绿边 。网上有种说法是GPU对数据进行了优化 , 使得GPU产生的图像分辨率 //和常规分辨率存在微小差异 , 造成图像色彩混乱 , 修复后存在绿边问题 。//测试发现 , 降低分辨率或者升高分辨率都可以绕开这个问题 。if (VideoAdapt.MODEL_SM_C9000.equals(Build.MODEL)) { return getCamcorderProfile(CamcorderProfile.QUALITY_HIGH); } // 优先选择 1080 P的配置 CamcorderProfile camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_1080P); if (camcorderProfile == null) { camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_720P); } // 某些机型上这个 QUALITY_HIGH 有点问题 , 可能通过这个参数拿到的配置是1080p , 所以这里也可能拿不到 if (camcorderProfile == null) { camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_HIGH); } // 兜底 if (camcorderProfile == null) { camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_480P); }视频合成
我们的视频拍摄有段落拍摄这种场景 , 商家可根据事先下载的模板进行分段拍摄 , 最后会对每一段的视频做拼接 , 拼接成一个完整的mp4文件 。mp4由若干个Box组成 , 所有数据都封装在Box中 , 且Box可再包含Box的被称为Container Box 。mp4中Track表示一个视频或音频序列 , 是Sample的集合 , 而Sample又可分为Video Smaple和Audio Sample 。Video Smaple代表一帧或一组连续视频帧 , Audio Sample即为一段连续的压缩音频数据 。(详见mp4文件结构 。)
基于上面的业务场景需要 , 视频合成的基础能力我们采用mp4parser技术实现(也可用FFmpeg等其他手段) 。mp4parser在拼接视频时 , 先将视频的音轨和视频轨进行分离 , 然后进行视频和音频轨的追加 , 最终将合成后的视频轨和音频轨放入容器里(这里的容器就是mp4的Box) 。采用mp4parser技术简单高效 , API设计简洁清晰 , 满足需求 。
但我们发现某些被编码或处理过的mp4文件可能会存在特殊的Box , 并且mp4parser是不支持的 。经过源码分析和原因推导 , 发现当遇到这种特殊格式的Box时 , 会申请分配一个比较大的空间用来存放数据 , 很容易造成OOM(内存溢出) , 见下图所示 。于是 , 我们对这种拼接场景下做了有效规避 , 仅在段落拍摄下使用mp4parser的拼接功能 , 保证处理过的文件不会包含这种特殊的Box 。

文章插图
视频裁剪
我们刚开始采用mp4parser技术完成视频裁剪 , 在实践中发现其精度误差存在很大的问题 , 甚至会影响正常的业务需求 。比如禁止裁剪出3s以下的视频 , 但是由于mp4parser产生的精度误差 , 导致4-5s的视频很容易裁剪出少于3s的视频 。究其原因 , mp4parser只能在关键帧(又称I帧 , 在视频编码中是一种自带全部信息的独立帧)进行切割 , 这样就可能存在一些问题 。比如在视频截取的起始时间位置并不是关键帧 , 会造成误差 , 无法保证精度而且是秒级误差 。以下为mp4parser裁剪的关键代码:
推荐阅读
- Android NDK-深入理解JNI
- Android如何使用注解进行代码检查
- 抖音 Android 性能优化系列:启动优化之理论和工具篇
- Android缩放手势的检测
- 自动识别 Android 不合理的内存分配
- Android面试题集锦之 Service
- 怎样搭高质量的Android项目框架,框架的结构具体描述?
- 谷歌Android 12L的适配机型,看得我们有点懵
- 浅谈Android类加载器
- Android 12推出正式版!这次的升级到底值不值得更新呢?
