文/小码哥_WS
博客地址:http://my.csdn.net/King1425
MediaProjection可以用来捕捉屏幕,具体来说可以截取当前屏幕和录制屏幕视频 (5.0以上)
先总结下系统是如何实现组合键截屏的:
都应该知道Android源码中对按键的捕获位于文件PhoneWindowManager.Java中
当满足按键条件时会用一个mHandler 开始post一个runnable,进入这个runnable中执行takeScreenshot()方法。
使用AIDL绑定了service服务到”com.android.systemui.screenshot.TakeScreenshotService”,注意在service连接成功时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理。接着我们找到实现这个服务service的类TakeScreenshotService,该类在(frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot包下
引用SurfaceControl类,调用了screenshot方法, 传入了屏幕的宽和高,这两个参数,接着进入SurfaceControl类中,位于frameworks/base/core/java/android/view目录下
最终到达native方法中nativeScreenshot
面就是java层的部分,接着到jni层,在\frameworks\base\core\jni\android_view_SurfaceControl.cpp中
到jni中,映射nativeScreenshot方法的是nativeScreenshotBitmap函数
最后辗转来到c++层,就是\frameworks\native\libs\gui下的SurfaceComposerClient.cpp中,实现ScreenshotClient声明的函数update
当进入到CAPTURE_SCREEN中,data会读取IGraphicBufferProducer生成出的图像buffe,接着调用 reply->writeInt32(res);返回给client.然后再回调到java层。以上就是系统截屏的原理。
那对于多媒体这块可以通过MediaProjection来实现截屏
实现思路:
首先获取MediaProjectionManager,和其他的Manager一样通过 Context.getSystemService() 传入参数MEDIA_PROJECTION_SERVICE获得实例。
接着调用MediaProjectionManager.createScreenCaptureIntent()弹出dialog询问用户是否授权应用捕捉屏幕,同时覆写onActivityResult()获取授权结果。
如果授权成功,通过MediaProjectionManager.getMediaProjection(int resultCode, Intent resultData)获取MediaProjection实例,通过MediaProjection.createVirtualDisplay(String name, int width, int height, int dpi, int flags, Surface surface, VirtualDisplay.Callback callback, Handler handler)创建VirtualDisplay实例。实际上在上述方法中传入的surface参数,是真正用来截屏或者录屏的。
截屏
截屏这里用到ImageReader类,这个类的getSurface()方法获取到surface直接传入MediaProjection.createVirtualDisplay()方法中,此时就可以执行截取。通过ImageReader.acquireLatestImage()方法即可获取当前屏幕的Image,经过简单处理之后即可保存为Bitmap。
录屏1 mp4
主体思路:
逻辑:录屏不需要操作视频原始数据,因此使用InputSurface作为编码器的输入。
视频:MediaProjection通过createVirtualDisplay创建的VirtualDisplay获取当前屏幕的数据。然后传入到MediaCodec中(即传入的Surface是通过MediaCodec的createInputSurface方法返回的),然后MediaCodec对数据进行编码,于是只需要在MediaCodec的输出缓冲区中拿到编码后的ByteBuffer即可。
简单说就是重定向了屏幕录制的数据的方向,这个Surface提供的是什么,录制的视频数据就传到哪里。Surface提供的是本地某个SurfaceView控件,那么就会将屏幕内容显示到这个控件上,提供MediaCodec就是作为编码器的输入源最终获得编码后的数据,提供ImageReader就会作为ImageReader的数据源,最终获得了视频的原始数据流。
音频:录制程序获得音频原始数据PCM,传给MediaCodec编码,然后从MediaCodec的输出缓冲区拿到编码后的ByteBuffer即可。
最终通过合并模块MediaMuxer将音视频混合。
录屏需要用到MediaCadec,这个类将原始的屏幕数据编码,在通过MediaMuxer分装为mp4格式保存。MediaCodec.createInputSurface()获取一个surface对象,传入
MediaProjection.createVirtualDisplay()即可获取屏幕原始多媒体数据.之后读取MediaCodec编码输出数据经过MediaMuxer封装处理为mp4即可播放,实现录屏。
录屏2 Gif
由于录制的是视频,得变成gif,有两种方案:
● 提取视频文件->解析视频->提取 Bitmap 序列(使用 MediaMetadataRetriever 提取某一时刻的图片,然后把很多某一时刻的图片串联起来编码成 gif。看来其也正是 gif 的原理,但实现出来的效果极差,无法准确提取到准确的图片,导致合成的 gif 图也无法连贯播放,播放起来也跳帧跳得很厉害。惨不忍睹)
● 利用FFmpeg直接转gif, 这种方法岗岗的。
之前我们演示过:
windows下编译最新版ffmpeg3.3-android,并通过CMake方式移植到Android studio2.3中 :http://blog.csdn.net/king1425/article/details/70338674
调用相关命令也可通过Jni实现。
github:https://github.com/WangShuo1143368701/VideoView/tree/master/mediaprojectionmediamuxer
一命二运三风水,四修阴德五读书!
刘桂林
微信号 : Android_LiuGuiLin
新浪微博:@刘某人程序员