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

时间:2014-11-30 23:58:53

标签: java android android-5.0-lollipop android-bitmap nexus6

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

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

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

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

我认为这个功能几近完成,但是I then discovered SurfaceViews(从代码的角度来看)是不可读的;你无法从他们那里获得位图。

在寻找其他解决方案时,我遇到了this question,其目标与我的目标非常相似,并且在该线程中suggested使用ImageReader而不是SurfaceView来源Surface传递给createVirtualDisplay API调用。

但是,当我更改我的代码以使用ImageReader代替SurfaceView时,我得到运行时logcat错误(没有异常),并且永远不会调用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调用期间发生了第一个错误,所有其他错误在执行返回系统代码后的某个时刻发生。

此错误的only meaningful result与Kitkat中的问题有关,我尝试使用的API不存在。尽管如此,我尝试了fix suggested here(在清单中放置android:hardwareAccelerated="false")。这并没有改变应用程序的行为。

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

P.S。我的开发平台是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);
                }
            });

2 个答案:

答案 0 :(得分:7)

我想我现在可以回答这个问题了,我遇到了同样的问题,在我将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。

答案 1 :(得分:0)

从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捕获屏幕的过程,以及大多数人在blog post中从ImageReader获取图像时所犯的错误,如果感兴趣,您可以阅读。