播放录制是在观众端录制视频内容存至本地。观众对观看内容非常感兴趣想要将该视频内容留存至本地时便可使用该功能。

一. 可行的方案

在Android端实现播放录制的方法大约有下面三种:

1. 录屏

在Android 5.0 (API Level 21)及以上版本提供了录屏功能,使用系统提供的类MediaProjection与VirtualDisplay可实现在Android端的录制屏幕内容的功能,此处不再赘述。
但此方法亦会录制应用的UI及可能的消息通知等与视频无关的内容,对Android的版本有一定要求。

2. 重封装(Remux)

播放器在播放视频时可获取原始音视频数据,当观众希望录制视频的时候,将原数数据进行一次Remux即成。
但是Remux的数据需要保证第一个视频帧是关键帧。因此播放器内部内部需要缓存已经解码并播放过的音视频数据直到新的关键帧(IDR帧)到来。

但此方案存在若干问题:

额外的内存开销。已经用于解码的音视频数据,其对应内存不可被释放,以备观众录制视频内容、直到新的GOP数据到来,上一个GOP的数据方可被释放。这样就带来了额外的内存开销。

内容多余。此方案必须从关键帧开始做Remux,并不一定是从用户想要录制的内容开始,特别是对于GOP较大的视频,此问题尤为明显。

3. 重编码&封装

此方法是将解码之后的YUV和PCM数据送入编码器,将编码后的数据重新封装为目标视频。

此方案需要重新编码,会占用相当的CPU资源。

我们最终采用了方案3作为初始方案,采用Android手机提供的硬编功能将音视频数据编码,将编码后的数据封装为MP4文件。

二. 踩过的坑

在开发过程中踩过不少坑,下面和大家分享下:

2.1 MediaCodec

MediaCodec是Android提供的关于音视频硬编/硬解功能的核心类,其接口及相应功能在此不在赘述。实现播放录屏功能时使用MediaCodec编码视频,遇到的最坑的问题是:

颜色空间,一般情况下软解视频输出YUV420Planar数据,而Android手机可能只支持YUV420Planar或YUV420SemiPlanar,其区别如下图所示。因此需要根据每个手机的实际情况做出适配。
Android播放器的录制实践_第1张图片

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团队的目标。团队在很用心的开发短视频SDK,欢迎试用!