前言
MediaRecorder是Android SDK提供用于录制音视频,关于音频的录制在我另一篇博客里已经介绍.传送门: https://www.cnblogs.com/guanxinjing/p/10976026.html ,而这篇博客将介绍MediaRecorder视频录制的入门和一些MediaRecorder视频录制的深坑.为什么只介绍简单的录制视频的入门操作,因为MediaRecorder在实际开发的时候肯定还需要配合Camera来使用.而Camera这个大坑又有Camera1和Camera2,所以我们需要分篇幅来介绍Camera1和Camera2与MediaRecorder的使用方式.
虽然是入门但是还是需要分2个录制操作来说明
- MediaRecorder简单的录制视频(不设置Camera)
- MediaRecorder设置Camera的录制视频(这里指的是Camera1)
虽然这2个在简单录制视频的时候操作没啥区别,但是设置Camera录制有一个坑,秉承着写博客就是为了记录深坑避免下次掉坑的精神,所以我要抓出来单独说明.
MediaRecorder简单的录制视频
实现流程
- 开启硬件加速(因为个人使用TextureView)
- 获取权限
- 初始化MediaRecorder
- 配置MediaRecorder
- 开始录制视频
- 停止录制视频
- 暂停录制与恢复录制
- 销毁释放
开启硬件加速
因为TextureView来预览图像使用效果会比SurfaceView与SurfaceTexture,在预览的时候更不会那么卡顿.但是使用它需要开启硬件加速,但是现在的手机设备上基本上都支持硬件加速,所以主流是使用TextureView.下面就是开启硬件加速的方式,在AndroidManifest.xml里给需要硬件加速的activity添加android:hardwareAccelerated="true" 属性
<activity android:name=".MediaRecorderVideoActivity"
android:hardwareAccelerated="true"></activity>
获取权限
<!--音频录制权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--相机权限-->
<uses-permission android:name="android.permission.CAMERA" />
<!--读取和写入存储权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
如果是Android5.0版本这4个权限是需要动态授权的.
初始化MediaRecorder
private void initMediaRecorder(){
mMediaRecorder = new MediaRecorder();
}
很简单就是new一个MediaRecorder
配置MediaRecorder
private void configMediaRecorder(){
File videoFile = new File(getExternalCacheDir(),"video.mp4");
Log.e(TAG, "文件路径="+videoFile.getAbsolutePath());
if (videoFile.exists()){
videoFile.delete();
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);//设置音频输入源 也可以使用 MediaRecorder.AudioSource.MIC
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//设置视频输入源
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//音频输出格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//设置音频的编码格式
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);//设置图像编码格式
// mMediaRecorder.setVideoFrameRate(30);//要录制的视频帧率 帧率越高视频越流畅 如果设置设备不支持的帧率会报错 按照注释说设备会支持自动帧率所以一般情况下不需要设置
// mMediaRecorder.setVideoSize(1280,1920);//设置录制视频的分辨率 如果设置设备不支持的分辨率会报错
mMediaRecorder.setVideoEncodingBitRate(8*1920*1080); //设置比特率,比特率是每一帧所含的字节流数量,比特率越大每帧字节越大,画面就越清晰,算法一般是 5 * 选择分辨率宽 * 选择分辨率高,一般可以调整5-10,比特率过大也会报错
mMediaRecorder.setOrientationHint(90);//设置视频的摄像头角度 只会改变录制的视频文件的角度(对预览图像角度没有效果)
Surface surface = new Surface(mTextureView.getSurfaceTexture());
mMediaRecorder.setPreviewDisplay(surface);//设置拍摄预览
mMediaRecorder.setOutputFile(videoFile.getAbsolutePath());//MP4文件保存路径
}
配置MediaRecorder是坑比较多的地方,现在我们来一一说下这些坑
- 坑1: 首先MediaRecorder的配置是有顺序要求的,随便配置参数将会报错,一般顺序是 设置音频输入源与视频输入源 > 设置音频编码格式/视频编码格式/音频输出格式 > 设置分辨率/帧率/摄像头角度 > 添加预览 > 添加保存文件路径
- 坑2: 在上面的代码上我注释了setVideoFrameRate(int rate) 与 setVideoSize(int width, int height) 这2个方法,设置录制视频帧率与设置视频分辨率.因为这2个设置的参数都是需要手机设备支持所输入的的值的,随便设置将会抛出 start failed -19 的错误
- 坑3: setOrientationHint()方法设置角度不会改变预览图像的角度
- 坑4: setPreviewDisplay()设置预览,其实只预览录制过程中的图像,停止录制,相机图像预览也停止了.如果想要一直预览就需要操作Camera来实现
- 坑5: 关于各种参数设置的格式(设置视频输入源,音频输入源,音频输出格式,音频编码格式,图像编码),这里如果你只是实现demo推荐全部使用****DEFAULT,上面的demo图像编码和音频编码使用MP4格式只是一个demo告诉你有很多其他格式.很多情况系统虽然提供了很多格式,但是下各个设备支持的格式是不同的.如果你是大量适配机型最好使用默认格式或者AAC音频编码格式与H264视频编码格式(目前这2个格式最通用),如果是只做单一设备开发,可以使用指定格式.
- 坑6 当你选择默认编码格式录制视频后发现在Ios设备上无法自动播放? 其实是因为默认编码格式选择了IOS设备不支持的格式,Ios只支持AAC音频编码格式与H264视频编码格式,在正常开发的时候建议使用这2个编码格式(他们是目前最通用的编码格式)
注意!上面的坑5关于参数配置的问题,当你发现出现MediaRecorder: start failed: -38 或者其他数值的报错,一定会去百度,这时候-38报错 百度可能会告诉是MIC没释放或者Camera没释放(Camera没释放的问题我下面那个demo会说明),这的确有可能,但是还有另外一种可能是设置setAudioEncoder或者setVideoEncoder在或者setAudioSource 上出现了问题,比如一些设备是支持setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);但是换调另外一个设备上就不支持了,所以这个时候请切换成setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);默认模式,使用还是重新提醒你如果有需要适配大量机型的APP那么尽量选择默认格式.如果你只是为单一Android设备开发你可以指定格式.
小提醒~如果你想一进入Activity就配置MediaRecorder好,那么你就需要设置TextureView的监听setSurfaceTextureListener(SurfaceTextureListener listener),必需它先启动准备好之后在配置MediaRecorder,否则会报错的因为TextureView在启动activity后需要一段时间初始化启动
如果你需要切换摄像头,那么你还需要注意重新配置MediaRecorder时还需要切换角度:
if (mCurrentCameraFacing){ //相机前后面向
mMediaRecorder.setOrientationHint(270);
}else {
mMediaRecorder.setOrientationHint(90);
}
开始录制视频
private void startRecorder(){
configMediaRecorder();//配置MediaRecorder 因为每一次停止录制后调用重置方法后都会取消配置,所以每一次开始录制都需要重新配置一次
try {
mMediaRecorder.prepare();//准备
mMediaRecorder.start();//开启
} catch (IOException e) {
e.printStackTrace();
}
}
注意!每一次停止录制后调用重置方法后都会取消配置,所以每一次开始录制都需要重新配置一次
停止录制视频
private void stopRecorder(){
mMediaRecorder.stop();//暂停
mMediaRecorder.reset();//重置 重置后将进入空闲状态,再次启动录制需要重新配置MediaRecorder
}
暂停录制与恢复录制
暂停录制,注意这里是pause()方法,不是stop()
private void pauseRecorder(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mMediaRecorder.pause();//暂停
}
}
恢复录制
private void resumeRecorder(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mMediaRecorder.resume();//恢复
}
}
销毁释放
private void destroy(){
if (mMediaRecorder != null){
mMediaRecorder.stop();
mMediaRecorder.release();//释放 释放之前需要先调用stop()
mMediaRecorder = null;
}
}
MediaRecorder设置Camera的录制视频
添加Camera来录制视频本来是另外一个篇幅应该说的事情,但是有个坑不得不在这里先说. 其他初始化和开启/停止/都与上面的介绍一致,下面我们来说说万恶的MediaRecorder配置:
private void configMediaRecorder(){
File videoFile = new File(getExternalCacheDir(),"video.mp4");
Log.e(TAG, "文件路径="+videoFile.getAbsolutePath());
if (videoFile.exists()){
videoFile.delete();
}
Camera camera = Camera.open();
camera.unlock();
mMediaRecorder.setCamera(camera);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频输入源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//设置视频输入源
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//音频输出格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//设置音频的编码格式
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);//设置图像编码格式
mMediaRecorder.setOrientationHint(90);//设置视频的摄像头角度 只会改变录制的视频角度
Surface surface = new Surface(mTextureView.getSurfaceTexture());
mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setOutputFile(videoFile.getAbsolutePath());
}
也是一个简单的配置,也就是多了有摄像头的代码,如果你需要选择摄像头可以自己获取摄像头信息来判断前后在获取对应id,打开对应摄像头,作为简单的demo就暂时不演示选择摄像头的代码.
然后重点来了这里有一个关键,你不会想到Camera.open();执行后摄像头居然是锁定状态的,就算是明白打开后就是锁定,但是你也不会发现在设置mMediaRecorder.setCamera(camera);之前是需要将摄像头解除锁定 camera.unlock();! 是的,就是这行代码困扰我了一下午!!!!!! 所以重点!重点!重点! 就是camera.unlock(); 最蛋疼的是如果你不做解除锁定的操作报错的提示是 start failed -19, 这个报错提示成功的让我反反复复研究是不是分辨率尺寸设置有问题........
而这个Camera.unlock()在官方注释里也有说明在启动在设置之前先需要解锁摄像头,如下:
* <p>Use this function to switch quickly between preview and capture mode without a teardown of
* the camera object. {@link android.hardware.Camera#unlock()} should be called before
* this. Must call before {@link #prepare}.</p>
setProfile
这个功能其实是相机预设的一些录制质量,格式和分辨率,官方注释说:
使用CamcorderProfile对象中的设置进行录制。这种方法应该在设置视频和音频源之后和setOutputfile()之前调用。如果使用延时摄像机配置文件,音频相关源或录制参数被忽略。
我们看到关键CamcorderProfile对象,那么看看什么CamcorderProfile的解释:
摄像机应用程序的预定义摄像机配置文件设置,这些设置是只读的。其实就是预设的一些摄像头配置设置
预设的属性有这些:
- QUALITY_LOW
- QUALITY_HIGH
- QUALITY_QCIF
- QUALITY_CIF
- QUALITY_480P
- QUALITY_720P
- QUALITY_1080P
- QUALITY_2160P
- QUALITY_TIME_LAPSE_LOW
- QUALITY_TIME_LAPSE_HIGH
- QUALITY_TIME_LAPSE_QCIF
- QUALITY_TIME_LAPSE_CIF
- QUALITY_TIME_LAPSE_480P
- QUALITY_TIME_LAPSE_720P
- QUALITY_TIME_LAPSE_1080P
- QUALITY_TIME_LAPSE_2160P
- QUALITY_HIGH_SPEED_LOW
- QUALITY_HIGH_SPEED_HIGH
- QUALITY_HIGH_SPEED_480P
- QUALITY_HIGH_SPEED_720P
- QUALITY_HIGH_SPEED_1080P
- QUALITY_HIGH_SPEED_2160P
使用代码:
if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) {
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
profile.videoBitRate = mPreviewSize.getWidth() * mPreviewSize.getHeight();
mMediaRecorder.setProfile(profile);
mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
} else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
profile.videoBitRate = mPreviewSize.getWidth() * mPreviewSize.getHeight();
mMediaRecorder.setProfile(profile);
mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
} else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) {
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA));
mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
} else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_CIF)) {
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_CIF));
mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
} else {
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
}
end