Android實時取景:用SurfaceView實現 -- Camera

對於基於攝像頭的Android應用,實時取景是一個基本前提,通過前置或後置攝像頭持續獲取捕獲到的內容,可以進一步做處理(人臉檢測、美顏、濾鏡等)。

所謂實時取景,簡單說就是調用android的攝像頭,把攝像頭捕獲的內容顯示在apk的界面上。只要應用不關閉,相機就持續捕獲,apk上看到的就是實時的取景了。

採用SurfaceView和Camera來做這件事。
是SDK自帶的SurfaceView類而不是實現它的子類;在佈局XML文件中使用SurfaceView而不是FrameLayout。因此,代碼量很少也很容易理解。

從View到SurfaceView

android應用,和用戶交互的GUI界面,是搭載在Activity上的。Activity創建的時候,往往會做setContentView(R.id.main_layout),這是根據xml佈局文件設定要預先確定好的各種view對象,這些組件在xml中進行設計、設定。當然也可以在Java代碼中進一步動態增加view對象。相當於layout作爲各種view的容器。

android sdk自帶了很多view的子類供使用。

View本身:繼承自java.lang.Object類,實現了Drawable.Callback, KeyEvent.Callback, AccessibilitiyEventSource接口。
直接子類有:
AnalogClock:模擬時鐘,有3個指針那種。
ImageView:顯示圖像。其實,任何Drawable對象都可以用ImageView來顯示。
KeyboardView:內置鍵盤。
MediaRouteButton:媒體路由按鈕(不太懂。似乎和多媒體、網絡路由相關)
ProgressBar:(可視化)進度條展示。
Space:空白視圖。輕量級視圖。作用:在不同組件之間插入縫隙、間隔。
SurfaceView:提供了一個專門用於繪製的Surface。Surface的格式、尺寸可以控制。SurfaceView控制這個surface的繪製位置。。。中文翻譯
TextView:文本視圖。
TextureView:紋理視圖。sdk4.0之後的API中可用。常被拿來和SurfaceView比較。
ViewGroup:用來容納其他view對象。是layout(佈局)和view containers(視圖容器)的基類。
ViewStub:視圖存根。大小爲0、不可見,用來佔坑的,apk運行時把坑交給資源。

間接子類有:
AbsListView
AbsSeekBar
AbsSpinner
AbsoluteLayout
AdapterView
AdapterViewAnimator
AdapterViewFlipper
以及其他56個間接子類。

可以看到,SurfaceView和TextureView兩個view子類,都能用於實時取景框的顯示。但是考慮到TextureView需要開啓硬件加速的支持,不考慮。以及,目前看來SurfaceView本身也能勝任實時取景的任務。

代碼

layout文件:surfaceview_main.xml

看到很多教程用的都是FrameLayout,而不是SurfaceView。我很不理解:爲什麼不用SurfaceView呢?不好用嗎?
anyway,我這裏就用SurfaceView了,在我測試過的代碼中是完全可用的。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
tools:context="com.example.chris.myapplication.MainActivity">

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

</LinearLayout>

java代碼 ChrisActivity.java

Surface、SurfaceView、SurfaceHolder這三者相當於MVC的存在,Surface是數據,SurfaceView負責顯示,SurfaceHolder控制了Surface。通過讓Activity實現SurfaceHolder.Callback接口,開發者自行實現下面三個函數,開發者就完成內容的處理:

public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
public void surfaceDestroyed(SurfaceHolder holder);

而具體到實現,一些額外的細節也要考慮到:相機的初始化和釋放;應用暫停時釋放相機,恢復時獲取相機;屏幕方向與顯示方向的一致。所以有如下代碼:

package com.example.chris.myapplication;

import android.app.Activity;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;

import java.io.IOException;

/**
 * Created by chris on 2017/6/25.
 * 網上找了一些博客、教程和代碼,稍微有點頭緒了,現在寫自己的Activity代碼
 */

@SuppressWarnings("deprecation")
// TODO:把camera換成camera2接口??
public class ChrisActivity extends Activity implements SurfaceHolder.Callback{
    private static final String TAG = "ChrisAcvitity";
    private Camera mCamera;
    private SurfaceHolder mHolder;
    private SurfaceView mView;

    @Override
    // 創建Activity時執行的動作
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.surfaceview_main);

        mView = (SurfaceView) findViewById(R.id.surfaceView);
        mHolder = mView.getHolder();
        mHolder.addCallback(this);
    }

    @Override
    // apk暫停時執行的動作:把相機關閉,避免佔用導致其他應用無法使用相機
    protected void onPause() {
        super.onPause();

        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }

    @Override
    // 恢復apk時執行的動作
    protected void onResume() {
        super.onResume();
        if (null!=mCamera){
            mCamera = getCameraInstance();
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
            } catch(IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }
    }


    // SurfaceHolder.Callback必須實現的方法
    public void surfaceCreated(SurfaceHolder holder){
        mCamera = getCameraInstance();
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch(IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    // SurfaceHolder.Callback必須實現的方法
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
        refreshCamera(); // 這一步是否多餘?在以後複雜的使用場景下,此步驟是必須的。
        int rotation = getDisplayOrientation(); //獲取當前窗口方向
        mCamera.setDisplayOrientation(rotation); //設定相機顯示方向
    }

    // SurfaceHolder.Callback必須實現的方法
    public void surfaceDestroyed(SurfaceHolder holder){
        mHolder.removeCallback(this);
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }

    // === 以下是各種輔助函數 ===

    // 獲取camera實例
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open();
        } catch(Exception e){
            Log.d("TAG", "camera is not available");
        }
        return c;
    }

    // 獲取當前窗口管理器顯示方向
    private int getDisplayOrientation(){
        WindowManager windowManager = getWindowManager();
        Display display = windowManager.getDefaultDisplay();
        int rotation = display.getRotation();
        int degrees = 0;
        switch (rotation){
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        android.hardware.Camera.CameraInfo camInfo =
                new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);

        // 這裏其實還是不太懂:爲什麼要獲取camInfo的方向呢?相當於相機標定??
        int result = (camInfo.orientation - degrees + 360) % 360;

        return result;
    }

    // 刷新相機
    private void refreshCamera(){
        if (mHolder.getSurface() == null){
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch(Exception e){
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here
        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        } catch (Exception e) {

        }
    }

}

AndroidManifest.xml 記得添加相機權限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.chris.myapplication" >

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

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

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

</manifest>


參考地址:
https://www.cnblogs.com/zjutzz/p/7077093.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章