Android のカメラのサンプル

@IT から提供されている_もはやケータイに必須のカメラをAndroidで制御しよう_という記事。カメラ機能なコンテンツアプリのサンプルとして、結構上質なので読んでみます。

Main Activity

アプリが起動したら最初に動くのはこの Activity です。リストなメニューを出しといて次画面に遷移する、というサンプルとして非常に参考になります。クラスの属性として以下な Object の配列を定義しておいて

 private Object[] activities = {
 "Hello", Hello.class,
 "AutoFocus", AutoFocus.class,
 "Parameters", Parameters.class,
 "Overlay", Overlay.class,
 };

onCreate の中でこの配列を ListView に設定してます。

 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main);

 CharSequence[] list = new CharSequence[activities.length / 2];
 for (int i = 0; i < list.length; i ) {
 list[i] = (String)activities[i * 2];
 }

 ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
 ListView listView = (ListView)findViewById(R.id.ListView01);
 listView.setAdapter(adapter);

 listView.setOnItemClickListener(new OnItemClickListener() {
 @Override
 public void onItemClick(AdapterView parent, View view, int position, long id) {
 Intent intent = new Intent(Main.this, (Class)activities[position * 2 1]);
 startActivity(intent);
 }
 });
 }

一連の手続き的には ListView への文字列の設定方法とかも参考になりますが、こーゆー方式で画面遷移の制御ができる、というのもなかなかに参考になります。
Main な Activity のお仕事はこれだけになります。基本的にリストの項目選択されたら Intent 投げて次の画面を出せば良いだけなので。

Hello Activity

手続き定義は onCreate メソドのみ。

 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
 requestWindowFeature(Window.FEATURE_NO_TITLE);
 setContentView(new CameraPreview(this));
 }

フルスクリーンにしてタイトルバーを非表示にしてます。最後に CameraPreview というクラスのオブジェクトを生成して setContentView に渡しています。
後で確認しますが、この CameraPreview クラスは SurfaceView を継承しているクラスです。カメラのプレビューを云々する Activity についてはこの手法 (SurfaceView なオブジェクトを生成して setContentView メソドに渡す) が一般的なようです。

CameraPreview

このクラス、SurfaceView を継承しつつ、SurfaceHolder.Callback インターフェースを実装してます。これもプレビューなパーツの定番な実装な模様。

class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
 protected Context context;
 private SurfaceHolder holder;
 protected Camera camera;

属性 (インスタンス変数) も上記三点は一般的と言えるかも。ちなみに private ではなくて protected なのはこのクラスを継承したクラスがこれらの属性を使う事ができるように、という配慮のはず。
SurfaceHolder な属性はおそらくコンストラクタのみで使用されている模様。
あと、SurfaceHolder.Callback インターフェースで実装が必要なメソッドが以下です。

  • void surfaceCreated(SurfaceHolder)
    • surface 生成時に呼び出される
  • void surfaceChanged(SurfaceHolder, int, int, int)
    • surface の format や size が変更された時に呼び出される
  • void surfaceDestroyed(SurfaceHolder)
    • surface 破棄時に呼び出される

とりあえず以下がコンストラクタです。その後に上記メソドを引用します。

 CameraPreview(Context context) {
 super(context);
 this.context = context;
 holder = getHolder();
 holder.addCallback(this);
 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
 }

SurfaceView からは getHolder で SurfaceHolder なオブジェクトが取得できる模様。callback を追加して SurfaceHolder.Callback な i/f を実装しているオブジェクトを登録。setType で surface の型 (?) を登録してます。
上記の例だとプッシュバッファというもののようですが、これは何かな。android developers によれば以下な記述があります。

int SURFACE_TYPE_GPU
This constant オンライン カジノ ゲーム is deprecated.
this is ignored, this value is set automatically when needed.
int SURFACE_TYPE_HARDWARE
This constant is deprecated.
this is ignored, this value is set automatically when needed.
int SURFACE_TYPE_NORMAL Surface type: creates a regular surface, usually in main, non
contiguous, cached/buffered RAM.
int SURFACE_TYPE_PUSH_BUFFERS Surface type: creates a “push” surface, that is a surface that
doesn”t owns its buffers.

GPU と HARDWARE は deprecated との事で選択肢としては NORMAL と PUSH_BUFFERS なんですが、push buffer というのは GPU に実行させる命令なリストをメインメモリ側に蓄積しといて一気に GPU に、的な実装らしい。
以下が SurfaceHolder.Callback な実装となります。

surfaceCreated メソド

基本的には Camera.open() で Camera なオブジェクトを取得して、Camera#setPreviewDisplay() で live preview を出力するようセットしている模様。
ちなみにこの実装では、Camera#setPreviewCallback() とか Camera#setOneShotPreviewCallback() とか Camera#setErrorCallback() などというメソドを使用して callback の登録をしていますが、中身は基本的にログを吐くのみになっています。ここ、動作確認できてなくて、どういった瞬間に呼び出されるのか、は不明。

 public void surfaceCreated(SurfaceHolder holder) {
 Log.d("TEST", "surfaceCreated");
 if (camera == null) {
 try {
 camera = Camera.open();
 } catch (RuntimeException e) {
 ((Activity)context).finish();
 Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
 }
 }
 if (camera != null) {
 camera.setPreviewCallback(new PreviewCallback() {
 @Override
 public void onPreviewFrame(byte[] data, Camera camera) {
 Log.d("TEST", "onPreviewFrame: preview: data=" data);
 }
 });
 camera.setOneShotPreviewCallback(new PreviewCallback() {
 @Override
 public void onPreviewFrame(byte[] data, Camera camera) {
 Log.d("TEST", "onPreviewFrame: short preview: data=" data);
 }
 });
 camera.setErrorCallback(new ErrorCallback() {
 @Override
 public void onError(int error, Camera camera) {
 Log.d("TEST", "onError: error=" error);
 }
 });
 }
 try {
 camera.setPreviewDisplay(holder);
 } catch (IOException e) {
 camera.release();
	 camera = null;
	 ((Activity)context).finish();
	 Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
 }
 }

例外処理とか、結構きちんと書かれています。

surfaceChanged メソド

このメソドですが、フォーマットとか幅やら高さが変わったら呼び出される callback なのかな。定義は以下です。

 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 Log.d("TEST", "surfaceChanged");
 if (camera == null) {
 ((Activity)context).finish();
 } else {
 camera.stopPreview();
	 setPictureFormat(format);
	 setPreviewSize(width, height);
	 camera.startPreview();
 }
 }

setPictureFormat とか setPreviewSize なメソドについては別途とさせて下さい。基本的には引数に変更された情報が渡されてくるので、それに沿って変更を加えている形です。

surfaceDestroyed メソド

実装は以下です。

 public void surfaceDestroyed(SurfaceHolder holder) {
 Log.d("TEST", "surfaceDestroyed");
	if (camera != null) {
 camera.stopPreview();
	 camera.release();
	 camera = null;
 }
 }

後始末ですね。

その他

ええと surfaceChanged で出てきた setPictureFormat とか setPreviewSize なメソドですが protected な修飾子が付けられて定義されてます。これ以外にも Camera.Parameters な設定項目に対するアクセサが定義されております。
以下に引用しておきます。

 protected void setPictureFormat(int format) {
 try {
	 Camera.Parameters params = camera.getParameters();
	 List supported = params.getSupportedPictureFormats();
	 if (supported != null) {
	 for (int f : supported) {
		 if (f == format) {
		 params.setPreviewFormat(format);
			camera.setParameters(params);
			break;
		 }
		}
 }
 } catch (Exception e) {
	 e.printStackTrace();
 }
 }

 protected void setPreviewSize(int width, int height) {
 Camera.Parameters params = camera.getParameters();
	List supported = params.getSupportedPreviewSizes();
	if (supported != null) {
	 for (Camera.Size size : supported) {
	 if (size.width <= width && size.height <= height) {
		 params.setPreviewSize(size.width, size.height);
		 camera.setParameters(params);
		 break;
 }
 }
 }
 }

何故に setPictureFormat のみ例外対応が入ってるのかが謎。また以下の項目について同様の set なメソドが定義されてます。

  • Antibanding : 階調の落差に関する設定
  • ColorEffect : 色合いを変更
  • FlashMode : フラッシュの設定
  • FocusMode : フォーカスの動作に関する設定
  • SceneMode : 撮影シーンの設定
  • WhiteBalance : ホワイトバランスの設定

これはシリーズ化させざるを得ないですね。全部掘る方向ですので、次回をお楽しみに。