文章目录
转载:
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()便不能继续工作了。