Android 使用MediaRecord录像的一些问题总结
最近一段时间都在做一些录像相关的事情,通过直接找到一些大神的博客,学习怎么使用MediaRecord,总算完成,如果只是这样就不写这篇文章了,关键在我们的技术支持是一个追求完美的人,他提出了三个问题:
1、录像出来的文件清晰度不够;
2、录像文件和系统对比,亮度不够;
3、帧数达不到要求;
总之就是和系统录像有差距,录制的视频文件要和系统录制出来的一样,头大,针对个问题,又是一番百度:
1、针对清晰度问题,直接采用系统配置的参数
private @Nullable CamcorderProfile getBestCamcorderProfile() { int quality = CamcorderProfile.QUALITY_HIGH; if (videoType == 1 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { quality = CamcorderProfile.QUALITY_1080P; } else if (videoType == 2 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { quality = CamcorderProfile.QUALITY_720P; } else if (videoType == 3 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { quality = CamcorderProfile.QUALITY_480P; } else { quality = CamcorderProfile.QUALITY_CIF; } return CamcorderProfile.get(quality); }
这样录制的视频清晰度绝对是系统级别的,如果不需要,可以自由调整比特率和分辨率来满足需求;
2、针对亮度不够的问题:
这里走了不少弯路,首先想到的是黑白平衡、亮度、饱和度、曝光级别,设置了都没用,至少在我测试的定制机上是没有效果的;后面又想到了自动聚焦和区域聚焦,结果把自己调到一个更大的坑,一把辛酸泪不多说;最后实在没办法,翻阅系统MediaRecord相关demo,想从这个里面找到原因,看了两天,txt上看真的头疼,好不容易看完,修改录像代码,测试发现没效果;最后只能去阅读camera 源码,在源码里面总算找到一个方法:
没错 就是这个方法,设置之后效果和系统录像一致,达到了要求;
3、第三个问题相比起来简直不是问题,camera直接提供了相应的方法:
查阅了不少大神博客,发现很少有用这个方法的,为了达到要求,设置了这个方法,和系统录像达到一样的帧率,不过要注意的是,最大最小帧率都是30帧,录像文件只有29.xx帧,会有一点损失。
三个问题总算解决,成功保住了饭碗,下面贴出代码:
public class MediaRecorderActivity extends BaseActivity implements SurfaceHolder.Callback, MediaRecorder.OnErrorListener , MediaRecorder.OnInfoListener { // ---------------------------- 常量------------------------------- // -------------------------- 全局变量------------------------------ private MediaRecorder mediarecorder;// 录制视频的类 private SizeSurfaceView surfaceview;// 显示视频的控件 // 用来显示视频的一个接口,我靠不用还不行,也就是说用mediarecorder录制视频还得给个界面看 private SurfaceHolder surfaceHolder; private int WIDTH_DEF = 1920; private int HEIGHT_DEF = 1080; private static final int ONE_MINUTE_TIME = 60 * 1000; private static final int ONE_HOUR_TIME = 60 * ONE_MINUTE_TIME; private String outString; private String parentString; private static Timer timer; private Camera mCamera; private int videoType = 3; //1 : 1080P 2 : 720 P 3 : 480 4: 流畅 //控制闪光灯 手电筒的 public static final int FLASH_MODE_OFF = 0; public static final int FLASH_MODE_ON = 1; public static int flashType = FLASH_MODE_OFF; private boolean isNotify = false; private long mMediaRecorderStartTImes = 0; //记录本地视频信息 private VideoData videoData = new VideoData(); private long fragmentVideoDataStartTimes; private ArrayList<FragmentVideoData> fragmentVideoDatas = new ArrayList<>(); public static void launch(Context context) { Intent i = new Intent(context, MediaRecorderActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); /** * 如果录像按钮不处于按下状态,结束录制本地视频 * 防止出现本地视频自己会开启的情况 */ if (!HandlerManager.isPress) { finish(); return; } setContentView(R.layout.activity_mediarecord); initView(); initData(); } @Override public void surfaceCreated(SurfaceHolder holder) { // 将holder,这个holder为开始在oncreat里面取得的holder,将它赋给surfaceHolder surfaceHolder = holder; initCamera(); start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // 将holder,这个holder为开始在oncreat里面取得的holder,将它赋给surfaceHolder surfaceHolder = holder; } @Override public void surfaceDestroyed(SurfaceHolder holder) { releaseCamera(); surfaceview = null; surfaceHolder = null; } @Override public void onDestroy() { super.onDestroy(); stopMediarecorder(); releaseCamera(); stopTimer(); LightPoingManager.getInstance().cancel(); getDuration(); VideoManager.getInstance().setIsPress(true); } @Override public void onError(MediaRecorder mr, int what, int extra) { TtsManager.getInstance().say("录制出错,已经结束本次录制"); releaseCamera(); finish(); } @Override public void onInfo(MediaRecorder mr, int what, int extra) { if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { releaseCamera(); finish(); } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { if (mCamera != null) { Camera.Parameters p = mCamera.getParameters(); String s = p.getFlashMode(); LogUtil.e(Camera.Parameters.FLASH_MODE_ON.equals(s) ? "手电筒打开" : "手电筒关闭"); } else { LogUtil.e("mCamera null"); } stopMediarecorder(); fileScan(outString); LogUtil.e("已达到录制上限,更换存储路径,即将重新开始录制"); start(); } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { releaseCamera(); finish(); } } private void initView() { if (surfaceview == null) { surfaceview = (SizeSurfaceView) findViewById(R.id.recorder_view); surfaceview.getHolder().addCallback(this); surfaceview.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); surfaceview.getHolder().setKeepScreenOn(true); surfaceview.setUserSize(true); } } private void initData() { VideoManager.VIDEO_TYPE = VideoManager.VIDEO_TYPE_RECORD; videoType = SpUtil.getLocalVideoType(); if (videoType == 1) { WIDTH_DEF = 1920; HEIGHT_DEF = 1080; } else if (videoType == 2) { WIDTH_DEF = 1280; HEIGHT_DEF = 720; } else if (videoType == 3) { WIDTH_DEF = 640; HEIGHT_DEF = 480; } else { WIDTH_DEF = 480; HEIGHT_DEF = 320; } getVideoPath(); } /** * A safe way to get an instance of the Camera object. */ public Camera getCameraInstance() { Camera c = null; try { c = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); // attempt to get a Camera instance } catch (Exception e) { // Camera is not available (in use or does not exist) TtsManager.getInstance().say("设备初始化失败"); releaseCamera(); finish(); } return c; // returns null if camera is unavailable } /** * 初始化摄像头 */ public void initCamera() { try { mCamera = getCameraInstance(); } catch (Exception e) { e.printStackTrace(); } if (mCamera == null) { TtsManager.getInstance().say("无设备可用"); releaseCamera(); finish(); return; } try { Camera.Parameters p = mCamera.getParameters(); p.setRecordingHint(true); //主要用于MediaRecord录像 p.setPreviewFpsRange(30000, 30000); //主要用于设置视频的帧数 p.setPreviewSize(WIDTH_DEF, HEIGHT_DEF); List<String> supportedFocus = p.getSupportedFocusModes(); boolean isHave = supportedFocus == null ? false : supportedFocus.indexOf(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) >= 0; if (isHave) { p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); } try { mCamera.setParameters(p); } catch (Exception e) { e.printStackTrace(); TtsManager.getInstance().say("参数错误,已结束本次录像"); releaseCamera(); finish(); } mCamera.setPreviewDisplay(surfaceHolder); mCamera.cancelAutoFocus();//只有加上了这一句,才会自动对焦。 mCamera.startPreview(); } catch (Exception e) { e.printStackTrace(); } } private void stopMediarecorder() { if (mediarecorder != null) { mediarecorder.setOnErrorListener(null); mediarecorder.setOnInfoListener(null); mediarecorder.setPreviewDisplay(null); // 停止录制 try { mediarecorder.stop(); } catch (IllegalStateException e) { // TODO 如果当前java状态和jni里面的状态不一致, e.printStackTrace(); mediarecorder = null; mediarecorder = new MediaRecorder(); } mediarecorder.reset(); // 释放资源 mediarecorder.release(); LogUtil.e("release()", " mediarecorder.release();"); mediarecorder = null; } FragmentVideoData fragmentVideoData = new FragmentVideoData(); fragmentVideoData.setId(System.currentTimeMillis()); fragmentVideoData.fragmentStartTimes = fragmentVideoDataStartTimes; fragmentVideoData.fragmentEndTimes = System.currentTimeMillis(); long duration = fragmentVideoData.fragmentEndTimes - fragmentVideoDataStartTimes; if (videoType == 1) { fragmentVideoData.duration = duration > 5 * ONE_MINUTE_TIME ? 5 * ONE_MINUTE_TIME : duration; } else if (videoType == 2) { fragmentVideoData.duration = duration > 5 * ONE_MINUTE_TIME ? 5 * ONE_MINUTE_TIME : duration; } else { fragmentVideoData.duration = duration > 20 * ONE_MINUTE_TIME ? 20 * ONE_MINUTE_TIME : duration; } try { if (!TextUtils.isEmpty(outString)) { fragmentVideoData.videoName = outString.substring(outString.lastIndexOf(File.separator) + 1, outString.length()); } } catch (Exception e) { e.printStackTrace(); } fragmentVideoData.videoStartTime = mMediaRecorderStartTImes; VideoDataManager.getInstance().saveFragmentVideoDataToDB(fragmentVideoData); fragmentVideoDatas.add(fragmentVideoData); } private void start() { if (mediarecorder == null) { mediarecorder = new MediaRecorder(); // 创建MediaRecorder } try { if (mCamera != null) { mCamera.stopPreview(); mCamera.unlock(); mediarecorder.setCamera(mCamera); } // 设置音频采集方式 mediarecorder.setAudioSource(AudioConfig.AUDIO_SOURCE); mediarecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //setVideoOrientRotation(orientRotation); CamcorderProfile mProfile = customMediaRecorder(); if (mProfile != null) { mediarecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediarecorder.setVideoFrameRate(mProfile.videoFrameRate); mediarecorder.setVideoSize(WIDTH_DEF, HEIGHT_DEF); if (videoType == 1) { mediarecorder.setVideoEncodingBitRate((int) (mProfile.videoBitRate * 1.03)); } else if (videoType == 2) { mediarecorder.setVideoEncodingBitRate((int) (mProfile.videoBitRate * 1.03)); } else if (videoType == 3) { mediarecorder.setVideoEncodingBitRate((int) (mProfile.videoBitRate * 0.7)); } else { mediarecorder.setVideoEncodingBitRate((int) (mProfile.videoBitRate)); } mediarecorder.setVideoEncoder(mProfile.videoCodec); if (mProfile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW && mProfile.quality <= CamcorderProfile.QUALITY_TIME_LAPSE_QVGA) { } else { mediarecorder.setAudioEncodingBitRate(mProfile.audioBitRate); mediarecorder.setAudioChannels(mProfile.audioChannels); mediarecorder.setAudioSamplingRate(mProfile.audioSampleRate); mediarecorder.setAudioEncoder(mProfile.audioCodec); } } else { mediarecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediarecorder.setVideoFrameRate(30); mediarecorder.setVideoSize(WIDTH_DEF, HEIGHT_DEF); if (videoType == 1) { mediarecorder.setVideoEncodingBitRate((int) (17000000 * 1.03)); } else if (videoType == 2) { mediarecorder.setVideoEncodingBitRate((int) (9000000 * 1.03)); } else if (videoType == 3) { mediarecorder.setVideoEncodingBitRate((int) (4500000 * 0.7)); } else { mediarecorder.setVideoEncodingBitRate((int) (1920000 * 0.7)); } mediarecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediarecorder.setAudioEncodingBitRate(128000); mediarecorder.setAudioChannels(2); mediarecorder.setAudioSamplingRate(48000); mediarecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); } mediarecorder.setOnErrorListener(this); mediarecorder.setOnInfoListener(this); int maxDuration = 0; if (videoType == 1) { maxDuration = 5 * ONE_MINUTE_TIME; } else if (videoType == 2) { maxDuration = 5 * ONE_MINUTE_TIME; } else { maxDuration = 20 * ONE_MINUTE_TIME; } mediarecorder.setMaxDuration(maxDuration); mediarecorder.setPreviewDisplay(surfaceHolder.getSurface()); if (mMediaRecorderStartTImes <= 0) { mMediaRecorderStartTImes = System.currentTimeMillis(); } fragmentVideoDataStartTimes = System.currentTimeMillis(); //设置输出文件的路径 outString = PathConstants.getVideoPath(parentString); mediarecorder.setOutputFile(outString); //开始录制前,先检查下空间是否足够,不够的话删除再开始录制 checkSpace(); //准备录制 mediarecorder.prepare(); mediarecorder.start(); LogUtil.e("mediarecorder()", " mediarecorder.start()"); if (timer == null) { timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { if (mediarecorder == null) { if (timer != null) { timer.cancel(); timer = null; } return; } RingtoneUtils.startAlarmWithStreamVolume("media_recorder_tip.mp3", 1); fileScan(outString); checkSpace(); checkBarrery(); } }, 30 * 1000, 30 * 1000); } } catch (Exception e) { e.printStackTrace(); TtsManager.getInstance().say("录制出错,已经结束本次录制"); releaseCamera(); finish(); } } //检测电量 如果不结束,导致录制的视频无法播放 private void checkBarrery() { int barreryLevel = SpUtil.getBattery(); if (barreryLevel <= 2) { TtsManager.getInstance().say("电量不足,即将结束本次录制"); finish(); } } //获取视频存放地址 private void getVideoPath() { List<StorageVolumeUtil.MyStorageVolume> svs = StorageVolumeUtil.getMountedVolumeList(ContextHolder.getContext()); if (svs != null && svs.size() > 0) { boolean flag = false; for (int i = 0, size = svs.size(); i < size; i++) { final StorageVolumeUtil.MyStorageVolume mv = svs.get(size - 1 - i); if (PathConstants.hasEnoughSpaceForWrite(mv.mPath)) { flag = true; parentString = mv.mPath; break; } LogUtil.e("路径:" + mv.mPath + " 大小:" + (mv.getAvailableSize() / PathConstants.M) + "M"); } if (!flag) { if (!TextUtils.isEmpty(parentString)) { FileUtil.deleteEarliestFile(PathConstants.getVideoCachePath(parentString)); } } String text = "现在开始"; if (videoType == 1) { text += "超清摄像"; } else if (videoType == 2) { text += "高清摄像"; } else if (videoType == 3) { text += "标清摄像"; } else { text += "摄像"; } TtsManager.getInstance().say(text); } else { String txt = "存储地址获取失败,已经结束本次录制"; TtsManager.getInstance().say(txt); releaseCamera(); finish(); } } /** * 检查存储空间 */ private void checkSpace() { long residual = PathConstants.getAvailableExternalSize(parentString); LogUtil.e("删除文件前:检测剩余空间" + (residual / PathConstants.M) + "M"); if (residual > 0) { if (residual / PathConstants.M < 700 && !isNotify) { isNotify = true; TtsManager.getInstance().say("剩余空间不足,即将开始覆盖旧数据"); } if (residual / PathConstants.M < 600) { if (!TextUtils.isEmpty(parentString)) { FileUtil.deleteEarliestFile(PathConstants.getVideoCachePath(parentString)); } } if (Configer.TestMode) { long residua2 = PathConstants.getAvailableExternalSize(parentString); LogUtil.e("删除文件后:检测剩余空间" + (residua2 / PathConstants.M) + "M"); } } } /** * 释放相机资源 */ private void releaseCamera() { try { if (mCamera != null) { mCamera.stopPreview(); mCamera.setPreviewCallback(null); mCamera.release(); LogUtil.e("release()", "mCamera.release()"); } } catch (RuntimeException e) { } finally { mCamera = null; } } //获取视频时长 private void getDuration() { long duration = 0; for (FragmentVideoData fragmentVideoData : fragmentVideoDatas) { duration += fragmentVideoData.duration; LogUtil.e("FragmentVideoData", "FragmentVideoData: " + fragmentVideoData.duration); } LogUtil.e("FragmentVideoData", "FragmentVideoDataAll: " + duration); if (duration > 2 && duration < 24 * ONE_HOUR_TIME) { if (VideoManager.getInstance().getIsPress()) { say(duration); } } else { TtsManager.getInstance().say("摄像结束"); } videoData.setId(System.currentTimeMillis()); videoData.setStartAllTimes(mMediaRecorderStartTImes); videoData.fragmentVideoDatas = fragmentVideoDatas; videoData.setDurationAll(duration); videoData.setEndAllTimes(videoData.getStartAllTimes() + duration); //上传视频数据 try { VideoDataManager.getInstance().saveVideoDataToDB(videoData); } catch (Exception e) { e.printStackTrace(); } //发广播扫描,否则生成的文件看不到 fileScan(outString); } private void say(long recordTime) { if (recordTime >= ONE_HOUR_TIME) { int hour = (int) (recordTime / ONE_HOUR_TIME); int minute = (int) ((recordTime - hour * ONE_HOUR_TIME) / ONE_MINUTE_TIME); int millis = (int) ((recordTime - hour * ONE_HOUR_TIME - minute * ONE_MINUTE_TIME) / 1000); TtsManager.getInstance().say("摄像结束,本次摄像时长" + hour + "小时" + minute + "分钟" + (millis > 0 ? (millis + "秒") : "")); } else if (recordTime >= 60 * 1000) { int minute = (int) (recordTime / ONE_MINUTE_TIME); int millis = (int) ((recordTime - minute * ONE_MINUTE_TIME) / 1000); TtsManager.getInstance().say("摄像结束,本次摄像时长" + minute + "分钟" + (millis > 0 ? (millis + "秒") : "")); } else { TtsManager.getInstance().say("摄像结束,本次摄像时长" + recordTime / 1000 + "秒"); } } /** * 出发扫描 mtp下的文件,在保存文件到 sd卡下后,不能显示,故这里触发一下扫描机制,让手机连上电脑后,就可以读出文件了 * * @param fName,文件的完整路径名 */ public void fileScan(String fName) { Uri data = Uri.parse("file:///" + fName); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, data)); LogUtil.e("扫描完成"); } public CamcorderProfile customMediaRecorder() { CamcorderProfile profile = null; if (mediarecorder != null) { //设置分辨率,应设置在格式和编码器设置之后 profile = getBestCamcorderProfile(); } return profile; } private @Nullable CamcorderProfile getBestCamcorderProfile() { int quality = CamcorderProfile.QUALITY_HIGH; if (videoType == 1 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { quality = CamcorderProfile.QUALITY_1080P; } else if (videoType == 2 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { quality = CamcorderProfile.QUALITY_720P; } else if (videoType == 3 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { quality = CamcorderProfile.QUALITY_480P; } else { quality = CamcorderProfile.QUALITY_CIF; } return CamcorderProfile.get(quality); } private void stopTimer() { if (timer != null) { timer.cancel(); timer = null; } } }
布局文件很简单:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.lolaage.policesystem.view.SizeSurfaceView android:id="@+id/recorder_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </FrameLayout>
public class SizeSurfaceView extends SurfaceView { private boolean isUserSize = false; private int mVideoWidth; private int mVideoHeight; private int mMeasuredWidth; private int mMeasuredHeight; public SizeSurfaceView(Context context) { super(context); } public SizeSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); } public SizeSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @SuppressLint("NewApi") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (isUserSize) { doMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mMeasuredWidth, mMeasuredHeight); setCameraDistance(0.5f); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } public boolean isUserSize() { return isUserSize; } public void setUserSize(boolean isUserSize) { this.isUserSize = isUserSize; } /** * 设置视频宽高 * @param width * @param height */ public void setVideoDimension(int width, int height) { mVideoWidth = width; mVideoHeight = height; } /** * 测量 * @param widthMeasureSpec * @param heightMeasureSpec */ private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec); int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec); if (mVideoWidth > 0 && mVideoHeight > 0) { int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize; float displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight; boolean shouldBeWider = displayAspectRatio > specAspectRatio; if (shouldBeWider) { // not high enough, fix height height = heightSpecSize; width = (int) (height * displayAspectRatio); } else { // not wide enough, fix width width = widthSpecSize; height = (int) (width / displayAspectRatio); } } mMeasuredWidth = width; mMeasuredHeight = height; } }