ronaldkwandy

CvCameraPreview

May 11th, 2018
141
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 32.80 KB | None | 0 0
  1. package com.example.user.testjavacv;
  2.  
  3. import android.app.Activity;
  4. import android.app.AlertDialog;
  5. import android.content.Context;
  6. import android.content.DialogInterface;
  7. import android.content.res.TypedArray;
  8. import android.graphics.Bitmap;
  9. import android.graphics.Canvas;
  10. import android.graphics.ImageFormat;
  11. import android.graphics.Rect;
  12. import android.graphics.SurfaceTexture;
  13. import android.hardware.Camera;
  14. import android.hardware.Camera.PictureCallback;
  15. import android.hardware.Camera.PreviewCallback;
  16. import android.hardware.Camera.Size;
  17. import android.os.Build;
  18. import android.util.AttributeSet;
  19. import android.util.Log;
  20. import android.view.Surface;
  21. import android.view.SurfaceHolder;
  22. import android.view.SurfaceView;
  23.  
  24. import org.bytedeco.javacpp.opencv_core.Mat;
  25. import org.bytedeco.javacv.AndroidFrameConverter;
  26. import org.bytedeco.javacv.FFmpegFrameFilter;
  27. import org.bytedeco.javacv.Frame;
  28. import org.bytedeco.javacv.FrameFilter;
  29. import org.bytedeco.javacv.OpenCVFrameConverter;
  30.  
  31. import java.io.IOException;
  32. import java.nio.ByteBuffer;
  33. import java.util.List;
  34.  
  35. import static org.bytedeco.javacpp.avutil.AV_PIX_FMT_NV21;
  36.  
  37. public class CvCameraPreview extends SurfaceView implements SurfaceHolder.Callback, PreviewCallback {
  38.  
  39.     private final String LOG_TAG = "CvCameraPreview";
  40.  
  41.     private static final int STOPPED = 0;
  42.     private static final int STARTED = 1;
  43.  
  44.     public static final int CAMERA_BACK = 99;
  45.     public static final int CAMERA_FRONT = 98;
  46.  
  47.     public static final int SCALE_FIT = 1;
  48.     public static final int SCALE_FULL = 2;
  49.  
  50.     private static final int MAGIC_TEXTURE_ID = 10;
  51.  
  52.     /**
  53.      * ASPECT_RATIO_W and ASPECT_RATIO_H define the aspect ratio
  54.      * of the Surface. They are used when {@link #onMeasure(int, int)}
  55.      * is called.
  56.      */
  57.     private final float ASPECT_RATIO_W = 4.0f;
  58.     private final float ASPECT_RATIO_H = 3.0f;
  59.  
  60.     /**
  61.      * The maximum dimension (in pixels) of the preview frames that are produced
  62.      * by the Camera object. Note that this should not be intended as
  63.      * the final, exact, dimension because the device could not support
  64.      * it and a lower value is required (but the aspect ratio should remain the same).<br />
  65.      * See {@link CvCameraPreview#getBestSize(List, int)} for more information.
  66.      */
  67.     private final int PREVIEW_MAX_WIDTH = 640;
  68.  
  69.     /**
  70.      * The maximum dimension (in pixels) of the images produced when a
  71.      * {@link PictureCallback#onPictureTaken(byte[], Camera)} event is
  72.      * fired. Again, this is a maximum value and could not be the
  73.      * real one implemented by the device.
  74.      */
  75.     private final int PICTURE_MAX_WIDTH = 1280;
  76.  
  77.     /**
  78.      * In this example we look at camera preview buffer functionality too.<br />
  79.      * This is the array that will be filled everytime a single preview frame is
  80.      * ready to be processed (for example when we want to show to the user
  81.      * a transformed preview instead of the original one, or when we want to
  82.      * make some image analysis in real-time without taking full-sized pictures).
  83.      */
  84.     private byte[] previewBuffer;
  85.  
  86.     /**
  87.      * The "holder" is the underlying surface.
  88.      */
  89.     private SurfaceHolder surfaceHolder;
  90.  
  91.     private FFmpegFrameFilter filter;
  92.     private int chainIdx = 0;
  93.     private boolean stopThread = false;
  94.     private boolean cameraFrameReady = false;
  95.     protected boolean enabled = true;
  96.     private boolean surfaceExist;
  97.     private Thread thread;
  98.     private CvCameraViewListener listener;
  99.     private AndroidFrameConverter converterToBitmap = new AndroidFrameConverter();
  100.     private OpenCVFrameConverter.ToMat converterToMat = new OpenCVFrameConverter.ToMat();
  101.     private Bitmap cacheBitmap;
  102.     protected Frame[] cameraFrame;
  103.     private int state = STOPPED;
  104.     private final Object syncObject = new Object();
  105.     private int cameraId = -1;
  106.     private int cameraType = Camera.CameraInfo.CAMERA_FACING_FRONT;
  107.     private Camera cameraDevice;
  108.     private SurfaceTexture surfaceTexture;
  109.     private int frameWidth, frameHeight;
  110.     private int scaleType = SCALE_FIT;
  111.  
  112.     public CvCameraPreview(Context context, AttributeSet attrs) {
  113.         super(context, attrs);
  114.  
  115.         TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.CvCameraPreview);
  116.         int camType = array.getInt(R.styleable.CvCameraPreview_camera_type, CAMERA_BACK);
  117.         int scaleType = array.getInt(R.styleable.CvCameraPreview_scale_type, SCALE_FIT);
  118.         array.recycle();
  119.  
  120.         initializer(camType == CAMERA_BACK ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, scaleType);
  121.     }
  122.  
  123.     public CvCameraPreview(Context context, int camType, int scaleType) {
  124.         super(context);
  125.  
  126.         initializer(camType == CAMERA_BACK ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, scaleType);
  127.     }
  128.  
  129.     private void initializer(int camType, int scaleType) {
  130.  
  131.         this.surfaceHolder = this.getHolder();
  132.         this.surfaceHolder.addCallback(this);
  133.  
  134.         this.scaleType = scaleType;
  135.         this.cameraType = camType;
  136.  
  137.         // deprecated setting, but required on Android versions prior to API 11
  138.         if (Build.VERSION.SDK_INT < 11) {
  139.             this.surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  140.         }
  141.     }
  142.  
  143.     public void setCvCameraViewListener(CvCameraViewListener listener) {
  144.         this.listener = listener;
  145.     }
  146.  
  147.     public int getCameraId() {
  148.         return cameraId;
  149.     }
  150.  
  151.     public void setCameraType(int camType) {
  152.         initializer(camType == CAMERA_BACK ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, scaleType);
  153.     }
  154.  
  155.     /**
  156.      * Called when the surface is created for the first time. It sets all the
  157.      * required {@link #cameraDevice}'s parameters and starts the preview stream.
  158.      *
  159.      * @param holder
  160.      */
  161.     @Override
  162.     public void surfaceCreated(SurfaceHolder holder) {
  163.         /* Do nothing. Wait until surfaceChanged delivered */
  164.     }
  165.  
  166.     /**
  167.      * [IMPORTANT!] A SurfaceChanged event means that the parent graphic has changed its layout
  168.      * (for example when the orientation changes). It's necessary to update the {@link CvCameraPreview}
  169.      * orientation, so the preview is stopped, then updated, then re-activated.
  170.      *
  171.      * @param holder The SurfaceHolder whose surface has changed
  172.      * @param format The new PixelFormat of the surface
  173.      * @param w      The new width of the surface
  174.      * @param h      The new height of the surface
  175.      */
  176.     @Override
  177.     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
  178.         if (this.surfaceHolder.getSurface() == null) {
  179.             Log.e(LOG_TAG, "surfaceChanged(): surfaceHolder is null, nothing to do.");
  180.             return;
  181.         }
  182.  
  183.         synchronized (syncObject) {
  184.             surfaceExist = false;
  185.             checkCurrentState();
  186.             /* Now use new surface. Say we have it now */
  187.             surfaceExist = true;
  188.             checkCurrentState();
  189.  
  190. //            if (!surfaceExist) {
  191. //                surfaceExist = true;
  192. //                checkCurrentState();
  193. //            } else {
  194. //                /** Surface changed. We need to stop camera and restart with new parameters */
  195. //                /* Pretend that old surface has been destroyed */
  196. //                surfaceExist = false;
  197. //                checkCurrentState();
  198. //                /* Now use new surface. Say we have it now */
  199. //                surfaceExist = true;
  200. //                checkCurrentState();
  201. //            }
  202.         }
  203.     }
  204.  
  205.     /**
  206.      * Called when syncObject lock is held
  207.      */
  208.     private void checkCurrentState() {
  209.         Log.d(LOG_TAG, "call checkCurrentState");
  210.         int targetState;
  211.  
  212.         if (enabled && surfaceExist && getVisibility() == VISIBLE) {
  213.             targetState = STARTED;
  214.         } else {
  215.             targetState = STOPPED;
  216.         }
  217.  
  218.         if (targetState != state) {
  219.             /* The state change detected. Need to exit the current state and enter target state */
  220.             processExitState(state);
  221.             state = targetState;
  222.             processEnterState(state);
  223.         }
  224.     }
  225.  
  226.     private void processExitState(int state) {
  227.         Log.d(LOG_TAG, "call processExitState: " + state);
  228.         switch (state) {
  229.             case STARTED:
  230.                 onExitStartedState();
  231.                 break;
  232.             case STOPPED:
  233.                 onExitStoppedState();
  234.                 break;
  235.         }
  236.         ;
  237.     }
  238.  
  239.     private void processEnterState(int state) {
  240.         Log.d(LOG_TAG, "call processEnterState: " + state);
  241.         switch (state) {
  242.             case STARTED:
  243.                 onEnterStartedState();
  244.                 if (listener != null) {
  245.                     listener.onCameraViewStarted(frameWidth, frameHeight);
  246.                 }
  247.                 break;
  248.             case STOPPED:
  249.                 onEnterStoppedState();
  250.                 if (listener != null) {
  251.                     listener.onCameraViewStopped();
  252.                 }
  253.                 break;
  254.         }
  255.     }
  256.  
  257.     private void onEnterStoppedState() {
  258.         /* nothing to do */
  259.     }
  260.  
  261.     private void onExitStoppedState() {
  262.         /* nothing to do */
  263.     }
  264.  
  265.     // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x
  266.     // Bitmap must be constructed before surface
  267.     private void onEnterStartedState() {
  268.         Log.d(LOG_TAG, "call onEnterStartedState");
  269.         /* Connect camera */
  270.         if (!connectCamera()) {
  271.             AlertDialog ad = new AlertDialog.Builder(getContext()).create();
  272.             ad.setCancelable(false); // This blocks the 'BACK' button
  273.             ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed.");
  274.             ad.setButton(DialogInterface.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
  275.                 public void onClick(DialogInterface dialog, int which) {
  276.                     dialog.dismiss();
  277.                     ((Activity) getContext()).finish();
  278.                 }
  279.             });
  280.             ad.show();
  281.  
  282.         }
  283.     }
  284.  
  285.     private void onExitStartedState() {
  286.         disconnectCamera();
  287.         if (cacheBitmap != null) {
  288.             cacheBitmap.recycle();
  289.         }
  290.         if (filter != null) {
  291.             try {
  292.                 filter.release();
  293.             } catch (FrameFilter.Exception e) {
  294.                 e.printStackTrace();
  295.             }
  296.         }
  297.     }
  298.  
  299.     @Override
  300.     public void surfaceDestroyed(SurfaceHolder holder) {
  301.         Log.d(LOG_TAG, "surfaceDestroyed");
  302.         synchronized (syncObject) {
  303.             surfaceExist = false;
  304.             checkCurrentState();
  305.         }
  306.     }
  307.  
  308.     /**
  309.      * [IMPORTANT!] Probably the most important method here. Lots of users experience bad
  310.      * camera behaviors because they don't override this guy.
  311.      * In fact, some Android devices are very strict about the size of the surface
  312.      * where the preview is printed: if its ratio is different from the
  313.      * original one, it results in errors like "startPreview failed".<br />
  314.      * This methods takes care on this and applies the right size to the
  315.      * {@link CvCameraPreview}.
  316.      *
  317.      * @param widthMeasureSpec  horizontal space requirements as imposed by the parent.
  318.      * @param heightMeasureSpec vertical space requirements as imposed by the parent.
  319.      */
  320.     @Override
  321.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  322.         int height = MeasureSpec.getSize(heightMeasureSpec);
  323.         int width = MeasureSpec.getSize(widthMeasureSpec);
  324.  
  325.         // do some ultra high precision math...
  326.         float expectedRatio = height > width ? ASPECT_RATIO_H / ASPECT_RATIO_W : ASPECT_RATIO_W / ASPECT_RATIO_H;
  327.         float screenRatio = width * 1f / height;
  328.         if (screenRatio > expectedRatio) {
  329.             if (scaleType == SCALE_FULL) {
  330.                 height = (int) (width / expectedRatio + .5);
  331.             } else {
  332.                 width = (int) (height * expectedRatio + .5);
  333.             }
  334.         } else {
  335.             if (scaleType == SCALE_FULL) {
  336.                 width = (int) (height * expectedRatio + .5);
  337.             } else {
  338.                 height = (int) (width / expectedRatio + .5);
  339.             }
  340.         }
  341.  
  342.         setMeasuredDimension(width, height);
  343.         Log.i(LOG_TAG, "onMeasure(): set surface dimension to " + width + "x" + height);
  344.     }
  345.  
  346.     private boolean connectCamera() {
  347.         /* 1. We need to instantiate camera
  348.          * 2. We need to start thread which will be getting frames
  349.          */
  350.         /* First step - initialize camera connection */
  351.         Log.d(LOG_TAG, "Connecting to camera");
  352.         if (!initializeCamera())
  353.             return false;
  354.  
  355.         /* now we can start update thread */
  356.         Log.d(LOG_TAG, "Starting processing thread");
  357.         stopThread = false;
  358.         thread = new Thread(new CameraWorker());
  359.         thread.start();
  360.  
  361.         return true;
  362.     }
  363.  
  364.     private void disconnectCamera() {
  365.         /* 1. We need to stop thread which updating the frames
  366.          * 2. Stop camera and release it
  367.          */
  368.         Log.d(LOG_TAG, "Disconnecting from camera");
  369.         try {
  370.             stopThread = true;
  371.             Log.d(LOG_TAG, "Notify thread");
  372.             synchronized (this) {
  373.                 this.notify();
  374.             }
  375.             Log.d(LOG_TAG, "Wating for thread");
  376.             if (thread != null)
  377.                 thread.join();
  378.         } catch (InterruptedException e) {
  379.             e.printStackTrace();
  380.         } finally {
  381.             thread = null;
  382.         }
  383.  
  384.         stopCameraPreview();
  385.  
  386.         /* Now release camera */
  387.         releaseCamera();
  388.  
  389.         cameraFrameReady = false;
  390.     }
  391.  
  392.     private boolean initializeCamera() {
  393.         synchronized (this) {
  394.             if (this.cameraDevice != null) {
  395.                 // do the job only if the camera is not already set
  396.                 Log.i(LOG_TAG, "initializeCamera(): camera is already set, nothing to do");
  397.                 return true;
  398.             }
  399.  
  400.             // warning here! starting from API 9, we can retrieve one from the multiple
  401.             // hardware cameras (ex. front/back)
  402.             if (Build.VERSION.SDK_INT >= 9) {
  403.  
  404.                 if (this.cameraId < 0) {
  405.                     // at this point, it's the first time we request for a camera
  406.                     Camera.CameraInfo camInfo = new Camera.CameraInfo();
  407.                     for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
  408.                         Camera.getCameraInfo(i, camInfo);
  409.  
  410.                         if (camInfo.facing == cameraType) {
  411.                             // in this example we'll request specifically the back camera
  412.                             try {
  413.                                 this.cameraDevice = Camera.open(i);
  414.                                 this.cameraId = i; // assign to cameraId this camera's ID (O RLY?)
  415.                                 break;
  416.                             } catch (RuntimeException e) {
  417.                                 // something bad happened! this camera could be locked by other apps
  418.                                 Log.e(LOG_TAG, "initializeCamera(): trying to open camera #" + i + " but it's locked", e);
  419.                             }
  420.                         }
  421.                     }
  422.                 } else {
  423.                     // at this point, a previous camera was set, we try to re-instantiate it
  424.                     try {
  425.                         this.cameraDevice = Camera.open(this.cameraId);
  426.                     } catch (RuntimeException e) {
  427.                         Log.e(LOG_TAG, "initializeCamera(): trying to re-open camera #" + this.cameraId + " but it's locked", e);
  428.                     }
  429.                 }
  430.             }
  431.  
  432.             // we could reach this point in two cases:
  433.             // - the API is lower than 9
  434.             // - previous code block failed
  435.             // hence, we try the classic method, that doesn't ask for a particular camera
  436.             if (this.cameraDevice == null) {
  437.                 try {
  438.                     this.cameraDevice = Camera.open();
  439.                     this.cameraId = 0;
  440.                 } catch (RuntimeException e) {
  441.                     // this is REALLY bad, the camera is definitely locked by the system.
  442.                     Log.e(LOG_TAG,
  443.                             "initializeCamera(): trying to open default camera but it's locked. "
  444.                                     + "The camera is not available for this app at the moment.", e
  445.                     );
  446.                     return false;
  447.                 }
  448.             }
  449.  
  450.             // here, the open() went good and the camera is available
  451.             Log.i(LOG_TAG, "initializeCamera(): successfully set camera #" + this.cameraId);
  452.  
  453.             setupCamera();
  454.  
  455.             updateCameraDisplayOrientation();
  456.  
  457.             initFilter(frameWidth, frameHeight);
  458.  
  459.             startCameraPreview(this.surfaceHolder);
  460.         }
  461.  
  462.         return true;
  463.     }
  464.  
  465.     /**
  466.      * It sets all the required parameters for the Camera object, like preview
  467.      * and picture size, format, flash modes and so on.
  468.      * In this particular example it initializes the {@link #previewBuffer} too.
  469.      */
  470.     private boolean setupCamera() {
  471.         if (cameraDevice == null) {
  472.             Log.e(LOG_TAG, "setupCamera(): warning, camera is null");
  473.             return false;
  474.         }
  475.         try {
  476.             Camera.Parameters parameters = cameraDevice.getParameters();
  477.             List<Size> sizes = parameters.getSupportedPreviewSizes();
  478.             if (sizes != null) {
  479.                 Size bestPreviewSize = getBestSize(sizes, PREVIEW_MAX_WIDTH);
  480.  
  481.                 frameWidth = bestPreviewSize.width;
  482.                 frameHeight = bestPreviewSize.height;
  483.  
  484.                 parameters.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
  485.  
  486.                 parameters.setPreviewFormat(ImageFormat.NV21); // NV21 is the most supported format for preview frames
  487.  
  488.                 List<String> FocusModes = parameters.getSupportedFocusModes();
  489.                 if (FocusModes != null && FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
  490.                     parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
  491.                 }
  492.  
  493.                 cameraDevice.setParameters(parameters); // save everything
  494.  
  495.                 // print saved parameters
  496.                 int prevWidth = cameraDevice.getParameters().getPreviewSize().width;
  497.                 int prevHeight = cameraDevice.getParameters().getPreviewSize().height;
  498.                 int picWidth = cameraDevice.getParameters().getPictureSize().width;
  499.                 int picHeight = cameraDevice.getParameters().getPictureSize().height;
  500.  
  501.                 Log.d(LOG_TAG, "setupCamera(): settings applied:\n\t"
  502.                         + "preview size: " + prevWidth + "x" + prevHeight + "\n\t"
  503.                         + "picture size: " + picWidth + "x" + picHeight
  504.                 );
  505.  
  506.                 // here: previewBuffer initialization. It will host every frame that comes out
  507.                 // from the preview, so it must be big enough.
  508.                 // After that, it's linked to the camera with the setCameraCallback() method.
  509.                 try {
  510.                     this.previewBuffer = new byte[prevWidth * prevHeight * ImageFormat.getBitsPerPixel(cameraDevice.getParameters().getPreviewFormat()) / 8];
  511.                     setCameraCallback();
  512.                 } catch (IOException e) {
  513.                     Log.e(LOG_TAG, "setupCamera(): error setting camera callback.", e);
  514.                 }
  515.  
  516.                 return true;
  517.             } else {
  518.                 return false;
  519.             }
  520.         } catch (Exception e) {
  521.             e.printStackTrace();
  522.             return false;
  523.         }
  524.     }
  525.  
  526.     protected void releaseCamera() {
  527.         synchronized (this) {
  528.             if (cameraDevice != null) {
  529.                 cameraDevice.release();
  530.             }
  531.             cameraDevice = null;
  532.         }
  533.     }
  534.  
  535.     /**
  536.      * [IMPORTANT!] Sets the {@link #previewBuffer} to be the default buffer where the
  537.      * preview frames will be copied. Also sets the callback function
  538.      * when a frame is ready.
  539.      *
  540.      * @throws IOException
  541.      */
  542.     private void setCameraCallback() throws IOException {
  543.         Log.d(LOG_TAG, "setCameraCallback()");
  544.         cameraDevice.addCallbackBuffer(this.previewBuffer);
  545.         cameraDevice.setPreviewCallbackWithBuffer(this);
  546.     }
  547.  
  548.     /**
  549.      * [IMPORTANT!] This is a convenient function to determine what's the proper
  550.      * preview/picture size to be assigned to the camera, by looking at
  551.      * the list of supported sizes and the maximum value given
  552.      *
  553.      * @param sizes          sizes that are currently supported by the camera hardware,
  554.      *                       retrived with {@link Camera.Parameters#getSupportedPictureSizes()} or {@link Camera.Parameters#getSupportedPreviewSizes()}
  555.      * @param widthThreshold the maximum value we want to apply
  556.      * @return an optimal size <= widthThreshold
  557.      */
  558.     private Size getBestSize(List<Size> sizes, int widthThreshold) {
  559.         Size bestSize = null;
  560.  
  561.         for (Size currentSize : sizes) {
  562.             boolean isDesiredRatio = ((currentSize.width / ASPECT_RATIO_W) == (currentSize.height / ASPECT_RATIO_H));
  563.             boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
  564.             boolean isInBounds = currentSize.width <= widthThreshold;
  565.  
  566.             if (isDesiredRatio && isInBounds && isBetterSize) {
  567.                 bestSize = currentSize;
  568.             }
  569.         }
  570.  
  571.         if (bestSize == null) {
  572.             bestSize = sizes.get(0);
  573.             Log.e(LOG_TAG, "determineBestSize(): can't find a good size. Setting to the very first...");
  574.         }
  575.  
  576.         Log.i(LOG_TAG, "determineBestSize(): bestSize is " + bestSize.width + "x" + bestSize.height);
  577.         return bestSize;
  578.     }
  579.  
  580.     /**
  581.      * In addition to calling {@link Camera#startPreview()}, it also
  582.      * updates the preview display that could be changed in some situations
  583.      *
  584.      * @param holder the current {@link SurfaceHolder}
  585.      */
  586.     private synchronized void startCameraPreview(SurfaceHolder holder) {
  587.         try {
  588.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  589.                 surfaceTexture = new SurfaceTexture(MAGIC_TEXTURE_ID);
  590.                 cameraDevice.setPreviewTexture(surfaceTexture);
  591.             } else
  592.                 cameraDevice.setPreviewDisplay(holder);
  593.             cameraDevice.startPreview();
  594. //            filter.start();
  595.         } catch (Exception e) {
  596.             Log.e(LOG_TAG, "startCameraPreview(): error starting camera preview", e);
  597.         }
  598.     }
  599.  
  600.     /**
  601.      * It "simply" calls {@link Camera#stopPreview()} and checks
  602.      * for errors
  603.      */
  604.     private synchronized void stopCameraPreview() {
  605.         try {
  606.             cameraDevice.stopPreview();
  607.             cameraDevice.setPreviewCallback(null);
  608. //            filter.stop();
  609.         } catch (Exception e) {
  610.             // ignored: tried to stop a non-existent preview
  611.             Log.i(LOG_TAG, "stopCameraPreview(): tried to stop a non-running preview, this is not an error");
  612.         }
  613.     }
  614.  
  615.     /**
  616.      * Gets the current screen rotation in order to understand how much
  617.      * the surface needs to be rotated
  618.      */
  619.     private void updateCameraDisplayOrientation() {
  620.         if (cameraDevice == null) {
  621.             Log.e(LOG_TAG, "updateCameraDisplayOrientation(): warning, camera is null");
  622.             return;
  623.         }
  624.  
  625.         int degree = getRotationDegree();
  626.  
  627.         cameraDevice.setDisplayOrientation(degree); // save settings
  628.     }
  629.  
  630.     private int getRotationDegree() {
  631.         int result = 0;
  632.         Activity parentActivity = (Activity) this.getContext();
  633.  
  634.         int rotation = parentActivity.getWindowManager().getDefaultDisplay().getRotation();
  635.         int degrees = 0;
  636.         switch (rotation) {
  637.             case Surface.ROTATION_0:
  638.                 degrees = 0;
  639.                 break;
  640.             case Surface.ROTATION_90:
  641.                 degrees = 90;
  642.                 break;
  643.             case Surface.ROTATION_180:
  644.                 degrees = 180;
  645.                 break;
  646.             case Surface.ROTATION_270:
  647.                 degrees = 270;
  648.                 break;
  649.         }
  650.  
  651.         if (Build.VERSION.SDK_INT >= 9) {
  652.             // on >= API 9 we can proceed with the CameraInfo method
  653.             // and also we have to keep in mind that the camera could be the front one
  654.             Camera.CameraInfo info = new Camera.CameraInfo();
  655.             Camera.getCameraInfo(cameraId, info);
  656.  
  657.             if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
  658.                 result = (info.orientation + degrees) % 360;
  659.                 result = (360 - result) % 360;  // compensate the mirror
  660.             } else {
  661.                 // back-facing
  662.                 result = (info.orientation - degrees + 360) % 360;
  663.             }
  664.         } else {
  665.             // TODO: on the majority of API 8 devices, this trick works good
  666.             // and doesn't produce an upside-down preview.
  667.             // ... but there is a small amount of devices that don't like it!
  668.             result = Math.abs(degrees - 90);
  669.         }
  670.         return result;
  671.     }
  672.  
  673.     private void initFilter(int width, int height) {
  674.         int degree = getRotationDegree();
  675.         Camera.CameraInfo info = new Camera.CameraInfo();
  676.         Camera.getCameraInfo(cameraId, info);
  677.         boolean isFrontFaceCamera = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
  678.         Log.i(LOG_TAG, "init filter with width = " + width + " and height = " + height + " and degree = "
  679.                 + degree + " and isFrontFaceCamera = " + isFrontFaceCamera);
  680.         String transposeCode;
  681.         String formatCode = "format=pix_fmts=rgba";
  682.         /*
  683.          0 = 90CounterCLockwise and Vertical Flip (default)
  684.          1 = 90Clockwise
  685.          2 = 90CounterClockwise
  686.          3 = 90Clockwise and Vertical Flip
  687.          */
  688.         switch (degree) {
  689.             case 0:
  690.                 transposeCode = isFrontFaceCamera ? "transpose=3,transpose=2" : "transpose=1,transpose=2";
  691.                 break;
  692.             case 90:
  693.                 transposeCode = isFrontFaceCamera ? "transpose=3" : "transpose=1";
  694.                 break;
  695.             case 180:
  696.                 transposeCode = isFrontFaceCamera ? "transpose=0,transpose=2" : "transpose=2,transpose=2";
  697.                 break;
  698.             case 270:
  699.                 transposeCode = isFrontFaceCamera ? "transpose=0" : "transpose=2";
  700.                 break;
  701.             default:
  702.                 transposeCode = isFrontFaceCamera ? "transpose=3,transpose=2" : "transpose=1,transpose=2";
  703.         }
  704.  
  705.         if (cameraFrame == null) {
  706.             cameraFrame = new Frame[2];
  707.             cameraFrame[0] = new Frame(width, height, Frame.DEPTH_UBYTE, 2);
  708.             cameraFrame[1] = new Frame(width, height, Frame.DEPTH_UBYTE, 2);
  709.         }
  710.  
  711.         filter = new FFmpegFrameFilter(transposeCode + "," + formatCode, width, height);
  712.         filter.setPixelFormat(AV_PIX_FMT_NV21);
  713.  
  714.         Log.i(LOG_TAG, "filter initialize success");
  715.     }
  716.  
  717.     @Override
  718.     public void onPreviewFrame(byte[] raw, Camera cam) {
  719.         processFrame(previewBuffer, cam);
  720.         // [IMPORTANT!] remember to reset the CallbackBuffer at the end of every onPreviewFrame event.
  721.         // Seems weird, but it works
  722.         cam.addCallbackBuffer(previewBuffer);
  723.     }
  724.  
  725.     /**
  726.      * [IMPORTANT!] It's the callback that's fired when a preview frame is ready. Here
  727.      * we can do some real-time analysis of the preview's contents.
  728.      * Just remember that the buffer array is a list of pixels represented in
  729.      * Y'UV420sp (NV21) format, so you could have to convert it to RGB before.
  730.      *
  731.      * @param raw the preview buffer
  732.      * @param cam the camera that filled the buffer
  733.      * @see <a href="http://en.wikipedia.org/wiki/YUV#Y.27UV420sp_.28NV21.29_to_ARGB8888_conversion">YUV Conversion - Wikipedia</a>
  734.      */
  735.     private void processFrame(byte[] raw, Camera cam) {
  736.         if (cameraFrame != null) {
  737.             synchronized (this) {
  738.                 ((ByteBuffer) cameraFrame[chainIdx].image[0].position(0)).put(raw);
  739.                 cameraFrameReady = true;
  740.                 this.notify();
  741.             }
  742.         }
  743.     }
  744.  
  745.     private class CameraWorker implements Runnable {
  746.         public void run() {
  747.             do {
  748.                 boolean hasFrame = false;
  749.                 synchronized (CvCameraPreview.this) {
  750.                     try {
  751.                         while (!cameraFrameReady && !stopThread) {
  752.                             CvCameraPreview.this.wait();
  753.                         }
  754.                     } catch (InterruptedException e) {
  755.                         Log.e(LOG_TAG, "CameraWorker interrupted", e);
  756.                     }
  757.                     if (cameraFrameReady) {
  758.                         chainIdx = 1 - chainIdx;
  759.                         cameraFrameReady = false;
  760.                         hasFrame = true;
  761.                     }
  762.                 }
  763.  
  764.                 if (!stopThread && hasFrame) {
  765.                     if (cameraFrame[1 - chainIdx] != null) {
  766.                         try {
  767.                             Frame frame;
  768.                             filter.start();
  769.                             filter.push(cameraFrame[1 - chainIdx]);
  770.                             while ((frame = filter.pull()) != null) {
  771.                                 deliverAndDrawFrame(frame);
  772.                             }
  773.                             filter.stop();
  774.                         } catch (FrameFilter.Exception e) {
  775.                             e.printStackTrace();
  776.                         }
  777.                     }
  778.                 }
  779.             } while (!stopThread);
  780.             Log.d(LOG_TAG, "Finish processing thread");
  781.         }
  782.     }
  783.  
  784.     /**
  785.      * This method shall be called by the subclasses when they have valid
  786.      * object and want it to be delivered to external client (via callback) and
  787.      * then displayed on the screen.
  788.      *
  789.      * @param frame - the current frame to be delivered
  790.      */
  791.     protected void deliverAndDrawFrame(Frame frame) {
  792.         Mat processedMat = null;
  793.  
  794.         if (listener != null) {
  795.             Mat mat = converterToMat.convert(frame);
  796.             processedMat = listener.onCameraFrame(mat);
  797.             frame = converterToMat.convert(processedMat);
  798.             if (mat != null) {
  799.                 mat.release();
  800.             }
  801.         }
  802.         cacheBitmap = converterToBitmap.convert(frame);
  803.         if (cacheBitmap != null) {
  804.             int width, height;
  805.             Canvas canvas = getHolder().lockCanvas();
  806.             if (canvas != null) {
  807.                 width = canvas.getWidth();
  808.                 height = cacheBitmap.getHeight() * canvas.getWidth() / cacheBitmap.getWidth();
  809.                 canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
  810.                 canvas.drawBitmap(cacheBitmap,
  811.                         new Rect(0, 0, cacheBitmap.getWidth(), cacheBitmap.getHeight()),
  812.                         new Rect(0,
  813.                                 (canvas.getHeight() - height) / 2,
  814.                                 width,
  815.                                 (canvas.getHeight() - height) / 2 + height), null);
  816.                 getHolder().unlockCanvasAndPost(canvas);
  817.             }
  818.         }
  819.  
  820.         if (processedMat != null) {
  821.             processedMat.release();
  822.         }
  823.     }
  824.  
  825.     public void recycleCacheBitmap() {
  826.         cacheBitmap.recycle();
  827.     }
  828.  
  829.     public interface CvCameraViewListener {
  830.         /**
  831.          * This method is invoked when camera preview has started. After this method is invoked
  832.          * the frames will start to be delivered to client via the onCameraFrame() callback.
  833.          *
  834.          * @param width  -  the width of the frames that will be delivered
  835.          * @param height - the height of the frames that will be delivered
  836.          */
  837.         public void onCameraViewStarted(int width, int height);
  838.  
  839.         /**
  840.          * This method is invoked when camera preview has been stopped for some reason.
  841.          * No frames will be delivered via onCameraFrame() callback after this method is called.
  842.          */
  843.         public void onCameraViewStopped();
  844.  
  845.         /**
  846.          * This method is invoked when delivery of the frame needs to be done.
  847.          * The returned values - is a modified frame which needs to be displayed on the screen.
  848.          * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
  849.          */
  850.         public Mat onCameraFrame(Mat mat);
  851.     }
  852.  
  853. }
Advertisement
Add Comment
Please, Sign In to add comment