I want to continuously receive frames from the camera and manipulate them. Since the manipulation will be a CPU heavy task, i have opened the camera on a separate handler thread so that any callbacks land on that thread as well.
However, the problem that i am having is that my onPreviewFrame()
never gets called, and i cannot seem to figure out why. I tried but i cannot figure out. There is no error either.
I understand, in order for the PreviewCallbacks to occur, i need to do the following:
- Open Camera
- Set Preview Display (a camera preview)
- Start the preview
I have done all of these and the preview even shows perfectly in the app UI. I wonder what part i am missing or doing wrong. Following is my relevant code.
ODFragment.java
public class ODFragment extends Fragment {
static View rootView;
static Context mainActivityContext;
static final String TAG = "DBG_" + "ODFragment";
static Camera mCamera;
static CameraPreview mCameraPreview;
static FrameLayout frameLayout_cameraLens;
static CameraHandlerThread mCameraHandlerThread = null;
static Handler mUiHandler = new Handler();
//static TessBaseAPI tessBaseAPI = new TessBaseAPI();
public ODFragment() {}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_od, container, false);
mainActivityContext = this.getActivity();
//one time tasks
frameLayout_cameraLens = (FrameLayout) rootView.findViewById(R.id.frameLayout_cameraLens);
setCameraViewDimensions();
return rootView;
}
@Override
public void onResume() {
super.onResume();
//1 open camera
if (mCameraHandlerThread == null) {
mCameraHandlerThread = new CameraHandlerThread("Camera Handler Thread");
}
if (!mCameraHandlerThread.isAlive() || mCameraHandlerThread.getCamera()==null)
{
synchronized (mCameraHandlerThread) {
Log.d(TAG, "onResume: starting mCameraHandlerThread");
mCameraHandlerThread.start();
mCameraHandlerThread.openCamera();
}
}
//rest of the steps will be invoked (hookOpenedCamera()) by cameraHandlerThread
}
public static void hookOpenedCamera(){
mUiHandler.post(new Runnable() {
@Override
public void run() {
//2 create camera and camera preview
mCamera = mCameraHandlerThread.getCamera();
mCameraPreview = new CameraPreview(getMainActivityContext(), mCamera);
//3 add cameraPreview to layout
frameLayout_cameraLens.addView(mCameraPreview);
//4 start preview
mCamera.startPreview();
//5 hook previewCallback
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) { //TODO: incomplete
Log.d(TAG, "onPreviewFrame: called");
}
});
}
});
}
@Override
public void onPause() {
super.onPause();
//-4
mCamera.stopPreview();
//-3
frameLayout_cameraLens.removeView(mCameraPreview);
//-2
mCameraPreview = null;
mCamera = null;
//-1
mCameraHandlerThread.setCamera(null);
mCameraHandlerThread.quit();
mCameraHandlerThread = null;
}
private void setCameraViewDimensions() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//calculate dimensions of cameraLens and height of ROI
DisplayMetrics displaymetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
final int width = displaymetrics.widthPixels;
final int height = (int) (width * 1.33333333334);
final int ROIHeight = height / 5;
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "run: frameLayout_cameraLens dim (BEFORE): "+frameLayout_cameraLens.getLayoutParams().width+" x "+frameLayout_cameraLens.getLayoutParams().height);
//set dimensions of cameraLens
frameLayout_cameraLens.getLayoutParams().width = width;
frameLayout_cameraLens.getLayoutParams().height = height;
frameLayout_cameraLens.requestLayout();
//set height of ROI
LinearLayout linearLayout = (LinearLayout) rootView.findViewById(R.id.ROI);
linearLayout.getLayoutParams().height = ROIHeight;
linearLayout.requestLayout();
Log.d(TAG, "run: frameLayout_cameraLens dim (AFTER): " +frameLayout_cameraLens.getLayoutParams().width+" x "+frameLayout_cameraLens.getLayoutParams().height);
}
});
}
});
thread.run();
}
public static Context getMainActivityContext() {
return mainActivityContext;
}
}
CameraHandlerThread.java
public class CameraHandlerThread extends HandlerThread {
static final String TAG = "DBG_" + "CameraHandlerThread";
Handler mCameraHandler = null;
Camera mCamera = null;
public CameraHandlerThread(String name) {
super(name);
}
@Override
public synchronized void start() {
super.start();
//prepare handler
if (mCameraHandler == null) {
mCameraHandler = new Handler(getLooper());
}
}
void openCamera() {
mCameraHandler.post(new Runnable() {
@Override
public void run() {
try {
//done on cameraHandlerThread (step 1)
mCamera = Camera.open();
//done on UiThread (steps 2, 3, 4)
ODFragment.hookOpenedCamera();
}
catch (RuntimeException e) {
Log.e(TAG, "failed to open camera");
}
}
});
}
public Camera getCamera() {
return this.mCamera;
}
public void setCamera(Camera camera) {
this.mCamera = camera;
}
}
CameraPreview.java
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
static final String TAG = "DBG_" + "CameraPreview";
private Camera.Size mPreviewSize = null;
public CameraPreview(Context context){
super(context);
}
public CameraPreview(Context context, Camera mCamera) {
super(context);
//Log.d(TAG, "CameraPreview() initialized");
//mCamera = camera;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
setCamera(mCamera);
mHolder = getHolder();
mHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview
try {
mCamera.setDisplayOrientation(90); //because only supporting portrait currently
mCamera.setPreviewDisplay(holder);
//mCamera.startPreview();
//Log.d(TAG, "surfaceCreated(): Started camera preview");
} catch (IOException e) {
Log.d(TAG, "surfaceCreated(): Error setting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
Camera.Parameters cameraParameters = mCamera.getParameters();
//change camera preview size
cameraParameters.setPreviewSize(getPreviewSize().width, getPreviewSize().height);
//continuous autofocus
cameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
//set focus area to top part of the camera view //TODO
//cameraParameters.setFocusAreas();
//set parameters now
mCamera.setParameters(cameraParameters);
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
//Log.d(TAG, "surfaceChanged(): Restarted camera preview");
} catch (Exception e){
Log.d(TAG, "surfaceChanged(): Error starting camera preview: " + e.getMessage());
}
}
private Camera.Size getPreviewSize() {
if (mPreviewSize == null) {
List<Camera.Size> mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mPreviewSize = mSupportedPreviewSizes.get(mSupportedPreviewSizes.size() - 6);
Log.d(TAG, "getPreviewSize(): Camera Preview Size selected: " + mPreviewSize.width + " x " + mPreviewSize.height);
}
return mPreviewSize;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
//Log.d(TAG, "surfaceDestroyed() invoked");
}
public void setCamera(Camera camera) {
this.mCamera = camera;
}
}
Thank you in advance
SOLVED
I realized that my
PreviewCallback
does get hooked. But after that, thesurfaceChanged()
gets called at a point, which stops and then starts theCameraPreview
. This removes my callback.So, i have to invoke my callback hooking routine, everywhere that i am invoking
startPreview