0

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
}
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307

2 Answers2

0

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.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • In Service, we couldn't use findViewById. That's why I used this "new SurfaceView(getApplicationContext())" – J'sbugnight Jul 23 '14 at 21:23
  • Yes you can. Use the context of the activity that opened the service. But it's not necessary. It's fine to create a SurfaceView, only make sure it gets displayed. – Alex Cohn Jul 23 '14 at 22:34
  • Please look at my Updated CameraService in the bottom. I changed my CameraService as you said. I used the "Inflator" so that could use findViewById in Service. Except for AsyncTask to do Camera.open() and configuration, all the thing as you told. Still doesn't work. – J'sbugnight Jul 24 '14 at 02:33
  • Could you give some changes on the code so that it could work, Alex? – J'sbugnight Jul 24 '14 at 02:37
  • Using `inflator`, do you see the expected layout on the screen? In similar situations, I use `putExtras()` and pass the calling `Activity` to the service. – Alex Cohn Jul 27 '14 at 10:13
  • Yes, it worked on google glass. putExtras is also a good way to pass the variable in intent. Any idea how to change the code to make it work??? – J'sbugnight Jul 28 '14 at 15:44
  • To begin with, I don't exactly understand your motivation. What is the purpose of taking picture from Service and not from the Activity that starts the Service? – Alex Cohn Jul 29 '14 at 09:18
  • i'm using the google glass to take the pics in background. i need to run other apps simultaneously. So i need to put the camera into service. – J'sbugnight Jul 29 '14 at 17:55
  • Unless it is an underdocumented very special feature of Google Glass, you need a foreground activity to take photos. If your app has root access, it is possible, but still not easy. – Alex Cohn Jul 29 '14 at 19:23
  • It seems that there are some successful examples that run camera in background on mobile android device. Some of them are not real background, just resize the preview to be very tiny. But..but there is someone who successfully run the camera in real background though I don't know how he did that. – J'sbugnight Jul 29 '14 at 21:14
0

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.

Jamal
  • 763
  • 7
  • 22
  • 32