Camera Api2 預覽,拍照,錄像

1引言

1.1編寫目的

Camera2 主要類的介紹,預覽,拍照,錄像的流程介紹。

1.2適用范圍

Camera API2的使用。

2 主要類的介紹

  1. CameraManager是一個用于檢測、連接和描述相機設備的系統(tǒng)服務,負責管理所有的CameraDevice相機設備。
    可以通過調用Context.getSystemService(java.lang.String)方法來獲取一個CameraManager的實例:
    CameraManager manager=(CameraManager)getSystemService(Context.CAMERA_SERVICE);


  1. CameraDevice連接到 Android 設備的單個攝像頭的表示,描述一個照相機設備,
    一個 Android 設備可能會有多個攝像頭,通過 CameraId 可以進行區(qū)別,當相機
    狀態(tài)回調函數(shù)執(zhí)行的時候,可以從回調中拿到當前 CameraDevice,并通過
    CameraDevice 對當前相機進行一些列操作。

  2. CameraCharacteristics 是描述相機設備的屬性類,其中的屬性都是固定的,繼承自 CameraMetadata 類。類比于舊 API 中的 CameraInfo 類。

包括:曝光補償(Exposure compensation)、自動曝光/自動對焦/自動白平衡模式(AE / AF / AWB mode)、自動曝光/自動白平衡鎖(AE / AWB lock)、自動對焦觸發(fā)器(AF trigger)、拍攝前自動曝光觸發(fā)器(Precapture AE trigger)、測量區(qū)域(Metering regions)、閃光燈觸發(fā)器(Flash trigger)、曝光時間(Exposure time)、感光度(ISO Sensitivity)、幀間隔(Frame duration)、鏡頭對焦距離(Lens focus distance)、色彩校正矩陣(Color correction matrix)、JPEG 元數(shù)據(jù)(JPEG metadata)、色調映射曲線(Tonemap curve)、裁剪區(qū)域(Crop region)、目標 FPS 范圍(Target FPS range)、拍攝意圖(Capture intent)、硬件視頻防抖(Video stabilization)等。

通過 CameraManager 的 getCameraCharacteristics(String cameraId) 方法獲取指定相機設備的 CameraCharacteristics 對象。

  1. CameraCaptureSession 是一個事務,用來向相機設備發(fā)送獲取圖像的請求。

主要有 setRepeatingRequest()capture() 方法。
setRepeatingRequest() 是重復請求獲取圖像數(shù)據(jù),常用于預覽或連拍,capture() 是獲取一次,常用于單張拍照。

CameraCaptureSession 類是一個抽象類.

  1. CameraRequest 代表了一次捕獲請求,從相機設備捕獲單個圖像所需的不可變的設置和輸出包,當程序調用 setRepeatingRequest()方法進行預覽時,或調用 capture()方法進行拍照時,都需要傳入 CameraRequest 參數(shù)。
    CameraRequest.Builder 則負責生成CameraRequest 對象

如:預覽前設置自動對焦
CaptureRequestBuilder.set(CaptureRequest.CONTORL_AF_MODE,CaptureRequest.CONTROL_AF_MOODE_CONTINUOUS_PICTURE);

CaptureRequest = CaptureRequestBuilder.build();
CameraCaptureSession.setRepeatingRequest(CaptureRequest,CaptureCallback,Handler);

  1. CaptureResult
    CaptureRequest描述是從圖像傳感器捕獲單個圖像的結果的子集的對象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)當CaptureRequest被處理之后由CameraDevice生成。

  2. CameraMetadata Camera API2/HAL3架構下使用了全新的CameraMetadata結構取代了之前的SetParameter/Paramters等操作,實現(xiàn)了Java到native到HAL3的參數(shù)傳遞。

3 app 層的主要流程

3.1 預覽的流程

3.1.1 先上圖,看圖講故事

圖1 預覽流程圖

3.1.2 預覽流程說明

  1. 獲取CameraManager
//獲取攝像頭的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  1. openCamera 打開相應ID的相機

·String CameraId:傳入要打開的攝像頭的 Id
·CameraDevice.stateCallback:相機的狀態(tài)回調接口
·Handler handler:用來確定Callback在哪個線程執(zhí)行,為null的話就在當前線程執(zhí)行

    public void openCamera( String cameraId,
            final CameraDevice.StateCallback callback,  Handler handler)
            throws CameraAccessException {
        openCameraForUid(cameraId, callback,
                CameraDeviceImpl.checkAndWrapHandler(handler), USE_CALLING_UID);
    }
  1. 實例化 CameraDevice.statCallback
    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            //開啟預覽
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };
  1. 生成CaptureRequest.Builder
    private CaptureRequest.Builder mPreviewRequestBuilder;

    // 創(chuàng)建預覽請求的Builder(TEMPLATE_PREVIEW表示預覽請求)
    private void getPreviewRequestBuilder() {
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //設置預覽的顯示界面,即將顯示預覽用的 surface 的實例,作為一個顯示層添加到該請求的目標列表中.
        mPreviewRequestBuilder.addTarget(mPreviewSurface);
        MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
        if (meteringRectangles != null && meteringRectangles.length > 0) {
            CamLog.d("PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
    }


  1. mCameraDevice.createCaptureSession 創(chuàng)建CaptureSession

·List<Surface>:捕獲數(shù)據(jù)的輸出Surface列表,此處傳入用于顯
示預覽圖像的 Surface 即可,即 Arrays.asList(surface),
·CameraCaptureSession.stateCallback:CameraCaptureSession的狀態(tài)回調接口,當它創(chuàng)建好后會回調onConfigured方法.
·Handler:第三個參數(shù)用來確定Callback在哪個線程執(zhí)行,為null的話就在當前線程執(zhí)行

public abstract void createCaptureSession( List<Surface> outputs,
             CameraCaptureSession.StateCallback callback,  Handler handler)
            throws CameraAccessException;
  1. CameraCaptureSession.StateCallback 回調

new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(CameraCaptureSession session) {
                            //該方法會從底層傳回一個CameraCaptureSession,該 session 可以開始處理捕獲請求,
                            mCaptureSession = session;
                            repeatPreview();
                        }

                        @Override
                        public void onConfigureFailed(CameraCaptureSession session) {

                        }
                    }

  1. CaptureRequest和mCaptureSession.setRepeatingRequest
private void repeatPreview() {
    mPreviewRequestBuilder.setTag(TAG_PREVIEW);
    mPreviewRequest = mPreviewRequestBuilder.build();
    //設置反復捕獲數(shù)據(jù)的請求,這樣預覽界面就會一直有數(shù)據(jù)顯示
    try {
        mCaptureSession.setRepeatingRequest(mPreviewRequest,mPreviewCaptureCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}



·CaptureReqeust:無限重復的請求,
·CameraCaptureSession.CaptureCallback:即 callback 的回調,不過預覽中
該回調中不用進行任何處理
·Handler:第三個參數(shù)用來確定Callback在哪個線程執(zhí)行,為null的話就在當前線程執(zhí)行

public abstract int setRepeatingRequest( CaptureRequest request,
             CaptureCallback listener,  Handler handler)
            throws CameraAccessException;

3.2 拍照的流程

3.2.1 先上圖,看圖講故事

圖2 預覽和拍照流程圖

3.2.2 拍照流程說明

  1. ImageReader 的使用

點擊拍照,使用 ImageReader 訪問呈現(xiàn)到 Surface 中的圖像,并進行保存。在預覽的 Surface 捕獲圖像的同時,我們也需要 ImageReader 來同時捕獲圖像數(shù)據(jù),所以在預覽的第五步中,CameraDevice.CreateCaptureSession()方法中,我們將 ImageReader 的實例也傳入第一個參數(shù)中,即 Arrays.asList(surface,ImageReader.getSurface())


    private ImageReader mImageReader;

    private void setupImageReader() {
        //前三個參數(shù)分別是需要的尺寸和格式,最后一個參數(shù)代表每次最多獲取幾幀數(shù)據(jù)
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                ImageFormat.JPEG, 1);
        //監(jiān)聽ImageReader的事件,當有圖像流數(shù)據(jù)可用時會回調onImageAvailable方法,它的參數(shù)就是預覽幀數(shù)據(jù),可以對這幀數(shù)據(jù)進行處理
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                // 開啟線程異步保存圖片
                new Thread(new ImageSaver(image)).start();
            }
        }, null);
    }


public static class ImageSaver implements Runnable {
        private Image mImage;
        private File mImageFile;

        public ImageSaver(Image image) {
            mImage = image;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(mImageFile);
                fos.write(data, 0, data.length);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImageFile = null;
                if (fos != null) {
                    try {
                        fos.close();
                        fos = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }




    //預覽第五步的代碼
    mCameraDevice.createCaptureSession(
            Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
            new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                            mCaptureSession = session;
                            repeatPreview();
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {

                }
            }, null);

  1. 生成CaptureRequest
    //首先我們創(chuàng)建請求拍照的CaptureRequest
    final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);

    mCaptureBuilder.addTarget(mPreviewSurface);
    mCaptureBuilder.addTarget(mImageReader.getSurface());

    //獲取屏幕方向
    int rotation = getWindowManager().getDefaultDisplay().getRotation();
    //設置拍照方向
    mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));

    CaptureRequest captureRequest = mCaptureBuilder.build();
  1. CameraCaputureSession.capture()方法進行拍照

· CaptureRequest:此次拍照的參數(shù)設置
· CaptureCallback:callback 對象,當這個請求被處理的時候觸發(fā),如果
為 null,不會生成 matedate 信息,但是仍會生成圖像信息
· Handler:為一個句柄,代表執(zhí)行 callback 的 handler,如果程序希望直
接在當前線程中執(zhí)行 callback,則可以將 handler 參數(shù)設為 null


public abstract int capture( CaptureRequest request,
            CaptureCallback listener,  Handler handler)
            throws CameraAccessException;

  1. CameraCaptureSession.CaptureCallback

//開始拍照,然后回調上面的接口重啟預覽,
// 因為mCaptureBuilder設置ImageReader作為target,所以會自動回調ImageReader的onImageAvailable()方法保存圖片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                               @NonNull CaptureRequest request,
                               @NonNull TotalCaptureResult result) {
                    //恢復預覽狀態(tài)
                    repeatPreview();
                }
            };

3.3 錄像的流程

3.3.1 先上圖,看圖講故事

圖3 預覽、拍照、錄像流程圖

3.3.2 錄像流程說明

  1. MediaRecorder 的使用

MediaRecorder是安卓提供的一個用于音視頻采集的類.



        mMediaRecorder = new MediaRecorder();

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        String mNextVideoAbsolutePath = Environment.getExternalStorageDirectory() + "/DCIM/videoBack.mp4";
        mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mMediaRecorder.setOrientationHint(90);

        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

  1. CaptureRequest.Builder的生成和添加Target

將 MedioRecorder 和預覽用的 Surface 的實例,添加到該請求的目標列表中。


    mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    List<Surface> surfaces = new ArrayList<>();
    SurfaceTexture texture = mTextureView.getSurfaceTexture();
    assert texture != null;
    texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

    // Set up Surface for the camera preview
    Surface previewSurface = new Surface(texture);
    surfaces.add(previewSurface);
    mPreviewRequestBuilder.addTarget(previewSurface);

    // Set up Surface for the MediaRecorder
    Surface recorderSurface = mMediaRecorder.getSurface();
    surfaces.add(recorderSurface);
    mPreviewRequestBuilder.addTarget(recorderSurface);

  1. 將預覽時創(chuàng)建的 session,close 掉

    private void closePreviewSession() {
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
    }

  1. createCaptureSession
·List<Surface>:新的用于捕獲圖像信息的 Surface 集合,此處為顯示預覽
信息的 surface 實例,以及記錄圖像信息用的 MediaRecorder 的實例
·CameraCaptureSession.StateCallback:用于通知新捕獲 session 的
callback
·Handler:為一個句柄,代表執(zhí)行 callback 的 handler,如果程序希望直
接在當前線程中執(zhí)行 callback,則可以將 handler 參數(shù)設為 null


public abstract void createCaptureSession( List<Surface> outputs,
            CameraCaptureSession.StateCallback callback, 
            Handler handler)
            throws CameraAccessException;

  1. CameraCaptureSession.StateCallback

new CameraCaptureSession.StateCallback()
{

        @Override
        public void onConfigured (@NonNull CameraCaptureSession session){

        mCaptureSession = session;
        try {
            //調用CaptureRequestBuilder.set()方法,設置捕獲的參數(shù),此處設置3A算法
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
            CaptureRequest request = mPreviewRequestBuilder.build();

            //調用CameraCaptureSession.setRepeatingReqest()方法,
            //通過此捕獲session,持續(xù)重復捕獲圖像
            mCaptureSession.setRepeatingRequest(request, null, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Start recording
                mMediaRecorder.start();
            }
        });
    }

        @Override
        public void onConfigureFailed (@NonNull CameraCaptureSession session){
    }
}
  1. MediaRecorder 停止錄制

    public void videoStop(View view) {
        mMediaRecorder.stop();
        mMediaRecorder.reset();

        Toast.makeText(this, "Video saved: 保存",
                Toast.LENGTH_SHORT).show();
        //重新開啟預覽
        startPreview();
    }

參考文章

  1. https://blog.csdn.net/qq_33529867/article/details/103484791
  2. https://www.cnblogs.com/renhui/p/8718758.html
  3. 官方Demo
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370