カメラの映像を画面に表示する (カメラプレビューの表示) - Android

Androidの端末のカメラの映像を画面に表示するコードを紹介します。

アプリケーション作成情報

  • ProjectName : SimpleCamera
  • ApplicationName : SimpleCamera
  • PackageName : iPentec.SimpleApp.SimpleCamera
  • Activity : SimpleCameraActivity
  • Androidのバージョンは2.3.3にしました

UI

変更しません。デフォルトのままです。

コード

AndoridManifest.xml

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

    <uses-sdk android:minSdkVersion="10" />
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera"/>
    <uses-feature android:name="android.hardware.camera.autofocus"/>
    <uses-feature android:name="android.hardware.camera.flash"/>
    
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name=".SimpleCameraActivity"
            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>

Permission, featureの追加方法

直接AndroidManifest.xmlを編集してもよいですが、以下の手順でGUIから編集もできます。
Permissionの追加
AndroidManifest.xmlファイルを開きます。下図の編集画面が表示されます。


画面下部の[Permissions]タブをクリックし選択します。下図の画面に変わります。Permissionsエリアの[Add]ボタンを押します。


下図のダイアログボックスが表示されますので、[Uses Permission]を選択し、[OK]ボタンを押します。


Permissionsエリアに[Uses Permission]アイテムが追加されます。右側の[Name]エリアのコンボボックスをクリックし一覧から[android.permission.CAMERA]を選択します。


featureの追加
AndroidManifest.xmlファイルを開き、画面下部の[Manifest]タブをクリックし選択します。画面下部の[Manifest Extras]エリアの[Add]ボタンを押します。


下図のダイアログが表示されるので、一覧の[Uses Feature]を選択し[OK]ボタンを押します。


Manifest Extras エリアに"Uses Feature"アイテムが追加されます。


右側の[Name]テキストボックスにFeature名を入力します。"android.hardware.camera"を入力します。


同様の手順で "android.hardware.camera.autofocus", "android.hardware.camera.flash"を追加します。


アプリケーション (SimpleCameraActivity.java)

package iPentec.SimpleApp.SimpleCamera;

import android.app.Activity;
import android.os.Bundle;
import android.view.*;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.app.*;

public class SimpleCameraActivity extends Activity {
    private Preview mPreview;
    Camera mCamera;
    int numberOfCameras;
    int cameraCurrentlyLocked;
    int defaultCameraId;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);        
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        mPreview = new Preview(this);        
        setContentView(mPreview);
        
        numberOfCameras = Camera.getNumberOfCameras();
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
        	Camera.getCameraInfo(i, cameraInfo);
        	if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
        		defaultCameraId = i;
        	}
        }
    }
    
    @Override
    protected void onResume() {
    	super.onResume();
    	mCamera = Camera.open();
    	cameraCurrentlyLocked = defaultCameraId;
    	mPreview.setCamera(mCamera);
    }
    
    @Override
    protected void onPause() {
    	super.onPause();
    	if (mCamera != null) {
    		mPreview.setCamera(null); 
    		mCamera.release();
    		mCamera = null;
    	}   
    } 
}

解説

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);        
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    mPreview = new Preview(this);        
    setContentView(mPreview);
        
    numberOfCameras = Camera.getNumberOfCameras();
    CameraInfo cameraInfo = new CameraInfo();
    for (int i = 0; i < numberOfCameras; i++) {
   	Camera.getCameraInfo(i, cameraInfo);
    	if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
            defaultCameraId = i;
        }
    }
}
onCreateでは、アプリケーションの画面モードをフルスクリーンにし、全画面にPreviewクラスのビューコントロールを表示します。(5行目まで)。Camera.getNumberOfCameras();でカメラの個数を取得し、個々のカメラの情報を取得します。カメラのうちfacing プロパティが"CAMERA_FACING_BACK"のもの(スクリーンの反対側に付いているカメラ)をデフォルトのカメラとして選択します。

protected void onResume() {
    super.onResume();
    mCamera = Camera.open();
    cameraCurrentlyLocked = defaultCameraId;
    mPreview.setCamera(mCamera);
}
onResume()はアプリの起動後に呼び出されるメソッドです。onCreate()と違いバックグラウンドから復帰した場合もonResume()は呼び出されます。onResumeではカメラ(デバイス)をオープンし、ビューコントロールにカメラをセットします。

protected void onPause() {
    super.onPause();
    if (mCamera != null) {
        mPreview.setCamera(null); 
        mCamera.release();
        mCamera = null;
    }   
} 
onPause()はアプリ内で画面が切り替わった際や、アプリがバックグラウンドになった場合、他のアプリに切り替えた場合などに呼び出されます。また、アプリ終了時や画面の向きが変わった場合にも呼び出されます。 onPauseでは、カメラがnullでなければカメラをリリースし、ビューコントロールのカメラの割り当てを解除しています。

画面描画コントロール (Preview.java)

package iPentec.SimpleApp.SimpleCamera;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import java.util.*;
import java.io.*;

public class Preview extends ViewGroup implements SurfaceHolder.Callback {
    private final String TAG = "Preview";
    SurfaceView mSurfaceView;
    SurfaceHolder mHolder;
    Size mPreviewSize;
    List<Size> mSupportedPreviewSizes;
    Camera mCamera;
    
    Preview(Context context) {
    	super(context);
    	mSurfaceView = new SurfaceView(context);
    	addView(mSurfaceView);

    	mHolder = mSurfaceView.getHolder();
    	mHolder.addCallback(this);
    	mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
    
    public void setCamera(Camera camera) {
    	mCamera = camera;
    	if (mCamera != null) {
    		mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
    		requestLayout();
    	}
    }
    
    public void switchCamera(Camera camera) { 
    	setCamera(camera);
    	try {     
    		camera.setPreviewDisplay(mHolder);
    	} catch (IOException exception) {
    		Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
    	}
    	Camera.Parameters parameters = camera.getParameters();
    	parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    	requestLayout(); 
    	camera.setParameters(parameters);
    }
    
    public void surfaceCreated(SurfaceHolder holder) {
    	try {
    		if (mCamera != null) {
    			mCamera.setPreviewDisplay(holder);
    		}  
    	}
    	catch (IOException exception) {
    		Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
    	}
    }
    
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
        	mCamera.stopPreview(); 
        }
    }
    
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {    
    	 Camera.Parameters parameters = mCamera.getParameters();
    	 parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    	 requestLayout();
    	 mCamera.setParameters(parameters);
    	 mCamera.startPreview();
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    	final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    	final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    	setMeasuredDimension(width, height);
    	if (mSupportedPreviewSizes != null) {
    		mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
    	}   	
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    	 if (changed && getChildCount() > 0) {
    		 final View child = getChildAt(0);
    		 final int width = r - l; 
    		 final int height = b - t;  
    		 int previewWidth = width;
    		 int previewHeight = height;
    		 if (mPreviewSize != null) { 
    			 previewWidth = mPreviewSize.width;
    			 previewHeight = mPreviewSize.height; 
    	}
    	// Center the child SurfaceView within the parent.
    	if (width * previewHeight > height * previewWidth) {
    		final int scaledChildWidth = previewWidth * height / previewHeight;
    		child.layout((width - scaledChildWidth) / 2, 0,
    				(width + scaledChildWidth) / 2, height);
    		} else {
    			final int scaledChildHeight = previewHeight * width / previewWidth;
    			child.layout(0, (height - scaledChildHeight) / 2,
    					width, (height + scaledChildHeight) / 2); 
    		}
    	} 
    }
    
    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {   
    	final double ASPECT_TOLERANCE = 0.1;
    	double targetRatio = (double) w / h;
    	if (sizes == null) return null;
    	Size optimalSize = null;
    	double minDiff = Double.MAX_VALUE;
    	int targetHeight = h;

    	for (Size size : sizes) {
    		double ratio = (double) size.width / size.height;
    		if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
    		if (Math.abs(size.height - targetHeight) < minDiff) {
    			optimalSize = size;
    			minDiff = Math.abs(size.height - targetHeight); 
    		}
    	}
    	if (optimalSize == null) {
    		minDiff = Double.MAX_VALUE; 
    		for (Size size : sizes) {    
    			if (Math.abs(size.height - targetHeight) < minDiff) { 
    				optimalSize = size;
    				minDiff = Math.abs(size.height - targetHeight);
    			}
    		}
    	}
    	return optimalSize;
    }
}

解説

Preview(Context context) {
    super(context);
    mSurfaceView = new SurfaceView(context);
    addView(mSurfaceView);

    mHolder = mSurfaceView.getHolder();
    mHolder.addCallback(this);
    mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
コンストラクタでは、コントロール内にSurfaceViewコントロールを作成して配置しています。SurfaceViewのHolderをgetHolder()メソッドで取得しコールバックを追加します。また、SurfaceViewのタイプをsetType()メソッドを用いて、SURFACE_TYPE_PUSH_BUFFERSに変更します。

public void setCamera(Camera camera) {
    mCamera = camera;
    if (mCamera != null) {
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
    	requestLayout();
    }
}
setCamera()メソッドはアプリケーションからコントロールに対してカメラをセットするメソッドです。セットされたカメラが有効な場合は、プレビュー領域のサイズを計算し、requestLayout() を呼び出してレイアウトを初期化します。

public void surfaceCreated(SurfaceHolder holder) {
    try {
        if (mCamera != null) {
    	    mCamera.setPreviewDisplay(holder);
        }
    }
    catch (IOException exception) {
        Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
    }
}
    
public void surfaceDestroyed(SurfaceHolder holder) {
    if (mCamera != null) {
        mCamera.stopPreview(); 
    }
}
surfaceCreated, surfaceDestroyedはsurfaceの作成、廃棄時に呼び出されるメソッドです。surfaceCreatedではSurfaceHolderを引数に与えてsetPreviewDisplay()メソッドを呼び出し、カメラのプレビュー画面をSurfaceに設定します。surfaceDestroyedではstopPreview()メソッドを呼び出しプレビュー画面を停止します。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    setMeasuredDimension(width, height);
    if (mSupportedPreviewSizes != null) {
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
    }   	
}
onMeasure()はリサイズの要求があった際に呼び出されます。resolveSizeメソッドを用い幅と高さを求めます。getSuggestedMinimumWidth(),getSuggestedMinimumHeight()は画面の最小の幅と高さを求めるメソッドです。
求めた幅と高さをsetMeasuredDimension()メソッドに与えViewのサイズを設定します。getOptimalPreviewSize()メソッドを呼び出し、プレビュー領域のサイズを計算します。

protected void onLayout(boolean changed, int l, int t, int r, int b) {
	 if (changed && getChildCount() > 0) {
		 final View child = getChildAt(0);
		 final int width = r - l; 
		 final int height = b - t;  
		 int previewWidth = width;
		 int previewHeight = height;
		 if (mPreviewSize != null) { 
			 previewWidth = mPreviewSize.width;
			 previewHeight = mPreviewSize.height; 
	}
	// Center the child SurfaceView within the parent.
	if (width * previewHeight > height * previewWidth) {
		final int scaledChildWidth = previewWidth * height / previewHeight;
		child.layout((width - scaledChildWidth) / 2, 0,
				(width + scaledChildWidth) / 2, height);
		} else {
			final int scaledChildHeight = previewHeight * width / previewWidth;
			child.layout(0, (height - scaledChildHeight) / 2,
					width, (height + scaledChildHeight) / 2); 
		}
	} 
}
onLayout()はビューが子コントロールのサイズと位置を決定する際に呼び出されます。引数にコントロールの座標の(Left,Top,Rifht,Bottom)値が渡されますので、その値からコントロールの幅と高さを求めます。求めた値を利用してchild(子コントロール)を画面の中心に配置します。

実行結果

アプリケーションを実行すると下図の画面が表示されます。カメラのプレビュー画像が画面全体に表示されます。


著者
iPentecのプログラマー、最近はAIの積極的な活用にも取り組み中。
とっても恥ずかしがり。
最終更新日: 2024-01-04
作成日: 2012-01-06
iPentec all rights reserverd.