Подтвердить что ты не робот

Как получить данные предварительного просмотра камеры Android?

В моем приложении камеры отображается предварительный просмотр камеры на экране, а также обрабатывается в фоновом режиме. Вот соответствующий код, максимально сжатый (например, без обработки ошибок или показаний полей):

public final class CameraView extends SurfaceView implements
          SurfaceHolder.Callback, Runnable, PreviewCallback {

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 

    void openCamera() {
        // Called from parent activity after setting content view to CameraView
        mCamera = Camera.open();
        mCamera.setPreviewCallbackWithBuffer(this);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        new Thread(this).start(); 

        // Set CameraView to the optimal camera preview size

        final Camera.Parameters params = mCamera.getParameters();
        final List<Camera.Size> sizes = params.getSupportedPreviewSizes();
        final int screenWidth = ((View) getParent()).getWidth();
        int minDiff = Integer.MAX_VALUE;
        Camera.Size bestSize = null;

        if (getResources().getConfiguration().orientation 
                == Configuration.ORIENTATION_LANDSCAPE) {
            // Find the camera preview width that best matches the
            // width of the surface.
            for (Camera.Size size : sizes) {
                final int diff = Math.abs(size.width - screenWidth);
                if (diff < minDiff) {
                    minDiff = diff;
                    bestSize = size;
                }
            }
        } else {
            // Find the camera preview HEIGHT that best matches the 
            // width of the surface, since the camera preview is rotated.
            mCamera.setDisplayOrientation(90);
            for (Camera.Size size : sizes) {
                final int diff = Math.abs(size.height - screenWidth);
                if (Math.abs(size.height - screenWidth) < minDiff) {
                    minDiff = diff;
                    bestSize = size;
                }
            }
        }

        final int previewWidth = bestSize.width;
        final int previewHeight = bestSize.height;

        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.height = previewHeight;
        layoutParams.width = previewWidth;
        setLayoutParams(layoutParams);

        params.setPreviewFormat(ImageFormat.NV21);
        mCamera.setParameters(params);

        int size = previewWidth * previewHeight * 
            ImageFormat.getBitsPerPixel(params.getPreviewFormat()) / 8;
        mBuffer = new byte[size];
        mCamera.addCallbackBuffer(mBuffer);

        mCamera.setPreviewDisplay(mHolder);
        mCamera.startPreview();
    }

    public void onPreviewFrame(byte[] data, Camera camera) {
        CameraView.this.notify();
    }

    public void run() {
        mThreadRun = true;
        while (mThreadRun) {
            synchronized (this) {
                this.wait();
                processFrame(mBuffer); // convert to RGB and rotate - not shown
            }
            // Request a new frame from the camera by putting 
            // the buffer back into the queue
            mCamera.addCallbackBuffer(mBuffer);
        }

        mHolder.removeCallback(this);
        mCamera.stopPreview();
        mCamera.setPreviewCallback(null);
        mCamera.release();
        mCamera = null;
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        mThreadRun = false;
    }
}

На всех устройствах предварительный просмотр камеры отображается правильно, а на большинстве (эмулятор, Samsung Galaxy S3 и т.д.) данные, хранящиеся в mBuffer, также правильны (после преобразования NV21 в RGB и вращения, конечно). Тем не менее, несколько устройств не предоставляют правильные данные в onPreviewFrame. Я уверен, что данные будут правильно преобразованы в RGB после их получения, поэтому проблема заключается в исходных данных, переданных в mBuffer. Я заметил этот отчет об ошибках, относящийся к формату предварительного просмотра камеры YV12 (alias YUV420p), но я использую старый default, NV21 ( псевдоним YUV420sp), который должен поддерживаться в соответствии с стандартом совместимости (см. 7.5.3.2, внизу страницы 29).

Например, для этой сцены (показано здесь в Camera Preview на вкладке Samsung Galaxy Tab 2):

enter image description here

данные, переданные в mBuffer на вкладке 2, выглядят следующим образом:

enter image description here

и на Motorola Droid 4 выглядит так:

enter image description here

Каков правильный способ получить данные предварительного просмотра камеры Android на всех устройствах?

Изменить: для processFrame(), я использовал OpenCV для преобразования в RGB и вращения. См. этот ответ и этот ответ.

4b9b3361

Ответ 1

Единственная проблема заключалась в том, что я не задавал ширину и высоту предварительного просмотра:

params.setPreviewSize(previewWidth, previewHeight);
mCamera.setParameters(params);

Это означало, что высота и ширина, которые я выделил для массива (пропорционально PreviewWidth * previewHeight), как правило, были намного больше размера фактических возвращаемых данных (пропорционально ширине предварительного просмотра и высоте предварительного просмотра). На некоторых телефонах значение по умолчанию было такого же размера, как previewWidth и previewHeight, поэтому проблем не было.

Ответ 2

Вы также можете попробовать это

public void takeSnapPhoto() {
camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        Camera.Parameters parameters = camera.getParameters();
        int format = parameters.getPreviewFormat();
        //YUV formats require more conversion
        if (format == ImageFormat.NV21 || format == ImageFormat.YUY2 || format == ImageFormat.NV16) {
            int w = parameters.getPreviewSize().width;
            int h = parameters.getPreviewSize().height;
            // Get the YuV image
            YuvImage yuv_image = new YuvImage(data, format, w, h, null);
            // Convert YuV to Jpeg
            Rect rect = new Rect(0, 0, w, h);
            ByteArrayOutputStream output_stream = new ByteArrayOutputStream();
            yuv_image.compressToJpeg(rect, 100, output_stream);
            byte[] byt = output_stream.toByteArray();
            FileOutputStream outStream = null;
            try {
                // Write to SD Card
                File file = createFileInSDCard(FOLDER_PATH, "Image_"+System.currentTimeMillis()+".jpg");
                //Uri uriSavedImage = Uri.fromFile(file);
                outStream = new FileOutputStream(file);
                outStream.write(byt);
                outStream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
            }
        }
    }
});}

Ответ 3

От работы с сканером штрих-кодов, который в значительной степени зависит от данных предварительного просмотра, я чувствую, что видел каждую ошибку под солнцем. Мое предложение просто не вызывать setPreviewFormat() и использовать его по умолчанию. По умолчанию это то, чего вы хотите здесь, к счастью. Кажется, что меньше устройств, которые не получают права по умолчанию, чем устройство, которое выполняет вызов вызова setPreviewFormat(). Попытайтесь, по крайней мере, может или не может быть.