设置权限
如果要在App中使用摄像头,必须先在Manifest中声明摄像头权限
<uses-permission android:name="android.permission.CAMERA" />
以及声明应用需要有摄像头
<uses-feature android:name="android.hardware.camera" /
如果摄像头并非应用必不可少的功能,则可以声明如下
<uses-feature android:name="android.hardware.camera" android:required="false" />
如果应用要在SD卡上存储图像或者视频,则需要在Manifest文件中指定以下权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在录制视频的时候,同时需要录制音频,所以也要声明录音权限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
自定义相机的一般步骤
- 是否有摄像头、是否有访问摄像头的权限
- 创建相机预览类,继承SurfaceView并实现SurfaceHolder接口,用来预览相机的即时图片;
- 有了相机预览类之后,搭建预览布局将预览视图和界面控件结合起来。
- 连接控件的监听器以响应用户的动作,拍摄照片或视频,比如一个按钮点击事件
- 拍摄照片和视频并保存输出
- 摄像头使用完毕后,要正确地释放相机以便于其他应用使用
相机是共享资源,必须合理管理才不至于和其他同样用到相机的应用发生冲突。
注意 : 当你的应用使用完相机之后,记得调用
Camera.release()
方法以释放Camera
对象。如果没有正确地释放相机,设备上的任何应用,随后任何访问相机的尝试,都有可能失败、退出。
检测相机
如果没有在manifest中配置需要摄像头功能,则需要在运行时检测是否有相机。
private boolean checkCameraHardware(Context context) {
// 支持所有版本
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
// Android 2.3 (API Level 9) 及以上的
// return Camera.getNumberOfCameras() > 0;
}
访问相机
public static Camera getCameraInstance(){
Camera c = null;
try {
// 在多个摄像头时,默认打开后置摄像头
c = Camera.open();
// Android 2.3(API 9之后可指定cameraId摄像头id,可选值为后置(CAMERA_FACING_BACK)/前置( CAMERA_FACING_FRONT)
// c = Camera.open(cameraId);
} catch (Exception e){
// Camera被占用或者设备上没有相机时会崩溃。
}
return c; // returns null if camera is unavailable
}
注意: 在部分设备上,打开摄像头时可能会花费较长的时间,因此此操作应该在子线程上执行,然后以回调的方式传递
Camera
对象,避免出现ANR;
检测相机功能
在获取到Camera对象之后,可以调用Camera.getParameters()
方法并检测该方法返回的Camera.Parameters
对象所支持的功能以获取到更多关于相机功能的信息。当使用API 9及以上时, 使用Camera.getCameraInfo()
检测摄像头是前置还是后置、图像的方向.
创建预览类
摄像头预览类继承自SurfaceView
,能够实时的展示来自摄像头的图像数据,方便用户捕捉图片和视频。
下面的代码演示了预览类的基本创建。这个类继承自SurfaceHolder.Callback
以获取创建和销毁该预览视图的回调事件,这些事件在指派相机预览输入时所需要的。
/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// 通知摄像头可以在这里绘制预览了
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// 什么都不做,但是在Activity中Camera要正确地释放预览视图
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// 如果预览视图可变或者旋转,要在这里处理好这些事件
// 在重置大小或格式化时,确保停止预览
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// 变更之前要停止预览
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// 在这里重置预览视图的大小、旋转、格式化
// 使用新设置启动预览视图
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
如果要给预览视图设置指定的大小,请在surfaceChanged()
方法中像上面的注释中提到的一样设置。当设置预览视图大小时,必须使用从getSupportedPreviewSizes()
获取到的值。不要在setPreviewSize()
中随意设置值。
注意:Android 7.0(API 24及以上), Depending on the window size and aspect ratio, you may may have to fit a wide camera preview into a portrait-orientated layout, or vice versa, using a letterbox layout.
在布局文件中放预览视图
下面是一个简单的例子,其中FrameLayout
作为预览视图的容器:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<Button
android:id="@+id/button_capture"
android:text="Capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
通常以横屏录制视频,所以在Manifest中配置对应的Activity中配置:
<activity android:name=".CameraActivity"
android:label="@string/app_name"
android:screenOrientation="landscape">
<!-- configure this activity to use landscape orientation -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
在Activity中获取预览视图的容器视图,然后将预览视图放置在容器视图中
public class CameraActivity extends Activity {
private Camera mCamera;
private CameraPreview mPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 创建Camera实例
mCamera = getCameraInstance();
// 创建预览视图,并作为Activity的内容
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
}
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
}
拍照
(后面再补上,暂时用不到)
录制视频
录制视频需要注意管理Camera以及MediaRecorder类。通过摄像头录制视频时,必须控制 Camera.lock()
和Camera.unlock()
的调用以允许MediaRecorder访问摄像头设备,另外还有 Camera.open()
和Camera.release()
的调用。
从Android 4.0 (API 14)开始, Camera.lock() 和 Camera.unlock() 的调用已经被自动管理了。
使用摄像头录制视频和拍照不同,录制视频需要特别的调用顺序。要想做好准备工作,必须遵从特定的执行顺序。
- 打开摄像头 - 使用
Camera.open()
获取Camera实例 - 连接预览视图 - 使用
Camera.setPreviewDisplay()
连接SurfaceView - 开始预览 - 使用
Camera.startPreview()
方法显示即时录像图片 - 开始录制视频 - 要完成视频录制,必须按顺序完成下面的不步骤:
解锁摄像头 - 调用
Camera.unlock()
解锁摄像头以供MediaRecorder使用-
配置MediaRecorder - 依次调用以下MediaRecorder的方法
-
setCamera()
- 设置视频录制所用到的摄像头 -
setAudioSource
- 设置音频源,使用MediaRecorder.AudioSource.CAMCORDER
-
setVideoSource()
- 设置视频源,使用MediaRecorder.VideoSource.CAMERA
- 设置视频的输出格式和编码方式。Android 2.2 (API 8)及以上版本,用
MediaRecorder.setProfile
方法,用CamcorderProfile.get()
方法获取 profile 实例 -
setOutputFile()
- 设置输出文件 -
setPreviewDisplay()
注意:MediaRecorder的这些方法必须依次调用,否则应用会出错,录制失败
-
准备MediaRecorder - 调用
MediaRecorder.prepare()
方法开始录制 - 调用
MediaRecorder.start()
- 停止录制 - 依次调用下面的方法以完成录制
- Stop MediaRecorder - 调用
MediaRecorder.stop()
- Reset MediaRecorder - 非必须的,调用
MediaRecorder.reset()
方法移除Recorder的配置设置 - Release MediaRecorder - 调用
MediaRecorder.release()
方法 - Lock the Camera - 调用
Camera.lock()
锁定摄像头,随后其他的MediaRecorder才能使用它。从Android 4.0(API 14)开始,只有在MediaRecorder.prepare()
方法调用失败时,才需要调用这个方法。
- Stop MediaRecorder - 调用
- Stop the Preview - 当Activity完成使用完摄像头后,调用
Camera.stopPreview()
停止预览; - Release Camera - 调用
Camera.release()
释放摄像头,其他应用才能使用。
温馨提示:如果应用通常用来拍摄视频,启动预览视图是使用 设置
setRecordingHint(boolean)
为true
。这个设置可以减少启动录制的时间。
配置MediaRecorder
当使用MediaRecorder录制视频时,必须以指定的顺序执行配置步骤,然后调用MediaRecorder.prepare()
检查和使用配置。
private boolean prepareVideoRecorder(){
mCamera = getCameraInstance();
mMediaRecorder = new MediaRecorder();
// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
// Step 4: Set output file
mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
// Step 5: Set the preview output
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
} catch (IOException e) {
Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
}
return true;
}
以下MediaRecorder的视频录制参数已经有了预设值,不过你也可以用下面的方法调整以适用自己的应用:
setVideoEncodingBitRate()
setVideoSize()
setVideoFrameRate()
setAudioEncodingBitRate()
setAudioChannels()
setAudioSamplingRate()
启动和停止MediaRecorder
当使用MediaRecorder启动和停止录制时,必须遵从指定的顺序:
- Camera.unlock()
- 如以上的代码示例配置MediaRecorder
- MediaRecorder.start()启动录制
- 录制视频
- MediaRecorder.stop()停止录制
- MediaRecorder.release()释放MediaRecorder
- Camera.lock()锁定摄像头
以下代码演示了怎样使用Camera和MediaRecorder通过一个按钮正确的启动和停止视频录制
当完成一个视频录制后,不要释放Camera,否则预览视图也会停止
private boolean isRecording = false;
// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isRecording) {
// stop recording and release camera
mMediaRecorder.stop(); // stop the recording
releaseMediaRecorder(); // release the MediaRecorder object
mCamera.lock(); // take camera access back from MediaRecorder
// inform the user that recording has stopped
setCaptureButtonText("Capture");
isRecording = false;
} else {
// initialize video camera
if (prepareVideoRecorder()) {
// Camera is available and unlocked, MediaRecorder is prepared,
// now you can start recording
mMediaRecorder.start();
// inform the user that recording has started
setCaptureButtonText("Stop");
isRecording = true;
} else {
// prepare didn't work, release the camera
releaseMediaRecorder();
// inform user
}
}
}
}
);
释放相机
一个设备上,摄像头是所有应用的共享资源,因此在自身的应用处于onPause
状态时,要立即释放掉正在使用的Camera对象
public class CameraActivity extends Activity {
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
...
@Override
protected void onPause() {
super.onPause();
releaseMediaRecorder(); // if you are using MediaRecorder, release it first
releaseCamera(); // release the camera immediately on pause event
}
private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock(); // lock camera for later use
}
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
}