溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

利用Android Camera2 的照相機(jī)api 實(shí)現(xiàn) 實(shí)時(shí)的圖像采集與預(yù)覽

發(fā)布時(shí)間:2020-07-11 15:31:46 來(lái)源:網(wǎng)絡(luò) 閱讀:25658 作者:DavidWillo 欄目:移動(dòng)開(kāi)發(fā)

   最近想要做一個(gè)客戶(hù)端往服務(wù)器推送實(shí)時(shí)畫(huà)面的功能,首先可以考慮到兩種思路,一種是在客戶(hù)端進(jìn)行視頻流的推送,主要利用RTSP等流媒體協(xié)議進(jìn)行傳輸,而另外一種是通過(guò)攝像頭獲取當(dāng)前畫(huà)面,將每一幀作為對(duì)象單獨(dú)傳輸。

   項(xiàng)目想要實(shí)現(xiàn)的功能最終目的是對(duì)實(shí)時(shí)畫(huà)面的每一幀進(jìn)行處理,可以考慮客戶(hù)端推流到服務(wù)器,再在服務(wù)器進(jìn)行幀解析的操作,但由于目前很多的流媒體推送框架在推流端或者服務(wù)端都或多或少存在限制,很少有完全開(kāi)源的項(xiàng)目,再加上傳送畫(huà)面的同時(shí)需要附帶部分的數(shù)據(jù),仍然需要另外建立連接進(jìn)行傳輸,所以暫時(shí)擱置這一方案。選擇第二種思路,獲取每一幀的畫(huà)面,單獨(dú)傳輸。

   要想獲取實(shí)時(shí)畫(huà)面,我們必須通過(guò)對(duì)安卓設(shè)備上的攝像頭進(jìn)行調(diào)用。

   從API21開(kāi)始,安卓引入了android.hardware.camera2這個(gè)包,來(lái)替代原有的camera類(lèi),原有的camera類(lèi)已經(jīng)不再建議使用了。camera2中最重要的變化是,攝像頭的調(diào)用不再是簡(jiǎn)單地進(jìn)行實(shí)例化,而是用一種類(lèi)似服務(wù)申請(qǐng)的方式來(lái)進(jìn)行調(diào)用。通過(guò)CameraManager來(lái)管理攝像服務(wù),需要通過(guò)建立CameraCaptureSession來(lái)建立一個(gè)調(diào)用攝像設(shè)備CameraDevices的會(huì)話(huà),來(lái)實(shí)現(xiàn)對(duì)攝像頭的調(diào)用。而CaptureRequest.Builder類(lèi)用于建立實(shí)際的調(diào)用請(qǐng)求,具體的參數(shù)設(shè)置也可以通過(guò)這個(gè)類(lèi)來(lái)實(shí)現(xiàn)(而不是對(duì)camera設(shè)備進(jìn)行直接設(shè)置),這樣做的目的是把對(duì)攝像頭的控制與攝像頭本身分離開(kāi)來(lái),用戶(hù)可以通過(guò)不同的session根據(jù)不同的配置來(lái)使用攝像頭。

   我們可以結(jié)合具體的代碼來(lái)分析新api中攝像頭調(diào)用的過(guò)程。

   首先我們想要對(duì)攝像設(shè)備進(jìn)行操作,需要獲得CameraManager的實(shí)例

    CameraManager cameraManager;
    cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

   我們可以調(diào)用openCamera函數(shù)打開(kāi)攝像頭設(shè)備

    cameraManager.openCamera(cameraId, cameraCallback, mainHandler);

   這里需要傳入三個(gè)參數(shù),cameraId是設(shè)備編號(hào),cameraCallback控制攝像服務(wù)的回調(diào),最后一個(gè)參數(shù)指定HandlerThread對(duì)象 

     cameraId = Integer.toString(CameraCharacteristics.LENS_FACING_FRONT);
     
     private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            Log.d("CameraCallback", "Camera Opened");
            cameraDevice = camera;
            takePreview();
        }

        @Override
        public void onDisconnected(CameraDevice cameraDevice) {
            Log.d("CameraCallback", "Camera Disconnected");
            closeCameraDevice();
        }

        @Override
        public void onError(CameraDevice cameraDevice, int i) {
            Log.d("CameraCallback", "Camera Error");
            Toast.makeText(PusherSurface.this, "攝像頭開(kāi)啟失敗", Toast.LENGTH_SHORT).show();
        }
    };

 回調(diào)函數(shù)用于指定連接攝像頭設(shè)備時(shí)不同狀態(tài)的操作。在這里,我們?cè)跀z像頭成功連接的時(shí)候調(diào)用  takePreview()函數(shù)開(kāi)啟攝像頭畫(huà)面的預(yù)覽。

private void takePreview() {
    try {
        final CaptureRequest.Builder previewRequestBuilder
                = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        previewRequestBuilder.addTarget(surfaceHolder.getSurface());
        previewRequestBuilder.addTarget(previewReader.getSurface());
        cameraDevice.createCaptureSession(Arrays.asList(surfaceHolder.getSurface(),
                previewReader.getSurface(),
                p_w_picpathReader.getSurface()), new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                if (cameraDevice == null) return;
                mCameraCaptureSession = cameraCaptureSession;

                try {
                    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                            CaptureRequest.CONTROL_AE_MODE_OFF);

                    CaptureRequest previewRequest = previewRequestBuilder.build();
                    mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                Toast.makeText(PusherSurface.this, "配置失敗", Toast.LENGTH_SHORT).show();
            }
        }, childHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

要從攝像設(shè)備中獲取圖像,我們首先需要建立一個(gè)camera capture session。函數(shù)

createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)的第一個(gè)參數(shù)傳入了我們想要繪制的視圖列表,第二個(gè)參數(shù)傳入的是建立攝像會(huì)話(huà)的狀態(tài)回調(diào)函數(shù),第三個(gè)參數(shù)傳入相應(yīng)的handler處理器。然后,我們需要利用capturerequest來(lái)定義攝像頭捕獲圖像時(shí)候的具體參數(shù),比如是否開(kāi)啟攝像頭,是否自動(dòng)對(duì)焦等。最后通過(guò)CamraCaptureSession.setRepeatingRequest來(lái)開(kāi)啟請(qǐng)求。這樣我們就可以從capturesession傳入的list中的surface列表獲得連續(xù)的圖像。留意到

previewRequestBuilder.addTarget(surfaceHolder.getSurface());
previewRequestBuilder.addTarget(previewReader.getSurface());

這里除了傳入xml界面布局中的surfaceHolder的surface外,還傳入了一個(gè)previewReader的surface。

previewReader是一個(gè)自定義的ImageReader對(duì)象。

previewReader = ImageReader.newInstance(1080, 1920, ImageFormat.YUV_420_888, 2);
        previewReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader p_w_picpathReader) {
                Image p_w_picpath = null;
                try {
                    p_w_picpath = p_w_picpathReader.acquireLatestImage();
                    Log.d("PreviewListener", "GetPreviewImage");
                    if (p_w_picpath == null) {
                        return;
                    }
                    byte[] bytes = ImageUtil.p_w_picpathToByteArray(p_w_picpath);
                    if (pushFlag == false)
                        uploadImg(bytes);
                } finally {
                    if (p_w_picpath != null) {
                        p_w_picpath.close();
                    }
                }

            }
        }, mainHandler);

ImageReader是一個(gè)可以讓我們對(duì)繪制到surface的圖像進(jìn)行直接操作的類(lèi)。在這里我們從攝像設(shè)備中傳入了連續(xù)的預(yù)覽圖片,也就是我們?cè)谄聊簧峡吹降漠?huà)面,它們的格式都是未經(jīng)壓縮的YUV_420_888類(lèi)型的(同樣的如果要操作拍攝后的圖片,就要設(shè)置成jpeg格式)。我們調(diào)用p_w_picpathReader.acquireLatestImage或者acquireNextImage來(lái)獲取圖像隊(duì)列中的圖片。并進(jìn)行操作。在這里我利用一個(gè)函數(shù)將圖像壓縮后轉(zhuǎn)化成byte[]格式,并調(diào)用uploadImg函數(shù)上傳至服務(wù)器。這樣,整個(gè)攝像頭的調(diào)用到預(yù)覽圖像的處理也就完成了。想要實(shí)現(xiàn)拍照功能也是大同小異,在這里我就不一一貼出了。

  歡迎更多安卓開(kāi)發(fā)者一同交流。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI