Android的相机开发

基础

相机开发中常用的API
  • package android.hardware.camera2 这个包中包含了主要的相机控制的API,可以用于在你的项目中进行拍照或者录像功能的实现
  • Camera 这个类是早些版本驱动相机的API
  • SurfaceView 用于相机预览的界面
  • MediaRecorder 用于录像的API
  • 通过Intent调用系统的拍照功能,通过设置MediaStore.ACTION_IMAGE_CAPTURE 或者 MediaStore.ACTION_VIDEO_CAPTURE 动作调用系统的拍照功能
权限声明
  • 相机开发权限声明
<uses-permission android:name="android.permission.CAMERA" />
  • 其他功能,如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);

    // 创建Intent对象并传递相应的action参数
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

    fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // 创建一个文件夹保存图片
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // 设置保存路径

    // 开启相机
    startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
  • 接收返回结果
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
        }
    }
}

构建一个相机APP

相机开发的基本步骤
  1. 检查是否能够使用相机
  2. 创建一个相机的预览类,该类继承自SurfaceView并且实现SurfaceHolder接口,用于接收并显示camera传递过来的数据
  3. 构建一个预览布局,该布局包含相机预览类以及用户自定义的控制接口
  4. 设置监听事件,捕获用户拍照或者录像动作,并作出相应的响应
  5. 将捕获到的照片或者录像保存
  6. 释放Camera资源,通过camera.release();方法
分步骤
1,检测相机功能是否可以使用

通过PackageManager中的hasSystemFeature()方法判断,示例代码如下:

/** 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;
    }
}

此外,2.3版本以后,可以通过Camera.getNumberOfCameras()方法来获取设备中相机的数量,可以用来判断是否有前置摄像头

2,获取相机实例

使用Camera类的静态方法open()来获取相机实例

/** 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正在被使用或者不存在的时候,应用将会直接退出。此外,在2.3版本或者更高版本中,还可以使用open(int)方法获取Camera的实例,传入得参数int类似于优先级的作用

3,相机参数的获取

通过camera.getParameters()方法获取到相机相对应的参数,并通过Parameter对象进行设置修改,在2.3及之后的版本,通过camera.getCameraInfo()方法来确定摄像头是否为前置或者后置,以及Image的方向

4,创建相机预览类
/** 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);
        // 不赞成设置,要求在3.0版本之前使用
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // 通知Camera预览图片放哪里,即将SurfaceView与相机绑定
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty.注意要在你的活动中释放相机预览
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // 确保在重新设置大小或者格式化之前停止预览
        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
        //重新设置代码编写
        //设置预览大小尺寸应该从方法getSupportPreviewSizes()方法中获取

        // start preview with new settings
        //开始预览
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}
5,创建简单的相机预览界面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="match_parent"
    android:layout_height="match_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>

当中的FramLayout相当于是相机预览的容器,而Button则是实现点击拍照,通常情况,相机画面默认的方向是水平方向的,所以需要将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>

或者通过Parameter的setRotation()方法对方向进行调整

//根据当前朝向修改保存图片的旋转角度
	private void updateCameraOrientation(){
		if(mCamera!=null){
			Camera.Parameters parameters = mCamera.getParameters();
			//rotation参数为 090180270。水平方向为0
			int rotation=90+mOrientation==360?0:90+mOrientation;
			//前置摄像头需要对垂直方向做变换,否则照片是颠倒的
			if(mIsFrontCamera){
				if(rotation==90) rotation=270;
				else if (rotation==270) rotation=90;
			}
			parameters.setRotation(rotation);//生成的图片转90°
			//预览图片旋转90°
			mCamera.setDisplayOrientation(90);//预览转90°
			mCamera.setParameters(parameters);
		}
	}

构建出相机的布局后,将相机预览类放到布局的FramLayout中,具体代码如下:

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);
    }
}
6-1 实现拍照功能
  • 拍照动作的实现,主要是通过Camera.takePicture()方法完成,该方法接收三个参数,分别为三个回调事件,ShutterCallback以及两个PictureCallback,为了接收图片,第三个PictureCallback必须实现,用来保存camera当中拍到的图片,简单的实现如下:
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());
        }
    }
};

通过给布局中的Button设置监听事件,当按钮被点击时触发拍照功能,即调用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);
        }
    }
);

通过以上代码即可简单实现拍照功能

6-2 实现录像功能
  • 相比较于拍照,录像功能的实现需要更加细致的管理,需要Camera同另一个类MediaRecorder协同配合完成。在使用MediaRecorder之前,还需要调用Camera.lock()和Camera.unlock()方法来允许MediaRecorder对象调用camera硬件。从4.0版本开始,这两个方法将会自动被调用,而不需要手动调用它们。
  • 与拍照不同,录像功能的实现必须按照指定的顺序执行才能成功,详细步骤如下
    1. 打开相机,获取到Camera的实例
    2. 绑定对应的预览SurfaceView,通过Camera.setPreviewDisplay()方法
    3. 调用Camera.startPreview()方法开启预览
    4. 开启录像功能,一下步骤必须全部实现
      1. 解锁相机,通过Camera.unlock()方法
      2. 配置MediaRecorder
        1. setCamera(),设置相机为录像功能,

释放Camera资源

Camera在内润是单例的,所有的程序都可以访问和获取该实例,而一旦程序在使用完Camera对象后没有及时的释放该实例,将会导致该资源不能被其他的程序所访问,所有访问Camera类都会被系统关闭。释放Camera的方法为Camera.release();实例代码如下:

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView mPreview;
    private MediaRecorder mMediaRecorder;

    ...

    //ActivityonPause方法中释放资源,如果同时调用了MediaRecorder,也需要将MediaRecorder一同释放
    @Override
    protected void onPause() {
        super.onPause();
        // if you are using MediaRecorder, release it first
        releaseMediaRecorder();       
         // release the camera immediately on pause event
        releaseCamera();             
    }

    private void releaseMediaRecorder(){
        if (mMediaRecorder != null) {
        // clear recorder configuration
        //释放前先清除MediaRecorder中的配置
            mMediaRecorder.reset();  
            // release the recorder object
            //释放MediaRecorder实例
            mMediaRecorder.release(); 
            mMediaRecorder = null;
            // lock camera for later use
            //Camera锁上,后面释放和调用时使用
            mCamera.lock();           
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
        // release the camera for other applications
            mCamera.release();        
            mCamera = null;
        }
    }
}

文件存储

  • 使用后生成的Image或者Video都应该保存在sd卡中,保存的路径有以下两个:
    • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),该方法中的路径是公有的,共享的,所有的应用都可以通过该路径得到路径下的图片资源文件,同时对其进行修改、删除等操作,也是推荐的保存路径。此外,当应用程序被卸载时,保存在该路径下的图片文件并不会删除,而会一直保存下来。
    • context.getExternalFileDir(Enviroment.DIRECTORY_PICTURES);该路径下保存的Image或者Video资源将会和你的Application关联,其他的程序不能对其中的资源进行访问、修改以及删除等操作;但当应用程序被卸载时,这些资源也将会被删除。
//示例代码
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 */
//获取Uri地址
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.
    //在使用之前最好先检查sd卡是否挂载

    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()){
        //如果创建失败,返回null
        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(Environment.DIRECTORY_PICTURES)方法是在2.2(APILevel 8)版本之后的,更早的版本应该使用Environment.getExternalStorageDirectory()替换。

Camera属性

  • camera中大部分的属性都可以通过Camera.Paramter中进行设置,详细属性说明看官方文档,以下三个属性还需要进行其他的设置
    • Metering and focus area
    • Face detection
    • Time lapse video
  • 不是所有的camera属性都是可以使用的,要根据手机硬件是否支持以及相对应的软件是否实现该功能。

检测Camera中的属性是否可用

camera中的一些属性的使用是取决于手机的硬件以及软件是否支持,所以在考虑使用这些属性的时候,就需要对手机是否支持进行检测判断,例如:

// 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.Paramters中的大部分属性是否支持都可以通过getSupport...()或者is...Supported()或者getMax()方法获取,需要注意的是,当你检测和设置了这些属性之后,Google Play将会约束你的应用不能在不支持这些功能的手机上进行安装。

  • 属性设置
// 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);

相机中大部分属性在修改过后就能立刻在Preview当中显示出来,不过图片的尺寸以及方向是不能立刻显示的,需要先停止预览,设置大小及方向,最后再重新开始预览才能显示出来,从4.0版本(APILevel 14)开始,预览的可以不需要重新启动就被改变。

设置感光和聚焦区域

在4.0版本(APILevel 14)之后,可以指定区域对聚焦以及感光进行指定的设置,以下为设置两个感光区域的示例代码:

// 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
    // set weight to 60%
    //设置权重
    meteringAreas.add(new Camera.Area(areaRect1, 600)); 
    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.setDisplayOrientation()改变屏幕方向,原坐标系统不会重绘。

人脸识别

  • 从4.0版本(APILevel 14)之后,提供了可以进行人脸识别的API,需要注意的是,在进行人脸识别的时候,setWhiteBalance(String), setFocusAreas(List)和setMeteringAreas(List) 将不会有效。
  • 人脸识别功能的开发步骤:
    • 检查设备当中是否支持该功能,通过getMaxNumDetectedFaces()方法进行获取
    • 创建相应的人脸识别监听并绑定到相机上
    • 在预览后开启人脸识别
//人脸识别监听器
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() );
        }
    }
}
  • 绑定监听器
//绑定监听器
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();
    }
}
  • 开启的完整代码
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());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值