捕获MediaProjection虚拟显示器输出到ImageReader的系统错误

我正在开发一个需要将屏幕捕获到位图进行传输的应用程序。 我正在尝试使用新的Android 5.0 android.media.projection API来进行屏幕捕获。

此API的工作流程最终会调用

mediaProjection.createVirtualDisplay("Test Screen", WIDTH, HEIGHT, DPI, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null); 

在我最初尝试捕获时,我从SurfaceView中获取了表面对象。 这工作正常; 最终结果是在屏幕上绘制的显示器的微小副本(导致Droste效果 )

我认为这个function几近完成,但我发现 SurfaceViews(从代码的角度来看)是不可读的; 你不能从他们那里得到一个位图。

在寻找其他解决方案时,我遇到了这个与我的目标非常相似的问题 ,并且在该线程中建议使用ImageReader而不是SurfaceView来获取传递给createVirtualDisplay API调用的Surface。

但是,当我更改我的代码以使用ImageReader代替SurfaceView时,我得到运行时logcat错误(没有exception),并且永远不会调用ImageReader的回调函数。 createVirtualDisplay调用还返回一个看似有效的VirtualDisplay对象。

这是logcat:

 9230-9270/com.android.techrocket9.nanoid E/BufferQueueProducer﹕ [unnamed-9230-0] dequeueBuffer: createGraphicBuffer failed 9230-9246/com.android.techrocket9.nanoid E/BufferQueueProducer﹕ [unnamed-9230-0] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count 9230-9246/com.android.techrocket9.nanoid E/BufferQueueProducer﹕ [unnamed-9230-0] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count 9230-9246/com.android.techrocket9.nanoid E/BufferQueueProducer﹕ [unnamed-9230-0] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count 9230-9246/com.android.techrocket9.nanoid E/BufferQueueProducer﹕ [unnamed-9230-0] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count 

第二行在它停止发生之前重复约100次。

单步执行调试器,我看到第一个错误发生在createVirtualDisplay调用期间,所有其他错误在执行返回系统代码后发生。

这个错误的唯一有意义的结果与Kitkat中的一个问题有关,我试图消耗的API不存在。 尽管如此,我尝试了这里建议的修复 (在清单中放置android:hardwareAccelerated="false" )。 这并没有改变应用程序的行为。

如何“设置缓冲区计数”或以其他方式解决此错误并将屏幕作为位图?

PS我的开发平台是Nexus 6。

完整的代码块,根据要求:

 MediaProjection mediaProjection = mgr.getMediaProjection(resultCode, data); ImageReader ir = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.JPEG, 5); VirtualDisplay v = mediaProjection.createVirtualDisplay("Test Screen", WIDTH, HEIGHT, getApplicationContext().getResources().getDisplayMetrics().densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, ir.getSurface(), null, null); 

编辑:关于工件问题,这里是我用来从图像中获取位图并显示它的代码:

  public void onImageAvailable(ImageReader reader) { Image image = null; ByteArrayOutputStream bos = null; try { image = reader.acquireLatestImage(); if (null == image){ return; } bos = new ByteArrayOutputStream(); final Image.Plane[] planes = image.getPlanes(); final ByteBuffer buffer = (ByteBuffer) planes[0].getBuffer().rewind(); final Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); //bitmap.compress(Bitmap.CompressFormat.WEBP, 50, bos); runOnUiThread(new Runnable() { public void run() { iv.setImageBitmap(bitmap); } }); 

我想我现在可以回答这个问题,我遇到了同样的问题,在我将ImageFormat.JPEG改为PixelFormat.RGBA_8888一切顺利。 似乎不支持ImageFormat.JPEG。

您需要使用以下代码来获取正确的位图:

  int width = img.getWidth(); int height = img.getHeight(); int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * width; byte[] newData = new byte[width * height * 4]; int offset = 0; bitmap = Bitmap.createBitmap(metrics,width, height, Bitmap.Config.ARGB_8888); ByteBuffer buffer = planes[0].getBuffer(); for (int i = 0; i < height; ++i) { for (int j = 0; j < width; ++j) { int pixel = 0; pixel |= (buffer.get(offset) & 0xff) << 16; // R pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G pixel |= (buffer.get(offset + 2) & 0xff); // B pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A bitmap.setPixel(j, i, pixel); offset += pixelStride; } offset += rowPadding; } 

通过这种方式,位图的内容就是你想要的。

PS:我真的想说,android的doc非常糟糕。 我们需要调查太多细节才能正确使用sdk api。

从ImageReader获取Image的更好方法是创建正确大小的位图并使用方法copyPixelsFromBuffer()。 创建ImageReader,如下所示:

 mImageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.RGB_565, 2); 

然后,您可以使用下面的代码从mImageReader获取图像。

 final Image.Plane[] planes = image.getPlanes(); final ByteBuffer buffer = planes[0].getBuffer(); int offset = 0; int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * mWidth; // create bitmap bitmap = Bitmap.createBitmap(mWidth+rowPadding/pixelStride, mHeight, Bitmap.Config.RGB_565); bitmap.copyPixelsFromBuffer(buffer); image.close(); 

我已经描述了使用MediaProjection API捕获屏幕的过程以及大多数人在博客文章中从ImageReader获取图像时所犯的错误,如果感兴趣的话可以阅读。