Android仿微信小视频录制功能
作为开博的第一篇文章,正好最近在学习Android视频录制功能,所以决定趁热记录下来。
关于Android视频录制功能的实现流程以及相关API的介绍网上有很多,这里就不再赘述。在学习的过程中主要参考了 Vitamio公司的VCamera SDK 项目,因为该项目在Java层全开源,所以在调用Android系统的相机进行录制方面记录下一些学习体会。
具体关于VCamera项目,请移步VCamera
嗯,还是先看效果:
定义
首先是定义视频录制接口,接口很简单只包含两个方法,因为是调用系统的API实现录制,所以在方法上只用考虑到start和stop方法,出于对功能的拓展性上(比如自定义相机功能),接口的方法还可以更丰富。
public interface IMediaRecorder {
/**
* 开始录制
* @return 录制失败返回null
*/
public MediaObject startRecord();
/**
* 停止录制
*/
public void stopRecord();
}
接着是bean:
/** 视频最大时长,默认10秒 */
private int mMaxDuration;
/** 视频目录 */
private String mOutputDirectory;
/** 对象文件 */
private String mOutputObjectPath;
/** 视频码率 */
private int mVideoBitrate;
/** 最终视频输出路径 */
private String mOutputVideoPath;
/** 最终视频截图输出路径 */
private String mOutputVideoThumbPath;
/** 文件夹及文件名 */
private String mKey;
/** 开始时间 */
private long mStartTime;
/** 结束时间 */
private long mEndTime;
/** 视频移除标志位 */
private boolean mRemove;
/** 两个构造方法 */
public MediaObject(String key, String path) {
this(key, path, DEFAULT_VIDEO_BITRATE);
}
public MediaObject(String key, String path, int videoBitrate) {
this.mKey = key;
this.mOutputDirectory = path;
this.mVideoBitrate = videoBitrate;
this.mOutputObjectPath = mOutputDirectory + File.separator + mKey + ".obj";
this.mOutputVideoPath = mOutputDirectory + File.separator + mKey +".mp4";
this.mOutputVideoThumbPath = mOutputDirectory + File.separator + mKey + ".jpg";
this.mMaxDuration = DEFAULT_MAX_DURATION;
}
实现
在视频录制过程中分为两个部分,第一个就是我们在界面上看到的摄像头传来的预览画面Preview
,另一个则是视频录制时使用的”录像机“Recorder
,同样为了提高拓展性,在功能实现上我们先将预览画面提出来实现它。因为也许你有许多不同的Recorder
,但是预览的方式只有那么一个。
那么抽象出一个录像父类非常重要,它实现了一些都需要用到的方法,其中当然包括我们的Preview
。
public abstract class MediaRecorderBase implements Callback, PreviewCallback, IMediaRecorder {
...}
非常直观哈,Callback
接口是SurfaceHolder
的回调接口,我们所看到的预览画面就是呈现在SurfaceView
上的,PreviewCallback
预览画面的回调接口,至于我们先前定义的IMediaRecorder
就留给后人去实现吧。
回到主线预览画面上,关于SurfaceView
这里不做介绍了,我们要做的是先把它的SurfaceHolder
拿过来
public void setSurfaceHolder(SurfaceHolder sh) {
if (sh != null) {
sh.addCallback(this);
if (!DeviceUtils.hasHoneycomb()) {
sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
}
}
然后就可以掌控这一切了。。
public void prepare() {
mPrepared = true;
if (mSurfaceCreated)
startPreview();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
this.mSurfaceHolder = holder;
this.mSurfaceCreated = true;
if (mPrepared && !mStartPreview)
startPreview();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
this.mSurfaceHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
release();
}
startPreview()
就是我们的预览方法:
/** 开始预览 */
public void startPreview() {
if (mStartPreview || mSurfaceHolder == null || !mPrepared)
return;
else
mStartPreview = true;
try {
//打开镜头
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
camera = Camera.open();
else
camera = Camera.open(mCameraId);
try {
//为preview设置SurfaceHolder
camera.setPreviewDisplay(mSurfaceHolder);
} catch (IOException e) {
if (mOnErrorListener != null) {
mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY, 0);
}
Log.e(" ", "setPreviewDisplay fail " + e.getMessage());
}
//获取摄像头参数
Parameters parameters = camera.getParameters();
prepareCameraParaments(parameters);
setPreviewCallback(parameters);
camera.setParameters(parameters);
camera.startPreview();
if (mOnPreparedListener != null)
mOnPreparedListener.onPrepared();
} catch (Exception e) {
e.printStackTrace();
if (mOnErrorListener != null) {
mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_PREVIEW, 0);
}
Log.e(" ", "startPreview fail :" + e.getMessage());
}
}
这里有几个方法依次说一下,首先是protected void prepareCameraParaments(Parameters parameters)
调用它会对相机进行一些参数上的处理,里面基本包括:
List<int[]> supportedPreviewFpsRange = parameters.getSupportedPreviewFpsRange();
这里返回一个数组列表,里面包含了设备所支持预览画面的FPS值,每个数组含有两个值,第一个值是最小FPS,另一个是最大FPS值,这样就构成了一个区间,可以检测你所期望的FPS值在不在设备支持中。那么:
parameters.setPreviewFrameRate(mFrameRate);
接着,
List<Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
这个返回了设备支持的预览画面的尺寸,Size有很多,但是重点在比例,16:9、4:3 、11:9等等,比例非常关键,它直接关系到后来我们的SurfaceView
的尺寸,选一个你想要的把它:
parameters.setPreviewSize(customSize.width, customSize.height);
我们知道小视频录制的时候是竖屏的,所以把它竖过来是必须的:
camera.setDisplayOrientation(90);
顺时针旋转90度,之所以这么做,API文档上有介绍,大意就是camera sensor没有转,但是我们可以把画面转过来。
这样就可以了,当然如果想设置更多,当然没问题,这里的Parameters
设置的参数实际上都是以Map键值对的形式传入的,具体的源码也有大神解读。那么,比如加一个防抖功能(前提是你的设备支持):
if ("true".equals(parameters.get("video-stabilization-supported")))
parameters.set("video-stabilization", "true");
再来看protected void setPreviewCallback(Parameters parameters)
方法,作用呢就是给Camera对象设置我们一开始实现的那个PreviewCallback
接口:
protected void setPreviewCallback(Parameters parameters) {
Camera.Size size = parameters.getPreviewSize();
if (size !=