Android Camera模塊解析之視頻錄製

《Android Camera架構》
《Android Camera進程間通訊類總結》
《Android Camera模塊解析之拍照》
《Android Camera模塊解析之視頻錄製》
《Android Camera原理之CameraDeviceCallbacks回調模塊》
《Android Camera原理之openCamera模塊(一)》
《Android Camera原理之openCamera模塊(二)》
《Android Camera原理之createCaptureSession模塊》
《Android Camera原理之setRepeatingRequest與capture模塊》
《Android Camera原理之編譯》
《Android Camera原理之camera provider啓動》
《Android Camera原理之cameraserver與cameraprovider是怎樣聯繫的》
《Android Camera原理之camera service與camera provider session會話與capture request輪轉》
《Android Camera原理之camera HAL底層數據結構與類總結》
《Android Camera原理之camera service類與接口關係》android

以前講解過camera2 api之間的關係以及使用camera2 api拍照的功能,本文咱們講解一下如何利用camera2 實現錄製視頻的功能。在講解功能實現的基礎上多探討一些。
拍照和錄製視頻的前期功能都是相似的,在拍照以前會有camera preview功能,錄製視頻以前也是有這個功能的,惟一的不一樣就是抓取的數據不一樣,拍照抓取的是image,視頻抓取的video,數據組織格式不同。git

Android L版本引入了Camera2 api,以前《Android Camera模塊解析之拍照》中已經詳細介紹了camera2 api主要類之間的調用關係。錄製視頻主要是調用了CameraDevice與CameraCaptureSession來錄製視頻,使用一個自定義的TextureView來渲染輸出的數據,preview界面使用TextureView來承載。github

  • 1.佈局中建立一個自定義的TextureView,前文已經介紹了爲何使用TextureView來渲染camera preview界面了。
  • 2.實現TextureView.SurfaceTextureListener 方法,監聽當前的TextureView來監聽Camera preview界面。
  • 3.實現 CameraDevice.StateCallback 來監聽CameraDevice的狀態 ,能夠監聽到Camera device 打開、鏈接、斷開等狀態,在這些狀態中能夠操做錄製、中止錄製等等。
  • 4.開始啓動camera preview,設置MediaRecorder接受的視頻格式。
  • 5.使用CameraDevice實例調用createCaptureRequest(CameraDevice.TEMPLATE_RECORD),建立一個CaptureRequest.Builder對象。
  • 6.實現CameraCaptureSession.StateCallback方法,使用CameraDevice實例調用createCaptureSession(surfaces, new CameraCaptureSession.StateCallback(){})
  • 7.MediaRecorder 的實例調用start()stop()方法開始視頻錄製和中止錄製操做。
  • 8.在onResume()onPause()中作好控制方法。

1、啓動設置預覽界面

1.1 設置TextureView顯示界面canvas

private TextureView.SurfaceTextureListener mSurfaceTextureListener
            = new TextureView.SurfaceTextureListener() {

        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
                                              int width, int height) {
            openCamera(width, height);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
                                                int width, int height) {
            configureTransform(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        }

    };
if (mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }

在設置SurfaceTextureListener以前,有一個判斷,mTextureView.isAvailable()判斷d當前TextureView設置的mSurface是否存在,這個mSurface就是SurfaceTexture,SurfaceTexture是從圖片流中捕捉圖片幀的介質api

public boolean isAvailable() {
        return mSurface != null;
    }

這個mSurface是怎麼來的呢?緩存

Surface設置流程.jpgsession

在TextureView 繪製的時候,獲取當前的Texture繪製層。數據結構

public final void draw(Canvas canvas) {
        // NOTE: Maintain this carefully (see View#draw)
        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /* Simplify drawing to guarantee the layer is the only thing drawn - so e.g. no background,
        scrolling, or fading edges. This guarantees all drawing is in the layer, so drawing
        properties (alpha, layer paint) affect all of the content of a TextureView. */

        if (canvas.isHardwareAccelerated()) {
            DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;

            TextureLayer layer = getTextureLayer();
            if (layer != null) {
                applyUpdate();
                applyTransformMatrix();

                mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
                displayListCanvas.drawTextureLayer(layer);
            }
        }
    }

關鍵的判斷:canvas.isHardwareAccelerated() 硬件加速開啓的狀況下才能進一步使用TextureView來渲染surface。而後判斷當前是否有SurfaceTexture,若是沒有的化,構造一個新的SurfaceTexture對象,當前的TextureView就有一個SurfaceTexture對象了。架構

1.2 執行openCameraapp

《Android Camera原理之openCamera模塊(一)》
《Android Camera原理之openCamera模塊(二)》
兩篇文章已經介紹了openCamera的底層邏輯。

manager.openCamera(cameraId, mStateCallback, null);
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            startPreview();
            mCameraOpenCloseLock.release();
            if (null != mTextureView) {
                configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
            Activity activity = getActivity();
            if (null != activity) {
                activity.finish();
            }
        }

    };

在獲得當前camera device已經onOpened回調以後,咱們真正開始預覽功能。

1.3 設置預覽

SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

            Surface previewSurface = new Surface(texture);
            mPreviewBuilder.addTarget(previewSurface);

            mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            mPreviewSession = session;
                            updatePreview();
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                            Activity activity = getActivity();
                            if (null != activity) {
                                Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
                            }
                        }

                    }, mBackgroundHandler);

Surface封裝的SurfaceTexture是CameraDevice預覽渲染的主要媒介,image stream渲染回執就在這上面進行的。

mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

這兒是設置預覽界面,這是一個CaptureRequest.Builder對象,在camera2 api中CaptureRequest是一個重要的創舉,能夠設置camera request請求緩存,稍後會講解這兒的底層原理。

public void createCaptureSession(List<Surface> outputs,
            CameraCaptureSession.StateCallback callback, Handler handler)
            throws CameraAccessException {
        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
        for (Surface surface : outputs) {
            outConfigurations.add(new OutputConfiguration(surface));
        }
        createCaptureSessionInternal(null, outConfigurations, callback,
                checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
                /*sessionParams*/ null);
    }

捕獲當前的surface流,能夠實現渲染出當前camera device前的影像。至此,camera preview流程已經完成工做,接下來開始錄製視頻的工做。

2、錄製視頻

2.1 設置MediaRecorder屬性

private void setUpMediaRecorder() throws IOException {
        final Activity activity = getActivity();
        if (null == activity) {
            return;
        }
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) {
            mNextVideoAbsolutePath = getVideoFilePath(getActivity());
        }
        mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        switch (mSensorOrientation) {
            case SENSOR_ORIENTATION_DEFAULT_DEGREES:
                mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
                break;
            case SENSOR_ORIENTATION_INVERSE_DEGREES:
                mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
                break;
        }
        mMediaRecorder.prepare();
    }
  • 設置音頻和視頻源,就是聲音從麥克風中取,視頻從Surface界面上取,就是從屏幕上取。
  • 設置輸出文件格式和輸出文件。
  • 設置視頻編碼碼率和幀率,碼率和幀率能夠顯示當前視頻是否卡頓。
  • 設置視頻寬高。
  • 設置音頻和視頻編碼,音頻使用 AAC編碼,視頻使用H264編碼。
MediaRecorder.prepare();

執行MediaRecorder.prepare();開始啓動MediaRecorder錄製。
prepare()函數中主要的工做是設置輸出文件File,準備開始IO;設置底層音視頻編碼緩存,開始執行編碼工做。底層的解析放在後續進行。

public void prepare() throws IllegalStateException, IOException
    {
        if (mPath != null) {
            RandomAccessFile file = new RandomAccessFile(mPath, "rw");
            try {
                _setOutputFile(file.getFD());
            } finally {
                file.close();
            }
        } else if (mFd != null) {
            _setOutputFile(mFd);
        } else if (mFile != null) {
            RandomAccessFile file = new RandomAccessFile(mFile, "rw");
            try {
                _setOutputFile(file.getFD());
            } finally {
                file.close();
            }
        } else {
            throw new IOException("No valid output file");
        }

        _prepare();
    }

2.2 開始錄製工做

getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            // UI
                            mButtonVideo.setText(R.string.stop);
                            mIsRecordingVideo = true;

                            // Start recording
                            mMediaRecorder.start();
                        }
                    });

錄製工做須要放在主線程中進行,否則獲取不到UI界面的信息。
MediaRecorder涉及到不少native方法,在本文中不一一展開,可是後續詳細分析的時候回談到這些native方法的具體是作什麼的。
mMediaRecorder.start();必定要在MediaRecorder.prepare();以後,由於prepare()不設置輸出文件和準備音視頻編碼方式,後續的start()便不能繼續工做了。

項目源碼:https://github.com/googlesamples/android-Camera2Video

 

小禮物走一走,來簡書關注我