开篇吐槽一下自己,从上班之后,由于公司没有什么活。就开启自己的抄抄模式。前期的时候一次性的转载了40片左右文章。原因也是在上班之际,白纸一张。那时候感觉抄着文章,感觉还可以能看懂。到了如今发现抄不动了。哎!!! 发现自己已经在奔溃中了。摘要:最近的公司的签写demo,需要在之前的签写、拍照、抽奖的基础上追加一个投屏的功能。一番收索之后,时间就是一周过去,想想自己都尴尬了。考虑了种种实现方式,什么只截取程序本身的界面啊,让后通过socket进行传输。通过直播流阿…等等。最后不得不放弃了。原因一个自己“low“, 对编码问题一窍不通,更不说推流了。
这里只对截图做记录,采取直接截屏的方式原因因为,只截取程序自身的图片。也还是需要用户授权。对于截取音视频。哈. . hahah… . . 尴尬的笑笑
/**
* Manages the retrieval of certain types of {@link MediaProjection} tokens.
*
*
* Get an instance of this class by calling {@link
* android.content.Context#getSystemService(java.lang.String)
* Context.getSystemService()} with the argument {@link
* android.content.Context#MEDIA_PROJECTION_SERVICE}.
*
*/
可用的公用的方法只有两个,分别为“createScreenCaptureIntent() , getMediaProjection()“ ,从上面可以真正进行截屏操作的类还是“MediaProjection“ 。这里需要注意一下,并不同于其他的 XXXXManager再获取实例对象后就可以调用公有方法了。这里需要分为两步:
Intent captureIntent = projectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, RECORD_REQUEST_CODE);
第二步
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RECORD_REQUEST_CODE) {
mediaProjection = projectionManager.getMediaProjection(resultCode, data);
}
}
MediaProjection
参数名称 | 含义 |
String name | 实际的流媒体显示实体名字,不能为空 |
int width | 实际的流媒体显示实体的宽度,单位为像素,必须大于0 |
int height | 实际的流媒体显示实体的高度,单位为像素,必须大于0 |
int dpi | 实际的流媒体显示实体的像素密度,单位为dp,必须大于0 |
int flags | 实际的流媒体显示实体标志的结合。(看下一个表格) |
Surface surface | 播放流媒体的surface实例,可为null |
VirtualDisplay.Callback callback | 实际的流媒体显示实体状态改变时的回调方法,可能为null |
Handler handler | 调用参数7回调方法的handler |
字段 | 含义 |
---|---|
VIRTUAL_DISPLAY_FLAG_PUBLIC | 公共显示:公共虚拟显示和大多数连接到系统其他显示器(如:HDMOH或无线显示器)相同。应用程序可以在显示器上打开窗口,系统可以将其它显示的内容镜像到上面。 如果没有设置时,为Display#FLAG_PRIVATE,私有显示属于创建它的应用程序。只有所有者和已经在该显示器上的应用程序才允许在上面放置窗口。 |
VIRTUAL_DISPLAY_FLAG_PRESENTATION | 演示显示:当该标志被设置时,显示在display category注册为DISPLAY_CATEGORY_PRESENTATION,应用程序可以自动将其内容投影到演示文稿显示,以提供更丰富的第二屏幕体验。 这个标志未被设置时,虚拟显示不被登记为演示显示。 应用程序仍然可以在显示器上投影他们的内容,但是他们通常不会自动完成。 该选项适用于更多特殊用途的显示器 |
VIRTUAL_DISPLAY_FLAG_SECURE | 安全显示:当该标志被设置时,虚拟显示被认为是安全的,Display#FLAG_SECURE。,例如空中加密,以防止显示内容被拦截或记录在永久介质上。创建一个安全的虚拟显示需要CAPTURE_SECURE_VIDEO_OUTPUT权限。 此权限保留供系统组件使用,不适用于第三方应用程序。 当这个标志未被设置时,虚拟显示被认为是不安全的。 如果在此显示器上显示,则安全窗口的内容将被清空。 |
VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | 当没有内容显示时,允许内容在专用显示器上被镜像:该标志与VIRTUAL_DISPLAY_FLAG_PUBLIC一起使用。 通常,公共虚拟显示器将自动镜像默认显示的内容,如果他们没有自己的窗口的话。 当这个标志被指定时,虚拟显示器将只显示它自己的内容,如果它没有窗口,它将被消隐。该标志与VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR互斥。 如果两个标志都被指定,那么仅适用于自己内容的行为将被应用。 只要VIRTUAL_DISPLAY_FLAG_PUBLIC和VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR都没有被设置,这个标志的行为就是隐含的。 这个标志只需要在创建公共显示时覆盖默认行为。 |
VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | 当没有内容显示时,允许内容在专用显示器上被镜像。此标志与VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY互斥。 如果两个标志都被指定,那么仅适用于自己内容的行为将被应用。只要设置了VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY且VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY尚未设置,则此标志的行为就是隐含的。 只有在创建私人显示时,此标志才能覆盖默认行为。创建自动镜像虚拟显示需要CAPTURE_VIDEO_OUTPUT或CAPTURE_SECURE_VIDEO_OUTPUT权限。 这些权限被保留供系统组件使用,不适用于第三方应用程序。 或者,可以使用适当的MediaProjection来创建自动镜像虚拟显示。 |
参数名 | 含义 |
int width | 默认宽度 (以像素为单位) |
int height | 默认高度 (以像素为单位) |
int format | 图片格式,可以是android.graphics.ImageFormat、android.graphics.PixelFormat其中的一种。这些格式也不是所有都支持(如: ImageFormat.NV21)。 |
int maxImages | 用户想要同时访问的最大图像数量。 这应该尽可能小,以限制内存使用。 一旦用户获得了maxImages图像,必须释放其中的一个图像,然后才能通过acquireLatestImage()或acquireNextImage()访问新的图像。 必须大于0。 |
getWidth()
返回图像的实际宽度,因为这个surfaceView在前面提到是可以set的。
getHeight()
返回图像的实际高度,因为这个surfaceView在前面提到是可以set的。
getImageFormat()
获取设置的ImageReader的格式,这里需要注意一下,每一种图片的格式只与自身兼容。如果在ImageReader中设置为PixelFormat.RGBA_8888,在创建图片的时候就需要设置为Bitmap.Config.ARGB_8888。
这里贴一段 PixelFormat和Bitmap.Config简短对应代码。具体的对应关系请看文章底部的参考链接
// bitmap configure
switch (manager.getDefaultDisplay().getPixelFormat()) {
case PixelFormat.A_8:
m_bitmap_config = Bitmap.Config.ALPHA_8;
break;
case PixelFormat.RGB_565:
m_bitmap_config = Bitmap.Config.RGB_565;
break;
case PixelFormat.RGBA_4444:
m_bitmap_config = Bitmap.Config.ARGB_4444;
break;
case PixelFormat.RGBA_8888:
m_bitmap_config = Bitmap.Config.ARGB_8888;
break;
}
getSurface()
获取用于为此ImageReader生成Images的surface。acquireNextImage在没有有效数据的时候会一直返回null,在同一时间只可以呈现一个源的数据。虽然说这个Surface可以承载不同Android的API数据。
close( )
释放此ImageReader相关的所有资源。在调用此方法后,ImageReader上的任何方法和通过acquireLatestImage()、acquireNextImage获取的Images提供的方法。都将抛出IllegalStateException。且尝试恢复一下之前状态数据
acquireLatestImage()
从ImageReader的队列中获取最新的Image ,删除旧images 。 如果没有新图像可用,则返回null 。如果已经close了,那么将不会是最新的数据图像。对于大多情况可以使用acquireNextImage(),它更加适合处理实时数据.。在使用这个方法读取图片的时候,要注意maxImages不能小于2,从字面上和上面的知识我们了解到它是获取一张,丢弃一张。如果小于2的话可能会导致预期丢弃失败
acquireNextImage()
从ImageReader的队列中获取下一个Image。 如果没有新图像可用,则返回null 。注意皮球,警告:考虑使用acquireLatestImage() ,因为它会自动释放较旧的图像,并允许运行较慢的处理最新的帧。 建议在批处理/后台处理中使用acquireNextImage() 。错误地使用此功能可能会导致图像出现延迟不断增加,然后是完全失速,看起来没有新的图像出现。
Image
类注释:提到这个类,对于我们来说有些陌生,其实我们是使用过它的。回忆一下调用系统相机拍照,是不是有些记忆了。可以查看文章底部的参考链接。Image是一个完整的多媒体图像缓冲区,如:MediaCodec、camera2。可以通过一个或多个ByteBuffer高效直接的访问,每一个缓冲区都封装在Plane这个平面布中。这里直接获取的是缓存流,这个和Bitmap有直接的区别。Image通常是由硬件直接生成或使用的,因此它们是整个系统的共享资源,在不使用的时候,应该尽快的关闭。不能直接作为UI资源。在使用ImageReader从各种媒体源读出图像时,超过getMaxImages范围,不关闭旧的Image那么将阻止新Image的可用性。往往抛出IllegalStateException。这里不清楚这个一或多具体指的什么。初步估计是多张Image,理由是,在ImageReader中可以设置mMaxImages,而且也建议是2张。通从别人的代码中猜测的,代码如下:
img = imageReader.acquireLatestImage();
if (img != null) {
Image.Plane[] planes = img.getPlanes();
if (planes[0].getBuffer() == null) {
return;
}
....
}
对于方法的话基本都是抽象方法。也不太好翻译,主要是晓不到如何下手。把几个公共的方法和获取流的方法记录一下。
setTimestamp( )
设置与此帧关联的时间戳。时间戳以纳秒为单位,通常是单调递增。 来自不同来源的图像的时间戳可能具有不同的时间基准,因此可能不具有可比性。 时间戳的具体含义和时间基础取决于提供图像的来源。 有关更多详细信息,请参阅Camera , CameraDevice , MediaPlayer和MediaCodec
getCropRect( )
获取关联的裁剪矩形。
setCropRect( )
设置相关联的裁剪矩形。
getPlanes( )
获取此图像的像素平面阵列。 平面的数量由图像的格式决定。 如果图像格式为PRIVATE ,则应用程序将获得一个空数组,因为图像像素数据不可直接访问。 应用程序可以通过调用getFormat()来检查图像格式。
通过 getPlanes( )可以获取到对应的数据流,现在怎么去把流转成图片问题接踵而来,里面有涉及到一些计算了。下面对Plane类中方法做一个记录:
getRowStride( )
此颜色平面的行跨度(以字节为单位)。这是图像中连续两行像素开始的距离。 请注意,对于某些格式(如RAW_PRIVATE ,stried未定义,对这些格式的图像调用getRowStride将导致抛出UnsupportedOperationException。 对于行跨度很好定义的格式,行跨度总是大于0。
getPixelStride( )
相邻像素采样之间的距离,以字节为单位。这是一行像素中两个连续像素值之间的距离。 它可能大于单个像素的大小,以考虑交错图像数据或填充格式。 请注意,某些格式(如RAW_PRIVATE像素跨距未定义,并且在这些格式的图像上调用getPixelStride将导致抛出UnsupportedOperationException。 对于像素跨度定义明确的格式,像素跨度总是大于0。
getBuffer( )
获取包含帧数据的直接ByteBuffer 。特别是,返回的缓冲区总是有isDirect = true ,所以底层数据可以被映射为JNI中的一个指针,而不用GetDirectBufferAddress做任何拷贝。对于原始格式,每个平面只保证包含最后一行中最后一个像素的数据。 换句话说,最后一行之后的步幅可能不会被映射到缓冲区中。 这是任何交错格式的必要条件。
看了上面半天只有一句话,你在说什么腌。列出上面的数据只是单纯的想说明,这个获取的Buffer不可以直接转换成Bitmap的。需要通过计算的。计算的公式如下:
try {
img = imageReader.acquireLatestImage();
if (img != null) {
Image.Plane[] planes = img.getPlanes();
if (planes[0].getBuffer() == null) {
return;
}
int width = img.getWidth();
int height = img.getHeight();
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * width;
Bitmap bitmap = Bitmap.createBitmap(width+rowPadding/pixelStride, height,
Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
bitmap = Bitmap.createBitmap(bitmap, 0, 0,width, height);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int options_ = 30;//压缩分辨率,比如取值为30,那么压缩了30%
bitmap.compress(Bitmap.CompressFormat.JPEG, options_, byteArrayOutputStream);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != bitmap) {
bitmap.recycle();
}
if (null != img) {
img.close();
}
}
到此5.0以上的截屏大概记录完成了。其中的代码都是使用开源大佬们的demo。不再次进行贴出,三个demo的代码分别在文章开篇的三个链接文章里面。感谢作者,感谢开源。
在5.0之下我们如何实现截屏了?在后面使用Android Stuio中有一个更加操作方便的功能就是可以在android studio的logcat界面中直接就截取屏幕。
第一步,截取安卓图片保存到sd卡
adb shell /system/bin/screencap -p /sdcard/screenshot.png(保存到SDCard)
第二步,把图片传到电脑上
adb pull /sdcard/screenshot.png d:/screenshot.png(保存到电脑)
通过以上shell命令就可完成截图,并传输到当前电脑。对于把这个命令变成一个程序,需要运行需要Root权限。没有什么实际的意义。软件商城里面有很多的截屏软件,对于具体实现并不知道。有知道的大佬可以告知一下,谢谢。