I am writing an Android application that uses the camera.
I took the sample code https://github.com/googlesamples/android-Camera2Basic/ provided by Google. However, I observed that calling the close() method of the CameraDevice takes too long (almost one second on my Samsung Galaxy S8).
This method is called from onPause(), thus the application hangs a little while when the camera fragment is closed.
#Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close(); // This call takes 1 second!
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
How can I avoid the application to become unresponsive when the closeCamera() method is being called?
I tried to call it from another thread than the UI thread but the application crashes in some cases.
I faced with this problem when my device (mi a1 - oreo) was flashed with patched boot.
If you have device with patched boot too, try to flash it with stock boot. It helped in my case.
Related
I'm writing an APP for taking multiple pictures by camera. (Like the burst mode in Camera2, but I'm working on older versions.)
It is implemented by takePicture() / onPictureTaken() callback, and work normally on several devices except some performance issues. However I got a RuntimeException of startPreview failed on an HTC Sensation device when trying to start preview for the 2nd picture.
Here is the functions about multiple shot:
final int multishot_count = 3;
....
public void TakePicture()
{
// Invoke multishot from UI when the camera preview is already started.
// startup of multishot task
...
m_take_picture_count = 0;
TakeOnePicture();
}
private void TakeOnePicture()
{
m_camera.takePicture(null, null, new Camera.PictureCallback()
{
#Override
public void onPictureTaken(byte[] data, final Camera camera)
{
m_take_picture_count++;
// feed the JPEG data to a queue and handle it in another thread
synchronized(m_jpeg_data_lock)
{
int data_size = data.length;
ByteBuffer buffer = ByteBuffer.allocateDirect(data_size);
buffer.put(data, 0, data_size);
m_jpeg_data_queue.offer(buffer);
if (m_take_picture_count == multishot_count)
m_is_all_pictures_taken = true;
}
if (m_take_picture_count < multishot_count)
{
m_camera.startPreview();
TakeOnePicture();
}
else
{
// Finalize the multishot task and process the image data
EndTakePictureTask end_take_picture_task = new EndTakePictureTask();
end_take_picture_task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
}
After the first picture being taken, the startPreview() function may fail in a rate of 10-20% on the specified device. My APP is always run in foreground and has a visible view to show the preview frames. I also try to bypass the JPEG data without processing it, but the error still occurs.
I found a similar report at here. It seems that the underlying thread for taking picture does not finish its works, but we have no way to check it. So I try to catch the exception from startPreview() and sleep a while:
if (m_take_picture_count < multishot_count)
{
boolean is_try_start_preview = true;
while (is_try_start_preview)
{
try
{
m_camera.startPreview();
is_try_start_preview = false;
}
catch (RuntimeException e)
{
try
{
Thread.sleep(50);
}
catch (InterruptedException e2)
{
e2.printStackTrace();
}
}
}
TakeOnePicture();
}
But once startPreview fails, it will fail forever no matter how many times I sleep.
I also found that you need to call setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) for the surface holder in older Android versions to let startPreview() work correctly. But the device has Android version 4.0.3 and should not require this call. I still try to add it but the issue remains the same. Moreover, the first call of startPreview() works correctly while the second call after onPictureTaken() gets the error. This is different from the issue of missing setType() in older versions.
Finally I use an workaround that may greatly hurt the performance:
if (m_take_picture_count < multishot_count)
{
try
{
m_camera.startPreview();
}
catch (RuntimeException e)
{
// Fail to start preview. Workaround: reopen camera.
RestartCamera();
}
TakeOnePicture();
}
That is, when startPreview() fails, I just close the camera, reopen it with the same settings, then start the preview to take another picture.
I want to know if I miss something about the take picture APIs, and is there a solution better than this brute-force workaround.
Thanks for the suggestion from Alex Cohn, I found a solution. This solution is only tested on the HTC Sensation device, but it has dramatic effect on this device.
I just put a sleep instruction before startPreview() that would be called in onPictureTaken():
try
{
Thread.sleep(20);
}
catch (Exception e)
{
e.printStackTrace();
}
Then I run a test 100 times, where each run takes 3 pictures in succession. With the sleep, I didn't get any error from startPreview() in 100 runs. If There is no sleep, I need to reopen the camera 78 times, and restart the device 8 times in 100 runs.
I have this code to turn camera flash on and off in android:
params = camera.getParameters();
params.setFlashMode(Parameters.FLASH_MODE_TORCH);
camera.setParameters(params);
camera.startPreview();
isFlashOn = true;
The problem appear if flash is on and user turn off screen. flash automatically turned off and return on when screen is on.
I need to keep flash on when screen off.
Any idea please?
Here is a link to the problem you're having. "The key is changing the state back to FLASH_MODE_OFF and then back to FLASH_MODE_TORCH."
In the solution, he creates a Timer Task to handle checking if the screen is on or not. He then turns off the flash and then back on.
Down below the linked solution is another solution which added a thread and made it go to sleep for 200 milliseconds before sending the torch command.
So I'd say the solution you're looking for is the combination of both solutions.
#Override
public void onCreate() {
// assume we start with screen on and save that state ;-)
this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
screenOn = this.pm.isScreenOn();
// program a timer which checks if the light needs to be re-activated
this.mTimer = new Timer();
this.mTimerTask = new TimerTask() {
public void run() {
// re-activate the LED if screen turned off
if(!pm.isScreenOn() && pm.isScreenOn() != screenOn) {
Log.i("SleepLEDservice", "re-activated the LED");
// really it's NOT ENOUGH to just "turn it on", i double-checked this
setFlashlight(Camera.Parameters.FLASH_MODE_OFF);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
setFlashlight(Camera.Parameters.FLASH_MODE_TORCH);
}
screenOn = pm.isScreenOn();
}
};
}
private void setFlashlight(String newMode) {
try {
this.frontCamPara = this.frontCam.getParameters();
if(this.frontCamPara.getFlashMode() != newMode) {
this.frontCamPara.setFlashMode(newMode);
this.frontCam.setParameters(frontCamPara);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Again, credit should go to #stefanjunker and #BlueJam as their answers are referenced in this.
I am able to pause/resume my game by making my game thread(which holds/synchronized with surfaceview) wait/notifyAll. This all runs well using the in game pause button.
However, when I click home/back button I am able to pause my game, but when I resumes my game by clicking on its icon, I receive non responsive game screen back to me.
I have put logs on OnResume() method but nothing gets printed in LogCat!
If I click on my game screen I get error dialog to "Force Close" or "Wait" on my game activity.
Why I am getting this non responsive screen as if my application Hangged after pause ?
How can I handle physical buttons in same way as inGame pause/resume button ?
here is my logcat view of the operation. you can see onResume sop not getting printed similar to onPause.
EDIT : Code called by my pause/resume button and by onPause/onResume functions
where "this" is thread class itself
protected void setPause(){
synchronized (this) {
isPaused = true;
}
}
protected void setResume(){
synchronized (this) {
isPaused = false;
this.notifyAll();
}
}
Game Loop Code :
synchronized (this) {
while (running) {
if (isPaused) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS
- (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
This how I am calling it from surface view :
public void surfaceCreated(SurfaceHolder holder) {
// start the game loop
System.out.println("Starting Game Loop #########");
if(isPaused){
System.out.println("GamePaused");
gameLoopThread.setResume();
}
else{
System.out.println("Game Starting");
gameLoopThread.setRunning(true);
}
gameLoopThread.start();
}
EDIT 2:
If I Pause my game using pause button and let my phone get locked automatically... after resuming I am able to play normally with out any issue.
As per logs I see surface destroyed is not getting called in this case.
After onResume, your SurfaceView is created again, but it lost all the data it had before (all info needed for onDraw). you have to save the data in surfaceDestroyed or before, and reload it in the onCreate of your surfaceview
I have an android game on the market and I've been getting crash reports of NullPointers when trying to use the canvas. I can assume it's because SurfaceHolder.lockCanvas() is returning null, however it does this mid gameplay because based on where it crashes, SurfaceHolder.lockCanvas() returned a valid canvas at least once.
This is difficult to debug because I can't re-create it on my own device, which makes me wonder if it has to do with specific devices. The only hint I have is that one of the devices it occurred on was a Nexus 7.
NOTE: This is not the same problem as the similar-named question by me. The other question was due to trying to use the canvas before it was available, whereas here it was available.
Below is a sample of my code:
public class GameView extends SurfaceView implements SurfaceHolder.Callback
{
class GameThread extends Thread
{
#Override
public void run()
{
while (running)
{
Canvas c = null;
try
{
c = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder)
{
long start = System.currentTimeMillis();
doDraw(c);
long diff = System.currentTimeMillis() - start;
if (diff < frameRate)
Thread.sleep(frameRate - diff);
}
} catch (InterruptedException e)
{
}
finally
{
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
public void surfaceCreated(SurfaceHolder holder)
{
if (gThread.getState() == Thread.State.TERMINATED)
{
gThread = new GameThread(getHolder(), getContext(), getHandler());
gThread.start();
}
else
{
gThread.start();
}
}
}
I also faced that problem. It happens sporadically when the surface is already destroyed (and surfaceDestroyed was already called), but the looper thread is already inside the while loop (and after checking the running variable). Then lockCanvas returns null (as the surface was destroyed). This may happen for example on screen orientation changes or activity changes. It is a "simple" race condition threading problem.
A simple fix is to add if (canvas == null) continue; after lockCanvas (or just break the loop). Then running will be checked again and the loop ends.
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().