Android 音視頻開發(fā)(三) -- Camera2 實(shí)現(xiàn)預(yù)覽钱豁、拍照功能

音視頻系列文章:
Android 音視頻開發(fā)(一) -- 使用AudioRecord 錄制PCM(錄音);AudioTrack播放音頻
Android 音視頻開發(fā)(二) -- Camera1 實(shí)現(xiàn)預(yù)覽疯汁、拍照功能

工程:VideoDemo

上一章牲尺,已經(jīng)我們已經(jīng)用 Camera1 實(shí)現(xiàn)了預(yù)覽和拍照的功能,但也說(shuō)到幌蚊,在API21的時(shí)候谤碳,Camera1已經(jīng)被標(biāo)注為棄用,因?yàn)樗腁PI功能和靈活性滿足不了現(xiàn)在日益復(fù)雜的相機(jī)開發(fā)了溢豆,所以在 API21之后蜒简,引入了 Camera2 。

一. Camera2 簡(jiǎn)介

從功能來(lái)講漩仙,Camera2 廢棄了 Camera1 的框架搓茬,它支持更多的功能,比如:

  1. 獲取更多的幀(預(yù)覽/拍照)信息队他,以及每一幀的參數(shù)配置
  2. 支持更多的圖片格式(yuv/raw)等
  3. 一些新特性
    ...

而今天要完成的效果如下:


GIF.gif

1.1. Pipeline

Camera2 的將攝像頭包裝成管道(Pipeline)卷仑,它會(huì)捕獲單個(gè)幀的輸入請(qǐng)求,每個(gè)請(qǐng)求捕獲單個(gè)圖像麸折,然后把這些數(shù)據(jù)包裝成數(shù)據(jù)包系枪,從而把這些圖像數(shù)據(jù)輸出到圖片緩沖區(qū)中。

這些請(qǐng)求時(shí)按順序處理的磕谅,它按順序處理每一幀的請(qǐng)求私爷,并返回請(qǐng)求結(jié)果給客戶端,看下面這張圖:

在這里插入圖片描述

假設(shè)我們要拍攝同時(shí)拍攝兩張不同尺寸的圖片膊夹,那么它的過(guò)程應(yīng)該是這樣的:

  1. 創(chuàng)建一個(gè)用于 Pipeline 獲取圖片信息的 CaptureRequest
  2. 創(chuàng)建兩個(gè)不同的 Surface 衬浑,用來(lái)接收?qǐng)D片數(shù)據(jù),并把他們加到 CaptureRequest 中
  3. 發(fā)送配置好的的 CaptureRequest 到Pipeline 中放刨,等待返回拍照結(jié)果

上面需要記住的是工秩,CaptureReuqest 創(chuàng)建之前,我們已經(jīng)把相機(jī)的數(shù)據(jù)都配置好进统,比如聚焦助币、閃光燈等,接著才把它輸入給 Camrea2 的底層螟碎,它會(huì)被放入到一個(gè)被叫做 In-Flight Capture Queue 的隊(duì)列中眉菱,當(dāng) In-Flight Capture Queue 隊(duì)列空閑時(shí),我們就可以從它拿到不同的 圖片數(shù)據(jù) 給到Surface 掉分,且 能拿到 CaptureResult 這個(gè)返回結(jié)果信息俭缓。

1.2. Supported Hardware Level

我們支持克伊,Camera 支持了很多新功能的特性,但這也要看你的手機(jī)廠商的支持程度华坦,所以愿吹,為了方便區(qū)分,Camera2 使用 Supported Hardware Level 來(lái)判斷你是否支持 Camera2 的特性惜姐,它分為4個(gè)登記:

  • LEGACY :向后兼容模式犁跪,支持Camera1 的功能,不支持 Camera2 的新特性
  • LIMITED :除了支持 Camera1 的特性歹袁,還支持部分 Camera2 的高級(jí)特性
  • FULL :支持所有 Camera2 的高級(jí)特性
  • LEVEL_3 :新增更多的 Camera2 特性耘拇,利于YUV 數(shù)據(jù)等

下面我們將根據(jù)這些特性,來(lái)完成 Camera2 的開發(fā)宇攻。

二. 相機(jī)預(yù)覽

要注意的是惫叛,Camera2 與 Camera1 是兩個(gè)不同的框架,不要被 Camera1 的思想禁錮逞刷,把它當(dāng)做新知識(shí)學(xué)習(xí)即可嘉涌,一個(gè)相機(jī)的流程圖如下:


在這里插入圖片描述
  1. 通過(guò)context.getSystemService(Context.CAMERA_SERVICE) 獲取CameraManager.
  2. 通過(guò) getCameraCharacteristics() 方法,拿到相機(jī)的所有信息夸浅,比如支持的預(yù)覽大小仑最,level 等
  3. 通過(guò) CameraManager 的 openCamera() 方法,從回調(diào)中拿到 CameraDevice 帆喇,它表示當(dāng)前相機(jī)設(shè)備
  4. CameraDevice 通過(guò) createCaptureRequest 創(chuàng)建 CaptureRequest.Builder 警医,用來(lái)配置相機(jī)屬性,通過(guò) createCaptureSession 創(chuàng)建 CameraCaptureSession 坯钦,它是 Pipeline 的實(shí)例预皇,然后交給底層處理

別忘了,您要添加以下權(quán)限:

    <uses-permission android:name="android.permission.CAMERA" /> <!-- 支持相機(jī)才能運(yùn)行 -->
    <!--需要設(shè)備有相機(jī)-->
    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

2.1. 獲取相機(jī)信息

從上面的流程圖支持婉刀,我們需要通過(guò) CameraManager 的 getCameraCharacteristics() 方法吟温,來(lái)獲取相機(jī)的信息;
CameraManager 是一個(gè)負(fù)責(zé)查詢和建立相機(jī)連接的系統(tǒng)服務(wù),它的功能不多突颊,主要如下:

  1. 將相機(jī)信息裝載到 CameraCharacteristics 中鲁豪。
  2. 根據(jù)指定的相機(jī)ID 連接相機(jī)
  3. 提供將閃光燈設(shè)置為手電筒的快捷方式

所以它的代碼如下:

try {
    //獲取相機(jī)服務(wù) CameraManager
    mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

    //遍歷設(shè)備支持的相機(jī) ID ,比如前置律秃,后置等
    String[] cameraIdList = mCameraManager.getCameraIdList();
    for (String cameraId : cameraIdList) {
        // 拿到裝在所有相機(jī)信息的  CameraCharacteristics 類
        CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
        //拿到相機(jī)的方向爬橡,前置,后置棒动,外置
        Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);

        if (facing != null) {
            //后置攝像頭
            if (facing == CameraCharacteristics.LENS_FACING_BACK) {
                mBackCameraId = cameraId;
                mBackCameraCharacteristics = characteristics;
            }else if (facing == CameraCharacteristics.LENS_FACING_FRONT){
                //前置攝像頭
                mFrontCameraId = cameraId;
                mFrontCameraCharacteristics = characteristics;
            }
            mCameraId = cameraId;
        }

        //是否支持 Camera2 的高級(jí)特性
        Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
        /**
         * 不支持 Camera2 的特性
         */
        if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY){
          //  Toast.makeText(this, "您的手機(jī)不支持Camera2的高級(jí)特效", Toast.LENGTH_SHORT).show();
         //   break;
        }

    }

} catch (CameraAccessException e) {
    e.printStackTrace();
}

上面的注釋都很清晰了糙申,我們是通過(guò) CameraCharacteristics 去知道當(dāng)前的相機(jī)方向;除了這些迁客,它還包含相機(jī)的其他信息郭宝,比如:

  • 是否有閃光燈 FLASH_INFO_AVAILABLE
  • 是否有 AE 模式 CONTROL_AE_AVAILABLE_MODES
  • 光爆等,如果你對(duì) Camera1 比較熟悉掷漱,那么 CameraCharacteristics 有點(diǎn)像 Camera1 的 Camera.CameraInfo 或者 Camera.Parameters

2.2 打開攝像頭

比 Camera1 好的就是粘室,Camera2 在打開攝像頭之前,就可以進(jìn)行參數(shù)的配置卜范,比如預(yù)覽尺寸等衔统。
我們知道,攝像頭需要 Surface 來(lái)裝載數(shù)據(jù)海雪,這里使用的是 TextureView 來(lái)裝載:

  mTextureView = findViewById(R.id.surface);

所以锦爵,當(dāng)它創(chuàng)建完成拿到寬高之后,我們就可以打開攝像頭了:

    private void openCamera(int width, int height) {
        //判斷不同攝像頭奥裸,拿到 CameraCharacteristics
        CameraCharacteristics characteristics = mCameraId.equals(mBackCameraId) ? mBackCameraCharacteristics : mFrontCameraCharacteristics;
        //拿到配置的map
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        //獲取攝像頭傳感器的方向
        mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        //獲取預(yù)覽尺寸
        Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);
             //獲取最佳尺寸
        Size bestSize = getBestSize(width, height, previewSizes);
        /**
         * 配置預(yù)覽屬性
         * 與 Cmaera1 不同的是险掀,Camera2 是把尺寸信息給到 Surface (SurfaceView 或者 ImageReader),
         * Camera2 會(huì)根據(jù) Surface 配置的大小湾宙,輸出對(duì)應(yīng)尺寸的畫面;
         * 注意攝像頭的 width > height 樟氢,而我們使用豎屏,所以寬高要變化一下
         */
        mTextureView.getSurfaceTexture().setDefaultBufferSize(bestSize.getHeight(),bestSize.getWidth());

        /**
         * 設(shè)置圖片尺寸侠鳄,這里圖片的話雕崩,選擇最大的分辨率即可
         */
        Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
        Size largest = Collections.max(
                Arrays.asList(sizes),
                new CompareSizesByArea());
        //設(shè)置imagereader戏售,配置大小,且最大Image為 1,因?yàn)槭?JPEG
        mImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),
                ImageFormat.JPEG,1);

        //拍照監(jiān)聽(tīng)
        mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);
        
        try {
            //打開攝像頭当辐,監(jiān)聽(tīng)數(shù)據(jù)
            mCameraManager.openCamera(mCameraId,new CameraDeviceCallback(),null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }

在用 mCameraManager.openCamera() 打開攝像頭之前,我們通過(guò)

//拿到配置的map
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

拿到可用流的map督怜,這樣就可以通過(guò) getOutputSizes() 等拿到相機(jī)支持的所有尺寸了异希,通過(guò)上一章 Android 音視頻開發(fā)(二) -- Camera1 實(shí)現(xiàn)預(yù)覽、拍照功能 也解釋了挡育,為啥要有最佳尺寸罢绽,不然會(huì)有圖片拉伸的問(wèn)題。
而與 Camera1 不同的是静盅,Camera2 會(huì)根據(jù) Surface 配置的大小良价,輸出對(duì)應(yīng)尺寸的畫面,所以這里設(shè)置 mTextureView 的大小即可蒿叠,注意攝像頭的 width > height 明垢,而我們使用豎屏,所以寬高要變化一下市咽。

接著設(shè)置圖片的尺寸痊银,這里當(dāng)然是越輕越好了,所以選擇最大尺寸即可施绎。ImageReader 等到后面拍照時(shí)再講解溯革。

最后調(diào)用 mCameraManager.openCamera() 贞绳,它有三個(gè)參數(shù):

  • cameraId :Camera 的 ID,比如前置致稀、后置和外置
  • CameraDevice.StateCallback :當(dāng)連接到相機(jī)時(shí)冈闭,該回調(diào)就會(huì)被調(diào)用,生成 CameraDevice
  • handler : 調(diào)用 CameraDevice.StateCallback 的 Handler抖单,傳null萎攒,則調(diào)用主線程,建議傳入 HandlerThread 的hander矛绘,畢竟這種都是耗時(shí)的耍休。

2.3. CameraDevice

CameraDevice 表示當(dāng)前的相機(jī)設(shè)備,它的主要職責(zé)有:

  1. 根據(jù)指定的參數(shù)創(chuàng)建 CameraCaptureSession
  2. 根據(jù)指定的模板創(chuàng)建 CaptureRequest
  3. 關(guān)閉相機(jī)設(shè)備
  4. 監(jiān)聽(tīng)相機(jī)狀態(tài)货矮,比如斷開羊精,開啟成功失敗的監(jiān)聽(tīng)

如下:

    class CameraDeviceCallback extends CameraDevice.StateCallback{

        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            //此時(shí)攝像頭已經(jīng)打開,可以預(yù)覽了
            createPreviewPipeline(camera);
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            camera.close();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            camera.close();
        }
    }

在 onOpened 創(chuàng)建我們CaptureRequest 囚玫,配置相機(jī)參數(shù)信息园匹,如下:

    private void createPreviewPipeline(CameraDevice cameraDevice){
        try {
            //創(chuàng)建作為預(yù)覽的 CaptureRequst.builder
            final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            Surface surface = new Surface(mTextureView.getSurfaceTexture());
            //添加 surface 容器
            captureBuilder.addTarget(surface);
            // 創(chuàng)建CameraCaptureSession,該對(duì)象負(fù)責(zé)管理處理預(yù)覽請(qǐng)求和拍照請(qǐng)求,這個(gè)必須在創(chuàng)建 Seesion 之前就準(zhǔn)備好劫灶,傳遞給底層用于皮遏制 pipeline
            cameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mCameraCaptureSession = session;
                   try {
                       //設(shè)置自動(dòng)聚焦
                       captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                       //設(shè)置自動(dòng)曝光
                       captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

                       //創(chuàng)建 CaptureRequest
                       CaptureRequest build = captureBuilder.build();
                        //設(shè)置預(yù)覽時(shí)連續(xù)捕獲圖片數(shù)據(jù)
                       session.setRepeatingRequest(build,null,null);
                   }catch (Exception e){

                   }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Toast.makeText(Camera2Activity.this, "配置失敗", Toast.LENGTH_SHORT).show();
                }
            },null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在開啟預(yù)覽之前裸违,我們需要先創(chuàng)建 CaptureRequest ,上面已經(jīng)說(shuō)過(guò) CaptureRequest 是向 CameraCaptureSession 提交 Capture 請(qǐng)求的信息載體本昏,內(nèi)部包含了本次的 Capture 參數(shù)配置和接受圖像數(shù)據(jù)的 Surface供汛。

CaptureRequest 可以配置的信息非常多,比如圖像格式涌穆、圖像分辨率怔昨、聚焦、閃光燈控制等宿稀,可以說(shuō)絕大部分配置都是通過(guò) CaptureRequest 配置的趁舀。

上面通過(guò) cameraDevice.createCaptureRequest() 來(lái)創(chuàng)建一個(gè) CaptureRequest.Builder 對(duì)象,其中createCaptureRequest() 方法的參數(shù)是 templateType 用于指定哪種模板祝沸,Camera2 根據(jù)不同場(chǎng)景矮烹,為我們配置了一些常用的參數(shù)模板:

  • TEMPLATE_PREVIEW:適用于配置預(yù)覽的模板
  • TEMPLATE_RECORD:適用于視頻錄制的模板。
  • TEMPLATE_STILL_CAPTURE:適用于拍照的模板罩锐。
  • TEMPLATE_VIDEO_SNAPSHOT:適用于在錄制視頻過(guò)程中支持拍照的模板奉狈。
  • TEMPLATE_MANUAL:適用于希望自己手動(dòng)配置大部分參數(shù)的模板。

這里我們需要一個(gè)預(yù)覽的 CaptureRequest 涩惑,所以選擇 TEMPLATE_PREVIEW的模板仁期。

接著,需要設(shè)置要承載圖像數(shù)據(jù)的 Surface,我們用到兩個(gè)跛蛋,一個(gè)是 TextureView 用來(lái)預(yù)覽的熬的,一個(gè)是 ImageReader 用來(lái)拍照的

目前這個(gè) CaptureRequest 是用來(lái)預(yù)覽的赊级,所以通過(guò) addTarget 設(shè)置進(jìn)去:

 //添加 surface 容器
 captureBuilder.addTarget(surface);

最后通過(guò) cameraDevice.createCaptureSession() 創(chuàng)建 CameraCaptureSession 押框,然后再配置一下聚焦和曝光的配置,就可以把 CaptureRequest 通過(guò) Session 發(fā)送給底層了:

// 創(chuàng)建CameraCaptureSession此衅,該對(duì)象負(fù)責(zé)管理處理預(yù)覽請(qǐng)求和拍照請(qǐng)求,這個(gè)必須在創(chuàng)建 Seesion 之前就準(zhǔn)備好强戴,傳遞給底層用于配置 pipeline
            cameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mCameraCaptureSession = session;
                   try {
                       //設(shè)置自動(dòng)聚焦
                       captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                       //設(shè)置自動(dòng)曝光
                       captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

                       //創(chuàng)建 CaptureRequest
                       CaptureRequest build = captureBuilder.build();
                        //設(shè)置預(yù)覽時(shí)連續(xù)捕獲圖片數(shù)據(jù)
                       session.setRepeatingRequest(build,null,null);
                   }catch (Exception e){

                   }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Toast.makeText(Camera2Activity.this, "配置失敗", Toast.LENGTH_SHORT).show();
                }
            },null);

2.4. 開啟和關(guān)閉預(yù)覽

在 Camera2 中亭螟,本質(zhì)上是不斷的重復(fù) Captrue 的過(guò)程挡鞍,每一次 Capture 都會(huì)把預(yù)覽的數(shù)據(jù)輸出到對(duì)應(yīng)的 Surface 中,所以预烙,為了達(dá)到預(yù)覽的效果墨微,需要使用:

    session.setRepeatingRequest(build,null,null);

它的三個(gè)參數(shù)如下:

  • request : 在不斷重復(fù)執(zhí)行 Capture 時(shí)使用的 CaptureRequest 對(duì)象
  • callback :監(jiān)聽(tīng)每一次 Capture 狀態(tài)的 CameraCaptureSession.CaptureCallback 對(duì)象,例如 onCaptureStarted() 意味著一次 Capture 的開始扁掸,而 onCaptureCompleted() 意味著一次 Capture 的結(jié)束翘县。
  • handler :用于執(zhí)行 CameraCaptureSession.CaptureCallback 的Handler 對(duì)象,傳null為主線程谴分,也可以使用其他線程的 Handler

關(guān)閉預(yù)覽
通過(guò)上面的理解锈麸,關(guān)閉預(yù)覽也很簡(jiǎn)單啦:

 //停止預(yù)覽
  mCameraCaptureSession.stopRepeating();

三. 拍照

上面我們學(xué)習(xí)了預(yù)覽,也提到了 ImageReader 是用來(lái)接收?qǐng)D像數(shù)據(jù)的牺蹄,那怎么拍照呢忘伞?

拍照其實(shí)也是一個(gè) Captrue,這樣的話沙兰,我們就可以再創(chuàng)建一個(gè) CaptureRequest 去執(zhí)行拍照的就可以了氓奈,代碼如下:

 //創(chuàng)建一個(gè)拍照的 session
 final CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
 //設(shè)置裝在圖像數(shù)據(jù)的 Surface
 captureRequest.addTarget(mImageReader.getSurface());
 //聚焦
 captureRequest.set(CaptureRequest.CONTROL_AF_MODE, 
         CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
 //自動(dòng)曝光
 captureRequest.set(CaptureRequest.CONTROL_AF_MODE,
         CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
 // 獲取設(shè)備方向
 int rotation = getWindowManager().getDefaultDisplay().getRotation();
 // 根據(jù)設(shè)備方向計(jì)算設(shè)置照片的方向
 captureRequest.set(CaptureRequest.JPEG_ORIENTATION
         , getOrientation(rotation));
 // 先停止預(yù)覽
 mCameraCaptureSession.stopRepeating();

代碼比較好理解,只是通過(guò) mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) 創(chuàng)建了一個(gè) 拍照的模板

而且在執(zhí)行拍照之前鼎天,先停止預(yù)覽:

 mCameraCaptureSession.stopRepeating();

接著就可以使用 mCameraCaptureSession.capture() 執(zhí)行拍照了:

mCameraCaptureSession.capture(captureRequest.build(), new CameraCaptureSession.CaptureCallback() {
                    @Override
                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                        super.onCaptureCompleted(session, request, result);
                        try {
                            //拍完之后舀奶,讓它繼續(xù)可以預(yù)覽
                            CaptureRequest.Builder captureRequest1 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                            captureRequest1.addTarget(new Surface(mTextureView.getSurfaceTexture()));
                            mCameraCaptureSession.setRepeatingRequest(captureRequest1.build(),null,null);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }
                },null);

可以看到,當(dāng)拍照結(jié)束的時(shí)候斋射,我們又讓它重新預(yù)覽了育勺,當(dāng)然這里也看你的需求去設(shè)置。

3.1 保存圖片

那保存圖片在哪里弄呢罗岖?還記得我們?cè)诖蜷_攝像頭的時(shí)候怀大,配置了 ImageReader 監(jiān)聽(tīng)嗎:

//拍照監(jiān)聽(tīng)
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);

在保存之前,我們先來(lái)了解一下呀闻,什么是 ImageReader

3.2 ImageReader

在 Camrea2 中化借,Imageread 是獲取圖像數(shù)據(jù)的一個(gè)重要途徑,我們可以通過(guò)它獲取各種各樣格式的圖像數(shù)據(jù)捡多,比如 JPEG蓖康、YUV和 RAW 等铐炫。通過(guò) ImageReader.newInstance() 方法創(chuàng)建 ImageReader 對(duì)象,如下:

//設(shè)置imagereader蒜焊,配置大小倒信,且最大Image為 1,因?yàn)槭?JPEG
mImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),
        ImageFormat.JPEG,1);
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);        

其中前面兩個(gè)號(hào)理解泳梆,第三個(gè)參數(shù)鳖悠,則是你要獲取的圖像數(shù)據(jù)的格式,這里使用 JPEG 即可优妙,而最后一個(gè)參數(shù)乘综,則表示最大 Image 的個(gè)數(shù),可以理解成 圖像池的大小套硼。

當(dāng)有圖像數(shù)據(jù)生成時(shí)卡辰,就是調(diào)用 ImageReader.OnImageAvailableListener 里面的 onImageAvailable() 方法

    /**
     * 拍照監(jiān)聽(tīng),當(dāng)有圖片數(shù)據(jù)時(shí),回調(diào)該接口
     */
    class ImageAvailable implements ImageReader.OnImageAvailableListener{

        @Override
        public void onImageAvailable(ImageReader reader) {
            new SavePicAsyncTask(reader).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }
    }

接著邪意,我們可以調(diào)用 ImageReader 的acquireNextImage()方法九妈,來(lái)獲取存有最新的 Image 對(duì)象,而 Image 對(duì)象里的圖像數(shù)據(jù)又根據(jù)不同格式被劃分成多個(gè)部分雾鬼,分別存儲(chǔ)在單獨(dú)的 Plane 對(duì)象里萌朱,我們可以調(diào)用 Image.getPalnes() 獲取所有有的 Palne 對(duì)象的數(shù)組,如下

//獲取捕獲的照片數(shù)據(jù)
Image image = imageReader.acquireLatestImage();
//拿到所有的 Plane 數(shù)組
Image.Plane[] planes = image.getPlanes();

最后則通過(guò) Plane.getBuffer() 獲取每一個(gè)在 Plane 里存儲(chǔ)的圖像數(shù)據(jù) ByteBuffer策菜。比如

格式 Image個(gè)數(shù) Plane 層級(jí)
JPEG 1 壓縮過(guò)的數(shù)據(jù)晶疼,所以行數(shù)為0,解壓縮需要使用BitmapFactory#decodeByteArray
YUV 3 一個(gè)明度通道+兩個(gè)色彩CbCr通道做入,UV的寬高是Y的一半冒晰。

YUV 可以用下圖來(lái)表示(圖片來(lái)源):

在這里插入圖片描述

所以,我們獲取的圖片如下:

FileOutputStream fos = null;
Image image = null;
try {
    fos = new FileOutputStream(file);
    //獲取捕獲的照片數(shù)據(jù)
    image = imageReader.acquireLatestImage();
    //拿到所有的 Plane 數(shù)組
    Image.Plane[] planes = image.getPlanes();
    //由于是 JPEG 竟块,只需要獲取下標(biāo)為 0 的數(shù)據(jù)即可
    ByteBuffer buffer = planes[0].getBuffer();
    data = new byte[buffer.remaining()];
    //把 bytebuffer 的數(shù)據(jù)給 byte數(shù)組
    buffer.get(data);
    Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
    //旋轉(zhuǎn)圖片
    if (mCameraId.equals(mFrontCameraId)){
        bitmap = BitmapUtils.rotate(bitmap,270);
        bitmap = BitmapUtils.mirror(bitmap);
    }else{
        bitmap = BitmapUtils.rotate(bitmap,90);
    }
    bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
    fos.flush();
    return bitmap;
}catch (Exception e){
    Log.d(TAG, "zsr doInBackground: "+e.toString());
}finally {
    CloseUtils.close(fos);
    //記得關(guān)閉 image
    if (image != null) {
        image.close();
    }
}

這樣壶运,我們就寫好了,下一章浪秘,我們學(xué)習(xí) CameraX蒋情。

參考:
https://developer.android.google.cn/reference/android/hardware/camera2/package-summary?hl=en
http://www.reibang.com/p/9a2e66916fcb
http://www.reibang.com/p/23e8789fbc10
http://www.reibang.com/p/067889611ae7

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耸携,隨后出現(xiàn)的幾起案子棵癣,更是在濱河造成了極大的恐慌,老刑警劉巖夺衍,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狈谊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)河劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門壁榕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赎瞎,你說(shuō)我怎么就攤上這事牌里。” “怎么了务甥?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵牡辽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我敞临,道長(zhǎng)态辛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任哟绊,我火速辦了婚禮因妙,結(jié)果婚禮上痰憎,老公的妹妹穿的比我還像新娘票髓。我一直安慰自己,他們只是感情好铣耘,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布洽沟。 她就那樣靜靜地躺著,像睡著了一般蜗细。 火紅的嫁衣襯著肌膚如雪裆操。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天炉媒,我揣著相機(jī)與錄音踪区,去河邊找鬼。 笑死吊骤,一個(gè)胖子當(dāng)著我的面吹牛缎岗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播白粉,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼传泊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鸭巴?” 一聲冷哼從身側(cè)響起眷细,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹃祖,沒(méi)想到半個(gè)月后溪椎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年校读,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奔害。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡地熄,死狀恐怖华临,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情端考,我是刑警寧澤雅潭,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站却特,受9級(jí)特大地震影響扶供,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裂明,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一椿浓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闽晦,春花似錦扳碍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至荠瘪,卻和暖如春夯巷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哀墓。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工趁餐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篮绰。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓后雷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親阶牍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喷面,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359