Android Camera模块解析之视频录制

2019/06/18 23:52
阅读数 1.3K

《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类与接口关系》

之前讲解过camera2 api之间的关系以及使用camera2 api拍照的功能,本文我们讲解一下如何利用camera2 实现录制视频的功能。在讲解功能实现的基础上多探讨一些。
拍照和录制视频的前期功能都是类似的,在拍照之前会有camera preview功能,录制视频之前也是有这个功能的,唯一的不同就是抓取的数据不同,拍照抓取的是image,视频抓取的video,数据组织格式不一样。

Android L版本引入了Camera2 api,之前《Android Camera模块解析之拍照》中已经详细介绍了camera2 api主要类之间的调用关系。录制视频主要是调用了CameraDevice与CameraCaptureSession来录制视频,使用一个自定义的TextureView来渲染输出的数据,preview界面使用TextureView来承载。

  • 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 设置TextureView显示界面

    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是从图片流中捕捉图片帧的介质

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

这个mSurface是怎么来的呢?

Surface设置流程.jpg

在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 执行openCamera

《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.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

 

小礼物走一走,来简书关注我

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部