android 2.2 (froyo) と1.6 (donut) でカメラプレビュー

HTC desire HD (2.2) でカメラのpreviewサイズを設定すると「予期せぬエラーで終了しました」がでる。
ぐぐってみるとどうやら2.1-1 以降でこの問題が起きるらしい。

http://code.google.com/p/android/issues/detail?id=7909


試してはないけど、sdk付属のカメラサンプルですら起動失敗する様子。
ここでの議論を要約すると、

  1. 2.1-1でsdkのカメラサンプルすら動かんorz
  2. setPreviewSizeをコメントアウトすればとりあえず動くよ。
  3. あと写真サイズの変更は問題なくできるよ。
  4. でもそれってなんかやだよね。
  5. このコード使ってちょ
    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.05;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;

        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        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);
            }
        }

        // Cannot find the one match the aspect ratio, ignore the requirement
        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;
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        Camera.Parameters parameters = mCamera.getParameters();

        List<Size> sizes = parameters.getSupportedPreviewSizes();
        Size optimalSize = getOptimalPreviewSize(sizes, w, h);
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);

        mCamera.setParameters(parameters);
        mCamera.startPreview();
    }

という感じだった。
これだけだとgetSupportedPreviewSizes()がapi level 5以降でないと対応してないから、HT-03AとかXperiaだとエラー出る。
なので汎用的に1.6でも2.2でも動くようにするにはオーバーラップ関数を自分で作らなくちゃならない。

同じ問題に直面してリフレクションで実装してる例があったで参考にした。
http://labs.techfirm.co.jp/android/cho/1647

これでparameters.getSupportedPreviewSizes()の部分をReflect.getSupportedParameters(params)に変更してあげればダイジョブなはず。

実際に書いたコード

import android.hardware.Camera;
import android.hardware.Camera.*;
import android.view.*;
import android.content.*;
import android.util.*;
import java.io.*;
import java.util.*;

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback{
	private final static String LOGTAG="CameraPreview";
	protected SurfaceHolder holder;
	protected Camera camera;
	public CameraPreview(Context context){
		super(context);
		holder=getHolder();
		holder.addCallback(this);
		holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
	}
	
	public void surfaceCreated(SurfaceHolder holder){
		camera=camera.open();
		try{
			camera.setPreviewDisplay(holder);
		}catch(IOException e){
			Log.d(LOGTAG,"setPreviewDisplay exception");
		}
	}
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
		camera.stopPreview();
		Camera.Parameters params=camera.getParameters();
		// params.setPreviewFormat(PixelFormat.JPEG); //if clear this comment, it cause setParameter Exception in api-level 5 emulator.
		List<Size> sizes=Reflect.getSupportedPreviewSizes(params);
		if(null != sizes){
			//for(int i=0;i<sizes.size();i++){
			//	Size s=sizes.get(i);
			//	Log.d(LOGTAG,"["+i+"]:"+s.width+","+s.height);
			//}
			Size optimalSize = getOptimalPreviewSize(sizes,width,height);
			//Log.d(LOGTAG,"optimalsize:"+optimalSize.width+","+optimalSize.height);
			//Log.d(LOGTAG,"defaultsize:"+width+","+height);
			params.setPreviewSize(optimalSize.width, optimalSize.height);
		}else{
			//Log.d(LOGTAG,"set previewsize normally:"+width+","+height);
			params.setPreviewSize(width, height);
		}
		camera.setParameters(params);
		camera.startPreview();
	}
	public void surfaceDestroyed(SurfaceHolder holder){
		camera.stopPreview();
		camera.release();
	}
	
	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
		final double ASPECT_TOLERANCE = 0.05;
		double targetRatio = (double) w / h;
		if (sizes == null) return null;
		
		Size optimalSize = null;
		double minDiff = Double.MAX_VALUE;
		
		int targetHeight = h;
		
		// Try to find an size match aspect ratio and size
		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);
		    }
		}
		
		// Cannot find the one match the aspect ratio, ignore the requirement
		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;
	}
}

Reflect.javaはもらってきたものから特に変更なし

import android.hardware.Camera;
import android.hardware.Camera.*;
import android.util.Log;
import java.lang.reflect.*;
import java.util.*;

public class Reflect{
	private final static String LOGTAG="Reflect";
	private static Method Parameters_getSupportedPreviewSizes;
	static{
		initCompatibility();
	}
	private static void initCompatibility(){
		try{
			Parameters_getSupportedPreviewSizes = Camera.Parameters.class
				.getMethod("getSupportedPreviewSizes",new Class[]{});
		}catch(NoSuchMethodException e){
			Log.d(LOGTAG,"no such method exception");
		}
	}
	@SuppressWarnings("unchecked")
	public static List<Size> getSupportedPreviewSizes(Camera.Parameters p){
		try{
			if(Parameters_getSupportedPreviewSizes != null){
				return (List<Size>)Parameters_getSupportedPreviewSizes.invoke(p);
			}else{
				return null;
			}
		}catch(InvocationTargetException e){
			Log.e(LOGTAG,"InvocationTargetException");
			Throwable cause=e.getCause();
			if(cause instanceof RuntimeException){
				throw (RuntimeException)cause;
			}else if(cause instanceof Error){
				throw (Error)cause;
			}else{
				throw new RuntimeException(e);
			}
		}catch(IllegalAccessException e){
			Log.e(LOGTAG,"IllegalAccessException");
			return null;
		}
	}
}

パーミッションの設定は

SDKのバージョンは4でテスト。

あとAndroidManifest.xmlのActivityに横向き、全表示追加。
android:screenOrientation="landscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"


HT-03A,HTC desire HDで実機動作の確認しました。
emulatorは1.6と2.2でちゃんと動きます。