Android Camera1 API guide

android_camera1.png

Camera1 API

Using the camera to take photos is relatively easy. It is somewhat harder to setup the camera preview to work properly.

Using the camera on the Android device can be done via the integration of existing camera application. In this case you would start the existing Camera application via an intent and use the return data of the application to access the result.

Alternatively you can also directly integrate the camera into your application via the Camera API.

This application will capture an image and save it to the image directory using Camera API.

Let's start!

Add the android.permission.CAMERA permission to access your camera and the android.permission.WRITE_EXTERNAL_STORAGE to be able to write to the SD card to your AndroidManifest.xml file.

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

For demo purpose I have created a simple layout with GridView. Change the activity_main.xml file in the res/layout folder to the following

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <GridView android:id="@+id/gvPhotos"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:padding="10dp"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:numColumns="auto_fit"
        android:columnWidth="100dp"
        android:stretchMode="columnWidth"
        android:gravity="center" />
</LinearLayout>

MainActivity will hold GridView with all saved photos.

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.GridView;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private GridView gvPhotos;
    private PhotoAdapter adPhotos;
    public static final String REQUEST_RESULT="REQUEST_RESULT";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActionBar actionBar = getSupportActionBar();
        actionBar.setLogo(R.drawable.ic_launcher);
        actionBar.setDisplayUseLogoEnabled(true);
        actionBar.setDisplayShowHomeEnabled(true);


        adPhotos = new PhotoAdapter(this, getPhotos());

        gvPhotos = (GridView)findViewById(R.id.gvPhotos);
        gvPhotos.setAdapter(adPhotos);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_photo:
                Intent i = new Intent(this, TakePhotoActivity.class);
                startActivityForResult(i, 1);
                break;
            default:
                break;
        }
        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            adPhotos.setPhotos(getPhotos());
        }
    }

    public List<Drawable> getPhotos() {
        File rootDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        File pictureDir = new File(rootDir, "CV");
        List<Drawable> list = new ArrayList<>();

        // fix java.lang.OutOfMemoryError
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 8;

        for(File f : pictureDir.listFiles()){
            Bitmap original = BitmapFactory.decodeFile(f.getAbsolutePath(), options);
            Drawable drawable = new BitmapDrawable(getResources(), original);
            list.add(drawable);
        }

        return list;
    }
}

Create new class PhotoAdapter for our GridView

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

import java.util.List;

public class PhotoAdapter extends BaseAdapter {
    private Context ctx;
    List<Drawable> list;

    public PhotoAdapter(Context ctx, List<Drawable> list) {
        this.ctx = ctx;
        this.list = list;
    }

    public int getCount() {
        return list.size();
    }

    public Object getItem(int position) {
        return position;
    }

    public long getItemId(int position) {
        return position;
    }

    public void setPhotos(List<Drawable> list) {
        this.list = list;
        notifyDataSetChanged();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        if (convertView == null) {
            imageView = new ImageView(ctx);
            imageView.setLayoutParams(new GridView.LayoutParams(185, 185));
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setPadding(5, 5, 5, 5);
        } else {
            imageView = (ImageView) convertView;
        }
        imageView.setImageDrawable(list.get(position));
        return imageView;
    }
}

Menu file

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_photo"
        android:title="Take photo"
        app:showAsAction="ifRoom"
        android:icon="@android:drawable/ic_menu_camera"
        >
    </item>

</menu>

Create the following TakePhotoActivity class which will be responsible for taking the photo and saving to the SD card. I'm going to use TextureView to display a content stream. Unlike SurfaceView, TextureView does not create a separate window but behaves as a regular View. This key difference allows a TextureView to be moved, transformed, animated, etc.

In order to use TextureView, all you need to do is get its SurfaceTexture. The SurfaceTexture can then be used to render content. In order to do this, you just need to do instantiate an object of this class and implement SurfaceTextureListener interface.

Then we need to override the below methods to complete implementation

  • public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2) - invoked when a TextureView's SurfaceTexture is ready for use.
  • public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) - invoked when the specified SurfaceTexture is about to be destroyed.
  • public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1,int arg2) - invoked when the SurfaceTexture's buffers size changed.
  • public void onSurfaceTextureUpdated(SurfaceTexture arg0) - invoked when the specified SurfaceTexture is updated through updateTexImage().

Any view that is displayed in the TextureView can be rotated and its alpha property can be adjusted by using setAlpha and setRotation methods.

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.os.Environment;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;


@SuppressWarnings("deprecation")
public class TakePhotoActivity extends Activity implements TextureView.SurfaceTextureListener {
    private final static String DEBUG_TAG = "APP";
    private final static String DIR_APP_PHOTO = "CV";
    private TextureView textureView;
    private Camera camera;
    private int cameraID = 0;

    public class PhotoHandler implements PictureCallback {
        private final Context context;

        public PhotoHandler(Context context) {
            this.context = context;
        }

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            File rootDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
            File pictureDir = new File(rootDir, DIR_APP_PHOTO);

            if (!pictureDir.exists() && !pictureDir.mkdirs()) {
                Log.d(TakePhotoActivity.DEBUG_TAG, "Can't create directory to save image.");
                Toast.makeText(context, "Can't create directory to save image.",  Toast.LENGTH_LONG).show();
                return;
            }

            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmss");
            String date = dateFormat.format(new Date());
            String photoFile = "photo_" + date + ".jpg";
            String filename = pictureDir.getPath() + File.separator + photoFile;

            File pictureFile = new File(filename);

            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
                Toast.makeText(context, "Photo saved to " + photoFile, Toast.LENGTH_LONG).show();
            } catch (Exception error) {
                Log.d(TakePhotoActivity.DEBUG_TAG, "File" + filename + "not saved: " + error.getMessage());
                Toast.makeText(context, "Image could not be saved.", Toast.LENGTH_LONG).show();
            }

            Intent i = new Intent();
            i.putExtra(MainActivity.REQUEST_RESULT, photoFile);
            setResult(RESULT_OK, i);
            finish();
        }
    }

    private int findBackCamera() {
        int cameraID = -1;
        int numberOfCameras = Camera.getNumberOfCameras();
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                Log.d(DEBUG_TAG, "Camera found!");
                cameraID = i;
                break;
            }
        }
        return cameraID;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.take_photo_activity);

        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            Toast.makeText(this, "No camera on this device", Toast.LENGTH_LONG).show();
        } else {
            cameraID = findBackCamera();
            if (cameraID < 0) {
                Toast.makeText(this, "No back camera found.", Toast.LENGTH_LONG).show();
            } else {
                camera = Camera.open(cameraID);
                textureView = (TextureView)findViewById(R.id.textureView);
                textureView.setSurfaceTextureListener(this);
            }
        }

    }

    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        if (camera != null) {
            try {
                Camera.Parameters parameters = camera.getParameters();
                parameters.setRotation(90);
                camera.setParameters(parameters);
                camera.setPreviewTexture(surface);
                camera.setDisplayOrientation(90);
                camera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        if (camera != null) {
            camera.stopPreview();
            camera.release();
        }
        return true;
    }

    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {}

    public void takePicture(View view) {
        if (camera != null) {
            camera.takePicture(null, null, new PhotoHandler(getApplicationContext()));
        }
    }
}

Result

android_camera2.png

Camera2 API

Compared with the old camera API, the Camera2 API introduced in the L is a lot more complex: more than ten classes are involved, calls (almost always) are asynchronized, plus lots of capture controls and meta data that you feel confused about.

Following are steps of using camera2 API

  1. Start from CameraManager.
  2. Setup the output targets.
  3. Get a CameraDevice.
  4. Create a CaptureRequest from the CameraDevice.
  5. Create a CaptureRequestSession from the CameraDevice.
  6. Submit a CaptureRequest to CaptureRequestSession.
  7. Get the CaptureResults.

You can see example of Camera2 API in android-Camera2Basic github.

comments powered by Disqus