●カメラ

まずはカメラを使用する権限を取得する必要があります。
MyApplication?\app\src\main\AndroidManifest.xml
この場所にある、AndroidManifest.xmlファイルを開きます。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.myapplication">
    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

赤文字の部分を追加してカメラの権限をアプリに与えます。

しかし、Android6.0以降は、Dangerous Permissionのためこのままでは動きません。
アプリ起動後にユーザーに権限を許可してもらう必要があります。
対応するには下の方を見てください。


■最低限の画像を表示するカメラアプリ

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new _view(this));
    }

    public class _view extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {
        private android.hardware.Camera camera;

        _view(android.content.Context context) {
            super(context);
            android.view.SurfaceHolder holder;
            holder = getHolder();
            holder.addCallback(this);
        }

        @Override
        public void surfaceCreated(android.view.SurfaceHolder holder) {
            camera = android.hardware.Camera.open();
            try {
                camera.setPreviewDisplay(holder);
            }catch(Exception e){
            }
            camera.startPreview();
        }

        @Override
        public void surfaceChanged(android.view.SurfaceHolder holder, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(android.view.SurfaceHolder holder) {
            camera.stopPreview();
            camera.release();
        }
    }
}

画像の向きや縮尺などはおかしいですが、何はともあれカメラの画像が表示されるはずです。


■画像の向きを合わせる

カメラは横向きが正しい方向として作られているため、画面も横向きにしないと画像が90度ずれます。
そこで、画面の向きを横向きに設定します。
AndroidManifest.xmlファイルを開きactivityの部分を探して赤文字部分を追加します。
<activity android:name=".MainActivity" android:screenOrientation="landscape">
画面を横向きにすると画像の方向も正しく表示されるはずです。


■タッチした時のイベント

SurfaceViewを継承したクラスの中で、onTouchEventをオーバーライトするとイベントを拾えます

public class _view extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {

    @Override
    public boolean onTouchEvent(android.view.MotionEvent event) {
        //画面がタッチされた
        return true;
    }

}


■無音カメラを作成する

タッチした時のイベント内でJPEG画像を取得して保存することにより無音カメラ状態になります。
撮影されたjpegファイルはDCIMフォルダ内のtest.JPGとして保存されます。

@Override
public boolean onTouchEvent(android.view.MotionEvent event) {
    //画面がタッチされた
    camera.setPreviewCallback(new android.hardware.Camera.PreviewCallback() {
        public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
            camera.setPreviewCallback(null);//コールバックを無効にして1回のみのデータ取得にする
            int width = camera.getParameters().getPreviewSize().width;//サイズ取得
            int height = camera.getParameters().getPreviewSize().height;
            try {
                android.graphics.YuvImage yuvimage = new android.graphics.YuvImage
                    (data, android.graphics.ImageFormat.NV21, width, height, null);
                java.io.ByteArrayOutputStream _ByteArrayOutputStream = new java.io.ByteArrayOutputStream();
                yuvimage.compressToJpeg(new android.graphics.Rect(0, 0, width, height), 80, _ByteArrayOutputStream);
                java.io.FileOutputStream fos = new java.io.FileOutputStream(android.os.Environment.getExternalStoragePublicDirectory
                    (android.os.Environment.DIRECTORY_DCIM).getPath() + "/test.JPG", false);
                fos.write(_ByteArrayOutputStream.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                android.content.Context context = getApplicationContext();
                android.widget.Toast t = android.widget.Toast.makeText(context, e.toString(), android.widget.Toast.LENGTH_LONG);
                t.show();
            }
        }
    });
    return true;
}


▼無音カメラを作成するにあたって色々ネットを巡って試行錯誤したものも載せておきます。

こちらも動作しますので。
ちなみにRAWデータをRGBに変換する関数、decodeYUV420SPはネットでの拾い物のコピペです。

@Override
public boolean onTouchEvent(android.view.MotionEvent event) {
    //画面がタッチされた
    camera.setPreviewCallback(new android.hardware.Camera.PreviewCallback() {
        public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
            camera.setPreviewCallback(null);//コールバックを無効にして1回のみのデータ取得にする
            int width = camera.getParameters().getPreviewSize().width;//サイズ取得
            int height = camera.getParameters().getPreviewSize().height;
            int[] rgb = new int[(width * height)];
            try {
                android.graphics.Bitmap bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
                decodeYUV420SP(rgb, data, width, height);//RAWをRGBに変換
                bitmap.setPixels(rgb, 0, width, 0, 0, width, height);
                android.graphics.Matrix matrix = new android.graphics.Matrix();
                matrix.postRotate(90);
                bitmap = bitmap.createBitmap(bitmap,0,0,width,height,matrix,true);//ビットマップを回転させる
                java.io.FileOutputStream fos = new java.io.FileOutputStream(android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DCIM).getPath() + "/test.JPG", false);
                bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 100, fos);//jpegに変換
                fos.flush();
                fos.close();
            } catch (Exception e) {
                android.content.Context context = getApplicationContext();
                android.widget.Toast t = android.widget.Toast.makeText(context, e.toString(), android.widget.Toast.LENGTH_LONG);
                t.show();
            }

        }
    });
    return true;
}

//この関数はネットでの拾い物のコピペ
void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
    final int frameSize = width * height;

    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (0xff & ((int) yuv420sp[yp])) - 16;
            if (y < 0) y = 0;
            if ((i & 1) == 0) {
                v = (0xff & yuv420sp[uvp++]) - 128;
                u = (0xff & yuv420sp[uvp++]) - 128;
            }

            int y1192 = 1192 * y;
            int r = (y1192 + 1634 * v);
            int g = (y1192 - 833 * v - 400 * u);
            int b = (y1192 + 2066 * u);

            if (r < 0) r = 0;
            else if (r > 262143) r = 262143;
            if (g < 0) g = 0;
            else if (g > 262143) g = 262143;
            if (b < 0) b = 0;
            else if (b > 262143) b = 262143;

            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
        }
    }
}


■カメラを切り替える

カメラの個数は camera.getNumberOfCameras() で取得できます。
カメラが2個ある端末では2の値が返ります。

フロント側のカメラを使う場合にはカメラをオープンするときの引数で指定します。
camera = android.hardware.Camera.open(android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
前後のカメラを切り替える定数
android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK = 0
android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT = 1

▼プログラム実行中に画面タッチで切り替える

カメラをリリースしてから再びオープンします。
コールバック関数の登録などは、camera.setPreviewCallback(null);などで一度無効にしてから再登録します。

int camera_no;
@Override
public void surfaceCreated(android.view.SurfaceHolder holder) {
    camera = android.hardware.Camera.open(camera_no);
    try {
        camera.setPreviewDisplay(holder);
    } catch (Exception e) {
    }
    camera.startPreview();
}

@Override
public void surfaceDestroyed(android.view.SurfaceHolder holder) {
    camera.stopPreview();
    camera.release();
}

@Override
public boolean onTouchEvent(android.view.MotionEvent event) {
    //画面がタッチされた
    if(0==camera_no){
        camera_no=1;
    }else{
        camera_no=0;
    }
    surfaceDestroyed(getHolder());
    surfaceCreated(getHolder());
    return true;
}


▼タイマーなどで連続撮影中にカメラを切り替える

タイマー内のコールバック関数の登録部分をフラグで操作するだけで切り替える事が出来ます。
下のプログラムは0.5秒毎に写真を連続撮影しますが、画面タッチでカメラを切り替えられます。

public class MainActivity extends AppCompatActivity {
    private boolean flag=true;
    private android.hardware.Camera camera;
    private java.util.Timer timer;
    private java.util.TimerTask timertask;
    private String directory_path;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new _view(this));
    }

    @Override
    protected void onResume() {
        super.onResume();
        //ファイル保存先フォルダ
        directory_path=android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DCIM).getPath()+"/test";
        //写真保存フォルダ作成
        java.io.File dir = new java.io.File(directory_path);
        if (!dir.exists()){
            dir.mkdir();
        }

        timertask =new java.util.TimerTask(){
            @Override
            public void run() {
                if(flag) {
                    camera.setPreviewCallback(new android.hardware.Camera.PreviewCallback() {
                        public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
                            camera.setPreviewCallback(null);//コールバックを無効にして1回のみのデータ取得にする

                            int width = camera.getParameters().getPreviewSize().width;//サイズ取得
                            int height = camera.getParameters().getPreviewSize().height;
                            try {
                                android.graphics.YuvImage yuvimage = new android.graphics.YuvImage
                                        (data, android.graphics.ImageFormat.NV21, width, height, null);
                                java.io.ByteArrayOutputStream _ByteArrayOutputStream = new java.io.ByteArrayOutputStream();
                                yuvimage.compressToJpeg(new android.graphics.Rect(0, 0, width, height), 80, _ByteArrayOutputStream);
                                java.util.Calendar calendar = java.util.Calendar.getInstance();
                                java.io.FileOutputStream fos = new java.io.FileOutputStream
                                        (directory_path + "/" + calendar.getTimeInMillis() + ".JPG", false);
                                fos.write(_ByteArrayOutputStream.toByteArray());
                                fos.flush();
                                fos.close();
                            } catch (Exception e) {
                                android.content.Context context = getApplicationContext();
                                android.widget.Toast t = android.widget.Toast.makeText
                                        (context, e.toString(), android.widget.Toast.LENGTH_LONG);
                                t.show();
                            }
                        }
                    });
                }
            }
        };
        //タイマーの起動
        timer=new java.util.Timer(true);
        timer.schedule(timertask,(long)1000,(long)500);
    }

    @Override
    protected void onPause() {
        super.onPause();
        //タイマーの後始末
        timer.cancel();
        timer.purge();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    public class _view extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {
        _view(android.content.Context context) {
            super(context);
            android.view.SurfaceHolder holder;
            holder = getHolder();
            holder.addCallback(this);
        }

        int camera_no;
        @Override
        public void surfaceCreated(android.view.SurfaceHolder holder) {
            camera = android.hardware.Camera.open(camera_no);
            try {
                camera.setPreviewDisplay(holder);
            } catch (Exception e) {
            }
            camera.startPreview();
        }

        @Override
        public void surfaceChanged(android.view.SurfaceHolder holder, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(android.view.SurfaceHolder holder) {
            camera.stopPreview();
            camera.release();
        }

        @Override
        public boolean onTouchEvent(android.view.MotionEvent event) {
            //画面がタッチされたのでカメラを切り替える
            if(0==camera_no){
                camera_no=1;
            }else{
                camera_no=0;
            }
            flag=false;
            camera.setPreviewCallback(null);
            surfaceDestroyed(getHolder());
            surfaceCreated(getHolder());
            flag=true;
            return true;
        }
    }
}


上のプログラムを電源ボタンを押して画面が消えても撮影し続けるようにしたもの。
細かい事は画面を消してもタイマーを動かし続けるにて。

public class MainActivity extends AppCompatActivity {
    private boolean flag=true;
    private android.hardware.Camera camera;
    private java.util.Timer timer=null;
    private String directory_path;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new _view(this));
        //ファイル保存先フォルダ
        directory_path=android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DCIM).getPath()+"/test";
        //写真保存フォルダ作成
        java.io.File dir = new java.io.File(directory_path);
        if (!dir.exists()){
            dir.mkdir();
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        flag=true;
        if(null!=timer) return;

        java.util.TimerTask timertask =new java.util.TimerTask(){
            @Override
            public void run() {
                if(flag) {
                    camera.setPreviewCallback(new android.hardware.Camera.PreviewCallback() {
                        public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
                            camera.setPreviewCallback(null);//コールバックを無効にして1回のみのデータ取得にする

                            int width = camera.getParameters().getPreviewSize().width;//サイズ取得
                            int height = camera.getParameters().getPreviewSize().height;
                            try {
                                android.graphics.YuvImage yuvimage = new android.graphics.YuvImage
                                        (data, android.graphics.ImageFormat.NV21, width, height, null);
                                java.io.ByteArrayOutputStream _ByteArrayOutputStream = new java.io.ByteArrayOutputStream();
                                yuvimage.compressToJpeg(new android.graphics.Rect(0, 0, width, height), 80, _ByteArrayOutputStream);
                                java.util.Calendar calendar = java.util.Calendar.getInstance();
                                java.io.FileOutputStream fos = new java.io.FileOutputStream
                                        (directory_path + "/" + calendar.getTimeInMillis() + ".JPG", false);
                                fos.write(_ByteArrayOutputStream.toByteArray());
                                fos.flush();
                                fos.close();
                            } catch (Exception e) {
                                android.content.Context context = getApplicationContext();
                                android.widget.Toast t = android.widget.Toast.makeText
                                        (context, e.toString(), android.widget.Toast.LENGTH_LONG);
                                t.show();
                            }
                        }
                    });
                }
            }
        };
        //タイマーの起動
        timer=new java.util.Timer(true);
        timer.schedule(timertask,(long)1000,(long)500);
    }

    @Override
    protected void onPause() {
        super.onPause();
        android.content.Context context=getApplicationContext();
        android.os.PowerManager pm = (android.os.PowerManager) getSystemService(context.POWER_SERVICE);
        if(pm.isScreenOn()){
            //画面がONの場合
            timer.cancel();
            timer.purge();
            timer=null;
        }
    }

    public class _view extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {
        _view(android.content.Context context) {
            super(context);
            android.view.SurfaceHolder holder;
            holder = getHolder();
            holder.addCallback(this);
        }

        int camera_no;
        @Override
        public void surfaceCreated(android.view.SurfaceHolder holder) {
            camera = android.hardware.Camera.open(camera_no);
            try {
                camera.setPreviewDisplay(holder);
            } catch (Exception e) {
            }
            camera.startPreview();
        }

        @Override
        public void surfaceChanged(android.view.SurfaceHolder holder, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(android.view.SurfaceHolder holder) {
            camera.stopPreview();
            camera.release();
        }

        @Override
        public boolean onTouchEvent(android.view.MotionEvent event) {
            //画面がタッチされたのでカメラを切り替える
            if(0==camera_no){
                camera_no=1;
            }else{
                camera_no=0;
            }
            flag=false;
            camera.setPreviewCallback(null);
            surfaceDestroyed(getHolder());
            surfaceCreated(getHolder());
            flag=true;
            return true;
        }
    }





■Dangerous Permissionに対応する

android6.0以前はインストールする段階で権限を取得できましたが、android6.0では実行時に権限を取得するように改良されました。
そのため、Android6.0以降は権限を取得するコードを書かないと例外が発生してしまい、このままでは動きません。
実行時に権限を取得するには次のようにする必要があります。

final int REQUEST_ID = 99;
void permission(String permission){
    //android version 6.0以上の場合は実行する必要がある
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
        //権限が許可されてない場合には
        if (android.support.v4.app.ActivityCompat.checkSelfPermission(this, permission)!= android.content.pm.PackageManager.PERMISSION_GRANTED){
            //権限をリクエストする
            android.support.v4.app.ActivityCompat.requestPermissions(this, new String[]{permission}, REQUEST_ID);
        }
    }
}
//権限のリクエスト結果が返ってくる
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == REQUEST_ID) {
        if (grantResults[0] != android.content.pm.PackageManager.PERMISSION_GRANTED) {
            //権限が許可されなかった
            android.widget.Toast.makeText(getApplicationContext(), "権限が許可されませんでした\nアプリを終了します。", android.widget.Toast.LENGTH_LONG).show();
            //アプリを終了する
            finish();
            moveTaskToBack(true);
        }
    }
}
上の関数を追加した上で、権限を取得するタイミングで、次の関数をコールします

        //カメラの権限をユーザーから取得する
        permission(android.Manifest.permission.CAMERA);
android6.0以上で関数をコールするとこのようなメッセージボックスが表示されます。



カメラを使用する前に権限を取得することによりandroid6.0以降でも例外が発生しなくなります。


▲トップページ > android