Android开发之打开闪光灯录制视频
Android的SDK在线API上对录制视频的方法、步骤都写得非常清楚,但是如果没有一点思路,写起来也比较式费事。录制视频的全过程要打开闪光灯(可能是因为项目需要,或者特殊原因),则必须按照一定的顺序进行开关,毕竟容易出错。要实现录制的同时开启闪光灯也不难,官方API给出了一个大体的步骤.因为要采集点视频数据,临时写了个简单的Demo学习下,必要时再深度开发。
首先在工程中的AndroidManifest.xml中添加权限声明,因为要使用到摄像头,故需要添加Camera的相关权限,另外还需要写SD卡的权限,如果同时需要录制音频,则还需要添加RECORD_AUDIO权限。
1 <uses-permission android:name="android.permission.CAMERA" /> 2 <uses-feature android:name="android.hardware.camera" /> 3 <uses-feature android:name="android.hardware.camera.autofocus" /> 4 <uses-permission android:name="android.permission.RECORD_AUDIO"/> 5 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
再来分析下要使用到的类,录制视频使用的MediaRecorder类,官方给出了调用MediaRecorder录制视频的一个简单状态机,展示了各个状态之间的转化。然后也给出了一个简单的调用方法,代码如下:
1 MediaRecorder recorder = new MediaRecorder(); 2 recorder.setAudioSource(MediaRecorder.AudioSource.MIC); 3 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 4 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 5 recorder.setOutputFile(PATH_NAME); 6 recorder.prepare(); 7 recorder.start(); // Recording is now started 8 ... 9 recorder.stop(); 10 recorder.reset(); // You can reuse the object by going back to setAudioSource() step 11 recorder.release(); // Now the object cannot be reused
录制视频是调用MediaRecorder类,但API中真正介绍如何录制视频的一般步骤却被放在了Camera类中,在线API上有句话提示“For more information about how to use MediaRecorder for recording video, read the Camera developer guide.”。转到Camera类去看看。
Camera类是用来控制照相机的,没错,就是这个类。照相机可以用来拍照,也可以用来录制视频(也叫捕捉视频),但是录制视频需要按照一定的步骤来编写程序,不然发生运行时错误是非常正常的。录制视频需要调用Camera和MediaRecorder类,下面说说一般步骤。
1) 打开照相机。直接调用Camera.open()来获取一个Camera的实例。
2) 设置预览控件。一般是设置在SurfaceView上面,通过调用Camera.setPreviewDisplay()来完成,但是这一步也可以放到MediaRecorder类DataSourceConfigured步骤中完成。
3) 开启预览。调用Camera.startPreview()。
4) 开始录制视频。为了确保你录制成功,请务必按要求完成下面的步骤。
A. 解锁照相机。通过调用Camera.unlock()解锁照相机,以便照相机被MediaRecorder使用。
B. 设置MediaRecorder。
这里有一系列的设置,根据需要设置吧。比如说,你只需要录制视频,就不必设置音频的输入源,也就不用设置音频的编码方式。对应于MediaRecorder state diagram中的Initialized和DataSourceConfigured。具体方法调用可以查看Android在线API的MediaRecorder类,上文已经将主要的代码贴出,下文还会贴出实例代码,这里就不详细介绍了。
C. 准备MediaRecorder。在调用MediaRecorder.prepare()之前一定要先设置好MediaRecorder对象的各项属性,后面设置会引发运行时错误。
D. 开始MediaRecorder。调用MediaRecorder.start()之后,就开始录制视频了。
5) 停止录制。
A. 停止MediaRecorder。调用MediaRecorder.stop()停止录制。
B. 恢复MediaRecorder的默认设置。调用MediaRecorder.reset()来取消你对MediaRecorder所做的设置,但调用玩之后,MediaRecorder对象还是可以再次使用的。
C. 释放MediaRecorder对象。调用MediaRecorder.release()释放资源,之后该MediaRecorder对象销毁了,再调用会出错。
D. 给Camera上锁。为了后面的MediaRecorder对象可以再次使用,需要调用Camera.lock(),Android 4.0以后,这个操作并不是必须的,除非MediaRecorder.prepare()调用失败。
6) 停止预览,调用Camera.stopPreview()。
7) 释放照相机资源,调用Camera.release()。
以上就是打开照相机录制视频的一般步骤,当然你可以可以在录制之前实现预览,决定什么时间开始录制,这个其实可以先开启照相机进行预览即可然后,需要录制时调用Camera.unlock(),然后按流程接入MediaRecorder进行录制。现在考虑第一种情况,直接开始录制。
权限要求已经贴出来了,再贴个布局文件,recordvideo.xml。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:background="#ffffff" 6 android:orientation="vertical" > 7 8 <SurfaceView 9 android:id="@+id/surfaceView" 10 android:layout_width="fill_parent" 11 android:layout_height="220dip" /> 12 13 <LinearLayout 14 android:layout_width="fill_parent" 15 android:layout_height="wrap_content" 16 android:layout_marginLeft="5dp" 17 android:layout_marginRight="5dp" 18 android:layout_marginTop="20dp" 19 android:gravity="right" 20 android:orientation="horizontal" > 21 22 <EditText 23 android:id="@+id/rv_testusername" 24 android:layout_width="156dp" 25 android:layout_height="wrap_content" 26 android:layout_weight="0.27" 27 android:ems="10" 28 android:hint="输入姓名或标识" /> 29 30 <Button 31 android:id="@+id/rv_record" 32 style="@style/mainactivitybtnstyle" 33 android:layout_width="wrap_content" 34 android:layout_height="wrap_content" 35 android:minHeight="40dp" 36 android:minWidth="70dp" 37 android:text="录制" /> 38 39 <Button 40 android:id="@+id/rv_stop" 41 style="@style/mainactivitybtnstyle" 42 android:layout_width="wrap_content" 43 android:layout_height="wrap_content" 44 android:layout_marginLeft="10dip" 45 android:minHeight="40dp" 46 android:minWidth="70dp" 47 android:text="停止" /> 48 </LinearLayout> 49 50 <LinearLayout 51 android:layout_width="fill_parent" 52 android:layout_height="fill_parent" 53 android:gravity="center_horizontal" 54 android:orientation="vertical" > 55 56 <ProgressBar 57 android:id="@+id/rv_schedule" 58 style="?android:attr/progressBarStyleHorizontal" 59 android:layout_width="fill_parent" 60 android:layout_height="wrap_content" /> 61 62 <TextView 63 android:id="@+id/rv_record_time" 64 android:layout_width="fill_parent" 65 android:layout_height="wrap_content" 66 android:gravity="center" 67 android:text="00:00:000" 68 android:textColor="#FF750000" 69 android:textSize="24sp" 70 android:textStyle="bold" /> 71 </LinearLayout> 72 73 </LinearLayout>
Activity代码,因为非常简单,就没有封装多线程什么的。
1 import java.io.File; 2 import java.text.SimpleDateFormat; 3 4 import android.content.Context; 5 import android.content.pm.FeatureInfo; 6 import android.content.pm.PackageManager; 7 import android.hardware.Camera; 8 import android.media.MediaRecorder; 9 import android.os.Bundle; 10 import android.os.Environment; 11 import android.os.Handler; 12 import android.os.Message; 13 import android.support.v7.app.ActionBarActivity; 14 import android.util.Log; 15 import android.view.SurfaceHolder; 16 import android.view.SurfaceView; 17 import android.view.View; 18 import android.widget.Button; 19 import android.widget.EditText; 20 import android.widget.ProgressBar; 21 import android.widget.TextView; 22 import android.widget.Toast; 23 24 import com.ict.util.IOUtil; 25 26 public class RecordVideoActivity extends ActionBarActivity { 27 private static final String TAG = "RecordVideo"; 28 private SurfaceView surfaceView; 29 private MediaRecorder mediaRecorder; 30 private boolean record; 31 private TextView testusername; 32 private Camera camera; 33 34 // 计时器相关 35 private MyChronograph myChronograph; 36 private TextView chronograph = null; 37 38 private ProgressBar schedule; 39 private boolean recordOver = false; 40 41 @Override 42 protected void onCreate(Bundle savedInstanceState) { 43 // TODO Auto-generated method stub 44 super.onCreate(savedInstanceState); 45 setContentView(R.layout.recordvideo); 46 setTitle("录制视频"); 47 mediaRecorder = new MediaRecorder(); 48 surfaceView = (SurfaceView) this.findViewById(R.id.surfaceView); 49 this.surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 50 this.surfaceView.getHolder().setFixedSize(320, 240);//设置分辨率 51 52 testusername = (EditText)findViewById(R.id.rv_testusername); 53 chronograph = (TextView)findViewById(R.id.rv_record_time); 54 schedule = (ProgressBar)findViewById(R.id.rv_schedule); 55 schedule.setMax(60); 56 ButtonClickListener listener = new ButtonClickListener(); 57 Button stopButton = (Button) this.findViewById(R.id.rv_stop); 58 Button recordButton = (Button) this.findViewById(R.id.rv_record); 59 stopButton.setOnClickListener(listener); 60 recordButton.setOnClickListener(listener); 61 } 62 63 @Override 64 protected void onDestroy() { 65 // TODO Auto-generated method stub 66 if(mediaRecorder!=null) 67 mediaRecorder.release(); 68 super.onDestroy(); 69 } 70 71 @Override 72 protected void onPause() { 73 // TODO Auto-generated method stub 74 super.onPause(); 75 } 76 77 @Override 78 protected void onResume() { 79 // TODO Auto-generated method stub 80 super.onResume(); 81 } 82 private final class ButtonClickListener implements View.OnClickListener{ 83 @Override 84 public void onClick(View v) { 85 if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ 86 Toast.makeText(RecordVideoActivity.this, "木有检测到SD扩展卡", 1).show(); 87 return ; 88 } 89 try { 90 switch (v.getId()) { 91 case R.id.rv_record: 92 // 要求输入用户名 93 String testuser; 94 if(testusername.getText()==null || testusername.getText().toString().equals("")){ 95 Toast.makeText(RecordVideoActivity.this, "请输入测试者姓名", Toast.LENGTH_LONG).show(); 96 return; 97 } 98 Log.i(TAG,"检测通过"); 99 recordOver = false; 100 testuser = testusername.getText().toString(); 101 testuser = android.os.Build.MODEL + "-" + testuser; 102 mediaRecorder.reset(); 103 if(isSurportFlashlight(RecordVideoActivity.this)){ 104 if (camera == null) 105 camera = Camera.open(); 106 Camera.Parameters myParameters = camera.getParameters(); 107 myParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); 108 camera.setParameters(myParameters); 109 camera.startPreview(); 110 camera.unlock(); 111 mediaRecorder.setCamera(camera); 112 } 113 mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 114 //mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 115 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 116 mediaRecorder.setVideoSize(320, 240); 117 mediaRecorder.setVideoFrameRate(30); //每秒30帧 118 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); 119 //mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 120 SimpleDateFormat ff = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss"); 121 String recordTimeString = String.valueOf(ff.format(System.currentTimeMillis())); 122 File videoFile = IOUtil.CreateNewFile(Environment.getExternalStorageDirectory().getPath()+"/phonedoctor/video", 123 testuser + "-" + recordTimeString+".3gp",null); 124 mediaRecorder.setOutputFile(videoFile.getAbsolutePath()); 125 mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface()); 126 mediaRecorder.prepare(); 127 mediaRecorder.start(); // 开始录制 128 // 开启计时线程 129 myChronograph = new MyChronograph(mHandler,60000); 130 myChronograph.start(); 131 Toast.makeText(RecordVideoActivity.this, "开始录制视频!", Toast.LENGTH_SHORT).show(); 132 record = true; 133 ((Button)findViewById(R.id.rv_record)).setEnabled(false); 134 break; 135 136 case R.id.rv_stop: 137 if(record){ 138 record = false; 139 mediaRecorder.stop(); 140 mediaRecorder.reset(); 141 Log.i(TAG,"TAG-1"); 142 if(camera!=null){ 143 camera.lock(); 144 camera.stopPreview(); 145 Camera.Parameters myParameters = camera.getParameters(); 146 myParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 147 camera.setParameters(myParameters); 148 camera.release(); 149 camera = null; 150 } 151 // 秒表线程控制 152 if(myChronograph!=null){ 153 myChronograph.exit(); 154 myChronograph = null; 155 } 156 ((Button)findViewById(R.id.rv_record)).setEnabled(true); 157 } 158 break; 159 } 160 } catch (Exception e) { 161 Toast.makeText(RecordVideoActivity.this, "发生异常", 1).show(); 162 e.printStackTrace(); 163 } 164 } 165 166 } 167 168 private Handler mHandler = new Handler(){ 169 170 @Override 171 public void handleMessage(Message msg) { 172 String[] strMsg; 173 switch (msg.what) { 174 case MsgNumber.UPTIME_UI: 175 strMsg = (String[]) msg.obj; 176 chronograph.setText(strMsg[0]); 177 if(!recordOver){ 178 int percent = Integer.parseInt(strMsg[1]); 179 if(percent==-1){ 180 recordOver = true; 181 schedule.setProgress(60); 182 Toast.makeText(RecordVideoActivity.this, "已录制一分钟!", Toast.LENGTH_SHORT).show(); 183 return; 184 } 185 percent = percent>60?60:percent; 186 schedule.setProgress(percent); 187 } 188 break; 189 190 default: 191 break; 192 } 193 } 194 195 }; 196 197 // 闪光灯判断 198 public boolean isSurportFlashlight(Context context) { 199 boolean flag = false; 200 PackageManager pm = context.getPackageManager(); 201 FeatureInfo[] features = pm.getSystemAvailableFeatures(); 202 for (FeatureInfo f : features) { 203 if (PackageManager.FEATURE_CAMERA_FLASH.equals(f.name)) { 204 flag = true; 205 break; 206 } 207 } 208 return flag; 209 } 210 }
运行效果图
至此,主要代码已经贴出,没什么技术含量,算是Android学习过程中的一个小结,Android在线API的一个阅读笔记。