Ale*_*aro 18 android bitmap surfaceview
借助MediaProjectionAndroid L中提供的API,可以实现
将主屏幕的内容(默认显示)捕获到Surface对象中,然后您的应用可以通过网络发送该对象
我已经设法让VirtualDisplay工作,我SurfaceView正确显示屏幕的内容.
我想要做的是捕获显示在其中的框架Surface,并将其打印到文件.我尝试过以下内容,但我得到的只是一个黑色文件:
Bitmap bitmap = Bitmap.createBitmap
(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
surfaceView.draw(canvas);
printBitmapToFile(bitmap);
Run Code Online (Sandbox Code Playgroud)
有关如何从中检索显示数据的任何想法Surface?
因此,作为@j__m建议,现在我的设置VirtualDisplay使用Surface的ImageReader:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
displayWidth = size.x;
displayHeight = size.y;
imageReader = ImageReader.newInstance(displayWidth, displayHeight, ImageFormat.JPEG, 5);
Run Code Online (Sandbox Code Playgroud)
然后我创建虚拟显示传递Surface到MediaProjection:
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
DisplayMetrics metrics = getResources().getDisplayMetrics();
int density = metrics.densityDpi;
mediaProjection.createVirtualDisplay("test", displayWidth, displayHeight, density, flags,
imageReader.getSurface(), null, projectionHandler);
Run Code Online (Sandbox Code Playgroud)
最后,为了获得"截图",我Image从中获取并从中ImageReader读取数据:
Image image = imageReader.acquireLatestImage();
byte[] data = getDataFromImage(image);
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
Run Code Online (Sandbox Code Playgroud)
问题是生成的位图是null.
这是getDataFromImage方法:
public static byte[] getDataFromImage(Image image) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
byte[] data = new byte[buffer.capacity()];
buffer.get(data);
return data;
}
Run Code Online (Sandbox Code Playgroud)
从Image返回的acquireLatestImage始终具有默认大小为7672320的数据并且解码返回null.
更具体地,当ImageReader尝试获取图像时,ACQUIRE_NO_BUFS返回状态.
use*_*723 14
在花了一些时间学习Android图形架构之后,我得到了它的工作.所有必要的组件都充分证明,但可引起头痛,如果你不熟悉的OpenGL,所以这里是一个很好的总结"傻瓜".
我假设你
BufferQueue就是这样ImageReader的.这班被评为较差开始用-这将是更好地称之为"ImageReceiver" -各地(通过任何其他公共API无法访问)接收BufferQueue结束一个愚蠢的包装.不要被愚弄:它不会执行任何转换.它不允许生成器支持的查询格式,即使C++ BufferQueue在内部公开该信息也是如此.它可能在简单的情况下失败,例如,如果生产者使用自定义的,模糊的格式(例如BGRA).
上面列出的问题是为什么我建议使用OpenGL ES glReadPixels作为通用后备,但仍试图如果有使用ImageReader的,因为它有可能允许以最少的拷贝/转换检索图像.
为了更好地了解如何使用OpenGL执行任务,让我们来看看Surface,由ImageReader/MediaCodec返回.没有什么特别的,只有SurfaceTexture顶部的普通Surface有两个陷阱:OES_EGL_image_external和EGL_ANDROID_recordable.
简单地说,OES_EGL_image_external是一个标志,必须传递给glBindTexture才能使纹理与BufferQueue一起工作.它不是定义特定的颜色格式等,而是从生产者那里收到的任何不透明的容器.实际内容可以是在YUV色彩空间(强制性相机API),RGBA/BGRA(经常使用的视频驱动器)或其他可能特定于厂商的格式.制作人可能会提供一些细节,例如JPEG或RGB565表示,但不会让你的希望高涨.
从Android 6.0开始,CTS测试覆盖的唯一生产者是Camera API(仅AFAIK的Java外观).究其原因,为什么有很多MediaProjection + RGBA8888 ImageReader的例子到处飞,是因为它是一个经常遇到的常用的单位和唯一的格式,通过OpenGL ES的规格为glReadPixels授权.不过,如果显示的作曲家决定使用完全不可读格式或简单的一个,通过的ImageReader类(如BGRA8888)不支持,你将不得不面对它不要感到惊讶.
从阅读规范可以看出,它是一个标志,传递给eglChooseConfig,以便轻轻地推动制作人生成YUV图像.或优化管道以便从视频内存中读取.或者其他的东西.我不知道任何CTS测试,确保它是正确的治疗(甚至规范本身表明,个别生产者可能硬编码给予特殊处理),所以如果它不恰当的话不要感到惊讶(参见Android 5.0模拟器)或默默忽略.Java类中没有定义,只需自己定义常量,就像Grafika一样.
那么在背景中"正确的方式"从VirtualDisplay读取应该做些什么呢?
您的OnFrameAvailableListener回调将包含以下步骤:
当使用ImageReader读取视频内存时,上述大多数步骤都在内部执行,但有些不同.创建缓冲区中行的对齐可以通过glPixelStore定义(默认为4,因此在使用4字节RGBA8888时不必考虑它).
请注意,除了使用着色器处理纹理外,GL ES不会在格式之间自动转换(与桌面OpenGL不同).如果您需要RGBA8888数据,请确保以该格式分配屏幕外缓冲区并从glReadPixels请求它.
EglCore eglCore;
Surface producerSide;
SurfaceTexture texture;
int textureId;
OffscreenSurface consumerSide;
ByteBuffer buf;
Texture2dProgram shader;
FullFrameRect screen;
...
// dimensions of the Display, or whatever you wanted to read from
int w, h = ...
// feel free to try FLAG_RECORDABLE if you want
eglCore = new EglCore(null, EglCore.FLAG_TRY_GLES3);
consumerSide = new OffscreenSurface(eglCore, w, h);
consumerSide.makeCurrent();
shader = new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT)
screen = new FullFrameRect(shader);
texture = new SurfaceTexture(textureId = screen.createTextureObject(), false);
texture.setDefaultBufferSize(reqWidth, reqHeight);
producerSide = new Surface(texture);
texture.setOnFrameAvailableListener(this);
buf = ByteBuffer.allocateDirect(w * h * 4);
buf.order(ByteOrder.nativeOrder());
currentBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Run Code Online (Sandbox Code Playgroud)
只有完成上述所有操作后,才能使用producerSideSurface 初始化VirtualDisplay .
帧回调代码:
float[] matrix = new float[16];
boolean closed;
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// there may still be pending callbacks after shutting down EGL
if (closed) return;
consumerSide.makeCurrent();
texture.updateTexImage();
texture.getTransformMatrix(matrix);
consumerSide.makeCurrent();
// draw the image to framebuffer object
screen.drawFrame(textureId, matrix);
consumerSide.swapBuffers();
buffer.rewind();
GLES20.glReadPixels(0, 0, w, h, GLES10.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
buffer.rewind();
currentBitmap.copyPixelsFromBuffer(buffer);
// congrats, you should have your image in the Bitmap
// you can release the resources or continue to obtain
// frames for whatever poor-man's video recorder you are writing
}
Run Code Online (Sandbox Code Playgroud)
上面的代码是一个大大简化的方法版本,可以在这个Github项目中找到,但是所有引用的类都直接来自Grafika.
根据您的硬件,您可能需要跳出一些额外的箍来完成任务:使用setSwapInterval,在制作屏幕截图之前调用glFlush等.其中大部分都可以通过LogCat的内容自行计算出来.
为了避免Y坐标反转,请将Grafika使用的顶点着色器替换为以下内容:
String VERTEX_SHADER_FLIPPED =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uTexMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vec2 coordInterm = (uTexMatrix * aTextureCoord).xy;\n" +
// "OpenGL ES: how flip the Y-coordinate: 6542nd edition"
" vTextureCoord = vec2(coordInterm.x, 1.0 - coordInterm.y);\n" +
"}\n";
Run Code Online (Sandbox Code Playgroud)
上述方法可以用来当ImageReader的不适合你,或者如果你想从GPU运动图像之前执行的图面内容有些着色器处理.
通过对屏幕外缓冲区执行额外复制可能会损害速度,但如果您知道接收缓冲区的确切格式(例如,来自ImageReader)并使用相同格式的glReadPixels,则运行着色器的影响将会很小.
例如,如果您的视频驱动程序使用BGRA内部格式,你会检查是否EXT_texture_format_BGRA8888支持(很可能会),分配离屏缓冲区,并以这种格式与glReadPixels retrive图像.
如果要执行一个完整的零拷贝或使用的格式,而不是OpenGL的(如JPEG)的支持,你还是最好使用ImageReader的.
各种"我如何捕捉SurfaceView的屏幕截图"答案(例如这一个)仍然适用:你不能这样做.
SurfaceView的表面是一个单独的图层,由系统合成,独立于基于视图的UI图层.曲面不是像素的缓冲区,而是缓冲区的队列,具有生产者 - 消费者安排.你的应用程序是在制作人一方.获得屏幕截图需要您在消费者方面.
如果将输出定向到SurfaceTexture而不是SurfaceView,则应用程序进程中将包含缓冲区队列的两侧.您可以使用GLES渲染输出并将其读入数组glReadPixels(). Grafika有一些使用Camera预览做这样的事情的例子.
要将屏幕捕获为视频,或通过网络发送,您可能希望将其发送到MediaCodec编码器的输入表面.
有关Android图形架构的更多详细信息,请点击此处.
我有这个工作代码:
mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 5);
mProjection.createVirtualDisplay("test", width, height, density, flags, mImageReader.getSurface(), new VirtualDisplayCallback(), mHandler);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = null;
FileOutputStream fos = null;
Bitmap bitmap = null;
try {
image = mImageReader.acquireLatestImage();
fos = new FileOutputStream(getFilesDir() + "/myscreen.jpg");
final Image.Plane[] planes = image.getPlanes();
final Buffer buffer = planes[0].getBuffer().rewind();
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
bitmap.compress(CompressFormat.JPEG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos!=null) {
try {
fos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
if (bitmap!=null)
bitmap.recycle();
if (image!=null)
image.close();
}
}
}, mHandler);
Run Code Online (Sandbox Code Playgroud)
我相信Bytebuffer上的rewind()做了伎俩,但并不确定为什么.我正在针对Android模拟器21进行测试,因为目前我还没有安装Android-5.0设备.
希望能帮助到你!