onPictureTaken never called

2019-03-05 10:34发布

问题:

The code works mostly fine. The only problem is that the onPictureTaken function never called. I need to use this function to store the image to SD Card.

MainActivity

public class MainActivity extends Activity {

//private static Camera mCamera;
//private CameraPreview mPreview;
private static String TAG = "CamraOne";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.camera_layout);
    Log.d(TAG, "setContentView end");
    Intent intent = new Intent(this, CameraServiceOne.class);
    Log.d(TAG,"start intent");
    startService(intent);
    Log.d(TAG,"run in bkgrd");

}

Camera Service

CameraService

public class CameraServiceOne extends Service{
private SurfaceHolder sHolder; 
//a variable to control the camera
private static Camera mCamera;
//the camera parameters
private Parameters parameters;
private static String TAG = "CameraOne";
/** Called when the activity is first created. */



@Override
public void onCreate()
{
    super.onCreate();

}
@Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);

mCamera = Camera.open();
SurfaceView sv = new SurfaceView(getApplicationContext());


try {
mCamera.setPreviewDisplay(sv.getHolder());
parameters = mCamera.getParameters();

//set camera parameters
mCamera.setParameters(parameters);
mCamera.startPreview();
Thread.sleep(1000);
Log.d(TAG,"take pic");
mCamera.takePicture(null, null, mCall);
Log.d(TAG,"pic end");
Thread.sleep(5000);
mCamera.stopPreview();
mCamera.release();


} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


//Get a surface
sHolder = sv.getHolder();
//tells Android that this surface will have its data constantly replaced
sHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}



public static Camera.PictureCallback mCall = new Camera.PictureCallback()
{
public void onPictureTaken(byte[] data, Camera camera)
{   
mCamera = null;
Log.d(TAG,"in callback");
//decode the data obtained by the camera into a Bitmap
/*
FileOutputStream outStream = null;
try{
outStream = new FileOutputStream("/sdcard/Image.jpg");
Log.d(TAG,"write pic");
outStream.write(data);
Log.d(TAG,"write end");
outStream.close();
} catch (FileNotFoundException e){
Log.d("CAMERA", e.getMessage());
} catch (IOException e){
Log.d("CAMERA", e.getMessage());
}
*/

File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (pictureFile == null){
Log.d(TAG, "Error creating media file, check storage permissions: ");
return;
}

try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
/*
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MyCameraApp");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
Uri.parse("file://"+ mediaStorageDir)));
*/
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
};


@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}



public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){

return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
if(Environment.getExternalStorageDirectory() == null){
Log.d("MyCameraApp","getExternalStorageDirectory null");
}
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "CameraServiceOne");
      // This location works best if you want the created images to be shared
      // between applications and persist after your app has been uninstalled.

      // Create the storage directory if it does not exist
      if (! mediaStorageDir.exists()){
          if (! mediaStorageDir.mkdirs()){
              Log.d("MyCameraApp", "failed to create directory path: " +    
mediaStorageDir.getPath());
              return null;
          }
      }

      // Create a media file name
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      File mediaFile;
      Log.d(TAG,"write mediafile");
      if (type == MEDIA_TYPE_IMAGE){
          mediaFile = new File(mediaStorageDir.getPath() + File.separator +
          "IMG_"+ timeStamp + ".jpg");
      } else if(type == MEDIA_TYPE_VIDEO) {
          mediaFile = new File(mediaStorageDir.getPath() + File.separator +
          "VID_"+ timeStamp + ".mp4");
      } else {
          return null;
      }

      return mediaFile;
  }

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameraone"
android:versionCode="1"
android:versionName="1.0" >

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


<uses-sdk
    android:minSdkVersion="19"
    android:targetSdkVersion="19" />

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >

    <service android:name=".CameraServiceOne"/>
    <activity
        android:name="com.example.cameraone.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

</manifest>

Updated CameraService

public class CameraServiceOne extends Service implements SurfaceHolder.Callback{
  private SurfaceHolder sHolder; 
  //a variable to control the camera
  private static Camera mCamera;
  //the camera parameters
  private Parameters parameters;
  private static String TAG = "CameraOne";
  /** Called when the activity is first created. */
@Override
public void onCreate()
{
    super.onCreate();

}
@Override
public void onStart(Intent intent, int startId) {
  // TODO Auto-generated method stub
  super.onStart(intent, startId);
  Log.d(TAG,"on start");
   mCamera = Camera.open();
   //change sv to findViewByID
   //SurfaceView sv = new SurfaceView(getApplicationContext());

   LayoutInflater inflater = (LayoutInflater)
           getSystemService(LAYOUT_INFLATER_SERVICE);
         View layout = inflater.inflate(R.layout.camera_layout, null);
         SurfaceView sv = (SurfaceView) layout.findViewById(R.id.camera_surfaceview);
          parameters = mCamera.getParameters();
          mCamera.setParameters(parameters);

          mCamera.startPreview();
          Log.d(TAG,"startPreview");
         sv.post(new Runnable() { public void run() { mCamera.takePicture(null, null, mCall); } });
/*
   try {
              mCamera.setPreviewDisplay(sv.getHolder());
              parameters = mCamera.getParameters();

               //set camera parameters
             mCamera.setParameters(parameters);
             mCamera.startPreview();
             Thread.sleep(1000);
             Log.d(TAG,"take pic");
             mCamera.takePicture(null, null, mCall);
             Log.d(TAG,"pic end");
             Thread.sleep(5000);
             mCamera.stopPreview();
             mCamera.release();


        } catch (IOException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
*/       

   //Get a surface
     sHolder = sv.getHolder();
    //tells Android that this surface will have its data constantly replaced
     sHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public static Camera.PictureCallback mCall = new Camera.PictureCallback()
{
   public void onPictureTaken(byte[] data, Camera camera)
   {    
       mCamera = null;
       Log.d(TAG,"in callback");
         //decode the data obtained by the camera into a Bitmap
       /*
         FileOutputStream outStream = null;
              try{
                  outStream = new FileOutputStream("/sdcard/Image.jpg");
                  Log   .d(TAG,"write pic");
                  outStream.write(data);
                  Log.d(TAG,"write end");
                  outStream.close();
              } catch (FileNotFoundException e){
                  Log.d("CAMERA", e.getMessage());
              } catch (IOException e){
                  Log.d("CAMERA", e.getMessage());
              }
    */

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions: ");
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
            /*
            File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), "MyCameraApp");
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
            Uri.parse("file://"+ mediaStorageDir)));
            */
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
   }
};


  @Override
  public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
  }



  public static final int MEDIA_TYPE_IMAGE = 1;
  public static final int MEDIA_TYPE_VIDEO = 2;

  /** Create a file Uri for saving an image or video */
  private static Uri getOutputMediaFileUri(int type){

        return Uri.fromFile(getOutputMediaFile(type));
  }

  /** Create a File for saving an image or video */
  private static File getOutputMediaFile(int type){
      // To be safe, you should check that the SDCard is mounted
      // using Environment.getExternalStorageState() before doing this.
    if(Environment.getExternalStorageDirectory() == null){
        Log.d("MyCameraApp","getExternalStorageDirectory null");
    }
      File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "CameraServiceOne");
      // This location works best if you want the created images to be shared
      // between applications and persist after your app has been uninstalled.

      // Create the storage directory if it does not exist
      if (! mediaStorageDir.exists()){
          if (! mediaStorageDir.mkdirs()){
              Log.d("MyCameraApp", "failed to create directory path: " + mediaStorageDir.getPath());
              return null;
          }
      }

      // Create a media file name
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      File mediaFile;
      Log.d(TAG,"write mediafile");
      if (type == MEDIA_TYPE_IMAGE){
          mediaFile = new File(mediaStorageDir.getPath() + File.separator +
          "IMG_"+ timeStamp + ".jpg");
      } else if(type == MEDIA_TYPE_VIDEO) {
          mediaFile = new File(mediaStorageDir.getPath() + File.separator +
          "VID_"+ timeStamp + ".mp4");
      } else {
          return null;
      }

      return mediaFile;
  }

 @Override
  public void surfaceCreated(SurfaceHolder holder) {
      // The Surface has been created, now tell the camera where to draw the preview.
  }       
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    // TODO Auto-generated method stub

}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
    // TODO Auto-generated method stub
}

回答1:

onPictureTaken was never called. I figured out it that because the Camera.takePicture() method was invoked many times, it caused onPictureTaken to not be called. If ShutterCallback has a code return, then onPictureTaken is also not called.



回答2:

You correctly found that takePicture() should not be called before startPreview() or immediately after it. But unfortunately it's not enough to add extra sleep() between them.

The trick is that there are few callbacks that must be executed before you can issue takePicture(): a SurfaceView must be ready, and preview start playing on this SurfaceView.

So, first of all you must display the sv view. You can simply prepare a SurfaceView as part of camera_layout.xml, and instead of calling new SurfaceView(getApplicationContext()) use findViewById(R.id.camera_surface_view).

Second, add implements SurfaceHolder.Callback to your CameraServiceOne class, and implement the callbacks (see e.g. the Android tutorial).

Now, you can post (another level of async execution) takePicture() directly from the SurfaceCreated():

sv.post(new Runnable() { public void run() { mCamera.takePicture(null, null, mCall); } } });

Finally, consider using a background HandlerThread for Camera.open() and configuration: these lengthy operations may freeze the UI (main) thread for unacceptably long time.