Camera SurfaceView

      在〈Camera SurfaceView〉中尚無留言

 

近日在Google Play上開發3D照相機, 可使用紅藍眼鏡或VR觀看拍攝後的3D立體照片, 所以將相關資訊撰寫於此系列之中. 若需下載app, 請於Google Play搜尋Immersive 3D Camera, 或點選如下網址

免費版 : https://play.google.com/store/apps/details?id=net.ddns.mahaljsp.immersivecameraad
付費版 : https://play.google.com/store/apps/details?id=net.ddns.mahaljsp.immersivecamera

immersive3dcamera

Camera API

Camera API在Android 5.0之前使用Camera API 1 實作. 但到了Android 5.0, 大輻的改版使用Camera API 2實作, 增加了製作的困難度, 但也增加了許多不可能達成的功能.

顯示介面

要將照相機所傳入的畫面顯示於Android 裝置, 可以使用SurfaceView 或GLSurfaceView二種元件. 這二種元件在本人的手機上都可達到每秒60frame的速度. 若只是簡單的顯示基本功能, 使用SurfaceView即可. 但若要於每個frame加上濾鏡或其他不同演算法功能時, 建議使用GLSurfaceView, 將每個frame的圖形交由OpenGL來處理. 本章節使用Camera API 2及SurfaceView 說明最基本的寫法

Layout設定

將整個Layout加上一個SurfaceView即可

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.administrator.cameratest1.MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

權限設定

使用照相機, 當然免不了要把圖片儲存於SDCard, 而操作照相機及SDCard都是屬於危險權限, 所以在取得權限上, 又要分為Android 6.0之前及之後的不同要求.

Android 6.0之前

只需於AndroidManifest.xml裏加上如下紅色字体之權限即可

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.administrator.cameratest1">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <uses-permission android:name="android.permission.CAMERA" />
    <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/AppTheme">
        <activity android:name=".MainActivity"
            android:screenOrientation="landscape"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Android 6.0及之後

Android 6.0及以後的版本, 除了上述的設定外, 尚需於Java code撰寫取得權限的程式碼, 如下

 public class MainActivity extends AppCompatActivity{
    CameraEngine cameraEngine;
    private final int REQUEST_PERMISSION=100;
    SurfaceView surfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN );
        setContentView(R.layout.activity_main);
        surfaceView=(SurfaceView)findViewById(R.id.surfaceView);
        cameraEngine=new CameraEngine(this);
        processPermission();
    }
    private void processPermission(){
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
            int hasPermission1=checkSelfPermission(Manifest.permission.CAMERA);
            int hasPermission2=checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if(hasPermission1!= PackageManager.PERMISSION_GRANTED ||
                    hasPermission2!= PackageManager.PERMISSION_GRANTED){
                requestPermissions(
                        new String[]{
                                Manifest.permission.CAMERA,
                                Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        REQUEST_PERMISSION);
                return;
            }
            else{
                openCamera();
            }
        }
        else{
            openCamera();
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode==REQUEST_PERMISSION){
            if (grantResults[0]==PackageManager.PERMISSION_GRANTED &&
                    grantResults[1]==PackageManager.PERMISSION_GRANTED){
                openCamera();
            }
            else{
                Toast.makeText(this, "No permission", Toast.LENGTH_LONG).show();
                AlertDialog.Builder dialog=new AlertDialog.Builder(this);
                dialog.setTitle("Immersive 3D Camera")
                        .setMessage(R.string.permissionInfo)
                        .setPositiveButton(R.string.button_exit, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                finish();
                            }
                        }).show();
            }
        }
        else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
    public void openCamera(){
        cameraEngine.openCamera();
        surfaceView.getHolder().addCallback(new CameraSurfaceViewCallback(cameraEngine));
    }
}

SurfaceView Callback

SurfaceView是照相機顯示畫面的元件, 但SurfaceView 是由另一個執行緒來負責掌控, 所以並不是在MainActivity的onCreate()中就可以Ready的. 因此必須在MainActivity取得照相機權限後, 再把Camera打開, 待SurfaceView Ready後, 才把SurfaceView傳入Camera.

那MainActivity如何得知SurfaceView己經Ready了呢? 此時在MainActivity就要加入surfaceView.addCallback(cameraSurfaceViewCallback) 來監控SurfaceView的狀況了, 當SurfaceView ready, 就回調 cameraSurfaceViewCallback物件.

CameraSurfaceViewCallback需實作SurfaceHolder.Callback介面, 並實作其方法. 當SurfaceView ready, 就先執行 public void surfaceCreated(SurfaceHolder holder) 方法. 因為就可以在此方法利用setPreview() 把SurfaceView傳給Camera

CameraSurfaceViewCallback程式碼如下 :

public class CameraSurfaceViewCallback implements SurfaceHolder.Callback {
    CameraEngine cameraEngine;
    public CameraSurfaceViewCallback(CameraEngine cameraEngine){
        this.cameraEngine=cameraEngine;
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d("Thomas", "surface created");
        cameraEngine.setPreview(holder);
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {}
}

CameraEngine

接下來就是照相機的重頭戲了, 這個Camera引擎有如下重點
1. 由系統服務取得照相機管理器 cameraManager
2. 由管理器取得裝置上的照相機屬性 characteristics
3. 建立HandlerThread執行緒及Handler來執行一直重複取得影像的工作
4. 開啟照相機
5. 開始預覽

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CameraEngine {
    CameraManager cameraManager;
    CameraCharacteristics characteristics;
    int cameraId=0;
    CameraDevice cameraDevice;
    CameraCaptureSession cameraCaptureSession;
    HandlerThread handlerThread;
    Handler handler;
    Context context;
    SurfaceHolder holder;
    CaptureRequest.Builder previewBuilder;
    public CameraEngine(Context context){
        this.context=context;
        cameraManager=(CameraManager)context.getSystemService(Context.CAMERA_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            try {
                characteristics= cameraManager.getCameraCharacteristics(String.valueOf(cameraId));
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
        handlerThread=new HandlerThread("Camera2");
        handlerThread.start();
        handler=new Handler(handlerThread.getLooper());
    }
    @SuppressWarnings("MissingPermission")
    public void openCamera(){
        try {
            cameraManager.openCamera(String.valueOf(cameraId), cameraOpenCallback, handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    public void setPreview(SurfaceHolder holder){
        this.holder=holder;
        try {
            previewBuilder=cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            previewBuilder.addTarget(holder.getSurface());
            cameraDevice.createCaptureSession(Arrays.asList(holder.getSurface()), cameraPreviewCallback, handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private CameraDevice.StateCallback cameraOpenCallback=new CameraDevice.StateCallback(){
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraDevice=camera;
        }
        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {}
        @Override
        public void onError(@NonNull CameraDevice camera, int error) {}
    };
    private CameraCaptureSession.StateCallback cameraPreviewCallback=new CameraCaptureSession.StateCallback(){
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            cameraCaptureSession=session;
            previewBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            try {
                cameraCaptureSession.setRepeatingRequest(previewBuilder.build(), null, handler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {}
    };
}

完整程式碼下載 : CameraTest1.rar

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *