Android使用MediaRecorder类实现视频和音频录制功能
一、前期基础知识储备
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以上系统进行运行时权限申请
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(); } }
总结
以上所述是小编给大家介绍的Android使用MediaRecorder实现录制视频功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
相关文章
Android开发Jetpack组件LiveData使用讲解
LiveData是Jetpack组件的一部分,更多的时候是搭配ViewModel来使用,相对于Observable,LiveData的最大优势是其具有生命感知的,换句话说,LiveData可以保证只有在组件( Activity、Fragment、Service)处于活动生命周期状态的时候才会更新数据2022-08-08android中soap协议使用(ksoap调用webservice)
kSOAP是如何调用ebservice的呢,首先要使用SoapObject,这是一个高度抽象化的类,完成SOAP调用。可以调用它的addProperty方法填写要调用的webservice方法的参数2014-02-02AndroidStudio修改Code Style来格式化自定义标签的xml文件方式
这篇文章主要介绍了AndroidStudio修改Code Style来格式化自定义标签的xml文件方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2020-03-03Android UI设计与开发之ViewPager介绍和简单实现引导界面
这篇文章主要为大家详细介绍了Android UI设计与开发之ViewPager介绍和简单实现引导界面,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2017-08-08
最新评论