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

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

  1. 录屏,生成mp4文件
  2. 利用ffmpeg开源软件将mp4转换成gif

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

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

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

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

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

  4. 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对象。

@Overrideprotected 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(一)录屏相关推荐

  1. Android录屏并利用FFmpeg转换成gif(二)交叉编译FFmpeg源码

    Android录屏并利用FFmpeg转换成gif(二) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...

  2. Android录屏并利用FFmpeg转换成gif(三) 在Android中使用ffmpeg命令

    Android录屏并利用FFmpeg转换成gif(三) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...

  3. Android录屏并利用FFmpeg转换成gif(四) 将mp4文件转换成gif文件

    Android录屏并利用FFmpeg转换成gif(四) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...

  4. android 获取drawable 对象,Android 实现将本地资源图片转换成Drawable的方法

    Android 实现将本地资源图片转换成Drawable的方法 发布时间:2020-11-06 16:37:09 来源:亿速云 阅读:255 作者:Leah 这篇文章将为大家详细讲解有关Android ...

  5. linux保存mp4格式的文件,Linux中利用ffmpeg转换手机支持的mp4格式视频文件

    首先当然是需要安装ffmpeg软件包,可以直接从源中进行安装!但我安装后并不能成功执行后面所需要执行的转换命令,所以我只能重新从源码编译安装ffmpeg: (1)下载ffmpeg源码包,注意版本不能太 ...

  6. Android自定义相机不存储照片,转换成Bitmap上传

    在最近的开发中,遇到一个需求,使用相机拍照,但是不能存储到本地,并上传到服务器,这边用到的服务器接受上传的是base64的,需要将图片转换成base64上传.接下来我们开始整. CameraPrevi ...

  7. 利用Visio转换成pdf图形时的注意事项

    我们在写毕业论文的时候,通常会画一些流程图之类的图形,这类图形通常需要手工绘制,而不是像数据分析图之类的,有现成的数据分析软件R.Python等可供自动绘制.我经常采用的手工绘制软件就有Visio.今 ...

  8. ffmpeg 切片花屏_利用ffmpeg解码H264,花屏,该如何解决

    利用ffmpeg解码H264,花屏 UINT CMP4File::VideoCap_Thread_Fun(void* pParam) { CMP4File  *pMP4File=(CMP4File*) ...

  9. Android 将drawable下的图片转换成bitmap、Drawable

    将drawable下的图片转换成bitmap 1. Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xx ...

最新文章

  1. 工作经验:Java 系统记录调用日志,并且记录错误堆栈
  2. 转:.Net 中的反射(反射特性) - Part.3
  3. Win32 汇编要点总结
  4. APPStore 审核收集
  5. QML基础类型之url
  6. python的史蒂芬加速迭代法_如何将Pandas迭代速度加快150倍?
  7. [51单片机] 定时器3-计时示例
  8. poj 2240 Bellman-Flod 求环
  9. Navicat 创建mysql事件
  10. 业务线开发流程图(四)
  11. TreeSet集合为什么要实现Comparable?
  12. web前端的十种jquery特效及源码下载
  13. SugarCRM 去掉 header 应用程序 下拉菜单
  14. 两端对齐几种实现方案
  15. centos6.x系统内核升级的方法
  16. 大数据之Hadoop图解概述
  17. el-checkbox-group 的坑
  18. 贝叶斯⑤——搜狗新闻分类实战(jieba + TF-IDF + 贝叶斯)
  19. 微信小程序实现类似微信提现、支付宝提现充值等 “自定义键盘“可实现自定义右下角搜索内容,手写input功能view组件,实现焦点获取事件
  20. 实战:k8s中网络策略实验(成功测试-博客输出)-20211005

热门文章

  1. Python图书库存管理系统
  2. 2023 08-01
  3. 页面通过点击事件传递多个参数到另一个页面
  4. 画面云管理服务器 华为 型号,手机型号云服务器
  5. 微票务v15.9.1演唱会 票务 现场活动
  6. 洛古最简单50题解(41-50)
  7. JAVA实现将GeoHash转化为对应的经纬度坐标
  8. 夺命雷公狗---无限极分类NO5
  9. 自然语言处理之情感分析
  10. ansible 快速部署 kubeadm 离线安装集群