Camera2 Record流程

  • Post author:
  • Post category:其他


转载:

Android Camera模块解析之视频录制


项目源码:

android-Camera2Video



概述

拍照和录制视频的前期功能都是类似的,在拍照之前会有camera preview功能,录制视频之前也是有这个功能的,唯一的不同就是抓取的数据不同,拍照抓取的是image,视频抓取的video,数据组织格式不一样。

录制视频主要是调用了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显示界面

Camera2VideoFragment.java android-Camera2Video-master\Application\src\main\java\com\example\android\camera2video

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设置流程

在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

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属性

Camera2VideoFragment.java android-Camera2Video-master\Application\src\main\java\com\example\android\camera2video

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()便不能继续工作了。