MediaRecorder:Android录制视频功能精炼详解

这里写图片描述

一、前期基础知识储备

Android提供了MediaRecorder这一个类来实现视频音频的录制。
这里写图片描述
由官方配图可知,MediaRecorder用于录制视频时需要调用一系列的API来设置和录制相关的配置,而且调用方法的顺序是固定的,必须按照这个顺序进行API调用才能正确利用手机摄像头实现录像功能。

调用MediaRecorder的录像API顺序如下:

1)Open Camera - Use the Camera.open() to get an instance of the camera object.
2)Connect Preview - Prepare a live camera image preview by connecting a SurfaceView to the camera using Camera.setPreviewDisplay().
3)Start Preview - Call Camera.startPreview() to begin displaying the live camera images.
4)Start Recording Video - The following steps must be completed in order to successfully record video:
a.Unlock the Camera - Unlock the camera for use by MediaRecorder by calling Camera.unlock().
b.Configure MediaRecorder - Call in the following MediaRecorder methods in this order:
setCamera() - Set the camera to be used for video capture,绑定Camera进行视频录制。
setAudioSource() - Set the audio source,设置音频源。
setVideoSource() - Set the video source,设置视频源。
setProfile() - Set the video output format and encoding,录制效果的配置。
setOutputFile() - Set the output file, 设置录制好的文件存储位置。
setPreviewDisplay() - Connect Preview,设置预览效果。
c.Prepare MediaRecorder- Prepare the MediaRecorder with provided configuration settings by calling MediaRecorder.prepare().
d.Start MediaRecorder - Start recording video by calling MediaRecorder.start().

停止录像时调用的API顺序如下:

1)Stop MediaRecorder - Stop recording video by calling MediaRecorder.stop().
2)Reset MediaRecorder - Optionally, remove the configuration settings from the recorder by calling MediaRecorder.reset().
3)Release MediaRecorder - Release the MediaRecorder by calling MediaRecorder.release().
4)Lock the Camera - Lock the camera so that future MediaRecorder sessions can use it by calling Camera.lock().
5)Stop the Preview - When your activity has finished using the camera, stop the preview using Camera.stopPreview().
6)Release Camera - Release the camera so that other applications can use it by calling Camera.release().

二、上代码,具体实现录制视频和视频播放功能

这里调用MediaRecorder的API实现视频录制功能并借用MediaPlayer多媒体播放类实现录制好的视频播放。
(1)布局文件如下,非常简单两个按钮下放置一个SurfaceView;

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/record_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="15dp"
                android:layout_weight="1"
                android:text="record" />

            <Button
                android:id="@+id/play_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="15dp"
                android:layout_weight="1"
                android:text="play" />
        </LinearLayout>

        <SurfaceView
            android:id="@+id/surface_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="20dp" />
    </LinearLayout>

(2)相机录像前的准备代码;

    /*
    * 相机预览前的准备工作代码 单独抽出来
    * */
    private boolean prepareVideoRecorder() throws IOException {
        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
            mMediaRecorder.reset();
        }
            /*camera相关设置部分*/
        mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
        if (mCamera != null) {
            //设置旋转角度,顺时针方向,因为默认是逆向90度的,这样图像就是正常显示了
            mCamera.setDisplayOrientation(90);
            mCamera.unlock();
            mMediaRecorder.setCamera(mCamera);
        }

            /*recorder设置部分*/
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
        mMediaRecorder.setOutputFile(getOutputMediaFile());
        mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
        mMediaRecorder.prepare();
        return true;
    }

(3)创建录像文件存储位置代码;

    /*
    * 获取手机外部存储路径
    * */
    private String getOutputFile() {
        File mediaFile = null;
        boolean OutputExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
        if (OutputExist) {
            mediaFile = Environment.getExternalStorageDirectory();
            return mediaFile.toString();
        }
        return null;
    }

    /*
    * 获取录制视频的日期 作为存储文件路径一部分
    * */
    private String getDate() {
        Log.d(TAG, "获取录制视频的日期 ");
        Calendar ca = Calendar.getInstance();
        int year = ca.get(Calendar.YEAR);           // 获取年份
        int month = ca.get(Calendar.MONTH);         // 获取月份
        int day = ca.get(Calendar.DATE);            // 获取日
        String date = "" + year + "_" + (month + 1) + "_" + day;
        return date;
    }

    /*
    *创建视频存储文件夹 录制好的视频存储在手机外部存储中 以录像时间+mp4格式命名
    * */
    private String getOutputMediaFile() {
        Log.d(TAG, "获取视频存储的位置 ");
        String mediaPath = getOutputFile();
        if (mediaPath != null) {
            File mediaFile = new File(mediaPath + "/recordVideo");
            if (!mediaFile.exists()) {
                mediaFile.mkdir();
            }
            return mMediaPath = mediaFile.getAbsolutePath() + File.separator + getDate() + ".mp4";
        }
        return null;
    }

(4)录制视频结束时释放相机资源;

    /*
    * 录制视频结束时释放相机资源
    * */
    private void releaseMediaRecorder() {
        Log.d(TAG, "录制结束后释放资源 ");
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();   // clear recorder configuration
            mMediaRecorder.release(); // release the recorder object
            mMediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

(5)点击录制视频按钮mRecordBtn开始录制和再次点击停止录制;

    private void initBtnClick() {
        StartRecording();

        mPlayBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mMediaPlayer == null) {
                    mMediaPlayer = new MediaPlayer();
                    mMediaPlayer.reset();
                    Uri uri = Uri.parse(mMediaPath);
                    mMediaPlayer = MediaPlayer.create(MainActivity.this,uri);
                    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    mMediaPlayer.setDisplay(mSurfaceHolder);
                    try{
                        mMediaPlayer.prepare();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    mMediaPlayer.start();
                }
            }
        });
    }
    private void StartRecording(){
        mRecordBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!mIsRecord) {
                    try {
                        Log.d(TAG, "首次点击开始录像 ");
                        if (prepareVideoRecorder()) {
                            mMediaRecorder.start();
                            mIsRecord = true;
                            mRecordBtn.setText("stop");
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    Log.d(TAG, "再次点击停止录像");
                    mMediaRecorder.stop();
                    releaseMediaRecorder();
                    mCamera.lock();
                    mRecordBtn.setText("record");
                    mIsRecord = false;

                    if (mCamera != null) {
                        mCamera.release();
                        mCamera = null;
                    }
                }
            }
        });
    }

(6)点击播放视频按钮 mPlayBtn开始播放录制刚刚录制好的视频;

        mPlayBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mMediaPlayer == null) {
                    mMediaPlayer = new MediaPlayer();
                    mMediaPlayer.reset();
                    Uri uri = Uri.parse(mMediaPath);
                    mMediaPlayer = MediaPlayer.create(MainActivity.this,uri);
                    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    mMediaPlayer.setDisplay(mSurfaceHolder);
                    try{
                        mMediaPlayer.prepare();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    mMediaPlayer.start();
                }
            }
        });

(7)针对6.0以上系统进行运行时权限申请

扫描二维码关注公众号,回复: 2876630 查看本文章
    private void requestCameraAndStoragePermission() {
        //检查用户是否授权
        for (int i = 0; i < permissions.length; i++) {
            if (ContextCompat.checkSelfPermission(MainActivity.this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                //没有授权则请求相应权限
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{permissions[i]}, 1);
            }
        }
        //利用权限申请工具类来实现
        mPermissionsUtils = PermissionsUtils.getInstance();
        mPermissionsUtils.chekPermissions(MainActivity.this,permissions, permissionsResult);
    }

    //创建监听权限的接口对象
    PermissionsUtils.IPermissionsResult permissionsResult = new PermissionsUtils.IPermissionsResult() {
        @Override
        public void passPermissons() {
        //StartRecording(); 注意这里的逻辑 并不是权限通过了就立即开始录像了 而是权限通过了 就可以打开Camera进行预览
            mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
        }

        @Override
        public void forbitPermissons() {
            Toast.makeText(MainActivity.this, "You denyied the permission", Toast.LENGTH_SHORT).show();
        }
    };
录制视频及播放录制视频完整代码如下
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{
    private static final String TAG = "MainActivity";

    private SurfaceView mSurfaceView;
    private Button mRecordBtn, mPlayBtn;
    private boolean mIsRecord = false; //是否正在录像
    private Camera mCamera;
    private MediaRecorder mMediaRecorder;
    private String mMediaPath;
    private MediaPlayer mMediaPlayer;
    private SurfaceHolder mSurfaceHolder;
    private PermissionsUtils mPermissionsUtils;
    private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                    Manifest.permission.CAMERA,
                                    Manifest.permission.RECORD_AUDIO};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //6.0及以上系统请求运行时权限 利用权限申请工具类(见下文)
        requestCameraAndStoragePermission();

        mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
        mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // 必须-设置Surface不需要维护自己的缓冲区
        mRecordBtn = (Button) findViewById(R.id.record_btn);
        mPlayBtn = (Button) findViewById(R.id.play_btn);
        initBtnClick();

        SurfaceHolder holder =  mSurfaceView.getHolder();
        holder.addCallback(this);
    }

    private void requestCameraAndStoragePermission() {
        //检查用户是否授权
        for (int i = 0; i < permissions.length; i++) {
            if (ContextCompat.checkSelfPermission(MainActivity.this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                //没有授权则请求相应权限
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{permissions[i]}, 1);
            }
        }
        //利用权限申请工具类来实现
        mPermissionsUtils = PermissionsUtils.getInstance();
        mPermissionsUtils.chekPermissions(MainActivity.this,permissions, permissionsResult);
    }

    //创建监听权限的接口对象
    PermissionsUtils.IPermissionsResult permissionsResult = new PermissionsUtils.IPermissionsResult() {
        @Override
        public void passPermissons() {
//            StartRecording(); 注意这里的逻辑 并不是权限通过了就立即开始录像了 而是权限通过了 就可以打开Camera进行预览
            mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
        }

        @Override
        public void forbitPermissons() {
            Toast.makeText(MainActivity.this, "You denyied the permission", Toast.LENGTH_SHORT).show();
        }
    };

    private void StartRecording(){
        mRecordBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!mIsRecord) {
                    try {
                        Log.d(TAG, "首次点击开始录像 ");
                        if (prepareVideoRecorder()) {
                            mMediaRecorder.start();
                            mIsRecord = true;
                            mRecordBtn.setText("stop");
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    Log.d(TAG, "再次点击停止录像");
                    mMediaRecorder.stop();
                    releaseMediaRecorder();
                    mCamera.lock();
                    mRecordBtn.setText("record");
                    mIsRecord = false;

                    if (mCamera != null) {
                        mCamera.release();
                        mCamera = null;
                    }
                }
            }
        });
    }

    private void initBtnClick() {
        StartRecording();

        mPlayBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mMediaPlayer == null) {
                    mMediaPlayer = new MediaPlayer();
                    mMediaPlayer.reset();
                    Uri uri = Uri.parse(mMediaPath);
                    mMediaPlayer = MediaPlayer.create(MainActivity.this,uri);
                    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    mMediaPlayer.setDisplay(mSurfaceHolder);
                    try{
                        mMediaPlayer.prepare();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    mMediaPlayer.start();
                }
            }
        });

    }

    /*
    * 相机预览前的准备工作代码 单独抽出来
    * */
    private boolean prepareVideoRecorder() throws IOException {
        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
            mMediaRecorder.reset();
        }
            /*camera相关设置部分*/
        mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
        if (mCamera != null) {
            //设置旋转角度,顺时针方向,因为默认是逆向90度的,这样图像就是正常显示了
            mCamera.setDisplayOrientation(90);
            mCamera.unlock();
            mMediaRecorder.setCamera(mCamera);
        }

            /*recorder设置部分*/
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
        mMediaRecorder.setOutputFile(getOutputMediaFile());
        mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
        mMediaRecorder.prepare();
        return true;
    }

    /*
    * 获取手机外部存储路径
    * */
    private String getOutputFile() {
        File mediaFile = null;
        boolean OutputExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
        if (OutputExist) {
            mediaFile = Environment.getExternalStorageDirectory();
            return mediaFile.toString();
        }
        return null;
    }

    /*
    * 获取录制视频的日期 作为存储文件路径一部分
    * */
    private String getDate() {
        Log.d(TAG, "获取录制视频的日期 ");
        Calendar ca = Calendar.getInstance();
        int year = ca.get(Calendar.YEAR);           // 获取年份
        int month = ca.get(Calendar.MONTH);         // 获取月份
        int day = ca.get(Calendar.DATE);            // 获取日
        String date = "" + year + "_" + (month + 1) + "_" + day;
        return date;
    }

    /*
    *创建视频存储文件夹 录制好的视频存储在手机外部存储中 以录像时间+mp4格式命名
    * */
    private String getOutputMediaFile() {
        Log.d(TAG, "获取视频存储的位置 ");
        String mediaPath = getOutputFile();
        if (mediaPath != null) {
            File mediaFile = new File(mediaPath + "/recordVideo");
            if (!mediaFile.exists()) {
                mediaFile.mkdir();
            }
            return mMediaPath = mediaFile.getAbsolutePath() + File.separator + getDate() + ".mp4";
        }
        return null;
    }

    /*
    * 录制视频结束时释放相机资源
    * */
    private void releaseMediaRecorder() {
        Log.d(TAG, "录制结束后释放资源 ");
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();   // clear recorder configuration
            mMediaRecorder.release(); // release the recorder object
            mMediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        mSurfaceHolder = surfaceHolder;
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        mSurfaceHolder = surfaceHolder;
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mSurfaceView = null;
        mSurfaceHolder = null;
        releaseMediaRecorder();

        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
        if (mMediaPlayer != null){
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }
}

三、延伸知识,运行时权限申请工具类

调用手机系统内置的摄像头进行视频录制时及录制视频后将视频保存在本地都需要申请系统权限,而且申请的权限(调用摄像头权限、存储权限)都属于26个危险权限,针对6.0以上的手机,需要进行运行时权限的申请,由于申请的权限过多,而且申请的时间不一致,所以这里提供一个权限申请工具类协助实现权限申请。(来自脚本之家文章:Android动态请求权限的工具类(可请求多个,并且功能完善)

完整代码如下
/**
 * 运行时权限申请工具类:
 * 检查用户是否授权——ContextCompat.checkSelfPermission
 * 如果没有授权,那么申请授权——ActivityCompat.requestPermissions
 * 申请授权之后的回调——onRequestPermissionsResult
 * 精髓:检查权限 申请权限的代码写在工具类内 同时写入一个接口 两个抽象方法-获取权限成功 + 获取权限失败 然后在外部使用权限工具类时实现这两个抽象方法
 * Created by Administrator on 2018/7/3.
 */

public class PermissionsUtils {
    private final int mRequestCode = 100;//权限请求码
    public static boolean showSystemSetting = true;

    private PermissionsUtils() {
    }

    private static PermissionsUtils permissionsUtils;
    private IPermissionsResult mPermissionsResult;

    /*
    * 单例模式创建PermissionUtils实例 工具类中的静态方法可以直接使用类名+方法名调用 非静态方法还是需要获取到工具类的实例 实例对方法进行调用
    * */
    public static PermissionsUtils getInstance() {
        if (permissionsUtils == null) {
            synchronized (PermissionsUtils.class) {
                if (permissionsUtils == null)
                permissionsUtils = new PermissionsUtils();
            }
        }
        return permissionsUtils;
    }

    /*
    * 检查用户是否授权 + 如果没有授权 则申请授权 - 系统标准方法
    * */
    public void chekPermissions(Activity context, String[] permissions, @NonNull IPermissionsResult permissionsResult) {
        mPermissionsResult = permissionsResult;

        if (Build.VERSION.SDK_INT < 23) {//6.0系统及以上才会动态申请权限 以下不用 所以直接return出去
            permissionsResult.passPermissons();
            return;
        }

        //创建一个mPermissionList,逐个判断哪些权限未授予,未授予的权限存储到mPerrrmissionList中
        List<String> mPermissionList = new ArrayList<>();
        //逐个判断你要的权限是否已经通过
        for (int i = 0; i < permissions.length; i++) {
            if (ContextCompat.checkSelfPermission(context, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                mPermissionList.add(permissions[i]);//添加还未授予的权限
            }
        }

        //申请权限
        if (mPermissionList.size() > 0) {//有权限没有通过,需要申请
            ActivityCompat.requestPermissions(context, permissions, mRequestCode);
        } else {
            //说明权限都已经通过,利用接口变量调用实现的接口方法 即有权限之后需要调用的方法
            permissionsResult.passPermissons();
            return;
        }
    }

    //请求权限后回调的方法
    //参数: requestCode  是我们自己定义的权限请求码
    //参数: permissions  是我们请求的权限名称数组
    //参数: grantResults 是我们在弹出页面后是否允许权限的标识数组,数组的长度对应的是权限名称数组的长度,数组的数据0表示允许权限,-1表示我们点击了禁止权限

    public void onRequestPermissionsResult(Activity context, int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        boolean hasPermissionDismiss = false;//有权限没有通过
        if (mRequestCode == requestCode) {
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == -1) {
                    hasPermissionDismiss = true;
                }
            }
            //如果有权限没有被允许
            if (hasPermissionDismiss) {
                if (showSystemSetting) {
                    showSystemPermissionsSettingDialog(context);//跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问
                } else {
                    mPermissionsResult.forbitPermissons();
                }
            } else {
                //全部权限通过,可以进行下一步操作。。。
                mPermissionsResult.passPermissons();
            }
        }
    }

    /**
     * 不再提示权限时的展示对话框
     */
    AlertDialog mPermissionDialog;

    private void showSystemPermissionsSettingDialog(final Activity context) {
        final String mPackName = context.getPackageName();
        if (mPermissionDialog == null) {
            mPermissionDialog = new AlertDialog.Builder(context)
                    .setMessage("已禁用权限,请手动授予")
                    .setPositiveButton("设置", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            cancelPermissionDialog();

                            Uri packageURI = Uri.parse("package:" + mPackName);
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                            context.startActivity(intent);
                            context.finish();
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //关闭页面或者做其他操作
                            cancelPermissionDialog();
                            //mContext.finish();
                            mPermissionsResult.forbitPermissons();
                        }
                    })
                    .create();
        }
        mPermissionDialog.show();
    }

    //关闭对话框
    private void cancelPermissionDialog() {
        if (mPermissionDialog != null) {
            mPermissionDialog.cancel();
            mPermissionDialog = null;
        }
    }

    public interface IPermissionsResult {
        void passPermissons();

        void forbitPermissons();
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_41101173/article/details/80907365