1. 程式人生 > >android硬解碼(白平衡java例項)

android硬解碼(白平衡java例項)

【Android】使用MediaCodec硬編碼實現視訊直播推流端(一)

2016年06月15日 18:37:15 gitzzp 閱讀數:7162 標籤: 視訊 直播 推流 MediaCodec 硬編碼 更多

個人分類: 多媒體相關

廢話不說,直接上程式碼。

佈局檔案

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.gitzzp.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="publish"
        android:id="@+id/publish"
        android:layout_alignParentTop="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop"
        android:id="@+id/stop"
        android:layout_toRightOf="@id/publish"
        android:layout_marginTop="0dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="switch"
        android:id="@+id/swCam"
        android:layout_alignBottom="@id/stop"
        android:layout_toRightOf="@id/stop" />

    <EditText
        android:id="@+id/vbitrate"
        android:textSize="14dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/publish"
        android:layout_marginTop="0dp" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14dp"
        android:id="@+id/url"
        android:layout_below="@id/publish"
        android:layout_above="@+id/frameLayout"
        android:layout_toRightOf="@id/vbitrate" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/vbitrate"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="0dp"
        android:id="@+id/frameLayout">

    <com.example.gitzzp.CameraPreview
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/preview" />
    </FrameLayout>
</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

非常簡單的佈局,沒什麼可說的,唯一要說的就是最後邊這個CameraPreview,這是一個自定義控制元件,繼承自SurfaceView,用於顯示攝像頭的預覽,具體程式碼後邊會貼出來。



MainActivity:

package com.example.gitzzp;

import android.app.Activity;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private SharedPreferences sp;
    private CameraPreview mCameraView = null;
    private String mNotifyMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //保持螢幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main1);

        // 響應螢幕旋轉事件
//        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);

        // 本地儲存資料
        sp = getSharedPreferences("SrsPublisher", MODE_PRIVATE);
        SrsEncoder.rtmpUrl = sp.getString("rtmpUrl", SrsEncoder.rtmpUrl);
        SrsEncoder.vbitrate = sp.getInt("vbitrate", SrsEncoder.vbitrate);
        Log.i(TAG, String.format("init rtmp url %s, vbitrate=%dkbps", SrsEncoder.rtmpUrl, SrsEncoder.vbitrate));

        // 設定程式剛開始顯示的url
        final EditText efu = (EditText) findViewById(R.id.url);
        efu.setText(SrsEncoder.rtmpUrl);

        // 設定初始化時的視訊位元速率
        final EditText evb = (EditText) findViewById(R.id.vbitrate);
        evb.setText(String.format("%dkbps", SrsEncoder.vbitrate / 1000));

        // for camera, @see https://developer.android.com/reference/android/hardware/Camera.html
        final Button btnPublish = (Button) findViewById(R.id.publish);
        final Button btnStop = (Button) findViewById(R.id.stop);
        final Button btnSwitch = (Button) findViewById(R.id.swCam);
        //佈局中的surfaceview
        mCameraView = (CameraPreview) findViewById(R.id.preview);
        btnPublish.setEnabled(true);
        btnStop.setEnabled(false);

        btnPublish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int vb = Integer.parseInt(evb.getText().toString().replaceAll("kbps", ""));
                SrsEncoder.vbitrate = vb * 1000;
                SrsEncoder.rtmpUrl = "替換為你自己想要推流的rtmp伺服器地址";
                Log.i(TAG, String.format("RTMP URL changed to %s", SrsEncoder.rtmpUrl));
                Log.i(TAG, String.format("Video bitrate changed to %skbps", SrsEncoder.vbitrate / 1000));
                SharedPreferences.Editor editor = sp.edit();
                editor.putInt("vbitrate", SrsEncoder.vbitrate);
                editor.putString("rtmpUrl", SrsEncoder.rtmpUrl);
                editor.commit();
                btnPublish.setEnabled(false);
                btnStop.setEnabled(true);
                //釋出直播
                mCameraView.startPublish();
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCameraView.stopPublish();
                btnPublish.setEnabled(true);
                btnStop.setEnabled(false);
            }
        });

        btnSwitch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCameraView.initCameraPreview(1);
            }
        });

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable ex) {
                mNotifyMsg = ex.getMessage();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), mNotifyMsg,Toast.LENGTH_SHORT).show();
                        btnPublish.setEnabled(true);
                        btnStop.setEnabled(false);
                        mCameraView.stopPublish();
                    }
                });
            }
        });

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    protected void onResume() {
        super.onResume();
        final Button btn = (Button) findViewById(R.id.publish);
        btn.setEnabled(true);
        mCameraView.handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mCameraView.initCameraPreview();
            }
        },100);

    }

    @Override
    protected void onPause() {
        super.onPause();
        mCameraView.stopPublish();
        //停止預覽
        mCameraView.stopCamera();
    }

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

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mCameraView.stopPublish();
        mCameraView.mEncoder.setScreenOrientation(newConfig.orientation);
        mCameraView.startPublish();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164

在MainActivity中做了一些控制元件和資料的初始化,監聽事件的新增,以及在各個生命週期中對surfaceview和camera的控制等操作。
當我們點選publish按鈕的時候,會根據我們設定的推流地址,位元速率等,呼叫surfaceview中的startPublish()來開始推流。swtich用於切換前置後置攝像頭。


 

CameraPreview:接收camera中的資料,並顯示出來,作為camera的預覽影象。

package com.example.gitzzp;

import android.app.Activity;
import android.content.Context;
import android.hardware.Camera;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import com.example.gitzzp.rtmp.RtmpPublisher;

import java.io.IOException;
import java.util.List;

/**
 * 相機預覽影象
 * Created by gitzzp on 16/6/2.
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {

    private static final String TAG = "CameraPreview";
    private SurfaceHolder mHolder;
    private Activity mContext;

    private AudioRecord mic = null;
    private boolean isPublish = false;
    private Thread aworker = null;//音訊錄製的執行緒
    public Camera mCamera = null;

    private int mPreviewRotation = 90;
    private int mDisplayRotation = 90;
    private int mCamId = Camera.getNumberOfCameras() - 1; // default camera 該裝置的攝像頭數量
    //    private byte[] mYuvFrameBuffer = new byte[SrsEncoder.VWIDTH * SrsEncoder.VHEIGHT * 3 / 2];
    private byte[] mYuvFrameBuffer = new byte[SrsEncoder.VWIDTH * SrsEncoder.VHEIGHT * 3 *10];

    private String mNotifyMsg;


    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    initCameraPreview();
                    break;
            }
        }
    };

    public CameraPreview(Context context) {
        this(context,null);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = (Activity) context;
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setKeepScreenOn(true);
    }

    public void initCameraPreview(){
        if (mCamera != null && mEncoder != null) {
            stopCamera();
            startCamera();
        }else if(mCamera == null && mEncoder != null) {
            startCamera();
        }
    }

    public void initCameraPreview(int num) {
        if (mCamera != null && mEncoder != null) {
            mCamId = (mCamId + num) % Camera.getNumberOfCameras();
            stopCamera();
            mEncoder.swithCameraFace();
            startCamera();
        }else if(mCamera == null && mEncoder != null) {
            mCamId = (mCamId + num) % Camera.getNumberOfCameras();
            mEncoder.swithCameraFace();
            startCamera();
        }
    }

    //開始推流
    public void startPublish() {
        int ret = mEncoder.start();
        //小於0表示有某個地方出錯 但是SrsEncoder中沒有做具體區分 統一以-1來進行返回 後期可以根據需求分別進行處理
        //返回0表示流建立成功
        if (ret < 0) {
            return;
        }
        //開啟攝像頭
        startCamera();

        aworker = new Thread(new Runnable() {
            @Override
            public void run() {
                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
                startAudio();
            }
        });
        isPublish = true;
        aworker.start();
    }


    public void startCamera() {
        if (mCamera != null) {
            Log.d(TAG, "start camera, already started. return");//攝像頭已經開啟
            return;
        }
        if (mCamId > (Camera.getNumberOfCameras() - 1) || mCamId < 0) {
            Log.e(TAG, "####### start camera failed, inviald params, camera No.="+ mCamId);
            return;
        }

        mCamera = Camera.open(mCamId);

        Camera.CameraInfo info = new Camera.CameraInfo();
        //獲取攝像頭資訊
        Camera.getCameraInfo(mCamId, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){//前置攝像頭
            mDisplayRotation = (mPreviewRotation + 180) % 360;
            mDisplayRotation = (360 - mDisplayRotation) % 360;
        } else {
            mDisplayRotation = mPreviewRotation;
        }

        Camera.Parameters params = mCamera.getParameters();
        /* preview size  */
        Camera.Size size = mCamera.new Size(SrsEncoder.VWIDTH, SrsEncoder.VHEIGHT);
        if (!params.getSupportedPreviewSizes().contains(size)) {
            //攝像頭預覽尺寸小於我們設定的預覽尺寸 丟擲異常
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
                    new IllegalArgumentException(String.format("Unsupported preview size %dx%d", size.width, size.height)));
        }

        /* picture size  */
        if (!params.getSupportedPictureSizes().contains(size)) {
            //圖片尺寸小於我們設定的圖片尺寸
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
                    new IllegalArgumentException(String.format("Unsupported picture size %dx%d", size.width, size.height)));
        }

        /***** set parameters *****/
        //params.set("orientation", "portrait");
        //params.set("orientation", "landscape");
//        params.setRotation(180);
        params.setPictureSize(SrsEncoder.VWIDTH, SrsEncoder.VHEIGHT);
        //設定預覽時的大小
        params.setPreviewSize(SrsEncoder.VWIDTH, SrsEncoder.VHEIGHT);
        //設定幀數
        int[] range = findClosestFpsRange(SrsEncoder.VFPS, params.getSupportedPreviewFpsRange());
        params.setPreviewFpsRange(range[0], range[1]);
        //預覽圖格式
        params.setPreviewFormat(SrsEncoder.VFORMAT);
        //閃光燈控制
        params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
        //白平衡控制
        params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
        //設定情景模式 改變該引數可以覆蓋上邊的幾個引數 例如 最初閃光燈是開啟的 在變成夜間模式之後 會關閉
        params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
        //設定自動對焦 共分兩步 這是第一步
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

        mCamera.setParameters(params);
        //預覽圖顯示的角度
        mCamera.setDisplayOrientation(mPreviewRotation);
        //緩衝區大小
        mCamera.addCallbackBuffer(mYuvFrameBuffer);
        //設定自動對焦的第二步
        mCamera.cancelAutoFocus();
        mCamera.setPreviewCallbackWithBuffer(this);
        try {
            //設定surfacebview顯示攝像頭的預覽介面
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.startPreview();
    }
    private int[] findClosestFpsRange(int expectedFps, List<int[]> fpsRanges) {
        expectedFps *= 1000;
        int[] closestRange = fpsRanges.get(0);
        int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
        for (int[] range : fpsRanges) {
            if (range[0] <= expectedFps && range[1] >= expectedFps) {
                int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
                if (curMeasure < measure) {
                    closestRange = range;
                    measure = curMeasure;
                }
            }
        }
        return closestRange;
    }

    public void stopCamera() {
        if (mCamera != null) {
            // need to SET NULL CB before stop preview!!!
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    private void onGetYuvFrame(byte[] data) {
        mEncoder.onGetYuvFrame(data);
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera c) {
//        onGetYuvFrame(data);
        //點選推流之後開始推流
        if(isPublish){
            onGetYuvFrame(data);
        }
        c.addCallbackBuffer(mYuvFrameBuffer);
    }

    private void onGetPcmFrame(byte[] pcmBuffer, int size) {
        mEncoder.onGetPcmFrame(pcmBuffer, size);
    }

    //開始錄音
    private void startAudio() {
        if (mic != null) {
            return;
        }

        int bufferSize = 2 * AudioRecord.getMinBufferSize(SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, SrsEncoder.AFORMAT);
        mic = new AudioRecord(MediaRecorder.AudioSource.MIC, SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, SrsEncoder.AFORMAT, bufferSize);
        mic.startRecording();

        byte pcmBuffer[] = new byte[4096];
        while (isPublish && !Thread.interrupted()) {
            int size = mic.read(pcmBuffer, 0, pcmBuffer.length);
            if (size <= 0) {
                Log.e(TAG, "***** audio ignored, no data to read.");
                break;
            }
            onGetPcmFrame(pcmBuffer, size);
        }
    }

    private void stopAudio() {
        isPublish = false;
        if (aworker != null) {
            Log.i(TAG, "stop audio worker thread");
            aworker.interrupt();
            try {
                aworker.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
                aworker.interrupt();
            }
            aworker = null;
        }

        if (mic != null) {
            mic.setRecordPositionUpdateListener(null);
            mic.stop();
            mic.release();
            mic = null;
        }
    }

    //停止推流之後 預覽狀態不應該停 也就是說我們應該在destory中停止攝像頭 而不是在這裡
    public void stopPublish() {
        stopAudio();
//        stopCamera();
        mEncoder.stop();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

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

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    public SrsEncoder mEncoder = new SrsEncoder(new RtmpPublisher.EventHandler() {
        @Override
        public void onRtmpConnecting(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpConnected(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpVideoStreaming(String msg) {
        }

        @Override
        public void onRtmpAudioStreaming(String msg) {
        }

        @Override
        public void onRtmpStopped(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpDisconnected(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpOutputFps(final double fps) {
            Log.i(TAG, String.format("Output Fps: %f", fps));
        }
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360

CameraPreview繼承自SurfaceView並且實現了SurfaceHolder.Callback和 Camera.PreviewCallback兩個介面。
SurfaceHolder.Callback用於監聽SurfaceView的建立、改變、銷燬等操作,本來應該在這裡完成對攝像頭的init和release等操作,這裡偷了個懶。
Camera.PreviewCallback用於接收camera的預覽,對應onPreviewFrame()方法,我們可以在這裡對攝像頭傳回的資料做一些自己需要的處理,我們在這裡呼叫了另外的類對我們攝像頭傳回的資料進行了YUV編碼。