您的位置:首页 > 移动开发 > Android开发

Android录屏并利用FFmpeg转换成gif(一)录屏

2018-01-13 12:28 316 查看

Android录屏并利用FFmpeg转换成gif(一) 录屏

写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方式还是比较困难的。本来我以为应该也有能直接在手机上录屏并生成gif文件这样的app,下载一个这样的APP来录gif要方便得多。结果发现目前几乎没有此类APP,我就想能不能自己写一个,然后查了查资料,感觉应该能做出来,于是就撸起袖子干起来了。总的来讲要实现这个功能可以分成两个部分(当然,如果有更好的实现方式欢迎大家提出来,谢谢!):

录屏,生成mp4文件

利用ffmpeg开源软件将mp4转换成gif

第一点比较容易实现,已有现成的开源代码供参考。难点在第二点,涉及到NDK开发相关的知识,及FFmpeg的集成,这方面知识我之前从未接触过,还是比较有挑战性的。

功能虽然很简单,但要讲解起来感觉还是要费点篇幅的,所以我分成了4篇文章来介绍,分别是:

Android录屏并利用FFmpeg转换成gif(一) 录屏,讲讲怎样录屏生成mp4文件

Android录屏并利用FFmpeg转换成gif(二) 交叉编译FFmpeg源码,说说如何根据我们的需求裁剪FFmepg并编译出可在android下运行的so包

Android录屏并利用FFmpeg转换成gif(三) 在Android中使用ffmpeg命令,说说如何在Android中使用ffmpeg命令,简化C代码的编写难度

Android录屏并利用FFmpeg转换成gif(四) 将mp4文件转换成gif文件,将2、3两步生成的so文件集成到android工程中,实现将mp4文件转换成gif文件,完成最终的工程。

本文是第一篇,下面就来说说录屏是怎么实现的。

原理

从数据处理的角度来讲,录屏一般要经历这样一个流程:

数据采集 —-> 编码 —-> 多路复用 —-> 封装成文件

数据采集:就是收集音视频的原始数据,包括从摄像头采集,从屏幕上采集等,我们这里是要从屏幕上采集

编码:就是将收集起来的音视频原始数据按一定的规则进行压缩

复用:就是将音频和视频同步起来,做什么事就说什么话,要对应起来

封装:就是将同步好的数据封装成播放格式,如mp4,avi等

那么在android中与以上几点对应的主要的API是哪些呢?

数据采集(屏幕上的数据)主要由 MediaProjection 类来负责

编码主要由 MediaCodec 类来负责

复用和封装由 MediaMuxer 类来负责

到这里我们应该对我们要做的事有大概的了解了,要怎么做也有个线索了,只要沿着这条线索慢慢细化,应该就能实现我们的需求。下面详细介绍一下如何用代码来实现以上的一个个流程。

一、数据采集

上面说了采集屏幕上的数据主要由 MediaProjection 类来负责的。 MediaProjection是一个使应用程序能够捕获屏幕内容和/或录制系统音频的Token。MediaProjection 对象要借助于 MediaProjectionManager 类来创建。具体如下:

先通过MediaProjectionManager 的 createScreenCaptureIntent()方法创建一个Intent, 然后用这个intent启动一个带返回结果的Activity。

MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE);


然后在onActivityResult()方法中创建MediaProjection对象。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection == null) {
Log.e(TAG, "media projection is null");
return;
}
}


然后再用这个MediaProjection对象创建一个虚拟显示(VirtualDisplay),这个虚拟显示相当于是对物理屏幕的一个投影,它将会渲染到一个Surface中,这个Surface是创建虚拟显示时传进去的参数,它承载了表示虚拟显示的数据,在编码时其输入数据就来自这个Surface。

mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
mSurface, null, null);
//注意上面的代码有一个'mSurface'的参数,这个参数实际上是由编码器创建的,
//用于从'mSurface'中接收输入数据进行编码


好,到这里数据采集就完成了,下面来看编码。

二、编码

编码主要是由 MediaCodec 类来负责,MediaCodec功能很强大,它既可以编码也可以解码,要创建一个编码器可以调用createEncoderByType(String type)方法来创建,而创建一个解码器则用createDecoderByType(String type)方法来创建,我们这里只使用它的编码功能,所以先创建一个视频编码器:

String MIME_TYPE = "video/avc"; // 代表要编码出 H.264/AVC 格式的视频数据
MediaCodec mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);


然后要配置一下:

mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);


format里面设置了输出的数据类型和一些其它的编码参数,有好多参数,具体见源码,这里主要讲流程。

MediaCodec.CONFIGURE_FLAG_ENCODE 代表这是一个编码器,如果是当编码器用必须要传这个参数。

编码的话肯定要有输入输出,到这里为止我们还没看到是从哪里输入数据的呢,其中的一种方式就是通过一个Surface来输入的,下面来创建一个输入Surface,很简单,调一下 createInputSurface() 方法即可:

mSurface = mEncoder.createInputSurface();


那这个Surface又是怎么得到数据的呢?刚才在数据采集我们说,屏幕上的视频数据已经通过一个虚拟显示渲染到了一个Surface上了,那么那个Surface跟我们现在这里创建的这个Surface有什么关系呢,其实就是同一个,我们先创建这个Surface,然后再创建虚拟显示,创建的时候将这个Surface当作参数传进去。这样原始数据就通过这个surface与编码器关联起来了,等编码器开始工作了就会自动从surface获取原始数据,不用我们干预了。编好码之后会将编码后的数据放到输出缓冲器中,我们就从输出缓冲器中拿到编码后的数据去复用。

现在编码器的代码还没完成,创建了输入Surface之后,还要调一下start方法才可以工作。

mEncoder.start();


好,现在编码器可以开始工作了。

有个地方要注意一下:

MediaCodec中的createInputSurface()方法只能在configure(MediaFormat,Surface,MediaCrypto,int)和start()之间调用。

也就是这个样子:

mEncoder.configure(format, null, null,  MediaCodec.CONFIGURE_FLAG_ENCODE);
mSurface = mEncoder.createInputSurface(); // 只能在 configure() 和start() 之间调用
mEncoder.start();


三、复用与封装

复用和封装都是由 MediaMuxer 类来处理,而且很智能化,我们只要设置一些参数,然后把编码器中的数据取出来传给复用器就可以了。

先创建一个复用器:

mMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);


第一个参数是输出文件的路径,包含文件名

第二个参数是输出文件的格式,只支持三种格式,mpeg4、3gpp、webm

然后做一些基本的设置并启动复用器:

MediaFormat newFormat = mEncoder.getOutputFormat();
mVideoTrackIndex = mMuxer.addTrack(newFormat); //添加指定格式的轨道(音轨或视频轨道)
mMuxer.start();


上面第一行代码取得的 MediaFormat 是配置编码器的时候传进去的媒体格式。

接下来就在一个死循环中不断去取编码器中输出的数据,然后传给复用器,最后由复用器输出到mp4文件中,直到人为终止的时候停止取数据,当终止后要手动释放相关资源。

while (!quit) {
int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
Log.i(TAG, "dequeue output buffer index=" + index);

ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
if (encodedData != null) {
encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
Log.i(TAG, "sent " + mBufferInfo.size + " bytes to muxer...");
}
mEncoder.releaseOutputBuffer(index, false);
}


当终止录屏后,就可以在指定的目录中看到生成的mp4文件了,更详细的可以查看源码。至此,录屏部分就介绍完了。下一篇开始将介绍如何将mp4转换成gif。

最后,上源码:

https://github.com/MingHuang1024/GifRecorder

注意:源码是完整的源码,即实现了从录屏到转换成gif的全部功能的,不仅仅是录屏,录屏的代码主要在 ScreenRecorder.java 类中,MainActivity.java中也有一点。如果要一个单独的录屏app源码,可以参考 https://github.com/yrom/ScreenRecorder,这个源码也是我参考的原型。

由于水平有限,如果文中存在错误之处,请大家批评指正,欢迎大家一起来分享、探讨!

博客:http://blog.csdn.net/MingHuang2017

GitHub:https://github.com/MingHuang1024

Email:minghuang1024@foxmail.com

微信:724360018
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ffmpeg gif 录屏