I have made a custom camera view in my application. Basically i need to do something for as long as the camera in my application is open, after focusing.
in my Camera.Parameters, i have used FOCUS_MODE_CONTINUOUS_PICTURE, and it works perfectly as intended. Now, i need a callback from this continuous autofocusing practice where i can take the current focused picture and do something with it.
An alternate approach was to hook a 'Timer' technique, a function that would fire after every certain time period. And then, i could mCamera.autofocus() inside it, take picture and do my work. Unfortunately, this proved a very bad technique, since autofocusing would pose complications, varying for different devices.
So, i was wondering, what would be the perfect solution to this.
UPDATE: After making an attempt with threads
What i want to do is, AutoFocus and take picture again and again as long as the app is in the foreground.
After a number of different attempts, this is the farthest i have been able to come, still not far enough.
Please see the runnable code:
public class OCRRunnable implements Runnable {
static final String TAG = "DBG_" + "OCRRunnable";
private final Object mPauseLockDummyObject;
private boolean mPaused;
private boolean mFinished;
Camera mCamera;
public OCRRunnable(Camera cameraParam) {
mPauseLockDummyObject = new Object();
mPaused = false;
mFinished = false;
mCamera = cameraParam;
}
#Override
public void run()
{
if (mCamera != null) { // since the main activity may have been late opening the camera
try {
mCamera.autoFocus(new mAutoFocusCallback());
Log.d(TAG, "run: mCamera.autofocus()");
} catch (Exception e) {
Log.e(TAG, "run: " + e.getMessage());
}
//sleep necessary //TODO: needs refinement
//try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
//Runnable regulator
synchronized (mPauseLockDummyObject) {
while (mPaused) {
try {
mPauseLockDummyObject.wait();
} catch (InterruptedException e) {
Log.e(TAG, "run: " + e.getMessage());
}
}
}
}
}
/**
* Call this on pause of the activity.
*/
public void onPause() {
//Log.d(TAG, "onPause: called");
synchronized (mPauseLockDummyObject) {
mPaused = true;
}
}
/**
* Call this on resume of the activity
*/
public void onResume() {
//Log.d(TAG, "onResume: called");
synchronized (mPauseLockDummyObject) {
mPaused = false;
mPauseLockDummyObject.notifyAll();
}
}
//////////////////////////////////////
protected class mAutoFocusCallback implements Camera.AutoFocusCallback {
#Override
public void onAutoFocus(boolean success, final Camera camera) {
camera.takePicture(null, null, new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.d(TAG, "onPictureTaken() called");
/* NEED TO RUN THIS CODE PART REPETITIVELY */
camera.cancelAutoFocus(); //be ready for next autofocus
camera.startPreview(); //re-start cameraPreview since taking a picture stops it
run(); //TODO
}
});
}
}
}
Here is my relevant fragment:
public class ODFragment extends Fragment {
View rootView;
static final String TAG = "DBG_" + MainActivity.class.getName();
private Camera mCamera;
private CameraPreview mCameraPreview;
int ROIHeight;
FrameLayout frameLayout_cameraLens;
TextView words_container;
Thread ocrThread;
OCRRunnable ocrRunnable; //TODO: this may cause problems because mCamera is null as of this moment
public ODFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_od, container, false);
//one time tasks
words_container = (TextView) rootView.findViewById(R.id.words_container);
setCameraViewDimensions();
ocrThread = new Thread(ocrRunnable); //start it in onResume()
return rootView;
}
#Override
public void onResume() {
super.onResume();
hookCamera();
}
#Override
public void onPause() {
super.onPause();
unhookCamera();
}
private void hookCamera() {
try {
// 1. open camera
mCamera = Camera.open();
// 2. initialize cameraPreview
mCameraPreview = new CameraPreview(this.getActivity(), mCamera);
// 3. add view to frameLayout
frameLayout_cameraLens.addView(mCameraPreview);
mCamera.startPreview();
// 4. hook camera related listeners and threads
ocrRunnable = new OCRRunnable(mCamera);
ocrThread = new Thread(ocrRunnable);
ocrThread.start();
ocrRunnable.onResume();
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "Could not Camera.open(): " + e.getMessage());
}
}
private void unhookCamera() {
try {
// -4. unhook camera related listeners ans threads
ocrRunnable.onPause();
ocrThread = null;
ocrRunnable = null;
// -3. remove view from frameLayout
frameLayout_cameraLens.removeView(mCameraPreview);
// -2. destroy cameraPreview
mCameraPreview = null;
// -1. close camera
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void setCameraViewDimensions() {
//calculate and set dimensions of cameraLens
DisplayMetrics displaymetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int width = displaymetrics.widthPixels;
int height = (int) (width * 1.3333333333333);
//Log.d(TAG, "frameLayout_cameraLens dimensions: "+height+"x"+width);
frameLayout_cameraLens = (FrameLayout) rootView.findViewById(R.id.frameLayout_cameraLens);
frameLayout_cameraLens.getLayoutParams().width = width;
frameLayout_cameraLens.getLayoutParams().height = height;
frameLayout_cameraLens.requestLayout();
//set height of ROI
ROIHeight = height / 5;
LinearLayout linearLayout = (LinearLayout) rootView.findViewById(R.id.ROI);
linearLayout.getLayoutParams().height = ROIHeight;
linearLayout.requestLayout();
}
}
Some points:
I think camera.autofocus() happens in a separate thread itself. That is why instead of putting a while(true) loop in run(), i have called run() in the end of the mAutoFocusCallback
Error at this point is that the execution comes to Log.d(TAG, "run: mCamera.autofocus()"); exactly once. Additionally, Log.d(TAG, "onPictureTaken() called"); not called even once.
Here is my relevant log:
05-29 12:51:58.460 W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
05-29 12:51:58.573 D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
05-29 12:51:58.655 W/FragmentManager: moveToState: Fragment state for VTFragment{fd65ec0 #0 id=0x7f0c006d android:switcher:2131492973:1} not updated inline; expected state 3 found 2
05-29 12:51:58.962 D/DBG_CameraPreview: CameraPreview() initialized
05-29 12:51:59.079 D/DBG_OCRRunnable: run: mCamera.autofocus()
05-29 12:51:59.097 I/Adreno-EGL: <qeglDrvAPI_eglInitialize:379>: EGL 1.4 QUALCOMM build: Nondeterministic_AU_msm8974_LA.BF.1.1.3_RB1__release_AU (I3f4bae6ca5)
OpenGL ES Shader Compiler Version: E031.29.00.00
Build Date: 02/14/16 Sun
Local Branch: mybranch18261495
Remote Branch: quic/LA.BF.1.1.3_rb1.10
Local Patches: NONE
Reconstruct Branch: NOTHING
05-29 12:51:59.101 I/OpenGLRenderer: Initialized EGL, version 1.4
05-29 12:51:59.366 I/Choreographer: Skipped 46 frames! The application may be doing too much work on its main thread.
05-29 12:51:59.556 I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy#c66e1c1 time:44964952
Use mCamera.autofocus(callback) - and the callback will be triggered when the focus is ready. This is your chance to take the picture. In onPictureTaken() you will probably call mCamera.cancelAutoFocus(); mCamera.autofocus(callback); to allow the camera re-focus again.
Naturally, you may choose not to take picture every time that the camera gets focused.
To improve performance of your camera app, make sure to call Camera.open() on a background HandlerThread - otherwise the focus and picture callback will block the UI thread. This may be acceptable when these callback happen once in a while, but in your scenario they will be activated pretty often.
Update Let me present the suggested loop more clear (in pseudocode):
cameraInit() {
Open camera, set FOCUS_MODE_CONTINUOUS_PICTURE
camera.autofocus(autofocusCallback)
}
autofocusCallback() {
if (it_is_time_to_take_picture) {
takePicture(pictureCallback)
}
else {
autofocusAgain()
}
}
pictureCallback.onPictureTaken(image) {
autofocusAgain()
save image
}
autofocusAgain() {
camera.cancelAutoFocus()
wait(100ms)
camera.autofocus(autofocusCallback)
}
Related
I am using CameraX 1.0.0-beta01 to take pictures from the a service. I am using ImageCapture and ImageAnalysis - i.e. no preview.
I am implementing LifeCycleOwner and calling
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
when I start my service and mLifecycleRegistry.markState(Lifecycle.State.DESTROYED)
when I finish taking pictures.
I get the following error reported from users, not specific to any android version or device.
Fatal Exception: java.lang.IllegalStateException: Decrementing use count occurs more times than incrementing
at androidx.camera.core.impl.DeferrableSurface.decrementUseCount(DeferrableSurface.java:256)
at androidx.camera.core.impl.DeferrableSurfaces.decrementAll(DeferrableSurfaces.java:173)
at androidx.camera.camera2.internal.CaptureSession.clearConfiguredSurfaces(CaptureSession.java:539)
at androidx.camera.camera2.internal.CaptureSession$StateCallback.onClosed(CaptureSession.java:928)
at androidx.camera.camera2.internal.CaptureSession.forceClose(CaptureSession.java:533)
at androidx.camera.camera2.internal.Camera2CameraImpl$StateCallback.onDisconnected(Camera2CameraImpl.java:1210)
at androidx.camera.camera2.internal.CameraDeviceStateCallbacks$ComboDeviceStateCallback.onDisconnected(CameraDeviceStateCallbacks.java:112)
at androidx.camera.camera2.internal.compat.CameraDeviceCompat$StateCallbackExecutorWrapper$2.run(CameraDeviceCompat.java:121)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.os.HandlerThread.run(HandlerThread.java:61)
I haven't been able to reproduce the error myself. What could cause this?
Update:
I found the source for DeferrableSurface.java, it's happening here:
/**
* Decrements the use count.
*
* <p>If this causes the use count to go to zero and the surface has been closed, this will
* complete the future returned by {#link #getTerminationFuture()}.
*/
public void decrementUseCount() {
// If this gets set, then the surface will terminate
CallbackToFutureAdapter.Completer<Void> terminationCompleter = null;
synchronized (mLock) {
if (mUseCount == 0) {
throw new IllegalStateException("Decrementing use count occurs more times than "
+ "incrementing");
}
mUseCount--;
if (mUseCount == 0 && mClosed) {
terminationCompleter = mTerminationCompleter;
mTerminationCompleter = null;
}
// ...
which is being called from DeferrableSurfaces.java:
/**
* Decrements the usage counts of every surface in the provided list.
*
* #param surfaceList The list of surfaces whose usage count should be decremented.
*/
public static void decrementAll(#NonNull List<DeferrableSurface> surfaceList) {
for (DeferrableSurface surface : surfaceList) {
surface.decrementUseCount();
}
}
which I suppose gets called when I mark the current state as DESTROYED. Tempted to say that this looks like a bug. Thoughts?
Update 2:
My code, stripped down to what's releveant:
public class MyService extends Service implements LifecycleOwner{
private LifecycleRegistry mLifecycleRegistry;
public static boolean serviceRunning = false;
private boolean isCameraOff = true;
private ImageCapture imageCapture;
private int pendingImages = 0;
#NonNull
#Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
/* called from activity when we should take a picture.
can be called several times, and we therefore increment an
int, keeping track of how many pictures should be taken. */
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
serviceRunning = true;
// Increment pending pictures
pendingImages++;
// if camera is not in use, setup camera and take picture, otherwise incrementing pendingImages var is enough
if(isCameraOff){
isCameraOff = false;
cameraSetup(); // takePicture called from the end of setup
}
return START_NOT_STICKY;
}
private void cameraSetup(){
// lifecycle for camera
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
// Camera provider is now guaranteed to be available
ProcessCameraProvider cameraProvider = (ProcessCameraProvider)cameraProviderFuture.get();
// Dummy Analyzer
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
// empty analyzer, for the time
imageAnalysis.setAnalyzer(executor, ImageProxy::close);
// ImageCapture
imageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.setTargetResolution(new Size(900,1200))
.build();
// Select front facing camera
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
.build();
// Bind Camera to lifecycle
cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, imageAnalysis);
// Give camera some time to open, 500 ms should do, before proceeding
Utils.sleepThread(500);
// take picture
takePicture();
} catch (InterruptedException | ExecutionException e) {
// Currently no exceptions thrown. cameraProviderFuture.get() should
// not block since the listener is being called, so no need to
// handle InterruptedException.
}
}, ContextCompat.getMainExecutor(this));
}
private Executor executor = Executors.newSingleThreadExecutor();
private void takePicture(){
// Create new file
File file = new File(Utils.createFilePath(context, getApplication()));
// Create Meta data
ImageCapture.Metadata meta = new ImageCapture.Metadata();
meta.setReversedHorizontal(false); // don't mirror photos
// Collect file options
ImageCapture.OutputFileOptions outputFileOptions =
new ImageCapture.OutputFileOptions.Builder(file)
.setMetadata(meta)
.build();
// Take picture
imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback(){
#Override
public void onImageSaved(#NonNull ImageCapture.OutputFileResults outputFileResults) {
// got picture
// Decrement the number of pictures we need to take
pendingImages--;
// take another image if pending
if(pendingImages > 0){
takePicture();
}else{
closeSession();
}
}
#Override
public void onError(#NonNull ImageCaptureException exception) {
exception.printStackTrace();
closeSession();
}
});
}
private void closeSession(){
// tell lifecycle and thus camera to close (has to be called from main thread)
// closeSession is called from the camera callback and is not on main thread.
new Handler(context.getMainLooper()).post(() -> {
mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
});
// mark camera as off
isCameraOff = true;
stopSelf(); // stop service
}
#Override
public void onDestroy() {
super.onDestroy();
serviceRunning = false;
}
// Service Stuff
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
Brief description of application:
I have Cordova/Ionic application and Custom Cordova plugin for native code execution.
Plugin contains separate CameraActivity (extends FragmentActivity) to work with Camera (parts of code based on Camera2Basic example).
On launch Activity displays AnaliseFragment, where application captures every Camera frame and passes image to the analyser on backround thread.
Execution steps are:
User presses button on Cordova UI
Cordova executes native plugin method via cordova.exec(..)
Native plugin starts CameraActivity for result via cordova.startActivityForResult(..)
CameraActivity displays AnaliseFragment
AnaliseFragment starts Camera capture session with two surfaces: first is displayed on TextureView and second analised by ImageAnaliser
Problem:
Rarely and randomly UI stops reacting on user and runnables not executed on UI thread. At the same time background threads continue working as normal: camera output is visible on TextureView and ImageAnaliser continue receive images from Camera.
Does anybody have any suggestion how to find/debug reason of such behavior? Or any ideas what can cause this?
I already tried:
log every lifecycle event of CameraActivity/AnaliseFragment = no calls between app normal state and ANR
add WAKELOCK to keep Cordova MainActivity alive = didn't help
log(trace) every method in AnalilseFragment and ImageAnaliser = nothing suspicious
Here is simplified code of AnaliseFragment:
public class AnaliseFragment extends Fragment {
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private ImageAnalyser mImageAnalyser;
// listener is attached to camera capture session and receives every frame
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
#Override
public void onImageAvailable(ImageReader reader) {
Image nextImage = reader.acquireLatestImage();
mBackgroundHandler.post(() ->
try {
mImageAnalyser.AnalizeNextImage(mImage);
}
finally {
mImage.close();
}
);
}
};
#Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
mImageAnalyser = new ImageAnalyser();
mImageAnalyser.onResultAvailable(boolResult -> {
// Runnable posted, but never executed
new Handler(Looper.getMainLooper()).post(() -> reportToActivityAndUpdateUI(boolResult));
});
}
#Override
public void onResume() {
super.onResume();
startBackgroundThread();
}
#Override
public void onPause() {
stopBackgroundThread();
super.onPause();
}
private void startBackgroundThread() {
if (mBackgroundThread == null) {
mBackgroundThread = new HandlerThread("MyBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Simplified code for ImageAnalyser:
public class ImageAnalyser {
public interface ResultAvailableListener {
void onResult(bool boolResult);
}
private ResultAvailableListener mResultAvailableListener;
public void onResultAvailable(ResultAvailableListener listener) { mResultAvailableListener = listener; }
public void AnalizeNextImage(Image image) {
// Do heavy analysis and put result into theResult
mResultAvailableListener.onResult(theResult);
}
}
There is some long-running operation in UI-thread. Try profile your app to figure out what does block your main thread.
After hours of profiling, debugging and code review I found, that
issue was caused by incorrect View invalidation from background thread
View.postInvalidate() method must be used - this method checks if View is still attached to window and then do invalidation. Instead I wrongly used View.invalidate(), when process my custom message from MainLooper, which rarely caused failures and made MainLooper stop processing any more messages.
For those who maybe have same problem I added both correct and incorrect code.
CORRECT:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private void invalidateGraphicOverlayViewFromBackgroundThread(){
mGraphicOverlayView.postInvalidate();
};
WRONG:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private final int MSG_INVALIDATE_GRAPHICS_OVERLAY = 1;
private Handler mUIHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE_GRAPHICS_OVERLAY:{
GraphicOverlayView overlay = (GraphicOverlayView)msg.obj;
// Next line can cause MainLooper stop processing other messages
overlay.invalidate();
break;
}
default:
super.handleMessage(msg);
}
}
};
private void invalidateGraphicOverlayViewFromBackgroundThread(){
Message msg = new Message();
msg.obj = mGraphicOverlayView;
msg.what = MSG_INVALIDATE_GRAPHICS_OVERLAY;
mUIHandler.dispatchMessage(msg);
};
I'm still pretty confused as to when / how to end threads in my Surfaceview application and was hoping someone could explain.
Currently, I am using this code:
Log.v("Destroyed","Surface Destroyed");
preThread.setRunning(false);
boolean retry = true;
while (retry) {
try {
preThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
The above code sits in my surfaceDestroyed method - firstly, is this correct?
In my surfaceCreated method I have the following code which should check to see if the thread still exists or has been stopped and if it's been stopped, then re-start it:
if (runthread==false){
if (preThread.getState()==Thread.State.TERMINATED){
preThread = new MainThread(thisholder, thiscontext, thishandler);}
else {}
preThread.setRunning(true);
preThread.start();
}
It seems to act really strange. Here is what I get:
*) When I first install the game and run it, through my logging, it says that the thread already exists, If I then press the back key, surfaceDestroyed is run but when I go back into the activity, again it says that the thread already exists.
*) If I press the home key, then surfaceDestroyed is run and when I go back into the activity it says that the thread was previously destroyed and starts a new one.
*) If I kill the activity using DDMS, surfaceDestroyed isn't run and when I go back into the activity it says that the thread already exists.
If I'm thinking straight, then the third scenario is the only one that seems to make sense.
I'm clearly doing something drastically wrong. The main problem is this:
If I hit the home key during the game and then end the app via DDMS in Eclipse, restart the app and hit the back key twice in quick succession (once, to go back to the previous activity, then again to get back to the splash screen) - the app force-closes and I get a "Fatal exception: Thread 12" in logcat. I have to assume this is because my thread is never ending and is trying to be-restarted? I'm not sure.
I've been trying to figure this out for what seems like an age so I really hope someone can explain what I'm doing wrong!
Many thanks!
Edit. Logcat output.
my Run() method:
public void run(){
//Main Loop
while (runthread){
Log.v("tracking","runthread is: "+runthread); //This should only be logged while this loop is running
timestart = System.currentTimeMillis(); //Get time at start of loop for FPS calc
try{c=mySurfaceHolder.lockCanvas(); //Set Canvas to locked
synchronized(mySurfaceHolder){
if (c==null){Log.v("Stop","Canvas is null for some reason - exiting, "+c+" - see?!!!");}
framesskipped = 0; // resetting frames skipped
doDraw(c); //Draw to the screen
updateMenu();
}
}
finally{
if (c != null){
mySurfaceHolder.unlockCanvasAndPost(c); //Post canvas
}
}
//work out timings
timeend = System.currentTimeMillis(); //get end time for current frame (for FPS)
frametime = timeend-timestart; //Set the frametime variable to the time the frame took to render & update (end time - start time)
sleepfor = (int) (33-frametime); // this is the time that the thread will sleep for if <target time
if (sleepfor>0){ // If the 'sleepfor' variable is >0 then set the thread to sleep for it's value (expressed in ms)
try {
OptionsThread.sleep(sleepfor); //send thread to sleep for value of sleepfor (determined above).
} catch (InterruptedException e) {} //in case of exception
} //close if statement
while (sleepfor<0 && framesskipped<maxframesskipped){ //if sleepfor is < 0 (ie, frame took longer to render than target time and the maxframesskipped has not reached it's limit)
updateMenu(); //Update animation variables without rendering to the screen while these conditions are met
sleepfor+=33; //time to sleep plus the time frame took to render
framesskipped++; //add one to framesskipped variable so this only skips a certain number of frames
}
}
}
New Logcat output showing nullPointerException and output of logging. runThread is never logged as false so I'm not sure how the line that logs canvas as null is reached!
Thanks
Edit:
OK, I've completely started from scratch and re-written the whole class- it's very much a stripped-down version of what I had before and here is the whole class:
import android.content.res.Resources;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class OptionsScreen extends SurfaceView implements
SurfaceHolder.Callback {
//Create Variables
private SurfaceHolder thisHolder;
private Context thisContext;
private Handler thisHandler;
private preThread thread;
private Bitmap background;
private Resources res;
private Context myContext;
private Handler myHandler;
private Canvas c;
// thisholder = getHolder();
public OptionsScreen(Context context) {
super(context);
myContext=context; //This is the context passed into this constructor (this)
thisHolder = getHolder(); //Get surface holder
thisHandler=getHandler(); //Get Handler
thisContext = getContext(); //Get context
res=getResources(); //Get resource
//add the callback surface holder
getHolder().addCallback(this);
//make focusable
setFocusable(true);
//create new thread
thread = new preThread(thisHolder, thisContext, thisHandler);
//create bitmaps from resources
background = BitmapFactory.decodeResource(res, R.drawable.sky);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.v("check","surfaceChanged run");
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("check","surfaceCreated run"+thread.getState());
int height = this.getHeight();
int width = this.getWidth();
if(thread.getState()==Thread.State.TERMINATED){ //Has thread been stopped previously? could happen if the home key is pressed
Log.v("check","Thread still exists!!!! - Starting a new one. "+thread.getState());
thread = new preThread(thisHolder, thisContext, thisHandler);
}
thread.setRunning(true);
thread.start();
Log.v("check","Thread - "+thread.getState());
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("check","surfaceDestroyed run"+thread.getState());
thread.setRunning(false); //Set to false to exit run() method
boolean retry = true; //Shut off rendering thread
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.v("check","Surface Touched");
Log.v("check","Thread - "+thread.getState());
// System.exit(0);
return super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas canvas) {
// if (canvas!=null){
canvas.drawBitmap(background, 0, 0, null);
Log.v("Stop","Canvas is "+canvas);
}
}
//*******************************************************************
//** run loop **
//*******************************************************************
protected class preThread extends Thread {
private SurfaceHolder mySurfaceHolder;
private Context myContext;
public preThread(SurfaceHolder surfaceholder, Context context, Handler handler) { //Constructor
mySurfaceHolder=surfaceholder;
myContext=context;
res = myContext.getResources();
}
// flag
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
while (running) {
try{c=mySurfaceHolder.lockCanvas();
synchronized(mySurfaceHolder){
Log.v("check","Drawing!!");
onDraw(c);
}
}
finally{
if (c != null){
mySurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
Threads are pretty difficult to manage, but after some hit and trial i think i have come up with a scheme which works correctly most of the time.
To End a Thread
if(m_hThread != null)
{
try
{
m_bThread = false; // m_bThread is the while condition of the thread
m_hThread.interrupt(); // incase the thread is in sleep
m_Thread.join(); // This call blocks and waits for thread to end
m_hThread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
For re-creating thread
if(m_hThread == null)
{
m_bThread = true; //while condition of thread
StartNewThread();
}
In your implementation, No need to retry Thread.join, either it will join in the first try or it will block until the thread joins. As for your cases only the first case seems to be weird, where you find the thread is already running, this can't possibly be true. Second and third make complete sense to me, and work like they should. When the user clicks Home button surfaceDestroyed is invoked and thread is Terminated.
thread continues as long as m_bThread is true,
while(m_bThread) //b
{
// Continuous Thread operations...
}
m_hThread is simply preThread in your code, and m_Thread is a also m_hThread just a typing mistake here.
Once a call is made to Camera.takePicture(), my preview will stop updating as described in the docs. What's the best way to detect that the image capture process is finished and call startPreview() to make it start updating again?
The call can't be placed in any of the callbacks passed to takePicture, according to the docs, as they should all have returned before I invoke it.
My current best guess is to create a Handler and post a delayed Runnable to it from the JPEG callback (or whichever is the most last defined callback to return).
Since the PictureCallback is started in a separate thread anyway (it will not lock the UI), you don't need to use an AsyncTask to call the capture.
There are two ways to do what you want to do, the easiest is the following:
mCamera.startPreview(); //preview has to be started before you can take a picture
mCamera.takePicture(null, null, mPictureCallback); //take a picture
private PictureCallback mPictureCallback = new PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
camera.startPreview(); //once your camera has successfully finished
//its capture it's safe to restart the preview
... //anything else you want to do to process that image
}
}
The second would be using an anonymous function, e.g.:
mCamera.takePicture(null, null, new PictureCallback(){
...
});
Both have their uses, depending on your needs.
You should start the mCamera.takePicture from within an AsyncTask (or a thread) however AsyncTaks's are the easier option.
A really simple implementation (which of course can be modified) is to:
The method called to take the picture
/**
* Execute the AsyncTask that will handle the preview of the captured photo.
*/
public void takePicture() {
TakePictureTask takePictureTask = new TakePictureTask();
takePictureTask.execute();
}
The AsyncTask subclass
/**
* A pretty basic example of an AsyncTask that takes the photo and
* then sleeps for a defined period of time before finishing. Upon
* finishing, it will restart the preview - Camera.startPreview().
*/
private class TakePictureTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPostExecute(Void result) {
// This returns the preview back to the live camera feed
mCamera.startPreview();
}
#Override
protected Void doInBackground(Void... params) {
mCamera.takePicture(null, null, mPictureCallback);
// Sleep for however long, you could store this in a variable and
// have it updated by a menu item which the user selects.
try {
Thread.sleep(3000); // 3 second preview
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
The PictureCallback field
private PictureCallback mPictureCallback = new PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
File file = null;
// Check whether the media is mounted with read/write permission.
if (Environment.MEDIA_MOUNTED.equals(
Environment.getExternalStorageState())) {
file = getOutputMediaFile();
}
if (file == null) {
Log.d(TAG, "Error creating media file, check storage persmissions!");
return;
}
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(data);
fileOutputStream.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
};
I am writing an application that needs pictures taken with the camera. The problem occurs when I try to take an actual picture. Here's the code that is troubling me:
final ShutterCallback shutterCallback = new ShutterCallback() {
#Override
public void onShutter() {
Log.d(TAG, "onShutter");
}
};
final PictureCallback callback = new PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.d(TAG, "onPictureTaken - jpeg");
try {
//async task for storing the photo
new SavePhotoTask(CameraView.this.ctx, data).execute();
} catch (final SavePhotoException e) {
//some exceptionhandling
}
}
};
this.camera = Camera.open();
this.camera.setPreviewDisplay(surfaceHolder);
final Camera.Parameters parameters = findBestParameters(w, h);
this.camera.setParameters(parameters);
this.camera.startPreview();
Log.d(TAG, "takePicture now!");
this.camera.takePicture(shutterCallback, null, callback);
On the emulator it seems to work out but on my phone (Motorola Defy - android 2.1).
The actual problem: on the phone the picturecallback is never happening "onPictureTaken" gets never called. The Shuttercallback is executed but the other is not (and I tried with raw instead of jpeg, same thing).
Does anyone know this problem? I just don't see where the difference lies to the emulator right now. I appreciate your help.
I finally went and debugged the problem. All of a sudden it worked, because debugging is much slower: It's a timing problem. The callback takes some time to be called. While debugging the phone had enough time to finish taking the picture ...
Also don't go calling Camera.stopPreview() and Camera.release() too early.
I was having this exact problem. After much debugging I finally realized that the stupid Camera object was getting garbage collected before it had a chance to call the callbacks!
I fixed it by creating a hard reference to the Camera object that I was using. I made it a member of my PictureTaker class, set it before calling takePicture() and then null it out in the jpeg callback after I receive my data. Then I just have to make sure my PictureTaker object won't get gc'd itself, which I do by keeping it around in my Application subclass for the life of the process.
This always works on my Droid RAZR:
public class PictureTaker implements Camera.PictureCallback
{
private Camera mCam;
private MyApp theApp;
public PictureTaker(MyApp app)
{
theApp = app;
}
public void takePicture()
{
try
{
mCam = Camera.open();
}
catch (Exception e)
{
System.out.println("Problem opening camera! " + e);
return;
}
if (mCam == null)
{
System.out.println("Camera is null!");
return;
}
try
{
SurfaceView view = MyApp.getPreviewSurface(); // my own fcn
mCam.setPreviewDisplay(view.getHolder());
mCam.startPreview();
mCam.takePicture(null, null, this);
}
catch (Exception e)
{
System.out.println("Problem taking picture: " + e);
}
}
public void onPictureTaken(byte[] data, Camera cam)
{
theApp.jpegPictureData(data); // also my own fcn
cam.stopPreview();
cam.release();
mCam = null;
}
}
In my case it wasn't calling on picturetaken on a particular device. The problem was caused because the camera was being opened twice on onResume() and oncreate().