【Android视频技术探索之旅:美团外卖商家端的实践】public static double correctTimeToSyncSample(Track track, double cutHere, boolean next) { double[] timeOfSyncSamples = new double[track.getSyncSamples().length]; long currentSample = 0; double currentTime = 0; for (int i = 0; i < track.getSampleDurations().length; i++) { long delta = track.getSampleDurations()[i]; int index = Arrays.binarySearch(track.getSyncSamples(), currentSample + 1); if (index >= 0) { timeOfSyncSamples[index] = currentTime; } currentTime += ((double) delta / (double) track.getTrackMetaData().getTimescale()); currentSample++; } double previous = 0; for (double timeOfSyncSample : timeOfSyncSamples) { if (timeOfSyncSample > cutHere) { if (next) { return timeOfSyncSample; } else { return previous; } } previous = timeOfSyncSample; } return timeOfSyncSamples[timeOfSyncSamples.length - 1];}为了解决精度问题 , 我们废弃了mp4parser , 采用MediaCodec的方案 , 虽然该方案会增加复杂度 , 但是误差精度大大降低 。
方案具体实施如下:先获得目标时间的上一帧信息 , 对视频解码 , 然后根据起始时间和截取时长进行切割 , 最后将裁剪后的音视频信息进行压缩编码 , 再封装进mp4容器中 , 这样我们的裁剪精度从秒级误差降低到微秒级误差 , 大大提高了容错率 。
视频处理
视频处理是整个视频能力最核心的部分 , 会涉及硬编解码(遵循OpenMAX框架)、OpenGL、音频处理等相关能力 。
下图是视频处理的核心流程 , 会先将音视频做分离 , 并行处理音视频的编解码 , 并加入特效处理 , 最后合成进一个mp4文件中 。

文章插图
在实践过程中 , 我们遇到了一些需要特别注意的问题 , 比如开发时遇到的坑 , 严重的兼容性问题(包括硬件兼容性和系统版本兼容性问题)等 。下面重点讲几个有代表性的问题 。
1. 偶数宽高的编解码器
视频经过编码后输出特定宽高的视频文件时出现了如下错误 , 信息里仅提示了Colorformat错误 , 具体如下:

文章插图
查阅大量资料 , 也没能解释清楚这个异常的存在 。基于日志错误信息 , 并通过系统源码定位 , 也只是发现是了和设置的参数不兼容导致的 。经过反复的试错 , 最后确认是部分编解码器只支持偶数的视频宽高 , 所以我们对视频的宽高做了偶数限制 。引起该问题的核心代码如下:
status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg, sp<AMessage> &outputFormat, sp<AMessage> &inputFormat) { if (!msg->findInt32("color-format", &tmp)) { return INVALID_OPERATION; } OMX_COLOR_FORMATTYPE colorFormat = static_cast<OMX_COLOR_FORMATTYPE>(tmp); status_t err = setVideoPortFormatType( kPortIndexInput, OMX_VIDEO_CodingUnused, colorFormat); if (err != OK) { ALOGE("[%s] does not support color format %d", mComponentName.c_str(), colorFormat); return err; } .......}status_t ACodec::setVideoPortFormatType(OMX_U32 portIndex,OMX_VIDEO_CODINGTYPE compressionFormat, OMX_COLOR_FORMATTYPE colorFormat,bool usingNativeBuffers) { ...... for (OMX_U32 index = 0; index <= kMaxIndicesToCheck; ++index) { format.nIndex = index; status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); if (err != OK) { return err; } ......}2. 颜色格式
我们在处理视频帧的时候 , 一开始获得的是从Camera读取到的基本的YUV格式数据 , 如果给编码器设置YUV帧格式 , 需要考虑YUV的颜色格式 。这是因为YUV根据其采样比例 , UV分量的排列顺序有很多种不同的颜色格式 , Android也支持不同的YUV格式 , 如果颜色格式不对 , 会导致花屏等问题 。
3. 16位对齐
这也是硬编码中老生常谈的问题了 , 因为H264编码需要16*16的编码块大小 。如果一开始设置输出的视频宽高没有进行16字节对齐 , 在某些设备(华为 , 三星等)就会出现绿边 , 或者花屏 。
4. 二次渲染
4.1 视频旋转
在最后的视频处理阶段 , 用户可以实时的看到加滤镜后的视频效果 。这就需要对原始的视频帧进行二次处理 , 然后在播放器的Surface上渲染 。首先我们需要OpenGL 的渲染环境(通过OpenGL的固有流程创建) , 渲染环境完成后就可以对视频的帧数据进行二次处理了 。通过SurfaceTexture的updateTexImage接口 , 可将视频流中最新的帧数据更新到对应的GL纹理 , 再操作GL纹理进行滤镜、动画等处理 。在处理视频帧数据的时候 , 首先遇到的是角度问题 。在正常播放下(不利用OpenGL处理情况下)通过设置TextureView的角度(和视频的角度做转换)就可以解决 , 但是加了滤镜后这一方案就失效了 。原因是视频的原始数据经过纹理处理再渲染到Surface上 , 单纯设置TextureView的角度就失效了 , 解决方案就是对OpenGL传入的纹理坐标做相应的旋转(依据视频的本身的角度) 。
推荐阅读
- Android NDK-深入理解JNI
- Android如何使用注解进行代码检查
- 抖音 Android 性能优化系列:启动优化之理论和工具篇
- Android缩放手势的检测
- 自动识别 Android 不合理的内存分配
- Android面试题集锦之 Service
- 怎样搭高质量的Android项目框架,框架的结构具体描述?
- 谷歌Android 12L的适配机型,看得我们有点懵
- 浅谈Android类加载器
- Android 12推出正式版!这次的升级到底值不值得更新呢?
