I want to run the front camera of a device from a background service, getting the captured frames by the camera in the background service using the callback that's called whenever a new frame is captured by the camera (eg, onPreviewFrame
), and apply some real time processing on the obtained frames.
After searching I understand that there are two ways to run the camera in the background:
1- setting a SurfaceView with size 1 X 1.
2- Using SurfaceTexture (This does not require any view which seems more efficient.)
I tried both ways, I could only run the camera from the background, but the callbacks for getting frames were never called, though I tried different tricks.
The code I used for method 1 is available here:
Note that I've also tried to call onPreviewFrame inside surfaceChanged, but I always get that the camera is null when trying to use it to set the callback! I even tried to re-instantiate it as camera = Camer.open()
, but I got an error saying "Fail to connect to camera service."
Here is the code for method 2:
public class BackgroundService extends Service implements TextureView.SurfaceTextureListener { private Camera camera = null; private int NOTIFICATION_ID= 1; private static final String TAG = "OCVSample::Activity"; // Binder given to clients private final IBinder mBinder = new LocalBinder(); private WindowManager windowManager; private SurfaceTexture mSurfaceTexture= new SurfaceTexture (10); public class LocalBinder extends Binder { BackgroundService getService() { // Return this instance of this service so clients can call public methods return BackgroundService.this; } }//end inner class that returns an instance of the service. @Override public IBinder onBind(Intent intent) { return mBinder; }//end onBind. @Override public void onCreate() { // Start foreground service to avoid unexpected kill startForeground(NOTIFICATION_ID, buildNotification()); } private Notification buildNotification () { NotificationCompat.Builder notificationBuilder=new NotificationCompat.Builder(this); notificationBuilder.setOngoing(true); //this notification should be ongoing notificationBuilder.setContentTitle(getString(R.string.notification_title)) .setContentText(getString (R.string.notification_text_and_ticker)) .setSmallIcon(R.drawable.vecsat_logo) .setTicker(getString(R.string.notification_text_and_ticker)); return(notificationBuilder.build()); } @Override public void onDestroy() { Log.i(TAG, "surfaceDestroyed method"); } /***********Ok now all service related methods were implemented**************/ /************now implement surface texture related methods*****************/ @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) { //Now the surfaceTexture available, so we can create the camera. camera = Camera.open(1); mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { Log.e(TAG, "frame captured"); } }); //now try to set the preview texture of the camera which is actually the surfaceTexture that has just been created. try { camera.setPreviewTexture(mSurfaceTexture); } catch (IOException e){ Log.e(TAG, "Error in setting the camera surface texture"); } camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] bytes, Camera camera) { Log.e(TAG, "frame captured"); } }); // the preview was set, now start it camera.startPreview(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) { //super implementation is sufficient. } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { //stop the preview. camera.stopPreview(); //release camera resource: camera.release(); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { Log.e(TAG, "frame captured from update"); } }); camera.stopPreview(); //stop current preview. try { camera.setPreviewTexture(mSurfaceTexture); } catch (IOException e){ Log.e(TAG, "Error in setting the camera surface texture"); } camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] bytes, Camera camera) { Log.e(TAG, "frame captured"); } }); camera.startPreview(); // start the preview again. }
Here is how I call the service from an activity:
@Override protected void onCreate(Bundle savedInstanceState) { //call the method in the super class.. super.onCreate(savedInstanceState); //inflate the xml layout. setContentView(R.layout.main); //prepare buttons. ImageButton startButton = (ImageButton) findViewById(R.id.start_btn); ImageButton stopButton = (ImageButton) findViewById(R.id.stop_btn); //prepare service intent: final Intent serviceIntent = new Intent(getApplicationContext(), BackgroundService.class); //set on click listeners for buttons (this should respond to normal touch events and simulated ones): startButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //startActivity(serviceIntent); //start eye gazing mode as a service running in the background: isBound= getApplicationContext().bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); } }); stopButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //stop eye gazing mode as a service running in the background: getApplicationContext().unbindService(mConnection); } }); }//end onCreate. private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { BackgroundService.LocalBinder binder = (BackgroundService.LocalBinder) service; mService = binder.getService(); isBound = true; } @Override public void onServiceDisconnected(ComponentName className) { isBound = false; } };
Please help, how can I get the frames callback to work? I tried a lot but I couldn't figure it out.
Any help is highly appreciated whether it uses method 1 or 2.
Thanks.