基于camera+SurfaceView+MediaRecorder的录制微视频

录制视频的主要逻辑就是使用的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;
    }
}

2.微视频录制使用的自定义进度条

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;
    }
}

3.录制视频Activity的xml布局




        

            

            

            

            

        

        

            

4.Activity中代码逻辑

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();
    }
}


你可能感兴趣的:(android)