Build A CAMERA(创建一个自定义的Camera)
一些开发人员需要一个(为应用程序定制或提供特殊功能)的相机用户界面(自定义相机)。创建一个定制的相机活动需要更多的代码,但它可以为你的用户提供更令人信服的体验。
为您的应用程序创建自定义相机接口的一般步骤如下:
1. 检测和访问摄像机-创建代码,以检查是否存在摄像头和允许访问。
2. 创建一个预览类,创建一个摄像机预览类继承SurfaceView实现SurfaceHolder接口。这类用于相机预览。
3. 建立一个预览布局,一旦你有相机预览类,创建一个视图布局,集成了你想要预览界面和用户界面控件。
4. 设置监听(为捕获),为像按钮一样的控件设置监听。
5. 捕获并保存文件-设置捕获图片或视频并保存到外部设备的代码。
6. 释放相机(重要)-在使用相机后,您的应用程序必须正确地释放它,供其他应用程序使用。
相机硬件是一个共享资源,必须小心管理,所以你的应用程序可能会与其他要使用它的其他应用程序发生冲突。
下面的章节将讨论如何检测相机的硬件,如何请求访问一个摄像头,如何捕捉照片或视频,以及如何释放相机:
记住:
当您的应用程序不在使用摄像头的时候,要通过Camera.release()方法释放相机对象。如果你的应用程序没有正确地释放相机,所有随后尝试访问相机的应用程序,包括那些你自己的应用程序,都将失败,可能会导致你或其他应用程序被关闭。
Camera.release():断开并释放相机的对象资源。只要不使用相机对象,就要释放。
1. 检测摄像机硬件
如果你的应用程序没有特别要求使用摄像头(在清单文件中声明Camera),你应该检查一下是否有一个摄像头在运行时可用。要执行此检查,使用PackageManager.hasSystemFeature()方法,如下面的代码示例所示:
/**Check if this device has a camera */
private boolean checkCameraHardware(Contextcontext) {
if(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// this device has a camera
return true;
}else {
// no camera on this device
return false;
}
}
安卓设备可以有多个摄像头,例如一个背对着相机的摄影和一个前置摄像头进行录像。Android 2.3(API Level 9)以及后面的版本允许你检查在设备上可使用的摄像头的个数。通过Camera.getNumberOfCameras()方法。
public static int getNumberOfCameras():返回此设备上可用的物理摄像头的数量。
2. 连接相机
如果你已经确定了你的应用程序运行的设备上有一个摄像头,你必须通过获取一个相机的实例来请求获得它(除非你使用的意图访问相机)。
为了访问私有摄像头,要使用Camera.open()
【open(int)】方法,并且try catch所有异常,下面代码所示:
public static Cameraopen ():创建一个新的相机对象(连接第一个后置摄像头),如果设备没有一个后置摄像头,返回null。
/** A safe way to get an instance of theCamera 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()方法的时候,要检查异常,如果不检查异常:当摄像头正被其他应用使用,或者不存在摄像头,会导致你的应用程序shut down。在Android2.3(API Level9)的设备上或更高的版本上,你可以使用通过Camera.open(int)方法连接特定的摄像头。上面的代码返回第一个,后置摄像头(该设备上的,即使有多个摄像头)。
public static Cameraopen (int cameraId):创建一个新的相机对象访问特定的硬件相机。如果这个摄像头被其他应用程序打开,将会抛出一个运行时异常。参数:between
0 and getNumberOfCameras()-1。
u 使用完摄像头后必须调用release()方法释放相机,否则这个摄像头仍然被锁定,是无法被其他应用程序访问的。
u 对于一个特定的摄像头硬件,你的应用程序应该在同一时间只有一个活动着的摄像头对象。
u 其他方法的回调被传递给了thread(线程)的event loop(事件轮询器, 消息循环,使线程随时处理事件,但不退出)叫做open()。如果这个线程没有event loop,回调会被传递给主线程的event loop。如果没有主线程的event loop,回调不会传递。
警告:在某些设备上,这种方法可能需要很长的时间才能完成。最好是从一个工作者线程调用这个方法(可能使用AsyncTask)以避免阻塞UI线程的主要应用。(此方法最好在子线程中调用)。
摄像头禁用?
public boolean getCameraDisabled (ComponentNameadmin);
确定是否设备的摄像头已被禁用或被当前的管理员,如果指定,或所有管理员。
Admin:管理组件的名称。
3. 检查相机功能
一旦你获得一个摄像头,你可以得到进一步得到更多摄像头的信息,通过Camera.getParameters()方法可以得到摄像头的功能信息,检查返回的相机所支持功能的参数对象。使用API级别9或更高的时候,可以通过Camera.getCameraInfo()方法确定一个摄像头是否在设备的正面或背面,以及图像的方向。
publicCamera.Parameters getParameters ():返回此相机的当前设置。如果要修改返回的相机配置参数,就必须通过setParameters(Camera.Parameters)使新的相机参数生效。
public void setParameters (Camera.Parameters params):修改相机的设置。
Camera.Parameters params:相机参数。
2 Camera.Parameters params类(以后补充)
4. 创建预览类
为了使用户可以拍摄照片和视频,要使他们必须能够看到设备摄像头看到的场景。相机预览类是一个图形,可以实时显示来自摄像头的数据,所以用户可以组织和拍摄照片或视频。
下面的示例代码演示了如何创建一个基本的相机预览类,它包含了一个视图布局。这个类实现了SurfaceHolder.Callback接口。用于捕捉事件回调(创建和销毁界面),需要指定摄像预览输入。
/** A basic Camerapreview class */
public class CameraPreview extendsSurfaceView 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
//在改变surface前要先停止预览。
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());
}
}
}
SurfaceHolder.Callback:
一个客户端可以实现这个接口从而接收到Surface的信息变化。
想让SurfaceHolder.CallBack生效,必须执行SurfaceHolder.addCallBack(SurfaceHolder.CallBack)方法。当使用一个SurfaceView,这个Surface只在surfaceCreated(SurfaceHolder)和surfaceDestroyed(SurfaceHolder)之间可获得。这个Callback在
SurfaceHolder.addCallback方法中设置。
abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height):
一旦Surface的结构变化(格式或者尺寸大小的变化)都会导致这个方法的调用。你应该此时更新Surface表面的图像。这个方法在surfaceCreated(SurfaceHolder)后至少调用一次。
Holder: 该SurfaceHolder的surface发生改变了。
Format: Surface的新的像素格式。
Width:Surface的新宽度。
Height:Surface的新高度。
abstract void surfaceCreated(SurfaceHolder holder):Surface第一次创建后立即调用。在这个方法中写希望渲染surface的代码。注意:只有一个线程可以在一个Surface上draw(),所以你不应该在Surface上draw(),如果此时该Surface在另一个线程中渲染。
Holder:该SurfaceHolder的surface正在被创建。
abstract void surfaceDestroyed(SurfaceHolder holder):Surface马上被销毁前调用。执行这个回调后,你不应该尝试去访问这个surface。在执行这个方法后,如果你有一个渲染线程直接访问这个surface,你必须确保这个线程不再接触这个Surface。
public abstract void addCallback (SurfaceHolder.Callback callback):为该holder添加一个Callback回调,这个holder可以关联多个Callback.
如果你想为你的相机预览界面设置特定的大小,就在surfacechanged()方法中设置。设置预览大小的时候,你必须使用从getsupportedpreviewsizes()方法中获取的值。不要在setpreviewsize()设定任意的值。
public List<Camera.Size> getSupportedPreviewSizes ():获取所支持预览的大小。返回一个相机所支持的场景模式的集合,返回null表示不支持场景模式设置。
Camera.Size
相机尺寸
总结:
属性:public int height 图片的高度
Public int width 图片的宽度
构造方法:
Camera.Size(int w, int h)
public void setPreviewSize (int width, int height):设置预览图片的尺寸。如果预览已经开始(启动了),应用程序应该在更改预览大小之前先停止预览。宽度和高度的边是基于摄像机的方向。也就是说,预览大小是在它被显示方向旋转之前的大小。因此,应用程序需要考虑的显示方向,同时设置预览大小。(这两个一起设置的时候要注意)例如,假设相机支持320x480 480x320的预览图片。应用程序需要一个3:2的预览比例。如果显示方向设置为0或180,预览大小应设置为480x320。如果显示方向设置为90或270,预览大小应设置为320x480。在设置图片大小和缩略图大小时,还应考虑显示方向。
Width: 图片的宽度,以像素为单位。
Height: 图片的宽度,以像素为单位。
public final void setDisplayOrientation (int degrees):设置顺时针旋转的预览显示的度数。这影响到预览框和snapshot后显示的图片。这个方法对垂直模式的应用有效。请注意:前置摄像头的预览显示在旋转之前水平翻转了,即
图像是反映沿摄像头传感器的中心垂直轴。所以用户可以看到自己像镜子一样。
这个方法不允许在摄像头预览的时候调用(setDisplayOrientation)
如果你想让相机的图像显示在同一个方向上显示,可以使用以下代码:
public static void setCameraDisplayOrientation(Activity activity,
int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
从API14开始,这种方法可以在预览时主动被调用。
int degrees:图片将顺时针旋转的角度。有效值分别为0、90、180和270。起始位置为0(景观)
public final void setPreviewDisplay (SurfaceHolder holder):用于设置实时预览的界面。
不论是surface还是surface texture 必须能够预览,并且预览界面可以用来拍照。同一个surface界面可以重新设定而不会损害surface。设置一个预览界面会重置所有预览界面的结构,通过setPreviewTexture(SurfaceTexture)方法实现。
当这个方法调用的时候,SurfaceHolder必须已经包含了一个surface。如果你使用SurfaceView,在调用setPreviewDisplay()或starting preview()之前,你必须先注册一个SurfaceHolder.Callback接口和调用addCallback(SurfaceHolder.Callback)方法,并且等待surfaceCreated(SurfaceHolder)的执行。
这个方法必须在startPreview()方法之前调用。唯一的例外是,在调用startPreview()方法之前,如果没有设置预览界面,(或者预览界面设置为空),这个方法会调用一次(传入一个非空参数)来设置预览界面。预览界面正在运行的时候,预览界面无法做改动。
Camera布局:
例如前面举的一个例子(相机预览类)。需要一个布局,来进行拍照和录像。本节将向您展示如何构建预览的基本布局和Activity。下面的布局代码提供了一个非常基本的视图,可以用来显示一个摄像头预览。在这个例子中,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>
在大多数设备,相机预览的默认方向是垂直的。此示例布局指定一个横向(纵向)的布局,下面的代码应用程序landscape的方向性。要使摄像头的预览方向为垂直方向,你应该在清单文件中增加以下代码。
<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 Level 8开始),你可以使用setDisplayOrientation()方法设置预览图像旋转。要想改变预览方向。在预览类的surfaceChanged()方法中,首先要通过Camera.stopPreview()方法停止预览。然后通过Camera.startPreview()方法重启预览。把你自定义的预览类添加到上面FrameLayout元素中。你的相机Activity必须确定当Activity执行pause(暂停)和shut
down(关闭)的时候,它已经释放了camera。
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()方法指的是在Accessing cameras中。
捕获图片:
一旦你已经建立了一个预览类和一个视图布局来显示它,你准备开始用您的应用程序捕捉图像。在您的应用程序代码中,您必须为用户界面控件设置侦听器来回应用户的拍照操作。要获取一张图片,使用Camera.takePicture()方法类获取图片。该方法需要三个参数,从而接收来自摄像机的数据。为了接收以JPEG格式的数据,你必须实现Camera.PictureCallback接口,来接收图片信息和把信息写到文件中去。以下的代码展示了实现了一个 Camera.PictureCallback接口来保存图片(从相机中获取的)。
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() 。下面的代码展示了如何在Button的View.OnClickListener方法中调用 Camera.takePicture() 方法。
// 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);
}
}
);
注意:在应用程序使用完摄像头后要及时通过Camera.release()方法释放摄像头。
捕获录像
使用Android框架的视频采集需要对相机对象和MediaRecorder类两者进行精心的协调管理。当使用摄像头录像,你必须管理Camera.lock()和Camera.unlock()方法让MediaRecorder可以访问相机硬件。除了 Camera.open() 和 Camera.release()需要调用之外。
public final void lock ():重新锁上的摄像头,以防止其他进程访问它。相机对象默认是锁定的,除非调用unlock()方法。通常用reconnect()代替。自从API级别14,相机会自动锁定在应用程序的start()中。应用程序可以使用相机,当启动后(start)。记录启动或停止后无需再调用此方法。如果你不录制视频,你可能不需要这种方法。
public final void unlock ():打开相机允许另一个进程访问它。通常,相机被锁定在的活动着的摄像机对象,直到release()被调用。允许进程间快速切换,你可以调用这个方法来暂时释放相机,让另一个应用来使用;一旦其他进程完成后你可以调用reconnect()方法取回相机。
这个方法必须在setCamera(Camera)方法之前调用。执行start()方法之后就不能调用了。
如果你不录制视频,你可能不需要这种方法。
注意:从安卓4.0(API Level 14)之后,Camera.lock()和Camera.unlock()由你自己管理。
不像使用摄像头来拍照,录像需要一个非常特别的调用顺序。你必须遵循一个特定的执行顺序,为了使您的应用程序成功地准备和捕捉视频。正如下面所说。