Camera frames are never captured in background ser

2019-03-22 03:43发布

问题:

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.