播放录制是在观众端录制视频内容存至本地。观众对观看内容非常感兴趣想要将该视频内容留存至本地时便可使用该功能。
一. 可行的方案
在 Android 端实现播放录制的方法大约有下面三种:
在 Android 5.0 (API Level 21)及以上版本提供了录屏功能,使用系统提供的类 MediaProjection 与 VirtualDisplay 可实现在 Android 端的录制屏幕内容的功能,此处不再赘述。 但此方法亦会录制应用的 UI 及可能的消息通知等与视频无关的内容,对 Android 的版本有一定要求。
播放器在播放视频时可获取原始音视频数据,当观众希望录制视频的时候,将原数数据进行一次 Remux 即成。 但是 Remux 的数据需要保证第一个视频帧是关键帧。因此播放器内部内部需要缓存已经解码并播放过的音视频数据直到新的关键帧(IDR 帧)到来。 但此方案存在若干问题:
额外的内存开销。已经用于解码的音视频数据,其对应内存不可被释放,以备观众录制视频内容、直到新的 GOP 数据到来,上一个 GOP 的数据方可被释放。这样就带来了额外的内存开销。 内容多余。此方案必须从关键帧开始做 Remux,并不一定是从用户想要录制的内容开始,特别是对于 GOP 较大的视频,此问题尤为明显。
此方法是将解码之后的 YUV 和 PCM 数据送入编码器,将编码后的数据重新封装为目标视频。 此方案需要重新编码,会占用相当的 CPU 资源。
我们最终采用了方案 3 作为初始方案,采用 Android 手机提供的硬编功能将音视频数据编码,将编码后的数据封装为 MP4 文件。
二. 踩过的坑
在开发过程中踩过不少坑,下面和大家分享下:
2.1 MediaCodec
MediaCodec 是 Android 提供的关于音视频硬编 /硬解功能的核心类,其接口及相应功能在此不在赘述。实现播放录屏功能时使用 MediaCodec 编码视频,遇到的最坑的问题是:
颜色空间,一般情况下软解视频输出 YUV420Planar 数据,而 Android 手机可能只支持 YUV420Planar 或 YUV420SemiPlanar,其区别如下图所示。因此需要根据每个手机的实际情况做出适配。 image.png 2.2 MediaMuxer
MediaMuxer 是 Android 于 4.3 版本引入的用于将编码后的音视频数据封装为 MP4 的核心类。实现播放录屏功能时最初是选用 MediaMuxer 将编码之后的音视频数据复用为 MP4,然则因为 Android 系统的碎片化,各厂商根据各自的需求对 ROM 做了相应修改,导致 MediaMuxer 的稳定性在各手机表现不太一致,与预期相差较远。
三. 最终的方案
最终的解决方案是使用 MediaCodec+FFMpeg,MediaCodec 将解码后的音视频数据编码,FFMpeg 将编码之后的音视频数据封装为 MP4 文件。其中视频编码默认使用 H.264/AVC 的 Baseline,音频编码使用 AAC-LC。
其基本架构图
播放器将解码后的音视频数据(YUV/PCM)交予录制模块,录制模块有两个缓存队列分别缓存解码后的音视频数据; 录制模块的音视频各有一个编码线程,每个线程持有一个 MediaCodec 的对象,分别从音视频队列取解码后的数据,然后将编码后的数据放入已编码数据队列。写数据线程会不断从已编码数据队列中拉取数据,然后调用封装模块的接口,将音视频数据封装为 MP4 ; 封装模块调用了 FFMpeg 提供的接口,将音视频数据写入本地磁盘。其中需要先写入音视频的关键信息例如: SPS/PPS、ADTS 等。 四. 结语
按照以上步骤便可实现在 Android 端的播放录制功能,但是依旧有改进的空间,例如使用 MediaCodec 做视频编码在少许低端机型依旧存在问题,后续会根据推出更加稳定的版本。
转码对于普通用户来说不可见的,但却是短视频 SDK 的一个重要过程。怎么样让转码过程耗时更短,转码图像质量更高,特效添加更灵活,减少我们团队自身的开发和维护成本,同时也为开发者提供最方便易用的 API,一直是金山云多媒体 SDK 团队的目标。
团队在很用心的开发短视频 SDK,欢迎试用!