視頻錄制控件
布局文件就是一個(gè)surfaceView
/**
* 視頻錄制控件
*/
public class MovieRecorderView extends LinearLayout implements OnErrorListener {
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
//屏幕方向
//手機(jī)頂部朝上
public static final int UP = 0;
//手機(jī)底部朝上
public static final int DOWN = 1;
//手機(jī)左邊朝上
public static final int LEFT = 2;
//手機(jī)右邊朝上
public static final int RIGHT = 3;
private int orientation = UP;
private MediaRecorder mMediaRecorder;
private Camera mCamera;
private Timer mTimer;// 計(jì)時(shí)器
private OnRecordFinishListener mOnRecordFinishListener;// 錄制完成回調(diào)接口
private int mWidth;// 視頻分辨率寬度
private int mHeight;// 視頻分辨率高度
private boolean isOpenCamera;// 是否一開始就打開攝像頭
private int mRecordMaxTime;// 一次拍攝最長(zhǎng)時(shí)間
private int mTimeCount;// 時(shí)間計(jì)數(shù)
private File mRecordFile = null;// 文件
private final int FRONT_CAMERA = 0; //前置攝像頭
private final int BACK_CAMERA = 1; //后置攝像頭
private int cameraType = BACK_CAMERA;
public MovieRecorderView(Context context) {
this(context, null);
}
public MovieRecorderView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MovieRecorderView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 初始化各項(xiàng)組件
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MovieRecorderView, defStyle, 0);
mWidth = a.getInteger(R.styleable.MovieRecorderView_video_width, 320);// 默認(rèn)320
mHeight = a.getInteger(R.styleable.MovieRecorderView_video_height, 240);// 默認(rèn)240
isOpenCamera = a.getBoolean(R.styleable.MovieRecorderView_is_open_camera, true);// 默認(rèn)打開
mRecordMaxTime = a.getInteger(R.styleable.MovieRecorderView_record_max_time, 10);// 默認(rèn)為10
LayoutInflater.from(context).inflate(R.layout.movie_recorder_view, this);
mSurfaceView = (SurfaceView) findViewById(R.id.surfaceview);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(new CustomCallBack());
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
a.recycle();
}
/**
*
*/
private class CustomCallBack implements Callback {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!isOpenCamera)
return;
try {
initCamera();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (!isOpenCamera)
return;
freeCameraResource();
}
}
/**
* 初始化攝像頭
*
* @date 2015-2-5
* @throws IOException
*/
@SuppressWarnings("deprecation")
public void initCamera() throws IOException {
if (mCamera != null) {
freeCameraResource();
}
int cameraCount = 0;
CameraInfo cameraInfo = new CameraInfo();
cameraCount = Camera.getNumberOfCameras();// 得到攝像頭的個(gè)數(shù)
for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraType == FRONT_CAMERA) {
//切換成前置
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//釋放資源
freeCameraResource();
try {
mCamera = Camera.open(camIdx);
if (mCamera == null)
return;
setCameraParams();
} catch (RuntimeException e) {
e.printStackTrace();
freeCameraResource();
}
}
}else{
//切換成后置
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
//釋放資源
freeCameraResource();
try {
mCamera = Camera.open(camIdx);
if (mCamera == null)
return;
setCameraParams();
} catch (RuntimeException e) {
e.printStackTrace();
freeCameraResource();
}
}
}
}
// try {
// mCamera = Camera.open();
// } catch (Exception e) {
// e.printStackTrace();
// freeCameraResource();
// }
// if (mCamera == null)
// return;
//
// setCameraParams();
}
/**
* 設(shè)置攝像頭屬性
*/
@SuppressWarnings("deprecation")
public void setCameraParams() {
try {
if (mCamera != null) {
Parameters params = mCamera.getParameters();
// params.set("orientation", "portrait");
setPreviewSize(params);
mCamera.setParameters(params);
}
//設(shè)置預(yù)覽豎屏
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
mCamera.unlock();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 切換前后攝像頭
*/
@SuppressWarnings("deprecation")
public void toggleCamera(){
int cameraCount = 0;
CameraInfo cameraInfo = new CameraInfo();
cameraCount = Camera.getNumberOfCameras();// 得到攝像頭的個(gè)數(shù)
for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraType == BACK_CAMERA) {
//切換成前置
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//釋放資源
freeCameraResource();
try {
mCamera = Camera.open(camIdx);
cameraType = FRONT_CAMERA;
if (mCamera == null)
return;
setCameraParams();
return;
} catch (RuntimeException e) {
e.printStackTrace();
freeCameraResource();
}
}
}else{
//切換成后置
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
//釋放資源
freeCameraResource();
try {
mCamera = Camera.open(camIdx);
cameraType = BACK_CAMERA;
if (mCamera == null)
return;
setCameraParams();
return;
} catch (RuntimeException e) {
e.printStackTrace();
freeCameraResource();
}
}
}
}
}
/**
* 釋放攝像頭資源
*
*/
private void freeCameraResource() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.lock();
mCamera.release();
mCamera = null;
}
}
private void createRecordDir() {
File sampleDir = new File(Environment.getExternalStorageDirectory() + File.separator + "im/video/");
if (!sampleDir.exists()) {
sampleDir.mkdirs();
}
File vecordDir = sampleDir;
// 創(chuàng)建文件
try {
mRecordFile = File.createTempFile("recording", ".mp4", vecordDir); //mp4格式
Log.i("TAG",mRecordFile.getAbsolutePath());
} catch (IOException e) {
}
}
/**
* 初始化
*
* @throws IOException
*/
private void initRecord() throws IOException {
mMediaRecorder = new MediaRecorder();
mMediaRecorder.reset();
if (mCamera != null)
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setOnErrorListener(this);
mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
mMediaRecorder.setVideoSource(VideoSource.CAMERA);// 視頻源
mMediaRecorder.setAudioSource(AudioSource.MIC);// 音頻源
mMediaRecorder.setOutputFormat(OutputFormat.MPEG_4);// 視頻輸出格式
mMediaRecorder.setAudioEncoder(AudioEncoder.AMR_NB);// 音頻格式
// mMediaRecorder.setVideoFrameRate(16);// 去掉這一行,有些手機(jī)會(huì)mMediaRecorder.start()失敗
mMediaRecorder.setVideoEncodingBitRate(1 * 1280 * 720);// 設(shè)置幀頻率,然后就清晰了
//輸出角度
switch (orientation) {
case UP:
mMediaRecorder.setOrientationHint(90);// 輸出旋轉(zhuǎn)90度,頂部朝上錄制
break;
case DOWN:
mMediaRecorder.setOrientationHint(270);
break;
case LEFT:
mMediaRecorder.setOrientationHint(0);
break;
case RIGHT:
mMediaRecorder.setOrientationHint(180);
break;
}
mMediaRecorder.setVideoSize(mWidth, mHeight);// 設(shè)置分辨率:
mMediaRecorder.setVideoEncoder(VideoEncoder.MPEG_4_SP);// 視頻錄制格式
// mediaRecorder.setMaxDuration(Constant.MAXVEDIOTIME * 1000);
mMediaRecorder.setOutputFile(mRecordFile.getAbsolutePath());
mMediaRecorder.prepare();
try {
mMediaRecorder.start();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 開始錄制視頻
*
* @param onRecordFinishListener
* 達(dá)到指定時(shí)間之后回調(diào)接口
*/
public void record(final OnRecordFinishListener onRecordFinishListener) {
this.mOnRecordFinishListener = onRecordFinishListener;
createRecordDir();
try {
if (!isOpenCamera)// 如果未打開攝像頭,則打開
initCamera();
initRecord();
mTimeCount = 0;// 時(shí)間計(jì)數(shù)器重新賦值
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
mTimeCount++;
// mProgressBar.setProgress(mTimeCount);
// 設(shè)置進(jìn)度
if (mOnRecordFinishListener != null)
mOnRecordFinishListener.onRecord(mTimeCount);
if (mTimeCount == mRecordMaxTime) {// 達(dá)到指定時(shí)間,停止拍攝
stop();
if (mOnRecordFinishListener != null)
mOnRecordFinishListener.onRecordFinish();
}
}
}, 0, 1000);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 停止拍攝
*
* @author liuyinjun
* @date 2015-2-5
*/
public void stop() {
stopRecord();
releaseRecord();
freeCameraResource();
}
/**
* 停止錄制
*
* @author liuyinjun
* @date 2015-2-5
*/
public void stopRecord() {
// mProgressBar.setProgress(0);
if (mTimer != null)
mTimer.cancel();
if (mMediaRecorder != null) {
// 設(shè)置后不會(huì)崩
mMediaRecorder.setOnErrorListener(null);
try {
mMediaRecorder.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
mMediaRecorder.setPreviewDisplay(null);
}
}
/**
* 釋放資源
*
* @author liuyinjun
* @date 2015-2-5
*/
private void releaseRecord() {
if (mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null);
try {
mMediaRecorder.release();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
mMediaRecorder = null;
}
public int getTimeCount() {
return mTimeCount;
}
/**
* @return the mVecordFile
*/
public File getmRecordFile() {
return mRecordFile;
}
/**
* 錄制完成回調(diào)接口
*
* @author liuyinjun
*
* @date 2015-2-5
*/
public interface OnRecordFinishListener {
public void onRecordFinish();
void onRecord(int progress);
}
/**
* 設(shè)置最大錄制時(shí)間
* @param max
*/
public void setMaxTime(int max){
mRecordMaxTime = max;
}
@Override
public void onError(MediaRecorder mr, int what, int extra) {
try {
if (mr != null)
mr.reset();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根據(jù)手機(jī)支持的視頻分辨率,設(shè)置預(yù)覽尺寸
*
* @param params
*/
@SuppressWarnings("deprecation")
private void setPreviewSize(Parameters params) {
if (mCamera == null) {
return;
}
//獲取手機(jī)支持的分辨率集合,并以寬度為基準(zhǔn)降序排序
List<Camera.Size> previewSizes = params.getSupportedPreviewSizes();
Collections.sort(previewSizes, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
if (lhs.width > rhs.width) {
return -1;
} else if (lhs.width == rhs.width) {
return 0;
} else {
return 1;
}
}
});
float tmp = 0f;
float minDiff = 100f;
float ratio = 3.0f / 4.0f;// 高寬比率3:4,且最接近屏幕寬度的分辨率,可以自己選擇合適的想要的分辨率
Camera.Size best = null;
for (Camera.Size s : previewSizes) {
tmp = Math.abs(((float) s.height / (float) s.width) - ratio);
// LogUtil.e(LOG_TAG,"tmp:" + tmp);
// if (tmp == 0 && getWindowWidth(getContext()) <= s.width) {
// break;
// }
if (tmp < minDiff) {
minDiff = tmp;
best = s;
Log.e("屏幕", "setPreviewSize: width:" + s.width + "...height:" + s.height);
}
}
// List<int[]> range = params.getSupportedPreviewFpsRange();
// int[] fps = range.get(0);
// LogUtil.e(LOG_TAG,"min="+fps[0]+",max="+fps[1]);
// params.setPreviewFpsRange(3,7);
params.setPreviewSize(best.width, best.height);//預(yù)覽比率
// params.setPictureSize(480, 720);//拍照保存比率
// Log.e(LOG_TAG, "setPreviewSize BestSize: width:" + best.width + "...height:" + best.height);
// 大部分手機(jī)支持的預(yù)覽尺寸和錄制尺寸是一樣的,也有特例,有些手機(jī)獲取不到,那就把設(shè)置錄制尺寸放到設(shè)置預(yù)覽的方法里面
if (params.getSupportedVideoSizes() == null || params.getSupportedVideoSizes().size() == 0) {
mWidth = best.width;
mHeight = best.height;
} else {
setVideoSize(params);
}
}
/**
* 根據(jù)手機(jī)支持的視頻分辨率,設(shè)置錄制尺寸
*
* @param params
*/
@SuppressWarnings("deprecation")
private void setVideoSize(Parameters params) {
if (mCamera == null) {
return;
}
//獲取手機(jī)支持的分辨率集合,并以寬度為基準(zhǔn)降序排序
List<Camera.Size> previewSizes = params.getSupportedVideoSizes();
Collections.sort(previewSizes, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
if (lhs.width > rhs.width) {
return -1;
} else if (lhs.width == rhs.width) {
return 0;
} else {
return 1;
}
}
});
float tmp = 0f;
float minDiff = 100f;
float ratio = 720.0f / 1080.0f;//高寬比率3:4,且最接近屏幕寬度的分辨率
Camera.Size best = null;
for (Camera.Size s : previewSizes) {
tmp = Math.abs(((float) s.height / (float) s.width) - ratio);
// if (tmp == 0 && getWindowWidth(getContext()) <= s.width) {
// break;
// }
if (tmp < minDiff) {
minDiff = tmp;
best = s;
Log.e("攝像頭", "setVideoSize: width:" + s.width + "...height:" + s.height);
}
}
// Log.e(LOG_TAG, "setVideoSize BestSize: width:" + best.width + "...height:" + best.height);
//設(shè)置錄制尺寸
mWidth = best.width;
mHeight = best.height;
}
public void setOrientation(int orientation){
this.orientation = orientation;
}
attrs文件
<declare-styleable name="MovieRecorderView">
<!-- 開始是否打開攝像頭 -->
<attr name="is_open_camera" format="boolean" />
<!-- 一次拍攝最長(zhǎng)時(shí)間 -->
<attr name="record_max_time" format="integer"/>
<!-- 視頻分辨率寬度 -->
<attr name="video_width" format="integer"/>
<!-- 視頻分辨率高度 -->
<attr name="video_height" format="integer"/>
</declare-styleable>