文档章节

Android Camera模块解析之视频录制

天王盖地虎626
 天王盖地虎626
发布于 06/18 23:52
字数 1709
阅读 22
收藏 0

《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

 

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

 

本文转载自:https://www.jianshu.com/p/779c3dc775e9

天王盖地虎626

天王盖地虎626

粉丝 28
博文 487
码字总数 20672
作品 0
南京
私信 提问
Android Camera原理之createCaptureSession模块

《Android Camera架构》 《Android Camera进程间通信类总结》 《Android Camera模块解析之拍照》 《Android Camera模块解析之视频录制》 《Android Camera原理之CameraDeviceCallbacks回调模...

天王盖地虎626
06/10
0
0
android camera2 API流程分析

Android camera2 API流程分析 Android5.0之后,新推出来了一个类,android.hardware.camera2,与原来的camera的类实现照相和拍视频的流程有所不同,原来的camera的类并没有深入分析。在做项目...

天王盖地虎626
05/21
0
0
android进阶4step2:Android音视频处理——音视频录制与播放

录音 MediaRecoder Android有一个内置的麦克风,通过它可以捕获音频和存储,或在手机进行播放。 有很多方法可以做到这一点,但最常见的方法是通 过MediaRecorder类。 MediaRecoder常用方法 ...

发条鱼
2018/12/17
0
0
JB4.2:添加一个SwithButton控制ShutterSound

在Andoird JB 4.2中,Camera的代码有非常大的变动,不管是frameworks,还是APP。逻辑更加严谨,google里的程序员真的很喜欢用设计模式,到处都可以看到设计模式的影子,刚刚拿到JB4.2源码的时...

Jerikc
2013/03/11
0
1
RecorderManager安卓仿微信自定义音视频录制第三方库

因为在项目中经常需要使用音视频录制,所以写了一个公共库RecorderManager,欢迎大家使用。 最新0.2.25版本更新:1.优化权限自动申请,可自动调起视频录制界面2.规范图片资源命名 一.效果展示...

明月春秋
01/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux的基本命令

目录的操作命令(增删改查) 增: mkdir 目录名称; 查: ls 可以看到该目录下的所有的目录和文件 ls -a,可以看到该目录下的所有文件和目录,包括隐藏的 ls -l,可以看到该目录下的所有目录和...

凹凸凸
今天
2
0
在古老unix中增加新用户

Installing 4.3 BSD Quasijarus on SIMH 目标:要在4.3BSD中新增加用户dmr,指定目录/home/dmr,uid为10 gid=31(guest组,系统已建立) 4.3BSD还没有adduser或useradd 直接修改/etc/passwd...

wangxuwei
今天
2
0
Bootstrap(六)表单样式

基本样式 所有设置了 .form-control 类的 <input>、<textarea> 和 <select> 元素都将被默认设置宽度属性为 width: 100%;。 将 label 元素和前面提到的控件包裹在 .form-group 中可以获得最好...

ZeroBit
昨天
3
0
SSL 证书格式转换

SSL 证书格式转换 不同服务器情况下,需要不同的证书格式。 比如 pem 转 pfx。 pem在window 平台下可以导入,但是无法正常使用。 需要转换成pfx。 推荐在线转换工具,由中国数字证书网站提供...

DrChenXX
昨天
2
0
HAProxy

xx

Canaan_
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部