Android MediaRecorder 后台视频录制

题记:记录下这几天一直在苦恼的一件事,心情各种起伏。先是有个变态的客户(嗯,真心觉得)提出原有的录音不够,还要有录像,不过是悄悄的,后台录像,懂的,不能在软件界面上面展示出来;这不是不道德的吗?可是木办法,咱只是做事的,我只能说从技术上肯定是可行,但是道德上,感觉挺混蛋的。于是开始学习看如何做。由于使用API提供的自定义视频,必须要按照那一套写法,感觉中间最关键还是是必须要有surfaceview这个界面预览,说白了,是必须在界面上面展现的。然后各种尝试,闲话不说啦,下面记录下尝试的过程。

一、制作demo。
demo主要实现的功能是activity一启动的时候就自动的启动一后台线程进行录像。当然,录像有时间限制,到时间后自动关闭。同时不显示预览效果。
主要用到了三个文件:RecordDemoActivity 主界面,RecordThread录像线程以及主界面的样式文件main.xml。

1. RecordDemoActivity  

package com.demo; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.widget.LinearLayout; /** * class name:RecordDemoActivity
* class description:demo
*
* * @version 1.0 2012/08/02 * @author bcaiw */ public class RecordDemoActivity extends Activity implements SurfaceHolder.Callback { private SurfaceView surfaceview;// 视频预览控件 private LinearLayout lay; //愿揽控件的 private SurfaceHolder surfaceHolder; //和surfaceView相关的 /** * onCreate方法 */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //初始化控件 init(); } /** * 初始化控件以及回调 */ private void init() { surfaceview = (SurfaceView) this.findViewById(R.id.surfaceview); lay=(LinearLayout)this.findViewById(R.id.lay); lay.setVisibility(LinearLayout.INVISIBLE); SurfaceHolder holder = this.surfaceview.getHolder();// 取得holder holder.addCallback(this); // holder加入回调接口 // 设置setType holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // 将holder,这个holder为开始在oncreat里面取得的holder,将它赋给surfaceHolder surfaceHolder = holder; } @Override public void surfaceCreated(SurfaceHolder holder) { // 将holder,这个holder为开始在oncreat里面取得的holder,将它赋给surfaceHolder surfaceHolder = holder; Log.i("process",Thread.currentThread().getName()); //录像线程,当然也可以在别的地方启动,但是一定要在onCreate方法执行完成以及surfaceHolder被赋值以后启动 RecordThread thread=new RecordThread(1000,surfaceview,surfaceHolder); thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // surfaceDestroyed的时候同时对象设置为null surfaceview = null; surfaceHolder = null; } }


2. RecordThread

package com.demo; import java.io.IOException; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import android.hardware.Camera; import android.media.CamcorderProfile; import android.media.MediaRecorder; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; /** * 录像线程 * * @author bcaiw * */ public class RecordThread extends Thread { private MediaRecorder mediarecorder;// 录制视频的类 private SurfaceHolder surfaceHolder; private long recordTime; private SurfaceView surfaceview;// 显示视频的控件 private Camera mCamera; public RecordThread(long recordTime, SurfaceView surfaceview, SurfaceHolder surfaceHolder) { this.recordTime = recordTime; this.surfaceview = surfaceview; this.surfaceHolder = surfaceHolder; } @Override public void run() { /** * 开始录像 */ startRecord(); /** * 启动定时器,到规定时间recordTime后执行停止录像任务 */ Timer timer = new Timer(); timer.schedule(new TimerThread(), recordTime); } /** * 获取摄像头实例对象 * * @return */ public Camera getCameraInstance() { Camera c = null; try { c = Camera.open(); } catch (Exception e) { // 打开摄像头错误 Log.i("info", "打开摄像头错误"); } return c; } /** * 开始录像 */ public void startRecord() { mediarecorder = new MediaRecorder();// 创建mediarecorder对象 mCamera = getCameraInstance(); // 解锁camera mCamera.unlock(); mediarecorder.setCamera(mCamera); // 设置录制视频源为Camera(相机) mediarecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mediarecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // 设置录制文件质量,格式,分辨率之类,这个全部包括了 mediarecorder.setProfile(CamcorderProfile .get(CamcorderProfile.QUALITY_LOW)); mediarecorder.setPreviewDisplay(surfaceHolder.getSurface()); // 设置视频文件输出的路径 mediarecorder.setOutputFile("/sdcard/sForm.3gp"); try { // 准备录制 mediarecorder.prepare(); // 开始录制 mediarecorder.start(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 停止录制 */ public void stopRecord() { if (mediarecorder != null) { // 停止录制 mediarecorder.stop(); // 释放资源 mediarecorder.release(); mediarecorder = null; if(mCamera!=null){ mCamera.release(); mCamera = null; } } } /** * 定时器 * @author bcaiw * */ class TimerThread extends TimerTask { /** * 停止录像 */ @Override public void run() { stopRecord(); this.cancel(); } } }


3.main.xml。

xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:id="@+id/lay"> android:id="@+id/surfaceview" android:layout_width="fill_parent" android:layout_height="fill_parent" />


demo简单的实现了一个大体过程。分析下:

a.activity里面提供surfaceview,但是其本身不允许设置隐藏的,可能api他本身有针对这个控件的检查机制,那么试想把他的父控件设置成invisable的,他不会连父控件也检查吧。将ld为lay的linearlayout控件设置成INVISABLE,事实证明录像的时候,预览不可见了,可行。

b.录像时必须要surfaceview对象,而线程是没有界面元素的,因此,实例化线程对象的时候,将surfaceview以及holder对象传递进来。

c.由于是后台的,因此需要有时间限制,时间一到,自动停止。定义一个定时器,Timer子类,重写run方法就可以了。

d.mediarecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW));这一句顶n句,包括了设置视频文件的格式,分辨率,码率等等。当然,如果想要自由一点,还是一个一个设。


这样录制隐藏视频的两大条件:【不显示界面】、【到时间自动停止】都已具备。少年们,可以搞起。


但是这还仅仅是个demo,一个demo而已。并不能马上结合到现有的项目中来,结合之前,且来分析分析它所缺的:

a.缺少交互。虽然是个线程单独录制,但是万一录制的过程中需要停下来怎么办?

b.启动和关闭的声音没解决。这个真心不知道怎么弄。

c.没有控制,主线程不知道你有没有在规定的时间内录制完,就是不知道你这个线程的状态,进而就不知道录像设备的到底是不是空着的,如果不是空着的,又是被谁用的。

d.异常处理。各种异常了,怎么办。

e.和其他如录音、拍照冲突了如何解决?经过实验,三者是不同共存的,就是说同时有录音和录像的时候,录像文件是损坏的。同时有录像和拍照的时候,拍照程序一启动,就说设备故障。这中间该如何协调处理?

目前能想到所可能面临的就这些,那么下篇分析引进多线程机制,主从线程直接相互通信来传递消息,就是我了解你在想什么,你也了解我想干什么。还有就是如何解决照相和视频冲突的问题。


后记:

技术这个,是越往下做,越觉得自己不懂的是真多。哈哈哈,今天看见一句话,傻逼的坚持也有牛逼的一天。哈哈哈,唯有不断前进。

你可能感兴趣的:(Android)