Camera
Android框架,包括在设备上提供的各种相机和照相功能,让您拍摄的照片和视频应用程序的支持。本文讨论了一种快速,简单的方法,以图像和视频捕捉和概述了为用户创建自定义相机体验先进的方法。
注意事项
使您的应用程序在Android设备上使用相机之前,你应该考虑你的应用打算如何使用这个硬件功能的几个问题。
相机要求 - 就是利用对您不希望在没有摄像头的设备上安装应用程序应用程序,以便重要的一个摄像头?如果是的话,你应该在你的清单申报相机的要求。
快速的图片或自定义相机 - 您的应用程序将如何使用摄像头?你只是有兴趣抢购快速图片或视频剪辑,或将您的应用程序提供了使用相机的新方式?对于得到一个快速扣或夹子,可以考虑使用现有的相机应用。为了开发一个定制的摄像功能,检查了大楼一个相机应用部分。
存储 - 是你的应用程序生成旨在只有你的应用程序中显示或共享以便其他应用程序,如画廊或其他媒体和社交应用程序可以使用它们的图像或视频?你想要的图片和视频,即使您的应用程序卸载可用?退房保存媒体文件部分,看看如何实现这些选项。
基础
Android框架支持通过android.hardware.camera2 API或相机意图捕捉图像和视频。以下是相关的类:
android.hardware.camera2
这个包是用于控制装置的相机的主要API。它可以用来当你构建一个摄像头应用程序来拍摄照片或录像。
Camera
这个类是控制装置摄像头老过时的API。
SurfaceView
这个类用于呈现现场摄像机预览给用户。
MediaRecorder
此类用于从相机录制视频。
Intent
MediaStore.ACTION_IMAGE_CAPTURE或MediaStore.ACTION_VIDEO_CAPTURE的意图动作类型可以用来捕捉图像或视频而不直接使用Camera对象。
清单声明
在用相机的API应用程序开发开始之前,你应该确保你的清单有相应的声明,从而允许使用摄像头的硬件和其他相关功能。
相机许可 - 您的应用程序必须要求使用设备摄像头的权限。
<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卡),还必须在清单中注明。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />录音权限 - 对于录制视频拍摄有声,应用程序必须请求音频采集许可。
<uses-permission android:name="android.permission.RECORD_AUDIO" />位置的权限 - 如果你的应用程序标签带有GPS位置信息的图片,你必须要求位置的权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />有关获取用户位置的详细信息,请参阅位置策略。
private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100; private Uri fileUri; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // create Intent to take a picture and return control to the calling application Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name // start the image capture Intent startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE); }当执行startActivityForResult()方法,用户看到相机应用接口。用户完成后拍照(或取消操作),用户界面返回到您的应用程序,你必须拦截的onActivityResult()方法来接收意向的结果,并继续你的应用程序的执行。有关如何接收完成意图的信息,请参阅接收相机意图的结果。
private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200; private Uri fileUri; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //create new Intent Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO); // create a file to save the video intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high // start the Video Capture Intent startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE); }当执行startActivityForResult()方法中,用户可以看到修改后的摄像头应用程序接口。用户完成拍摄后的视频(或取消操作),用户界面返回到您的应用程序,你必须拦截的onActivityResult()方法来接收意向的结果,并继续你的应用程序的执行。有关如何接收完成意图的信息,请参阅下一节。
private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100; private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) { if (resultCode == RESULT_OK) { // Image captured and saved to fileUri specified in the Intent Toast.makeText(this, "Image saved to:\n" + data.getData(), Toast.LENGTH_LONG).show(); } else if (resultCode == RESULT_CANCELED) { // User cancelled the image capture } else { // Image capture failed, advise user } } if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) { if (resultCode == RESULT_OK) { // Video captured and saved to fileUri specified in the Intent Toast.makeText(this, "Video saved to:\n" + data.getData(), Toast.LENGTH_LONG).show(); } else if (resultCode == RESULT_CANCELED) { // User cancelled the video capture } else { // Video capture failed, advise user } } }一旦您的活动获得成功的结果,所拍摄的图像或视频在您的应用程序访问指定的位置可用。
/** Check if this device has a camera */ private boolean checkCameraHardware(Context context) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){ // this device has a camera return true; } else { // no camera on this device return false; } }Android设备可以有多个摄像头,例如背对着相机摄影和一个前置摄像头用于视频通话。的Android 2.3(API等级9),后来允许您检查摄像机使用Camera.getNumberOfCameras()方法的设备上可用的数量。
/** A safe way to get an instance of the Camera object. */ 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.open时,务必检查异常()。未检查异常,如果相机在使用或不存在会导致应用程序被系统关闭。
/** 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) { // The Surface has been created, now tell the camera where to draw the preview. try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { Log.d(TAG, "Error setting camera preview: " + e.getMessage()); } } public void surfaceDestroyed(SurfaceHolder holder) { // empty. Take care of releasing the Camera preview in your activity. } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // If your preview can change or rotate, take care of those events here. // Make sure to stop the preview before resizing or reformatting it. if (mHolder.getSurface() == null){ // preview surface does not exist return; } // stop preview before making changes try { mCamera.stopPreview(); } catch (Exception e){ // ignore: tried to stop a non-existent preview } // set preview size and make any resize, rotate or // reformatting changes here // start preview with new settings try { mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); } catch (Exception e){ Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } } }如果要设置特定大小的摄像头预览,如意见指出在上面的surfaceChanged()方法来设置此。当设置预览大小,您必须使用值从getSupportedPreviewSizes()。不要在setPreviewSize()方法来设置任意值。
<?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>在大多数设备,摄像头预览的默认方向为横向。此示例布局指定的水平(横向)的布局和下面的代码修复应用为横向的方向。对于渲染相机预览简单,你应该加入以下到您的清单应用程序的预览活动方向更改为横向。
<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>注:摄像头预览并不一定要在横向模式。在Android 2.2的(API等级8级)开始,您可以使用setDisplayOrientation()方法来设置预览图像的旋转。为了改变预览方向为用户重新定向预览类的surfaceChanged()方法中的电话,先停止Camera.stopPreview预览()与Camera.startPreview再次改变方向,然后开始预览( )。
public class CameraActivity extends Activity { private Camera mCamera; private CameraPreview mPreview; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Create an instance of Camera mCamera = getCameraInstance(); // Create our Preview view and set it as the content of our activity. mPreview = new CameraPreview(this, mCamera); FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview); preview.addView(mPreview); } }注:在上面的例子中的getCameraInstance()方法是指在访问摄像机中所示的示例性方法。
private PictureCallback mPicture = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); if (pictureFile == null){ Log.d(TAG, "Error creating media file, check storage permissions: " + e.getMessage()); return; } try { FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(data); fos.close(); } catch (FileNotFoundException e) { Log.d(TAG, "File not found: " + e.getMessage()); } catch (IOException e) { Log.d(TAG, "Error accessing file: " + e.getMessage()); } } };触发通过调用Camera.takePicture()方法捕捉图像。下面的示例代码显示了如何调用从按钮View.OnClickListener此方法。
// 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) { // get an image from the camera mCamera.takePicture(null, null, mPicture); } } );注意:mPicture构件在下面的例子中是指上述示例代码。
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; }在此之前的Android 2.2(API等级8),你必须直接设置,而不是使用CamcorderProfile输出格式和编码格式的参数。这种方法被证明在下面的代码:
// Step 3: Set output format and encoding (for versions prior to API Level 8) mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);对于MediaRecorder下面的视频录像给定参数的默认设置,但是,您可能需要调整您的应用这些设置:
setVideoEncodingBitRate()
setVideoSize()
setVideoFrameRate()
setAudioEncodingBitRate()
setAudioChannels()
setAudioSamplingRate()
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 } } } } );注:在上面的例子中,准备视频记录器()方法是指在配置MediaRecorder所示的例子的代码。此方法需要锁定的摄像头,配置和准备MediaRecorder实例的照顾。
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; } } }注意:如果您的应用程序不能正确地释放相机,所有的后续尝试访问摄像机,包括那些由你自己的应用程序,将失败,并可能导致您或其他应用程序被关闭。
public static final int MEDIA_TYPE_IMAGE = 1; public static final int MEDIA_TYPE_VIDEO = 2; /** Create a file Uri for saving an image or video */ private static Uri getOutputMediaFileUri(int type){ return Uri.fromFile(getOutputMediaFile(type)); } /** Create a File for saving an image or video */ private static File getOutputMediaFile(int type){ // To be safe, you should check that the SDCard is mounted // using Environment.getExternalStorageState() before doing this. File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), "MyCameraApp"); // This location works best if you want the created images to be shared // between applications and persist after your app has been uninstalled. // Create the storage directory if it does not exist if (! mediaStorageDir.exists()){ if (! mediaStorageDir.mkdirs()){ Log.d("MyCameraApp", "failed to create directory"); return null; } } // Create a media file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); File mediaFile; if (type == MEDIA_TYPE_IMAGE){ mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_"+ timeStamp + ".jpg"); } else if(type == MEDIA_TYPE_VIDEO) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_"+ timeStamp + ".mp4"); } else { return null; } return mediaFile; }注:Environment.getExternalStoragePublicDirectory()是Android 2.2的(API 8级)或更高版本可用。如果你的目标与早期版本的Android设备,可以使用Environment.getExternalStorageDirectory()代替。欲了解更多信息,请参见保存共享文件。
// get Camera parameters Camera.Parameters params = mCamera.getParameters(); List<String> focusModes = params.getSupportedFocusModes(); if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { // Autofocus mode is supported }您可以使用相机的大部分功能上面显示的技术。该Camera.Parameters对象提供了一个getSupported ...(),是...支持()或GetMax的...()方法来判断是否(以及在多大程度上)功能的支持。
// get Camera parameters Camera.Parameters params = mCamera.getParameters(); // set the focus mode params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); // set Camera parameters mCamera.setParameters(params);这种技术适用于几乎所有的拍照功能,而且大多数参数可以在任何时候,你已经获得了摄像机对象的实例后更改。变更参数通常可见于在应用程序的相机预览立即给用户。在软件方面,参数的修改可能需要几帧实际生效的摄像头硬件处理新的指令,然后发送更新的图像数据。
// Create an instance of Camera mCamera = getCameraInstance(); // set Camera parameters Camera.Parameters params = mCamera.getParameters(); if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>(); Rect areaRect1 = new Rect(-100, -100, 100, 100); // specify an area in center of image meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60% Rect areaRect2 = new Rect(800, -1000, 1000, -800); // specify an area in upper right of image meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40% params.setMeteringAreas(meteringAreas); } mCamera.setParameters(params);该Camera.Area对象包含两个数据参数:一个矩形对象指定的摄像机视场之内的区域和权重值,它告诉相机什么水平的重要性这一领域应测光给予或聚焦计算。
在一个Camera.Area对象的矩形字段描述映射到一个2000×2000单元格具有矩形形状。坐标-1000,-1000表示的顶部,在摄像机图像的左上角和坐标1000,1000代表的底部,在摄像机图像的右下角,如下面的插图。
class MyFaceDetectionListener implements Camera.FaceDetectionListener { @Override public void onFaceDetection(Face[] faces, Camera camera) { if (faces.length > 0){ Log.d("FaceDetection", "face detected: "+ faces.length + " Face 1 Location X: " + faces[0].rect.centerX() + "Y: " + faces[0].rect.centerY() ); } } }创建这个类之后,然后将其设置到应用程序的Camera对象,如下面的示例代码:
mCamera.setFaceDetectionListener(new MyFaceDetectionListener());您的应用程序每次启动时必须启动人脸检测功能(或重新启动)的摄像头预览。创建启动人脸检测,所以你可以把它根据需要,如下面的示例代码的方法。
public void startFaceDetection(){ // Try starting Face Detection Camera.Parameters params = mCamera.getParameters(); // start face detection only *after* preview has started if (params.getMaxNumDetectedFaces() > 0){ // camera supports face detection, so can start it: mCamera.startFaceDetection(); } }你必须每次启动时启动人脸检测(或重新启动)的摄像头预览。如果您在创建预览类显示预览类中,添加在预览类的startFaceDetection()方法既surfaceCreated()和surfaceChanged()方法,如下面的示例代码。
public void surfaceCreated(SurfaceHolder holder) { try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); startFaceDetection(); // start face detection feature } catch (IOException e) { Log.d(TAG, "Error setting camera preview: " + e.getMessage()); } } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { if (mHolder.getSurface() == null){ // preview surface does not exist Log.d(TAG, "mHolder.getSurface() == null"); return; } try { mCamera.stopPreview(); } catch (Exception e){ // ignore: tried to stop a non-existent preview Log.d(TAG, "Error stopping camera preview: " + e.getMessage()); } try { mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); startFaceDetection(); // re-start face detection feature } catch (Exception e){ // ignore: tried to stop a non-existent preview Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } }注意:请记住调用startPreview后调用此方法()。不要尝试启动人脸检测在相机应用程序的主活动的onCreate()方法,为预览不可用在您的应用程序这一点是执行。
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher) mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH)); ... // Step 5.5: Set the video capture rate to a low number mMediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds这些设置必须为MediaRecorder较大的配置过程的一部分来完成。对于一个完整的配置代码示例,请参阅配置MediaRecorder。一旦配置完成后,你就开始录像,如果你是录制正常的视频剪辑。有关配置和运行MediaRecorder的更多信息,请参见捕获视频。