Системная ошибка при записи вывода виртуального дисплея MediaProjection в ImageReader

Я работаю над приложением, которое должно захватывать экран для растрового изображения для передачи. Я пытаюсь использовать новые API Android 5.0 android.media.projection для захвата экрана.

Рабочий процесс для этого API завершается вызовом

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

В моей первоначальной попытке этого захвата я получил поверхностный объект из SurfaceView. Это работает правильно; конечный результат - крошечная копия отображаемого на экране дисплея (в результате получается эффект Droste)

Я думал, что функция почти завершена, но потом я обнаружил, что SurfaceViews (с точки зрения кода) не читаются; Вы не можете получить растровое изображение от них.

В поисках других решений я столкнулся с этим вопросом, цель которого очень похожа на мою, и в этом потоке предлагается использовать ImageReader вместо SurfaceView для создания источника Surface, который вы передаете вызову API createVirtualDisplay.

Однако, когда я изменяю свой код для использования ImageReader вместо SurfaceView, я получаю ошибки logcat во время выполнения (без исключений), и функция обратного вызова для ImageReader никогда не вызывается. Вызов createVirtualDisplay также возвращает кажущийся действительным объект VirtualDisplay.

Вот логкат:

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);
                }
            });

2 ответа

Решение

Я думаю, что могу ответить на этот вопрос сейчас, я столкнулся с той же проблемой, и после того, как я изменяю 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 довольно плохая. нам нужно изучить слишком много деталей, чтобы правильно использовать SDK API.

Лучший способ получить изображение из ImageReader - просто создать растровое изображение правильного размера и использовать метод copyPixelsFromBuffer(). Создайте ImageReader следующим образом:

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

Затем вы можете получить изображение из m ImageReader, используя код ниже.

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 в блоге, который вы можете прочитать, если интересно.

Другие вопросы по тегам