コスギデンサン >> 情報系メモ >> Android

Camera2でプレビューしてみる 2018/8
Target Version : 27
参考サイト

•なるべく最小のコードでCamera2のプレビューをしてみたい。
•プレビューだけでいいので、フォーカスやズーム、フラッシュ等いらない。
•撮影もしない。



AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.kd2.simplecamera1">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        <activity
                android:name=".MainActivity"
                android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
※ android:theme=@style/Theme.AppCompat.Light.NoActionBar を指定しないと、タイトルバーの分だけアスペクト比が崩れる。
※ android:screenOrientation="portrait" を追加しないと、画面が回転しない。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextureView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/preview"/>
</android.support.constraint.ConstraintLayout>

MainActivity.kt
package jp.kd2.simplecamera1

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;

class MainActivity : AppCompatActivity() {

    // ? -> Nullable Type
    private var mTextureView: TextureView? = null
    private val TAG = MainActivity::class.java.simpleName

     // Activity初期化
     // 立ち上げ時と、タテ→ヨコ、ヨコ→タテの変換時にも呼ばれる。
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mTextureView = findViewById(R.id.preview);
    }

    // 描画時
    override fun onResume() {
        super.onResume()
        // TextureViewが利用可能になったらカメラを開く
        if (mTextureView!!.isAvailable()) {
            // call now
            val width = mTextureView!!.getWidth()
            val height = mTextureView!!.getHeight()
            openCamera(width, height)
        } else {
            // 動画等を描画する際にコールバックする関数を登録する。
            mTextureView!!.setSurfaceTextureListener(mSurfaceTextureListener)
        }
    }

    // 停止時
    public override fun onPause() {
        super.onPause()
        closeCamera()
    }

    /******************** カメラ制御 ********************/

    private var mCameraDevice: CameraDevice? = null
    private var mSize: Size? = null

    // カメラの状態によるコールバックメソッド
    private val mStateCallback = object : CameraDevice.StateCallback() {
        // カメラに接続された時
        override fun onOpened(cameraDevice: CameraDevice) {
            mCameraDevice = cameraDevice
            createCameraPreviewSession()
        }
        // カメラが切断させたとき
        override fun onDisconnected(cameraDevice: CameraDevice) {
            mCameraDevice = null
        }
        // エラー
        override fun onError(cameraDevice: CameraDevice, i: Int) {
            mCameraDevice = null
        }
    }

    // カメラに接続する。
    private fun openCamera(width: Int, height: Int) {
        // パーミッションのチェック
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            // パーミッションを求める。
            requestCameraPermission()
            return
        }
        val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
        try {
            val cameraId = cameraManager.cameraIdList[0]
            val characteristics = cameraManager.getCameraCharacteristics(cameraId)
            val map = characteristics.get(
                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
            mSize = map!!.getOutputSizes(SurfaceTexture::class.java)[0]
            cameraManager.openCamera(cameraId, mStateCallback, null)
        } catch (t: Throwable) {
            Log.d(TAG, t.message)
        }
    }

    // カメラを切断する。
    private fun closeCamera() {
        try {
            if (null != mCameraDevice)
                mCameraDevice!!.close()
        } catch (t: Throwable) {
            Log.d(TAG, t.message)
        }
    }

    /******************** プレビュー制御 ********************/

    private var mCaptureRequestbuilder: CaptureRequest.Builder? = null

    // 描画領域のイベントリスナ
    private val mSurfaceTextureListener = object : TextureView.SurfaceTextureListener {
        override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
            openCamera(width, height)
        }

        override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {}

        override fun onSurfaceTextureDestroyed(texture: SurfaceTexture): Boolean {
            return true
        }

        override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {}
    }

    // 描画領域のコールバックメソッド
    private val mCaptureStateCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
            try {
                cameraCaptureSession.setRepeatingRequest(mCaptureRequestbuilder!!.build(), null, null)
            } catch (t: Throwable) {
                Log.d(TAG, t.message)
            }
        }

        override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {}
    }

    // プレビューセッションの開始
    private fun createCameraPreviewSession() {
        val texture = mTextureView!!.getSurfaceTexture()
        texture.setDefaultBufferSize(mSize!!.getWidth(), mSize!!.getHeight())
        val surface = Surface(texture)

        try {
            mCaptureRequestbuilder = mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            mCaptureRequestbuilder!!.addTarget(surface)

            mCameraDevice!!.createCaptureSession(listOf(surface), mCaptureStateCallback, null)
        } catch (t: Throwable) {
            Log.d(TAG, t.message)
        }
    }

    /******************** パーミッション制御 ********************/

    private val REQUEST_CAMERA_PERMISSION = 1

    // ユーザにパーミッションの許可を求める。
    private fun requestCameraPermission() {
        requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
    }

    // パーミッションの結果を取得後のコールバックメソッド
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
                                            grantResults: IntArray) {
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                // パーミッションが許可されなかった場合。
                this.finish()
            }
        } else {
            // 許可ダイアログの承認結果を受け取る。
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }
}