SurfaceView實現簡單的相機

SurfaceView實現簡單的相機

視頻流

SurfaceView繼承自View,主要用來展示視頻流的繪製,典型的應用場景是相機,視頻播放器,遊戲界面繪製等。它獨立於UI線程進行繪製,所以不會阻塞UI線程。本文將結合一個簡單的相機demo介紹SurfaceView的使用。

Github Demo地址

我們實現的相機功能很簡單,可以進行相機預覽,點擊拍照按鈕拍照,並展示拍攝的照片,點擊確定返回相機預覽界面。

佈局如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
>

  <SurfaceView        
        android:id="@+id/preview_surface_view"                
        android:layout_width="match_parent"        
        android:layout_height="match_parent"/>    

  <ImageView        
        android:id="@+id/image_view"        
        android:layout_width="match_parent"        
        android:layout_height="match_parent"        
        android:visibility="gone"/>    

  <Button        
        android:id="@+id/take_photo_btn"        
        android:layout_width="wrap_content"        
        android:layout_height="wrap_content"        
        android:layout_alignParentBottom="true"        
        android:layout_centerHorizontal="true"        
        android:text="拍照"/>    

  <Button        
        android:id="@+id/confirm_btn"        
        android:layout_width="wrap_content"        
        android:layout_height="wrap_content"        
        android:layout_alignParentBottom="true"        
        android:layout_centerHorizontal="true"        
        android:text="確定"/>

</RelativeLayout>

SurfaceView的初始化如下,首先根據SurfaceView獲取SurfaceHolder,SurfaceHolder是一個接口,提供了控制SurfaceView界面的函數,比如控制界面的尺寸、格式,編輯界面像素,監控界面的變化等。然後給SurfaceHolder添加CallBack回調函數,三個回調函數對應了界面的三個狀態,創建,修改和銷燬。在這裏我們在界面創界後開始進行相機預覽。

public class MainActivity extends AppCompatActivity {
    ...
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private byte[] pictureDataBytes;

    @Override
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);
        ...
        surfaceHolder = surfaceView.getHolder();    
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {        
            @Override        
            public void surfaceCreated(SurfaceHolder holder) {            
                startPreview();        
            }        

            @Override        
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {        

            }        

            @Override        
            public void surfaceDestroyed(SurfaceHolder holder) {        
            
            }    
        });
}

相機的初始化在onResume裏進行,銷燬在onPause裏進行:

@Override
protected void onResume() {    
    super.onResume();  
    initCamera();
}

@Override
protected void onPause() {    
    super.onPause();    
    releaseCamera();
}

private void initCamera() {    
    if (camera == null) {        
        camera = Camera.open();    
    }
}

private void releaseCamera() {    
    if (camera != null) {        
        camera.release();        
        camera = null;    
    }
}

啓動相機預覽的函數實現如下,通過setPreviewDisplay指定顯示預覽的view:

private void startPreview() {    
    if (camera != null) {        
        try {            
            camera.setPreviewDisplay(surfaceHolder);            
            if (isCapturing) {                
                camera.startPreview();            
            }        
        } catch (IOException e) {            
            e.printStackTrace();            
            Toast.makeText(this, "Unable to open camera.", Toast.LENGTH_SHORT)                    .show();        
        }    
    }
}

點擊拍照按鈕,進行拍照並展示。這裏爲了防止相機拍攝的照片太大導致OOM錯誤,要對圖片進行壓縮,壓縮包括兩個方式,尺寸壓縮和像素格式壓縮,可以參考博客從Oppo手機拍照無法展示談圖片壓縮

@OnClick(R.id.take_photo_btn)
protected void onTakePhotoClicked(View v) {    
    camera.takePicture(null, null, new Camera.PictureCallback() {        
        @Override        
        public void onPictureTaken(byte[] data, Camera camera) {            
            pictureDataBytes = data;            
            showPicture();        
        }    
    });
}

private void showPicture() {    
    Bitmap bitmap = BitmapFactory.decodeByteArray(pictureDataBytes, 0, pictureDataBytes.length);    

    BitmapFactory.Options options = new BitmapFactory.Options();    
    options.inSampleSize = calculateSampleSize(bitmap.getHeight(), bitmap.getWidth());    
    options.inPreferredConfig = Bitmap.Config.RGB_565;

    InputStream inputStream = bitmapToStream(bitmap);    
    imageView.setImageBitmap(BitmapFactory.decodeStream(inputStream, null, options));
    ...
}

計算採樣比例採用下面的函數:

// 獲取採樣比例
public int calculateSampleSize(int outWidth, int outHeight) {    
    int scale = 1;    
    if (outHeight > MAX_HEIGHT || outWidth > MAX_WIDTH) {        
        int maxSize = MAX_WIDTH > MAX_HEIGHT ? MAX_WIDTH : MAX_HEIGHT;        
        scale = (int) Math.pow(2, (int) Math.round(Math.log(maxSize /(double) Math.max(outHeight, outWidth)) / Math.log(0.5)));    
    }    

    return scale;
}

最後記得在Manifest裏添加相機權限:

<uses-permission android:name="android.permission.CAMERA"/>

Github Demo地址

參考:
https://developer.android.com/reference/android/view/SurfaceView.html
http://archive.oreilly.com/oreillyschool/courses/android2/CameraAdvanced.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章