Glass - Slow camera \\ FileObserver notification -

2019-04-14 01:46发布

问题:

I have basically implemented the new XE12\GDK2 cameramanager sample code to capture an image on application start. However, the notification to the FileObserver callback takes anywhere from 3 to 30 seconds to get the notification of the image file creation. Taking a picture using the default 'Take a Picture' app works just fine so I dont thin it is an OS\update issue. My app's behavior is like: - Take the picture - Tap to accept Wait 3 to 30 seconds - Get the callback and the imageview is updated with the captured image.

I dont think I have modified a single line of the sample code provided in the GDK 2.0 camera tutorial. So wondering what I am missing.

I have attached the relevant section of the code below. Any tips\pointers highly appreciated.

@Override
protected void onStart() {
    super.onStart();
     Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
       // String path = Environment.getExternalStorageDirectory().getPath();

        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }       
}


private void processPictureWhenReady(final String picturePath) {

        final File pictureFile = new File(picturePath);

           if (pictureFile.exists()) {
           // The picture is ready; process it. Takes 3-30 seconds to get here!
               try {

                Bitmap imageBitmap = BitmapFactory.decodeFile(picturePath);
                int w = imageBitmap.getWidth();
                int h = imageBitmap.getHeight();       

         Bitmap bm2 = Bitmap.createScaledBitmap(imageBitmap, w/2, h/2, true);

                imageBitmap = bm2.copy(bm2.getConfig(), true);
                //m_ImageView.setImageBitmap(bm2);


               } catch (Exception e) {
                   Log.e("Exc", e.getMessage()); 
               }

            } else {
                tm = System.currentTimeMillis();
                // The file does not exist yet. Before starting the file observer, you
                // can update your UI to let the user know that the application is
                // waiting for the picture (for example, by displaying the thumbnail
                // image and a progress indicator).

                final File parentDirectory = pictureFile.getParentFile();
                FileObserver observer = new FileObserver(parentDirectory.getPath()) {
                    // Protect against additional pending events after CLOSE_WRITE is
                    // handled.
                    private boolean isFileWritten;

                    @Override
                    public void onEvent(int event, String path) {
                        if (!isFileWritten) {
                            // For safety, make sure that the file that was created in
                            // the directory is actually the one that we're expecting.
                            File affectedFile = new File(parentDirectory, path);
                            isFileWritten = (event == FileObserver.CLOSE_WRITE
                                    && affectedFile.equals(pictureFile));

                            if (isFileWritten) {
                                stopWatching();

                                // Now that the file is ready, recursively call
                                // processPictureWhenReady again (on the UI thread).
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        processPictureWhenReady(picturePath);
                                    }
                                });
                            }
                        }
                    }
                };
                observer.startWatching();

            }
    }

回答1:

Answering my own question - though I got the clarifications from Jenny Murphy and John Feig :-). Hopefully it helps others.

To the first point - why is image capture using the sample code from the GDK guide so slow: This is the expected behavior. The Glass camera intent (ACTION_IMAGE_CAPTURE) performs a ton of proprietary post-processing on the captured image - auto-HDR etc which takes time. This is cleverly disguised in the 'Take a picture' command by only displaying the preview image (which is available immediately.). As proof, try to find the image you just took in your time-line. You will not see it for several seconds (around 8 seconds on average in my experience.). Frankly, unless you are ok just grabbing the preview image, the camera intent may not be very useful in most apps.

The solution is to use the Camera directly using default Android APIs. For convenience, I have pasted a snippet of this code. Please excuse if it is kind of basic for many of you. A lot of the code is copied from John Feig's GIFCamera glassware on GitHub

activity_main layout contains a SurfaceView called preview

<SurfaceView
    android:id="@+id/preview"
    android:layout_width="500dp"
    android:layout_height="500dp"       
    android:layout_alignParentTop="true"
    android:layout_marginTop="20dp"
     />

MainActivity.java

public class MainActivity extends Activity implements PhotoCallback {
public byte[] m_jpg = null;
Camera cam = null;      
SurfaceHolder m_sh;

 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {

        @Override
        public void surfaceCreated(SurfaceHolder hldr) {
            m_sh = hldr;                
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {               
            myCapHandler2(); //Start Camera Preview etc.
        }
    };

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);         
    setContentView(R.layout.activity_main);     
    SurfaceView preview = (SurfaceView) findViewById(R.id.preview);     
    preview.getHolder().addCallback(mSurfaceHolderCallback);
}

public void myCapHandler2() {
        //open camera
        try {           
        cam = Camera.open(0);

        Camera.Parameters params = cam.getParameters();
        List<Size> sizes = params.getSupportedPreviewSizes();
        params.setJpegQuality(90);          
        params.setPreviewFpsRange(30000, 30000);            
        params.setPictureSize(sizes.get(1).width, sizes.get(1).height);
        params.setPreviewSize(sizes.get(1).width, sizes.get(1).height);
        cam.setParameters(params);

         try {
                 cam.setPreviewDisplay(m_sh);
                } 
                catch (IOException e) {
                    e.printStackTrace();
            }

            // Important: Call startPreview() to start updating the preview
            // surface. Preview must be started before you can take a picture.
         cam.startPreview();

         cam.takePicture(null, null,
                    new PhotoHandler(this));

        } catch (Exception e) {

            if (null != cam) { 
                cam.stopPreview(); 
                cam.release();
                }
            }
    }   

@Override
public void pictureTaken(byte[] jpg) {
    m_jpg = jpg;

    //Picture captured - release the camera for other apps
    cam.stopPreview();
    cam.release();      
}

@Override
public void onPause() {
    if (null != cam) { 
        cam.stopPreview(); 
        cam.release();
        }
}

@Override
public void onDestroy() {
if (null != cam) { 
    cam.stopPreview(); 
    cam.release();
    }
 }
}

PhotoHandler.java

import android.hardware.Camera;
import android.os.AsyncTask;



public class PhotoHandler implements Camera.PictureCallback {


private PhotoCallback photoCallback;

public PhotoHandler(PhotoCallback photoCallback) {
    super();
    this.photoCallback = photoCallback;
}

@Override
public void onPictureTaken(byte[] data, Camera camera) {

    new ProcessCapturedImage().execute(data);
}

private class ProcessCapturedImage extends AsyncTask<byte[], Void, byte[]> {

      @Override
      protected byte[] doInBackground(byte[]... params) {           

          if (null == params || null == params[0])
              return null;

          return params[0];

      }

    @Override
    protected void onPostExecute(byte[] params) {
        photoCallback.pictureTaken(params);
    }
}

}

PhotoCallback.java

public interface PhotoCallback {
public void pictureTaken(byte[] jpg);
}

All the best with your camera glassware.