public void run() {
    Log.i(TAG, "Starting processing thread");
    mFps.init();

    while (true) {
      Bitmap bmp = null;

      synchronized (this) {
        if (mCamera == null) break;

        if (!mCamera.grab()) {
          Log.e(TAG, "mCamera.grab() failed");
          break;
        }

        bmp = processFrame(mCamera);

        mFps.measure();
      }

      if (bmp != null) {
        Canvas canvas = mHolder.lockCanvas();
        if (canvas != null) {
          canvas.drawBitmap(
              bmp,
              (canvas.getWidth() - bmp.getWidth()) / 2,
              (canvas.getHeight() - bmp.getHeight()) / 2,
              null);
          mFps.draw(canvas, (canvas.getWidth() - bmp.getWidth()) / 2, 0);
          mHolder.unlockCanvasAndPost(canvas);
        }
        bmp.recycle();
      }
    }

    Log.i(TAG, "Finishing processing thread");
  }
  /**
   * This method shall be called by the subclasses when they have valid object and want it to be
   * delivered to external client (via callback) and then displayed on the screen.
   *
   * @param frame - the current frame to be delivered
   */
  protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
    Mat modified;

    if (mListener != null) {
      modified = mListener.onCameraFrame(frame);
    } else {
      modified = frame.rgba();
    }

    boolean bmpValid = true;
    if (modified != null) {
      try {
        Utils.matToBitmap(modified, mCacheBitmap);
      } catch (Exception e) {
        Log.e(TAG, "Mat type: " + modified);
        Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
        Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
        bmpValid = false;
      }
    }

    if (bmpValid && mCacheBitmap != null) {
      Canvas canvas = getHolder().lockCanvas();
      if (canvas != null) {
        canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
        Log.d(TAG, "mStretch value: " + mScale);

        if (mScale != 0) {
          canvas.drawBitmap(
              mCacheBitmap,
              new Rect(0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
              new Rect(
                  (int) ((canvas.getWidth() - mScale * mCacheBitmap.getWidth()) / 2),
                  (int) ((canvas.getHeight() - mScale * mCacheBitmap.getHeight()) / 2),
                  (int)
                      ((canvas.getWidth() - mScale * mCacheBitmap.getWidth()) / 2
                          + mScale * mCacheBitmap.getWidth()),
                  (int)
                      ((canvas.getHeight() - mScale * mCacheBitmap.getHeight()) / 2
                          + mScale * mCacheBitmap.getHeight())),
              null);
        } else {
          canvas.drawBitmap(
              mCacheBitmap,
              new Rect(0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
              new Rect(
                  (canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
                  (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
                  (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
                  (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()),
              null);
        }

        if (mFpsMeter != null) {
          mFpsMeter.measure();
          mFpsMeter.draw(canvas, 20, 30);
        }
        getHolder().unlockCanvasAndPost(canvas);
      }
    }
  }