dcsimg
November 22, 2017
Hot Topics:

Working with a Camera in Android

  • November 3, 2017
  • By Chunyen Liu
  • Send Email »
  • More Articles »

A camera is perhaps one of the most desirable hardware features among the regular mobile phone functionalities. It goes beyond the traditional purpose of just taking pictures. Modern mobile cameras, as shown in Figure 1, also do on-the-fly image processing, object recognition, 3-D photo capture, security authentication, virtual reality, and so on. With the gigantic selection of versatile camera apps, many things become excitingly possible.

We will cover the basics of what the Android Camera API can do, with simple examples included to make learning easier. Although many apps are still utilizing the deprecated Camera API, we should be gearing towards the currently recommended Camera2 API, introduced in API level 21. We suggest you keep the Android software development kit up to date. If you need some help with that, please check out the previous tutorial, "Exploring the Android SDK and AVD Managers."

And, because the Camera2 API package is still not widely discussed, there are really not many references or examples you can find out there. Google's own developer advocate did point out some examples, so you should at least check out Camera2Basic, which demostrates how to get camera characteristics, get a camera preview, and save pictures.

Modern mobile camera
Figure 1: Modern mobile camera

General Camera Settings

To gain access for your app to the mobile camera and its features, you are required to present to your users the list of specific permissions and features needed. Since Android 6.0 API level 23, users can have more control of granting or denying permissions dynamically instead of just during the installation. Therefore, to handle various cases for permissions, you are recommended to first identify the app's current permissions and test against protected services and data and then make dynamic permission requests when necessary. That will keep your app from being stopped during the operation. In the AndroidManifest.xml file, here are some permissions and features you will likely use to control and camera and save the resulting media files onto the external storage devices, such as a SD card.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name=
   "android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name=
   "android.hardware.camera" android:required="true" />
<uses-feature android:name=
   "android.hardware.camera.autofocus" />

In Listing 1, at the beginning of the app, we try to make sure the permissions specified in the manifest file are granted—the permission statuses for Manifest.permission.CAMERA and Manifest.permission.WRITE_EXTERNAL_STORAGE. If not, we should dynamically request the permissions through requestPermissions(). Once all the permissions are granted through the verification of our counter, permissionCount, the app will continue to operate.

public class TutorialOnCamera extends Activity {
   static final int REQUEST_PERMISSION_CAMERA = 1001;
   static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE =
      1002;
   int permissionCount = 0;

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

      setContentView(R.layout.examples);

      // Request permissions at runtime
      permissionCount = 0;
      if (checkSelfPermission(Manifest.permission.CAMERA) !=
            PackageManager.PERMISSION_GRANTED)
         requestPermissions(new String[]
            {Manifest.permission.CAMERA},
            REQUEST_PERMISSION_CAMERA);
      else
         permissionCount++;

      if (checkSelfPermission
         (Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
         PackageManager.PERMISSION_GRANTED)
         requestPermissions(new String[]
            {Manifest.permission.WRITE_EXTERNAL_STORAGE},
            REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE);
      else
         permissionCount++;

      // Start the default camera intent
      Button button_send_to_another = (Button)
         findViewById(R.id.button_by_intent);
      button_send_to_another.setOnClickListener
            (new View.OnClickListener() {
         // @Override
         public void onClick(View arg0) {
             if (permissionCount >= 2)
                ...code snipped...
         }
      });
   }

   @Override
   public void onRequestPermissionsResult(int requestCode,
         String[] permissions, int[] grantResults) {
      super.onRequestPermissionsResult(requestCode, permissions,
         grantResults);

      switch (requestCode) {
         case REQUEST_PERMISSION_CAMERA:
         case REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE:
            if (grantResults[0] ==
               PackageManager.PERMISSION_GRANTED)
               permissionCount++;
         default:
            break;
      }
   }
}

Listing 1: Dynamic permission request

Using the Default Camera Simply by Issuing an Android Intent

Activating the default camera app from inside your software is very straightforward. If you do not plan to do anything fancy (a customized user interface, for example) to the camera processing and just want the resulting photos or videos, you can issue a system-provided Android intent, MediaStore.ACTION_IMAGE_CAPTURE, and start the activity by using startActivityForResult. An output file location also can be specified if you prefer saving the result on the physical storage; for example, cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);, Otherwise, a bitmap for the captured photo will be returned in the bundle, as in Bitmap b = (data.getExtras()).getParcelable("data"); For simplicity, we display the result in the image view on screen. They are illustrated in Listing 2 and Figure 2.

public class DefaultCameraByIntent extends Activity {
   static final int ID_ACT_CAMERA = 20001;

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

      setContentView(R.layout.defcam);

      issueCameraIntent();
   }

   private void issueCameraIntent() {
      Intent cameraIntent = new Intent
       (MediaStore.ACTION_IMAGE_CAPTURE);
       startActivityForResult(cameraIntent, ID_ACT_CAMERA);
   }

   protected void onActivityResult(int requestCode,
         int resultCode, Intent data) {
      super.onActivityResult(requestCode, resultCode, data);

      if (requestCode == ID_ACT_CAMERA &&
            resultCode == RESULT_OK) {
         Bitmap b = (data.getExtras()).getParcelable("data");

         ImageView iv = (ImageView)findViewById(R.id.iv);
         iv.setImageBitmap(b);
      }
   }
}

Listing 2: Android camera intent

Sample result by default camera
Figure 2: Sample result by default camera

What Characteristics Do Your Mobile Camera Support?

The advantages of the newly improved Camera2 API over the previous version are highlighted with the performance on newer hardware, faster handling of multiple images and multiple cameras, more filters and effects, and the like. You start by getting a list of cameras on your mobile device through CameraManager. For each camera, there are many different supported characteristics you can query from CameraCharacteristics Listing 3 and Figure 3 basically illustrate how to get an inventory of camera characteristics. The official Camera2 API page has the complete listing.

public class Camera2Characts extends Activity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      setContentView(R.layout.cam2char);

      CameraManager manager = (CameraManager)
         getSystemService(CAMERA_SERVICE);
      String result = "";
      try {
         for (String cid : manager.getCameraIdList()) {
            CameraCharacteristics cc =
               manager.getCameraCharacteristics(cid);

            Integer camdir = cc.get
               (CameraCharacteristics.LENS_FACING);
            if (camdir != null && camdir ==
                  CameraCharacteristics.LENS_FACING_FRONT)
               result += ("Camera " + cid +
                  ": value=" + camdir + ", facing front\n");
            else
               result += ("Camera " + cid + ": value=" +
                  camdir + ", not facing front\n");

            Boolean flash = cc.get
               (CameraCharacteristics.FLASH_INFO_AVAILABLE);
            if (flash != null && flash == true)
               result += ("Camera " + cid + ": value=" + flash +
                  ", flash available\n");
            else
               result += ("Camera " + cid + ": value=" + flash +
                  ", flash not available\n");

            List<CameraCharacteristics.Key<?>> keys = cc.getKeys();
            result += ("Camera " + cid + ": " + keys.toString() +
               "\n\n");
         }
      } catch (CameraAccessException e) {
         e.printStackTrace();
      }

      result = result.replaceAll("CameraCharacteristics.Key\\
         (", "");
      result = result.replaceAll("\\)", "");
      TextView tv = (TextView) findViewById(R.id.tv);
      tv.setText(result);
   }
}

Listing 3: Camera2 Characteristics on the Device

Sample characteristics by Camera2
Figure 3: Sample characteristics by Camera2

Taking Pictures with the Camera2 API

Now, we will start preparing an example to capture the photo with Camera2 API. First, a live preview panel in the layout is extended from TextureView and set up as follows:

<com.androidlet.cliu.tutorialoncamera.MyTextureView
   android:id="@+id/mtv"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:layout_alignParentEnd="true"
   android:layout_alignParentBottom="true" />

In Figure 4, the live preview is the large image whereas the 50x50 thumbnail on top is simply an ImageView. In Listing 4, we begin the photo capturing process by setting up the output folder and file. When a user presses the capture button, the software goes into the startCapture() mode and specifies the preferred result format in setCaptureResult(). Then, the main operations are carried out in captureStillPicture(). Once the result photo is ready, it is stored to the external storage by SavePhotoToFile(). We then load the photo file back in and scale it down for the thumbnail, as in loadPhotoIntoView().

Note: A sizable portion of the code is re-purposed from Google's excellent sample projects, so you definitely should check out the links at the end of this tutorial, especially about the camera preview session build and control.

Camera2 preview and result thumbnail
Figure 4: Camera2 preview and result thumbnail

public class Camera2Take extends Activity {
   private static final String TAG = "TutorialOnCamera";
   private static String mFileFolder = "";
   private static String mFilename = "cam2.jpg";
   private static boolean mFileFolderOK = false;
   private static File mFile;
   private static Bitmap mBitmap = null;
   private MyTextureView mTextureView;

   ...

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

      setContentView(R.layout.cam2take);
      mTextureView = (MyTextureView)findViewById(R.id.mtv);

      // Create file folder
      mFileFolder = Environment.getExternalStorageDirectory() +
         "/TutorialOnCamera/";
      File sddir = new File(mFileFolder);
      if (!sddir.mkdirs()) {
         if (sddir.exists())
            mFileFolderOK = true;
         else
            mFileFolderOK = false;
      } else
         mFileFolderOK = true;
      mFile = new File(mFileFolder + mFilename);

      // Take Photos with Camera2
      Button button_takephoto = (Button)
         findViewById(R.id.button_takephoto);
      button_takephoto.setOnClickListener(new
            View.OnClickListener() {
         // @Override
         public void onClick(View arg0) {
            startCapture();
         }
      });
   }

   private void loadPhotoIntoView() {
      if (mBitmap != null)
         mBitmap.recycle();

      try {
         FileInputStream fis = new FileInputStream(mFileFolder +
            mFilename);
         BufferedInputStream bis = new BufferedInputStream(fis);
         mBitmap = BitmapFactory.decodeStream(bis);
         bis.close();
         fis.close();

         Matrix m = new Matrix();
         m.setScale(50.0f / (float)mBitmap.getWidth(),
                50.0f / (float)mBitmap.getHeight());
         mBitmap = Bitmap.createBitmap(mBitmap, 0, 0,
                mBitmap.getWidth(),
                mBitmap.getHeight(),
                m, true);

         ImageView thumb = (ImageView)findViewById(R.id.thumb);
         thumb.setImageBitmap(mBitmap);

      } catch (Exception e) {
         Log.e(TAG, e.toString());
      }
   }

   ...
    
   private final ImageReader.OnImageAvailableListener
      mOnImageAvailableListener
         = new ImageReader.OnImageAvailableListener() {
      @Override
      public void onImageAvailable(ImageReader ir) {
         mHandler.post(new SavePhotoToFile(ir.acquireNextImage(),
            mFile));
      }
   };
    
   private void setCaptureResult(int width, int height) {
      CameraManager manager = (CameraManager)getSystemService
         (Context.CAMERA_SERVICE);

      try {
         for (String cameraId : manager.getCameraIdList()) {
            ...                
            mImageReader.setOnImageAvailableListener
               (mOnImageAvailableListener, mHandler);
            ...
         }
      } catch (CameraAccessException e) {
         e.printStackTrace();
      } catch (NullPointerException e) {
         Log.d(TAG, "setCaptureResult(): NullPointerException");
      }
   }

   private void captureStillPicture() {
      try {
         ...

         CameraCaptureSession.CaptureCallback CaptureCallback
                = new CameraCaptureSession.CaptureCallback() {

            @Override
            public void onCaptureCompleted
               (@NonNull CameraCaptureSession session,
                @NonNull CaptureRequest request,
                @NonNull TotalCaptureResult result) {
                endCapture();
               loadPhotoIntoView();
            }
         };

         mCaptureSession.stopRepeating();
         mCaptureSession.abortCaptures();
         mCaptureSession.capture(captureBuilder.build(),
            CaptureCallback, null);
      } catch (CameraAccessException e) {
         e.printStackTrace();
      }
   }

   private void startCapture() {
      ...
   }

   private void endCapture() {
      ...
   }

   ...

   private static class SavePhotoToFile implements Runnable {
      private final Image mImage;
      private final File mFile;

      SavePhotoToFile(Image image, File file) {
         mImage = image;
         mFile = file;
      }
      ...
   }

  ...
}

Listing 4: Capturing a photo with Camera2

Conclusion

We have covered the basic mobile camera usage from a developer's perspective. Depending on what your app needs when it comes to camera-related processing, you can do it as simply as directing the work flow by issuing an Android intent to start the default camera app without doing much except for picking up the result. If you plan to customize the camera usage—user interface, color filter control, realtime on-camera drawing—you will need to study the Camera2 API carefully, and then develop your app that makes good use of available features.

The developer references or discussions for the newly recommended API since level 21 are not widely available, so you need to look into the some examples offered by Google. Hopefully, when more and more developers have experiences using the API, we can get our questions answered to make the app development easier later.

References

About the Author

Author Chunyen Liu has been a software veteran in Taiwan and the United States. He is a published author of 40+ articles and 100+ tiny apps, a software patentee, technical reviewer, and programming contest winner by ACM/IBM/SUN. He holds advanced degrees in Computer Science with 20+ graduate-level classes. On the non-technical side, he is enthusiastic about the Olympic sport of table tennis, being a USA certified umpire, certified coach, certified referee, and categorized event winner at State Championships and the US Open.





Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date