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:
https://stackoverflow.com/questions/35160911/why-onpreviewframe-is-not-being-called-from-a-background-service
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.