Android 使用MediaProjection+ImageReader捕捉屏幕画面
#.基本使用流程如下
mProjectionManager = (MediaProjectionManager)activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent captureIntent = mProjectionManager.createScreenCaptureIntent();
activity.startActivityForResult(captureIntent, SCREEN_CAPTURE_REQUEST_CODE);
MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(//VirtualDisplay名称,必须非空VIRTUAL_DISPLAY_NAME,//VirtualDisplay的尺寸宽高,必须大于0mDisplayWidth, mDisplayHeight,//像素密度dpi,必须大于0,一般取1VIRTUAL_DISPLAY_DPI,//设置标识位为公共显示器,一般都是使用该标志位DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,//VirtualDisplay的图像绘制目标Surface,//例如,可以是SurfaceView的Surface,用于显示;// 或者是MediaCodec的输入Surface,用于编码;// 或者是ImageReader的输入Surface,用于其它处理;//这里用SurfaceTexture中的Surface,直接将源画面转化成纹理displaySurface,//注册状态回调接口,当状态改变时触发//第二个参数为Handler,指明了回调方法的执行线程。// 回调方法会在该Handler所对应的Looper消息队列中被执行,即在其所在线程执行;// 若传入null则会在当前线程中执行。
// mDisplayCallback, null );null, null );
4.1所以虚拟现实器创建时设定的输出Surface很重要:如果要截屏,那么可以使用ImageReader的输入Surface,然后通过ImageReader将Surface中的画面以Image形式读取出来;如果做录屏直播,可以不断取出上面ImageReader的画面,然后通过OpenGL绘制到MediaCodec的输入Surface去编码;也可以为画面捕捉的线程初始化EGL环境,直接创建一个SurfaceTexture,获取对应的Surface,设置给虚拟现实器,此时虚拟显示器的输出画面会被自动转化为OpenGL外部纹理。经过OpenGL中间处理后,最终将这个纹理绘制到MediaCodec的输入Surface去编码。
4.2另外,注意:MediaProjection捕获的屏幕画面是整个屏幕范围的画面,包括导航条Navigaiton Bar。所以设置虚拟现实器尺寸时,要使用getRealMetrics来获取尺寸,如果使用getMetrics方法,获取高度不包含导航条。若实际捕获画面尺寸大于虚拟现实器尺寸,会将捕获画面等比例放缩到虚拟现实器能够容纳的程度,然后空余位置会显示黑边,效果类似“centerInside”。
mediaProjection.stop();
virtualDisplay.release();
#.ImageReader介绍
ImageReader内部关联一个Surface,可以将该Surface交给Camera或VirtualDisplay等作为画面输出目标。ImageReader可以直接从Surface读取图像数据,其内部有图像数据缓存队列,画面生产方不断把画面绘制到ImageReader的Surface上,并被存入缓存队列;而画面使用方,不断通过ImageReader从其缓存队列上取出Image对象。maxImages设定了ImageReader最多缓存多少Image,当缓存数量达到maxImages后,若老的Image若一直不close()为队列释放空间,将不会有新的Image放入队列,那么Surface上的画面可能还未被存入队列就被新的画面刷新,结果就是所谓"丢帧"。
//输出图像的宽、高,图像颜色格式、缓存队列最多缓存的Image数量
mImageReader = ImageReader.newInstance(width, height, format, maxImages);
//从ImageReader队列中获取最新的一帧Image(队列末尾),并且将前面老的Image都close()掉,如果没有新的可用的Image则返回null。
//这是Android推荐用的方式,因为能获取到尽可能新的画面。
// 不过稍加思索可知,当maxImages=1时,其实与下面.acquireNextImage()的效果没有差异。
//注意用完Image,及时Image.close()
image = mImageReader.acquireLatestImage();//从ImageReader的队列中获取下一帧Image,如果没有新的则返回null。
//注意用完Image,及时Image.close()
image = mImageReader.acquireNextImage();
//有新的可用图像时的通知回调
//第二个参数为Handler,指明了回调方法的执行线程。
// 回调方法会在该Handler所对应的Looper消息队列中被执行,即在其所在线程执行;
// 若传入null则会在当前线程中执行。
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {}
}, null);
/*将Image对象的ByteBuffer中字节数据写进Bitmap里,因为Bitmap接收的是像素格式的数据,所以需要做一些处理*/
//获取Image的图片像素宽、高
int width = image.getWidth();
int height = image.getHeight();
//获取Image中的ByteBuffer
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
//获取Image中每个像素的Byte数(像素间距),这里因为是RGBA4个通道,所以每个像素的间距是4
int pixelStride = planes[0].getPixelStride();
//获取Buffer中每行像素的字节宽度rowStride
int rowStride = planes[0].getRowStride();
//因为内存对齐的缘故,所以buffer的行宽度与上面获取到的width*pixelStride会有差异
//内存对齐的padding字节数 = Buffer行宽 - Image中图片宽度×像素间距
int rowPadding = rowStride - pixelStride * width;
//接收ByteBuffer数据的Bitmap需要的像素宽度 = Image中图片宽度 + 内存对齐宽度/像素间距
//每行的对应位置会填充一些无效数据
//(其实直接写成rowStride/pixelStride也行,按步骤写只是为了让逻辑清晰)
Bitmap tempBmp = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
tempBmp.copyPixelsFromBuffer(buffer);
//释放Image,以便继续从输入Surface接收新的画面
image.close();
//将tempBmp中每行像素中的无效数据过滤掉
mLastBitmap = Bitmap.createBitmap(tempBmp, 0, 0, width, height);
//释放tempBmp
tempBmp.recycle();
return mLastBitmap;
#.当使用SurfaceTexture作为虚拟显示器输出目标时,非常关键易出错的一步:
//这一步非常关键,它设置了SurfaceTexture中Surface的大小,否则默认输出的纹理大小为1*1像素
//注意:实际运行代码发现,设置的大小,应该与屏幕捕获的画面大小一致,否则:
// 1.正常情况下,画面从Surface的左上角开始绘制,超出的部分会被裁减掉;
// Surface上多余的部分会保留底色,而这部分底色一般显示出来会是黑色(要看显示的控件怎么处理底色)
// 2.若这里设置的宽、高与捕获画面的宽、高正好相反,则画面会居中缩放到Surface能容纳的大小,
// 不会发生裁剪,但是Surface上多余部分会保留底色。
// 利用这个特点,如果需要在横竖屏切换时裁掉多余黑色部分,可以调整OpenGL裁剪矩阵,将多余黑色部分裁减掉
mSurfaceTexture.setDefaultBufferSize(mDisplayWidth, mDisplayHeight);
Android 使用MediaProjection+ImageReader捕捉屏幕画面相关推荐
- Android 12 新APP启动画面(SplashScreen API)简介源码分析
以往的启动画面 默认情况下刚启动APP时会显示一会白色背景 如果把这个启动背景设置为null,则一闪而过的白色会变成黑色 如果把启动Activity设置为背景透明[< item name=&qu ...
- Android Camera2相机预览画面放大缩小(数码变焦DigitalZoom)功能实现
一.前言 Android自定义相机开发中,常常会有通过手势放大或缩小相机预览画面的需求,即数码变焦DigitalZoom. 二.接口说明 1. 获取最大的放大倍数 float maxZoom = mC ...
- android手机 无电池开机画面,华为手机开不了机停在开机画面怎么办【详解】
手机对于我们来说都是不陌生的,因为手机的发展太迅速了,同时也加快了手机的普及率.而智能手机的更新速度也是非常的快的.功能和性能也变得越来越好了,尤其是华为手机这几年变化真的是非常的大的,但是很多华为手 ...
- Android上电到现实充电画面,android 电池(二):android关机充电流程、充电画面显示(一)...
上一篇我们讲了锂电池的充放电的流程和电池的一些特性,这一节我们重点说一下android关机充电是怎么.充电画面显示是怎么实现的,这个在工作中也比较有用,我们开始做这一块的时候也走了不少的弯路.我记得我 ...
- 电视android界面卡,电视盒子画面卡顿怎么办?这三个方法完美解决困扰
进入 2020 年,不知道有多少家里还在用几年前购买的电视盒子,其实电视盒子就和我们的手机一样,虽然使用频率相比较手机而言没有那么频繁,但随着近些年的发展,智能电视软件越来越大,对电视盒子的配置需要也 ...
- android打开小屏登录画面,Android炫酷登录界面
来看一波图片吧 CoverEyeLogin.gif 动画效果介绍 1.当用户输入用户名时,小猫头鹰的眼睛是没有被捂住的 2.当用户输入密码时,小猫头鹰会用手捂住眼睛 3.如果用户名和密码都已经输入完毕 ...
- android 如何快速检测到画面变化_电瓶修复—如何快速检测电池的好坏2
本公众号会不断更新详细的电瓶修复技术及分享开店经验 (连载2)2.全新电池安装上去后,没几天或者一个多月客户反映跑的不远. 这种基本可以排除是电池故障引起的.尤其如果你安装的天能.超威之类的大厂电池. ...
- android手机 无电池开机画面,安卓手机无法开机的6种解决方法
现在安卓智能机十分普遍,但是也容易出现多重问题.关于手机不能开机是很多朋友都碰到的问题.那么手机不能开机怎么办?不用担心,下面学习啦小编就为大家介绍安卓手机突然无法开机的6种解决方法,希望可以帮到大家 ...
- Teamviewer11现在无法捕捉屏幕画面。这可能是由于快速的用户切换或远程桌面会话断开/最小化。...
如果你用"远程桌面"连过去开启Teamviewer的话,当你退出"远程桌面"后,外网用Teamviewer连接就会出现这个问题. 解决方法: 不用远程连接过去开 ...
最新文章
- kotlin中的异常处理_如何使用assertFailsWith在Kotlin中测试异常
- Centos 7.4 中http-2.4 的基本实现和 https 的实现
- 131. 直方图中最大的矩形【单调栈】
- CentOS 安装过程中格式化 SATA 硬盘巨慢的问题
- android 弹出菜单 toast,Android学习第二天:Toast(提醒)、Menu(菜单)、Intent的显式和隐式(包括打开、适配网站,调用拨号界面等)...
- 【QQ输入法】QQ输入法-剪切板 释放内存
- 三星s10能升级android11,三星 S10+手机已在测试 Android 11 系统
- Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 359404 bytes) in
- 本机Android应用程序教程:WhatsApp克隆
- 【kafka】WARN Attempting to send response via channel for which there is no open connection
- 腾讯推出基于区块链存证的“点亮莫高窟”活动
- 暴力猴插件的安装及使用
- for循环的经典例题
- 《疯狂动物城》 —浪潮分布式存储让动画渲染更高效
- 20145213《Java程序设计》第五周学习总结
- 远控软件GHOST源码免杀
- java读取文件的字节数据
- 【179期】这些最常用的Linux命令都不会,你怎么敢去面试?
- windows下服务器的数据的迁移解决方案
- mysql5.1为什么programdata文件夹里只有frm文件
热门文章
- 计算机非全日制硕士 选校,非全日制硕士研究生,到底值不值得报考?看完这篇就懂了!...
- 迭代重建算法中投影矩阵的计算
- 抓取前程无忧招聘信息
- uptown funk 火星哥霸占BILLBOARD排行榜8周的嗨爆神曲
- HTML5小游戏-简单抽奖小游戏
- 抖音同城怎么引流?抖音如何引流到线下实体店?
- 英文参考文献按照首字母排序使用matlab实现
- 【物联网】老程序教你一招,10行代码让超声波模块秒变声控开关
- Python程序设计实战:输出古诗实战
- 2022软件测试技能 APP自动化测试 Python+Appium+Uiautomator2 实战教程