Android开发学习之以CameraAPI方式实现相机功能(一)——快速实现相机

今天无意当中发现在《Android开发学习之基于ZBar实现微信扫一扫》中的一部分代码可以用来以硬件方式实现一个照相机的功能,在《Android开发学习之调用系统相机完成拍照的实现》中我们是以Intent方式调用系统内置的相机来完成拍照的,今天呢,就让我们来以Camera类为核心来开发自己的相机应用吧。在Android的官方文档中,以硬件方式定制相机应用的步骤如下:


1. 检查和访问Camera

创建代码来检查Camera和所申请访问的存在性;

2. 创建一个预览类

继承SurfaceView来创建一个Camera的预览类,并实现SurfaceHolder接口。这个类用来预览来自Camera的实施图像。

3. 构建一个预览布局

一旦有了Camera预览类,就可以把这个预览类和你想要的用户界面控制结合在一起来创建一个视图布局。

4. 针对采集建立监听

把监听器与响应用户动作(如按下按钮)的界面控制连接到一起来启动图像或视频的采集。

5. 采集和保存文件

针对真正采集的图片或视频,以及输出的保存来编写代码。

6. 释放Camera

使用Camera之后,你的应用程序必须释放Camera,以便其他应用程序能够使用。

Camera硬件是一个必须要认真管理的共享资源,因此你的应用程序在使用它时,不能跟其他应用程序发生冲突。下文将讨论如何检查Camera硬件、如何申请对Camera的访问,如何采集图片和视频,以及在应用使用完成后如何释放Camera。

警告:在应用程序使用完Camera时,要记住通过调用Camera.release()方法来释放Camera对象。如果你的应用程序没有正确的释放Camera,所有的后续的视图对Camera的访问,包括你自己的应用程序,都会失败,并可能到你的或其他的应用程序关闭。

下面我们就来按照上面的步骤来一步步的制作一个属于自己的相机应用吧!

第一步:检查和访问相机

/** 官方建议的安全地访问摄像头的方法  **/
	public static Camera getCameraInstance(){ 
	    Camera c = null; 
	    try { 
	        c = Camera.open();
	    } 
	    catch (Exception e){
	    Log.d("TAG", "Error is "+e.getMessage());
	    } 
	    return c;
	}
	
	/** 检查设备是否支持摄像头  **/
	private boolean CheckCameraHardware(Context mContext)
	{
		if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA))
		{ 
	        // 摄像头存在 
	        return true; 
	    } else { 
	        // 摄像头不存在 
	        return false; 
	    } 
	}
	

第二步:创建一个预览类。这里我们使用的是官方API中提供的一个基本的预览类:

package com.android.OpenCamera;

import java.io.IOException;

import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/** 一个基本的相机预览界面类    **/
@SuppressLint("ViewConstructor")
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

	/** Camera **/
	private Camera mCamera;
	/** SurfaceHolder **/
	private SurfaceHolder mHolder;
	
	/** CamreaPreview构造函数   **/
	@SuppressWarnings("deprecation")
	public CameraPreview(Context mContext,Camera mCamera) 
	{
		super(mContext);
		this.mCamera=mCamera;
        // 安装一个SurfaceHolder.Callback,
        // 这样创建和销毁底层surface时能够获得通知。
        mHolder = getHolder(); 
        mHolder.addCallback(this); 
        // 已过期的设置,但版本低于3.0的Android还需要
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) 
	{
		
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) 
	{
	   try {
		mCamera.setPreviewDisplay(holder);
		mCamera.startPreview();
		mCamera.setDisplayOrientation(90);
	   } catch (IOException e) {
		Log.d("TAG", "Error is "+e.getMessage());
	   }	
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder arg0) 
	{
		// 如果预览无法更改或旋转,注意此处的事件
        // 确保在缩放或重排时停止预览
        if (mHolder.getSurface() == null){ 
          // 预览surface不存在
          return; 
        } 
        // 更改时停止预览 
        try { 
            mCamera.stopPreview(); 
        } catch (Exception e){ 
          // 忽略:试图停止不存在的预览
        } 
        // 在此进行缩放、旋转和重新组织格式
        // 以新的设置启动预
        try { 
            mCamera.setPreviewDisplay(mHolder); 
            mCamera.setDisplayOrientation(90); 
            mCamera.startPreview(); 
        } catch (Exception e){ 
            Log.d("TAG", "Error is " + e.getMessage()); 
        } 
		
	}

}

第三步:构建预览布局。这里我们使用FrameLayout来加载第二步创建的预览类,使用一个按钮进行拍照并完成储存,使用一个ImageView显示拍照的缩略图,当我们点击这个缩略图时时,系统将会调用相应的程序来打开这个图片。


    
    
    
        

第四步:针对采集监听。其中PictureCallback接口用于处理拍照的结果,这里我们做三件事情,第一,保存图片;第二,显示缩略图;第三、保存当前图片的Uri,以便于系统的访问。ShutterCallback接口用于处理按下快门的事件,这里我们使用MediaPlayer类来播放快门按下时的音效。

/** 拍照回调接口  **/
	private PictureCallback mPicureCallback=new PictureCallback()
	{
		@Override
		public void onPictureTaken(byte[] mData, Camera camera) 
		{
			File mPictureFile = StorageHelper.getOutputFile(StorageHelper.MEDIA_TYPE_IMAGE); 
	        if (mPictureFile == null){  
	            return; 
	        }
	        try { 
	        	/** 存储照片  **/
	            FileOutputStream fos = new FileOutputStream(mPictureFile); 
	            fos.write(mData); 
	            fos.close(); 
	            /** 设置缩略图  **/
	            Bitmap mBitmap=BitmapFactory.decodeByteArray(mData, 0, mData.length);
	            ThumbsView.setImageBitmap(mBitmap);
	            /** 获取缩略图Uri  **/
	            mUri=StorageHelper.getOutputUri(mPictureFile);
	            /**停止预览**/
	            mCamera.stopPreview();
	            /**开始预览**/
	            mCamera.startPreview();
	        } catch (FileNotFoundException e) { 
	        	e.printStackTrace();
	        } catch (IOException e) {
				e.printStackTrace();
			}
		}
	};
	
/** 快门回调接口  **/
    private ShutterCallback mShutterCallback=new ShutterCallback()
    {
		@Override
		public void onShutter() 
		{
			mPlayer=new MediaPlayer();
			mPlayer=MediaPlayer.create(MainActivity.this,R.raw.shutter);
			try {
				mPlayer.prepare();
			} catch (IllegalStateException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			mPlayer.start();
		}
    };

第五步:采集与保存。采集与保存在第四步已经给出了,这里给出一个用于保存文件的辅助类StorageHelper类:

package com.android.OpenCamera;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Environment;

@SuppressLint("SimpleDateFormat")
public final class StorageHelper 
{
   public static final int MEDIA_TYPE_IMAGE=0;
   public static final int MEDIA_TYPE_VIDEO=1;
   
   public static Uri  getOutputUri(File mFile)
   {
	   return Uri.fromFile(mFile);
   }
   
   
   public static File getOutputFile(int mType)
   {
	   File mMediaFileDir=new File(Environment.getExternalStorageDirectory(),"OpenCamera");
	   if(!mMediaFileDir.exists())
	   {
		   if(!mMediaFileDir.mkdir())
			{
			   return null;
			}
	   }
	   File mMediaFile=null;
	   /**  创建文件名   **/
	   String mFileName=new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
	   switch(mType)
	   {
	     case MEDIA_TYPE_IMAGE:
	    	 mMediaFile=new File(mMediaFileDir.getPath()+File.separator+"IMG_"+mFileName+".jpg");
	    	 break;
	     case MEDIA_TYPE_VIDEO:
	    	 mMediaFile=new File(mMediaFileDir.getPath()+File.separator+"VID_"+mFileName+".mp4");
	    	 break; 
	    	 default:
	    		 mMediaFile=null;
	   }
	   return mMediaFile;
   }
}

第六步:相机的释放

if (mCamera != null){ 
            mCamera.release();
            mCamera = null; 
        } 

通过以上步骤,我们就完成了一个简单的相机,最后给出主要的逻辑代码:

package com.android.OpenCamera;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {

	
	/** 相机   **/
	private Camera mCamera;  
	/** 预览界面  **/
	private CameraPreview mPreview;
	/** 缩略图  **/
	ImageView ThumbsView;
	/** 当前缩略图Uri **/
	private Uri mUri;
	/** MediaPlayer **/
	private MediaPlayer mPlayer;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		/** 隐藏标题栏  **/
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		/** 隐藏状态栏  **/
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        /** 禁用锁屏,因为再次唤醒屏幕程序就会终止,暂时不知道怎么解决  **/
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		setContentView(R.layout.activity_main);
		
		/** 硬件检查  **/
		if(CheckCameraHardware(this)==false)
		{
			Toast.makeText(this, "很抱歉,您的设备可能不支持摄像头功能!", Toast.LENGTH_SHORT).show();
			return;
		}
		
		/** 获取相机  **/
		mCamera=getCameraInstance();
		/** 获取预览界面   **/
        mPreview = new CameraPreview(this, mCamera); 
        FrameLayout mFrameLayout = (FrameLayout)findViewById(R.id.PreviewView); 
        mFrameLayout.addView(mPreview); 
        mCamera.startPreview();
        /** 拍照按钮  **/
        Button BtnCapture = (Button) findViewById(R.id.BtnCapture);
        BtnCapture.setOnClickListener(new OnClickListener()
        {
			@Override
			public void onClick(View v) 
			{
				/** 使用takePicture()方法完成拍照  **/
				mCamera.autoFocus(new AutoFocusCallback()
				{
					/** 自动聚焦聚焦后完成拍照  **/
					@Override
					public void onAutoFocus(boolean isSuccess, Camera camera) 
					{
						if(isSuccess&&camera!=null)
						{
							mCamera.takePicture(mShutterCallback, null, mPicureCallback);
						}
					}
					
				});
			}
        });
        
        /** 相机缩略图  **/
        
        ThumbsView = (ImageView)findViewById(R.id.ThumbsView);
        ThumbsView.setOnClickListener(new OnClickListener()
        {
			@Override
			public void onClick(View v) 
			{
				/** 使用Uri访问当前缩略图  **/
				Intent intent = new Intent(Intent.ACTION_VIEW); 
			    intent.setDataAndType(mUri, "image/*");
			    startActivity(intent);
			}
        });
	}
	/** 快门回调接口  **/
    private ShutterCallback mShutterCallback=new ShutterCallback()
    {
		@Override
		public void onShutter() 
		{
			mPlayer=new MediaPlayer();
			mPlayer=MediaPlayer.create(MainActivity.this,R.raw.shutter);
			try {
				mPlayer.prepare();
			} catch (IllegalStateException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			mPlayer.start();
		}
    };
    
	/** 拍照回调接口  **/
	private PictureCallback mPicureCallback=new PictureCallback()
	{
		@Override
		public void onPictureTaken(byte[] mData, Camera camera) 
		{
			File mPictureFile = StorageHelper.getOutputFile(StorageHelper.MEDIA_TYPE_IMAGE); 
	        if (mPictureFile == null){  
	            return; 
	        }
	        try { 
	        	/** 存储照片  **/
	            FileOutputStream fos = new FileOutputStream(mPictureFile); 
	            fos.write(mData); 
	            fos.close(); 
	            /** 设置缩略图  **/
	            Bitmap mBitmap=BitmapFactory.decodeByteArray(mData, 0, mData.length);
	            ThumbsView.setImageBitmap(mBitmap);
	            /** 获取缩略图Uri  **/
	            mUri=StorageHelper.getOutputUri(mPictureFile);
	            /**停止预览**/
	            mCamera.stopPreview();
	            /**开始预览**/
	            mCamera.startPreview();
	        } catch (FileNotFoundException e) { 
	        	e.printStackTrace();
	        } catch (IOException e) {
				e.printStackTrace();
			}
		}
	};
	
	
	/** 官方建议的安全地访问摄像头的方法  **/
	public static Camera getCameraInstance(){ 
	    Camera c = null; 
	    try { 
	        c = Camera.open();
	    } 
	    catch (Exception e){
	    Log.d("TAG", "Error is "+e.getMessage());
	    } 
	    return c;
	}
	
	/** 检查设备是否支持摄像头  **/
	private boolean CheckCameraHardware(Context mContext)
	{
		if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA))
		{ 
	        // 摄像头存在 
	        return true; 
	    } else { 
	        // 摄像头不存在 
	        return false; 
	    } 
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		if (mCamera != null){ 
            mCamera.release();
            mCamera = null; 
        } 
	}

	@Override
	protected void onResume() 
	{
		super.onResume();
		if(mCamera == null)
        {
            mCamera = getCameraInstance();
            mCamera.startPreview(); 	
        }
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

这里一直有一个困惑我的问题,就是当手机锁屏以后再次唤醒屏幕,程序会立即终止。因为找不到原因,所以目前只能用代码控制设备禁止锁屏来避免这个问题的发生。这个问题和 《 Android开发学习之基于ZBar实现微信扫一扫》是一样的,留给大家去思考了。记得给程序加入以下权限:


        
        

晚上看到Camera360开放了SDK的消息。主推拍照和滤镜API,其实开源的滤镜有很多啦,结合我们今天的例子,我们完全可以做出一个不错的相机应用来,作为长期受益于开源社区的回报,我决定在整合滤镜以后把全部的程序开源,希望大家支持我哦。唉,明天要考试了,我竟然还能如此充满激情的编程,其实这就是编程的魅力啦,在编程的世界里,我可以完全按照自己的意愿去做自己喜欢的,而这就够了,当老师和同学们都觉得我在抱怨专业课的时候,其实我只是想让自己的内心感到快乐而已。好了,晚安,各位!

Android开发学习之以CameraAPI方式实现相机功能(一)——快速实现相机_第1张图片

你可能感兴趣的:(Android开发学习之以CameraAPI方式实现相机功能(一)——快速实现相机)