录制视频的主要逻辑就是使用的Camera+SurfaceView实现摄像头和界面的录像同步,然后把Camera和MediaRecorder结合起来实现录像数据的存储.
1.主要录制控件VideoRecorder自定义控件的实现extendsSurfaceView
package com.fanday.jokes.view;
/**
* Created by fanday on 2017/3/18.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class VideoRecorder extends SurfaceView implements MediaRecorder.OnErrorListener{
private SurfaceHolder mSurfaceHolder;
private MediaRecorder mMediaRecorder;
private Camera mCamera;
private Timer mTimer;
private OnRecordStausChangeListener mOnRecordStausChangeListener;// 录制状态变化回调接口
private int mXpx;//视频分辨率宽度
private int mYpx;//视频分辨率高度
private int mOutFormat;//0是mp4,1是3gp
private int mRecordMaxTime;// 一次拍摄最长时间
private int mVideoEncodingBitRate;//声音的编码位率
private int mVideoFrameRate;//录制的视频帧率
private String mOutputDirPath = Environment.getExternalStorageDirectory()
+ File.separator + "avideo/";//输出目录
private String mSuffix;//视频文件后缀
private boolean mIsOpenCamera;// 是否一开始就打开摄像头
private int mTimeCount;// 时间计数
private File mVecordFile = null;// 视频输出文件
public VideoRecorder(Context context) {
this(context, null);
}
public VideoRecorder(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VideoRecorder(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, com.fanday.jokes.R.styleable.VideoRecorder, defStyleAttr, 0);
mXpx = typedArray.getInteger(com.fanday.jokes.R.styleable.VideoRecorder_vrv_x_px, 320);// 默认320
mYpx = typedArray.getInteger(com.fanday.jokes.R.styleable.VideoRecorder_vrv_y_px, 240);// 默认240
mOutFormat = typedArray.getInteger(com.fanday.jokes.R.styleable.VideoRecorder_vrv_out_format, 1);//默认3gp
mRecordMaxTime = typedArray.getInteger(com.fanday.jokes.R.styleable.VideoRecorder_vrv_record_max_time, 10);//默认最长10秒
mVideoEncodingBitRate = typedArray.getInteger(com.fanday.jokes.R.styleable.VideoRecorder_vrv_video_encoding_bit_rate, 1 * 1024 * 1024);
mVideoFrameRate = typedArray.getInteger(com.fanday.jokes.R.styleable.VideoRecorder_vrv_video_frame_rate, 10);
mIsOpenCamera = typedArray.getBoolean(com.fanday.jokes.R.styleable.VideoRecorder_vrv_is_open_camera, true);
typedArray.recycle();
switch (mOutFormat) {
case 0:
mSuffix = ".mp4";
break;
case 1:
mSuffix = ".3gp";
break;
}
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(new VideoRecorder.CustomCallBack());
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
private class CustomCallBack implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!mIsOpenCamera)
return;
openCamera();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (!mIsOpenCamera)
return;
freeCameraResource();
}
}
/**
* 初始化摄像头
*/
public void openCamera() {
try {
if (mCamera != null) {
freeCameraResource();
}
try {
mCamera = Camera.open();
} catch (Exception e) {
e.printStackTrace();
freeCameraResource();
}
if (mCamera == null)
return;
setCameraParams();
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
mCamera.unlock();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 设置摄像头为竖屏
*/
private void setCameraParams() {
if (mCamera != null) {
// Camera.Parameters params = mCamera.getParameters();
// params.set("orientation", "portrait");
// mCamera.setParameters(params);
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size preSize = getCloselyPreSize(true, getWidth(), getHeight(), parameters.getSupportedPreviewSizes());
parameters.setPreviewSize(preSize.width, preSize.height);
mCamera.setParameters(parameters);
}
}
/**
* 通过对比得到与宽高比最接近的预览尺寸(如果有相同尺寸,优先选择)
*
* @param isPortrait 是否竖屏
* @param surfaceWidth 需要被进行对比的原宽
* @param surfaceHeight 需要被进行对比的原高
* @param preSizeList 需要对比的预览尺寸列表
* @return 得到与原宽高比例最接近的尺寸
*/
public static Camera.Size getCloselyPreSize(boolean isPortrait, int surfaceWidth, int surfaceHeight, List preSizeList) {
int reqTmpWidth;
int reqTmpHeight;
// 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
if (isPortrait) {
reqTmpWidth = surfaceHeight;
reqTmpHeight = surfaceWidth;
} else {
reqTmpWidth = surfaceWidth;
reqTmpHeight = surfaceHeight;
}
//先查找preview中是否存在与surfaceview相同宽高的尺寸
for (Camera.Size size : preSizeList) {
if ((size.width == reqTmpWidth) && (size.height == reqTmpHeight)) {
return size;
}
}
// 得到与传入的宽高比最接近的size
float reqRatio = ((float) reqTmpWidth) / reqTmpHeight;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
Camera.Size retSize = null;
for (Camera.Size size : preSizeList) {
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
retSize = size;
}
}
return retSize;
}
/**
* 释放摄像头资源
*/
private void freeCameraResource() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.lock();
mCamera.release();
mCamera = null;
}
}
private void createRecordDir() {
//录制的视频保存文件夹
File sampleDir = new File(mOutputDirPath);//录制视频的保存地址
if (!sampleDir.exists()) {
sampleDir.mkdir();
}
//创建文件
try {
mVecordFile = File.createTempFile("recording", mSuffix, sampleDir);
} catch (IOException e) {
e.printStackTrace();
Log.e("CSDN_LQR", "创建视频文件失败");
}
}
private void initRecord() throws IOException {
mMediaRecorder = new MediaRecorder();
try {
mMediaRecorder.reset();
if (mCamera != null){
mMediaRecorder.setCamera(mCamera);
}
mMediaRecorder.setOnErrorListener(this);
mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);//视频源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//音频源
mMediaRecorder.setOrientationHint(90);//输出旋转90度,保持坚屏录制
switch (mOutFormat) {
case 0:
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);// 视频输出格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 音频格式
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);// 视频录制格式
break;
case 1:
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
break;
}
//设置视频录制的分辨率。必须放在设置编码和格式的后面,否则报错
//mMediaRecorder.setVideoSize(mXpx, mYpx);
// 设置录制的视频帧率。必须放在设置编码和格式的后面,否则报错
//mMediaRecorder.setVideoFrameRate(mVideoFrameRate);
mMediaRecorder.setVideoEncodingBitRate(mVideoEncodingBitRate);
// mediaRecorder.setMaxDuration(Constant.MAXVEDIOTIME * 1000);
mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());
mMediaRecorder.prepare();
} catch (Exception e) {
e.printStackTrace();
}
try {
mMediaRecorder.start();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 开始录制视频
*/
public void record(final VideoRecorder.OnRecordStausChangeListener onRecordStausChangeListener) {
this.mOnRecordStausChangeListener = onRecordStausChangeListener;
createRecordDir();
try {
if (mOnRecordStausChangeListener != null)
mOnRecordStausChangeListener.onRecordStart();
if (!mIsOpenCamera)//如果未打开摄像头,则打开
openCamera();
initRecord();
mTimeCount = 0;//时间计数器重新赋值
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
mTimeCount++;
if (mOnRecordStausChangeListener != null)
mOnRecordStausChangeListener.onRecording(mTimeCount, mRecordMaxTime);
if (mTimeCount == mRecordMaxTime) {//达到指定时间,停止拍摄
stop();
if (mOnRecordStausChangeListener != null)
mOnRecordStausChangeListener.onRecrodFinish();
}
}
}, 0, 1000);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 停止录制并释放相机资源
*/
public void stop() {
stopRecord();
releaseRecord();
freeCameraResource();
}
/**
* 停止录制
*/
public void stopRecord() {
if (mTimer != null)
mTimer.cancel();
if (mMediaRecorder != null) {
//设置后不会崩
mMediaRecorder.setOnErrorListener(null);
mMediaRecorder.setPreviewDisplay(null);
try {
mMediaRecorder.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
*/
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 interface OnRecordStausChangeListener {
public void onRecrodFinish();
public void onRecording(int timeCount, int recordMaxTime);
public void onRecordStart();
}
@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();
}
}
/**
* @return 已经录制的秒数
*/
public int getTimeCount() {
return mTimeCount;
}
/**
* @return 录制的视频文件
*/
public File getVecordFile() {
return mVecordFile;
}
public boolean isOpenCamera() {
return mIsOpenCamera;
}
public void setOpenCamera(boolean openCamera) {
mIsOpenCamera = openCamera;
}
public String getSuffix() {
return mSuffix;
}
public void setSuffix(String suffix) {
mSuffix = suffix;
}
public String getOutputDirPath() {
return mOutputDirPath;
}
public void setOutputDirPath(String outputDirPath) {
mOutputDirPath = outputDirPath;
}
public int getVideoFrameRate() {
return mVideoFrameRate;
}
public void setVideoFrameRate(int videoFrameRate) {
mVideoFrameRate = videoFrameRate;
}
public int getVideoEncodingBitRate() {
return mVideoEncodingBitRate;
}
public void setVideoEncodingBitRate(int videoEncodingBitRate) {
mVideoEncodingBitRate = videoEncodingBitRate;
}
public int getRecordMaxTime() {
return mRecordMaxTime;
}
public void setRecordMaxTime(int recordMaxTime) {
mRecordMaxTime = recordMaxTime;
}
public int getOutFormat() {
return mOutFormat;
}
public void setOutFormat(int outFormat) {
mOutFormat = outFormat;
}
public int getYpx() {
return mYpx;
}
public void setYpx(int ypx) {
mYpx = ypx;
}
public int getXpx() {
return mXpx;
}
public void setXpx(int xpx) {
mXpx = xpx;
}
}
package com.fanday.jokes.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
* @创建者 fanday
* @描述 仿微信小视频进度条
*/
public class RecordProgress extends View {
private Paint mPaint = new Paint();
private boolean mIsStart = false;
private int mRecordTime = 10 * 1000;//默认最长录制时间是10秒
private int mProgressColor = Color.BLACK;//默认进度条是黑色
private long mStartTime;
private Context mContext;
public RecordProgress(Context context) {
super(context, null);
}
public RecordProgress(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public RecordProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mProgressColor);
stop();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
long currTime = System.currentTimeMillis();
if (mIsStart) {
int measureWidth = getMeasuredWidth();
float mSpeed = measureWidth / 2.0f / mRecordTime;// 速度 = 一边距离 / 总时间
float durTime = (currTime - mStartTime);//时间间隔
float dist = mSpeed * durTime;//一边在durTime行走的距离
if (dist < measureWidth / 2.0f) {//判断是否到达终点
canvas.drawRect(dist, 0.0f, measureWidth - dist, getMeasuredHeight(), mPaint);//绘制进度条
invalidate();
return;
} else {
stop();
}
}
canvas.drawRect(0.0f, 0.0f, 0.0f, getMeasuredHeight(), mPaint);
}
public void start() {
mIsStart = true;
mStartTime = System.currentTimeMillis();
invalidate();
setVisibility(VISIBLE);
}
public void stop() {
mIsStart = false;
setVisibility(INVISIBLE);
}
public int getProgressColor() {
return mProgressColor;
}
public void setProgressColor(int progressColor) {
mProgressColor = progressColor;
mPaint.setColor(mProgressColor);
}
public int getRecordTime() {
return mRecordTime;
}
public void setRecordTime(int recordTime) {
mRecordTime = recordTime * 1000;
}
}
package com.fanday.jokes.activity;
import android.Manifest;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.fanday.jokes.R;
import com.fanday.jokes.bean.Video;
import com.fanday.jokes.utils.Utils;
import com.fanday.jokes.view.RecordProgress;
import com.fanday.jokes.view.VideoRecorder;
import java.io.File;
import cn.bmob.v3.datatype.BmobFile;
import cn.bmob.v3.exception.BmobException;
import cn.bmob.v3.listener.SaveListener;
import cn.bmob.v3.listener.UploadFileListener;
public class VideoActivity extends AppCompatActivity implements VideoRecorder.OnRecordStausChangeListener {
private VideoRecorder mVrvVideo;
private Button mBtnVideo;
private TextView mTvTipOne;
private TextView mTvTipTwo;
private RecordProgress mRp;
String[] pers={Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO};
private int persCount=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ContextCompat.checkSelfPermission(this,pers[0])!= PackageManager.PERMISSION_GRANTED
||ContextCompat.checkSelfPermission(this,pers[1])!= PackageManager.PERMISSION_GRANTED
||ContextCompat.checkSelfPermission(this,pers[2])!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,pers,101);
}else{
initView();
initListener();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 101:
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initView();
initListener();
} else {
Toast.makeText(VideoActivity.this,"你拒绝了摄像头权限,无法使用该功能",Toast.LENGTH_SHORT).show();
}
break;
}
}
private void initView() {
setContentView(R.layout.activity_main);
mVrvVideo = (VideoRecorder) findViewById(R.id.vrvVideo);
mBtnVideo = (Button) findViewById(R.id.btnVideo);
mTvTipOne = (TextView) findViewById(R.id.tvTipOne);
mTvTipTwo = (TextView) findViewById(R.id.tvTipTwo);
mRp = (RecordProgress) findViewById(R.id.rp);
mRp.setRecordTime(10);
}
private void initListener() {
mBtnVideo.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mRp.start();
mRp.setProgressColor(Color.parseColor("#1AAD19"));
mTvTipOne.setVisibility(View.VISIBLE);
mTvTipTwo.setVisibility(View.GONE);
//开始录制
mVrvVideo.record(VideoActivity.this);
break;
case MotionEvent.ACTION_UP:
mRp.stop();
mTvTipOne.setVisibility(View.GONE);
mTvTipTwo.setVisibility(View.GONE);
//判断时间
if (mVrvVideo.getTimeCount() > 3) {
if (!isCancel(v, event)) {
onRecrodFinish();
}
} else {
if (!isCancel(v, event)) {
Toast.makeText(getApplicationContext(), "视频时长太短", Toast.LENGTH_SHORT).show();
if (mVrvVideo.getVecordFile() != null)
mVrvVideo.getVecordFile().delete();
}
}
resetVideoRecord();
break;
case MotionEvent.ACTION_MOVE:
if (isCancel(v, event)) {
mTvTipOne.setVisibility(View.GONE);
mTvTipTwo.setVisibility(View.VISIBLE);
mRp.setProgressColor(Color.parseColor("#FF1493"));
} else {
mTvTipOne.setVisibility(View.VISIBLE);
mTvTipTwo.setVisibility(View.GONE);
mRp.setProgressColor(Color.parseColor("#1AAD19"));
}
break;
}
return true;
}
});
}
private boolean isCancel(View v, MotionEvent event) {
int[] location = new int[2];
v.getLocationOnScreen(location);
if (event.getRawX() < location[0] || event.getRawX() > location[0] + v.getWidth() || event.getRawY() < location[1] - 40) {
return true;
}
return false;
}
@Override
public void onRecrodFinish() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvTipOne.setVisibility(View.GONE);
mTvTipTwo.setVisibility(View.GONE);
resetVideoRecord();
//打开播放界面mVrvVideo.getVecordFile().toString()
showDialog();
}
});
}
private void showDialog() {
new AlertDialog.Builder(this).setTitle("录制完成")
.setMessage("是否上传").
setNegativeButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
upload();
}
}).show();
}
ProgressDialog dialog;
private void upload() {
dialog=new ProgressDialog(this);
dialog.show();
final BmobFile bmobFile = new BmobFile(new File(mVrvVideo.getVecordFile().toString()));
bmobFile.uploadblock(new UploadFileListener() {
@Override
public void done(BmobException e) {
if(e==null){
//bmobFile.getFileUrl()--返回的上传文件的完整地址
//toast("上传文件成功:" + );
post(bmobFile.getFileUrl());
}else{
//toast("上传文件失败:" + e.getMessage());
}
}
@Override
public void onProgress(Integer value) {
// 返回的上传进度(百分比)
dialog.setProgress(value);
}
});
}
private void post(String fileUrl) {
Video v = new Video();
v.setUsername(Utils.getSp(this,"username"));
v.setVideoPath(fileUrl);
v.setHeadImg(Utils.getSp(this,"imgUrl"));
v.save(new SaveListener() {
@Override
public void done(String objectId,BmobException e) {
dialog.dismiss();
if(e==null){
Toast.makeText(getApplicationContext(), "上传完成", Toast.LENGTH_SHORT).show();
finish();
}else{
Toast.makeText(getApplicationContext(), "上传失败", Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onRecording(int timeCount, int recordMaxTime) {
}
@Override
public void onRecordStart() {
}
/**
* 停止录制(释放相机后重新打开相机)
*/
public void resetVideoRecord() {
mVrvVideo.stop();
mVrvVideo.openCamera();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}