1

My problem is this: I want a background service, that will obtain frames from the camera in real-time, so that I can analyze them. I've seen a lot of similar topics here that supposedly address this issue, but none of them has really worked in my case.

My first attempt was to create an Activity, which started a Service, and inside the service I created a surfaceView, from which I got a holder and implemented a callback to it in which I prepared the camera and everything. Then, on a previewCallback, I could make a new thread, which could analyze the data I was getting from the onPreviewFrame method of PreviewCallback.

That worked well enough, while I had that service in the foreground, but as soon as I opened up another application (with the service still running in the background), I realized that the preview wasn't there so I couldn't get the frames from it.

Searching on the internet, I found out I could perhaps solve this with SurfaceTexture. So, I created an Activity which'd start my service, like this:

public class SurfaceTextureActivity extends Activity {

public static TextureView mTextureView;

public static Vibrator mVibrator;   
public static GLSurfaceView mGLView;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mGLView = new GLSurfaceView(this);

    mTextureView = new TextureView(this);

    setContentView(mTextureView);


    try {
        Intent intent = new Intent(SurfaceTextureActivity.this, RecorderService.class);
        intent.putExtra(RecorderService.INTENT_VIDEO_PATH, "/folder-path/");
        startService(intent);
        Log.i("ABC", "Start Service "+this.toString()+" + "+mTextureView.toString()+" + "+getWindowManager().toString());
    }
    catch (Exception e)  {
        Log.i("ABC", "Exc SurfaceTextureActivity: "+e.getMessage());
    }

}

}

And then I made the RecorderService implement SurfaceTextureListener, so that I could open the camera and do the other preparations, and then perhaps capture the frames. My RecorderService currently looks like this:

public class RecorderService extends Service implements TextureView.SurfaceTextureListener, SurfaceTexture.OnFrameAvailableListener {

    private Camera mCamera = null;
    private TextureView mTextureView;
    private SurfaceTexture mSurfaceTexture;
    private float[] mTransformMatrix;

    private static IMotionDetection detector = null;
    public static Vibrator mVibrator;

    @Override
    public void onCreate() {
        try {

            mTextureView = SurfaceTextureActivity.mTextureView;
            mTextureView.setSurfaceTextureListener(this);

            Log.i("ABC","onCreate");

//          startForeground(START_STICKY, new Notification()); - doesn't work

        } catch (Exception e) {
            Log.i("ABC","onCreate exception "+e.getMessage());
            e.printStackTrace();
        }

    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) 
    {
        //How do I obtain frames?!
//      SurfaceTextureActivity.mGLView.queueEvent(new Runnable() {
//          
//          @Override
//          public void run() {
//              mSurfaceTexture.updateTexImage();
//              
//          }
//      });
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
            int height) {

        mSurfaceTexture = surface;
        mSurfaceTexture.setOnFrameAvailableListener(this);
        mVibrator = (Vibrator)this.getSystemService(VIBRATOR_SERVICE);

         detector = new RgbMotionDetection();

        int cameraId = 0;
        Camera.CameraInfo info = new Camera.CameraInfo();

        for (cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
            Camera.getCameraInfo(cameraId, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
                break;
        }

        mCamera = Camera.open(cameraId);
        Matrix transform = new Matrix();

        Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
        int rotation = ((WindowManager)(getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay()
                .getRotation();
        Log.i("ABC", "onSurfaceTextureAvailable(): CameraOrientation(" + cameraId + ")" + info.orientation + " " + previewSize.width + "x" + previewSize.height + " Rotation=" + rotation);

        try {

        switch (rotation) {
        case Surface.ROTATION_0: 
            mCamera.setDisplayOrientation(90);
            mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
                    previewSize.height, previewSize.width, Gravity.CENTER));
            transform.setScale(-1, 1, previewSize.height/2, 0);
            break;

        case Surface.ROTATION_90:
            mCamera.setDisplayOrientation(0);
            mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
                    previewSize.width, previewSize.height, Gravity.CENTER));
            transform.setScale(-1, 1, previewSize.width/2, 0);
            break;

        case Surface.ROTATION_180:
            mCamera.setDisplayOrientation(270);
            mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
                    previewSize.height, previewSize.width, Gravity.CENTER));
            transform.setScale(-1, 1, previewSize.height/2, 0);
            break;

        case Surface.ROTATION_270:
            mCamera.setDisplayOrientation(180);
            mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
                    previewSize.width, previewSize.height, Gravity.CENTER));
            transform.setScale(-1, 1, previewSize.width/2, 0);
            break;
        }

            mCamera.setPreviewTexture(mSurfaceTexture);


        Log.i("ABC", "onSurfaceTextureAvailable(): Transform: " + transform.toString());

        mCamera.startPreview();
//      mTextureView.setVisibility(0);

        mCamera.setPreviewCallback(new PreviewCallback() {

            @Override
            public void onPreviewFrame(byte[] data, Camera camera) {
                if (data == null) return;
                Camera.Size size = mCamera.getParameters().getPreviewSize();
                if (size == null) return;

                //This is where I start my thread that analyzes images
                DetectionThread thread = new DetectionThread(data, size.width, size.height);
                thread.start();

            }
        });
        } 
        catch (Exception t) {
             Log.i("ABC", "onSurfaceTextureAvailable Exception: "+ t.getMessage());
        }
    }

However, similarly as in the other case, since my analyzing thread starts inside the onSurfaceTextureAvailable, which is only when the texture is there, and not when I open up another application, the frame capturing won't continue when I open up something else.

Some ideas have shown that it's possible, but I just don't know how. There was an idea, that I could implement SurfaceTexture.onFrameAvailable and then once new frame is available, trigger a runnable to be ran on render thread (GLSurfaceView.queueEvent(..)) and finally run a runnable call SurfaceTexture.updateTexImage(). Which is what I've tried (it's commented out in my code), but it doesn't work, the application crashes if I do that.

What else can I possibly do? I know that this can work somehow, because I've seen it used in apps like SpyCameraOS (yes, I know it's open-source and I've looked at the code, but I couldn't make a working solution), and I feel like I'm missing just a small piece somewhere, but I have no idea what I'm doing wrong. I've been at this for the past 3 days, and no success.

Help would be greatly appreciated.

Community
  • 1
  • 1
NoelAramis
  • 303
  • 1
  • 6
  • 18
  • Are you trying to display the frames, or just capture them? If you don't need to display them -- which I wouldn't think you would for "background" capture -- get rid of anything with the word "View" in the name. Use an offscreen surface as the destination for rendering from the SurfaceTexture. See Grafika (https://github.com/google/grafika) for some examples of working with camera and SurfaceTexture (maybe "continuous capture"). – fadden Jan 30 '15 at 17:22
  • I've looked at the ContinuousCaptureActivity and I've made it into a service, however... as soon as I press Back or Home, surfaceDestroyed is called and because of that, the continuous capture stops. I can't seem to make it go on even after the program is put in the background. I'm particularly interested to find out how I can use an offscreen surface as the destination for rendering like you mentioned. Any examples? – NoelAramis Jan 31 '15 at 14:55
  • One example can be found in Grafika's TextureUploadActivity.java -- note how the OffscreenSurface is used in `runTextureTest()`. The test is uploading and rendering a set of textures repeatedly to try to figure out the speed of texture upload, and it does it all off-screen. Code for accessing the pixels via `glReadPixels()` is included at the end, where it saves off the last iteration for debugging. – fadden Jan 31 '15 at 16:25
  • That makes sense, but how am I supposed to render from the SurfaceTexture into the offscreen surface, when the surface doesn't exist anymore? In your `createPixelSources()` you're creating the ByteBuffer yourself, and then creating an image texture out of it in `runTextureTest()`, but how do I continuously feed the offscreen surface with camera's frame data, when the SurfaceTexture is not getting updated with new frame data because it was destroyed when I pressed Back/Home? – NoelAramis Jan 31 '15 at 17:34
  • The SurfaceTexture doesn't get destroyed unless it's associated with a TextureView. Create your own SurfaceTexture. See, for example, Grafika's TextureFromCameraActivity (search for `new SurfaceTexture`). You may also want to peek at DoubleDecodeActivity, which detaches and reattaches SurfaceTextures from TextureViews during orientation changes to avoid stopping video playback... I don't think it's what you want to be doing, but it does show that you can keep the TextureView's SurfaceTexture alive and active even when the Activity has paused and the TextureView is gone. – fadden Jan 31 '15 at 22:39
  • Also: bear in mind that a "Surface" is a queue of buffers with a producer-consumer relationship. The Camera is the producer. The internal name for SurfaceTexture is "GLConsumer" -- all it does is receive buffers from the queue and convert them to a GLES texture in the current EGL context. SurfaceTextures are not tied to the Activity unless it's part of something that is, like a TextureView object. Longer explanation: https://source.android.com/devices/graphics/architecture.html – fadden Jan 31 '15 at 22:42
  • Thank you very much. That was indeed very helpful, and I managed to solve my problem by creating the offscreen surface and make a current read from the window surface, which took the existing surface holder as an argument. How do I accept your answer? – NoelAramis Feb 01 '15 at 11:01
  • I've summarized the comments into an answer. – fadden Feb 01 '15 at 16:49
  • @NoelAramis I'm trying to do something similar, can you please share your code ? Thanks! – Sloosh Nov 04 '16 at 14:44

1 Answers1

2

Summarizing the comments: direct the output of the Camera to a SurfaceTexture that isn't tied to a View object. A TextureView will be destroyed when the activity is paused, freeing its SurfaceTexture, but if you create a separate SurfaceTexture (or detach the one from the TextureView) then it won't be affected by changes in Activity state. The texture can be rendered to an off-screen Surface, from which pixels can be read.

Various examples can be found in Grafika.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • 1
    I'd up this if it was more helpful than "look over there"... >.> – Managarm Apr 22 '15 at 09:26
  • Is there any sample code to run SurfaceTexure in the background? – Dania Feb 03 '16 at 09:04
  • @Dania: I'm not sure what "run SurfaceTexture in the background" means. You should post a new question explaining what you're trying to do and what you've tried so far. – fadden Feb 03 '16 at 16:48
  • @fadden, thanks a lot for your response. I want to get the frames obtained by the camera in a background service not an activity. I posted a question that explains in details what I want to do and shows the work I tried. Here is the question, http://stackoverflow.com/questions/35183408/camera-frames-are-never-captured-in-background-service-android. Please help me get this solved, I tried a lot but nothing worked. Thank you. – Dania Feb 03 '16 at 17:12