转载: 实现自己的Camera
Android自带的Camera应用虽然可以满足大多数情景,但是其灵活性上还有不足。但是Android允许我们定制自己的Camera。
在Android的hardware包中有一个Camera类。这个类就是获取Camera服务的,可以定制Camera等。
可以通过open()方法获取其实例。
在使用这个类是需要在AndroidManifest.xml文件中加入相应的权限和特性
如:
<uses-permission android:name = "android.permission.CAMERA" />
<uses-feature android:name = "android.hardware.camera" />
<uses-feature android:name = "android.hardware.camera.autofocus" />
本文实例:
package demo.camera;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.content.ContentValues;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.LinearLayout;
/**
* Android自带的Camera应用程序可以完成很多功能。但是当其不能满足我们需要的时候
* 我们可以定制自己的Camera。Android提供了Camera类来辅助我们实现自己的Camera。
* 这个例子就来定义一个自己的Camera
* 首先,在Manifest中需要引入权限<uses-permission android:name="android:permission.CAMERA"/>
* 我们需要用来存放取景器的容器,这个容器就是SurfaceView。
* 使用SurfaceView的同时,我们还需要使用到SurfaceHolder,SurfaceHolder相当于一个监听器,可以监听
* Surface上的变化,通过其内部类CallBack来实现。
* 为了可以获取图片,我们需要使用Camera的takePicture方法同时我们需要实现Camera.PictureCallBack类,实现onPictureTaken方法
* @author Administrator
*
*/
public class MyCamera extends Activity implements SurfaceHolder.Callback,Camera.PictureCallback{
public static final int MAX_WIDTH = 200;
public static final int MAX_HEIGHT = 200;
private SurfaceView surfaceView;
private Camera camera; //这个是hardare的Camera对象
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.setContentView(R.layout.camera);
surfaceView = (SurfaceView)this.findViewById(R.id.myCameraView);
surfaceView.setFocusable(true);
surfaceView.setFocusableInTouchMode(true);
surfaceView.setClickable(true);
surfaceView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
camera.takePicture(null, null, null, MyCamera.this);
}
});
//SurfaceView中的getHolder方法可以获取到一个SurfaceHolder实例
SurfaceHolder holder = surfaceView.getHolder();
//为了实现照片预览功能,需要将SurfaceHolder的类型设置为PUSH
//这样,画图缓存就由Camera类来管理,画图缓存是独立于Surface的
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(this);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 当Surface被创建的时候,该方法被调用,可以在这里实例化Camera对象
//同时可以对Camera进行定制
camera = Camera.open(); //获取Camera实例
/**
* Camera对象中含有一个内部类Camera.Parameters.该类可以对Camera的特性进行定制
* 在Parameters中设置完成后,需要调用Camera.setParameters()方法,相应的设置才会生效
* 由于不同的设备,Camera的特性是不同的,所以在设置时,需要首先判断设备对应的特性,再加以设置
* 比如在调用setEffects之前最好先调用getSupportedColorEffects。如果设备不支持颜色特性,那么该方法将
* 返回一个null
*/
try {
Camera.Parameters param = camera.getParameters();
if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE){
//如果是竖屏
param.set("orientation", "portrait");
//在2.2以上可以使用
//camera.setDisplayOrientation(90);
}else{
param.set("orientation", "landscape");
//在2.2以上可以使用
//camera.setDisplayOrientation(0);
}
//首先获取系统设备支持的所有颜色特效,有复合我们的,则设置;否则不设置
List<String> colorEffects = param.getSupportedColorEffects();
Iterator<String> colorItor = colorEffects.iterator();
while(colorItor.hasNext()){
String currColor = colorItor.next();
if(currColor.equals(Camera.Parameters.EFFECT_SOLARIZE)){
param.setColorEffect(Camera.Parameters.EFFECT_SOLARIZE);
break;
}
}
//设置完成需要再次调用setParameter方法才能生效
camera.setParameters(param);
camera.setPreviewDisplay(holder);
/**
* 在显示了预览后,我们有时候希望限制预览的Size
* 我们并不是自己指定一个SIze而是指定一个Size,然后
* 获取系统支持的SIZE,然后选择一个比指定SIZE小且最接近所指定SIZE的一个
* Camera.Size对象就是该SIZE。
*
*/
int bestWidth = 0;
int bestHeight = 0;
List<Camera.Size> sizeList = param.getSupportedPreviewSizes();
//如果sizeList只有一个我们也没有必要做什么了,因为就他一个别无选择
if(sizeList.size() > 1){
Iterator<Camera.Size> itor = sizeList.iterator();
while(itor.hasNext()){
Camera.Size cur = itor.next();
if(cur.width > bestWidth && cur.height>bestHeight && cur.width <MAX_WIDTH && cur.height < MAX_HEIGHT){
bestWidth = cur.width;
bestHeight = cur.height;
}
}
if(bestWidth != 0 && bestHeight != 0){
param.setPreviewSize(bestWidth, bestHeight);
//这里改变了SIze后,我们还要告诉SurfaceView,否则,Surface将不会改变大小,进入Camera的图像将质量很差
surfaceView.setLayoutParams(new LinearLayout.LayoutParams(bestWidth, bestHeight));
}
}
camera.setParameters(param);
} catch (Exception e) {
// 如果出现异常,则释放Camera对象
camera.release();
}
//启动预览功能
camera.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 当Surface被销毁的时候,该方法被调用
//在这里需要释放Camera资源
camera.stopPreview();
camera.release();
}
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// data是一个原始的JPEG图像数据,
//在这里我们可以存储图片,很显然可以采用MediaStore
//注意保存图片后,再次调用startPreview()回到预览
Uri imageUri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
try {
OutputStream os = this.getContentResolver().openOutputStream(imageUri);
os.write(data);
os.flush();
os.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
camera.startPreview();
}
}
精简实例:
import java.io.IOException;
import java.util.List;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
private final static String TAG = "CameraView";
private static int DEF_DISPLAY_ROTATION = 90; // degree
private SurfaceHolder mHolder;
private Camera mCamera;
private Size mPreviewSize;
private List<Size> mSupportedPreviewSizes;
private byte[] mPreviewData = new byte[640*480*3/2];
public CameraView(Context context, AttributeSet set) {
super(context, set);
mCamera = null;
mHolder = this.getHolder();
mHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mHolder = holder;
try {
mCamera = Camera.open(1);
} catch (Exception e) {
e.printStackTrace();
}
if (mCamera != null) {
mCamera.addCallbackBuffer(mPreviewData);
mCamera.setPreviewCallbackWithBuffer(mPreviewCallback);
this.requestLayout();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (null == holder.getSurface()) {
return;
}
mHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.setPreviewCallbackWithBuffer(null);
mCamera.release();
mCamera = null;
mHolder = null;
}
public void setCameraDisplayOrientation(Camera camera, int degree) {
try {
camera.setDisplayOrientation(degree);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mCamera == null) {
return;
}
int width = r - l;
int height = b - t;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
if (null == mPreviewSize || null == mCamera || null == mHolder) {
return;
}
setCameraParam();
try {
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setCameraDisplayOrientation(mCamera, DEF_DISPLAY_ROTATION);
mCamera.startPreview();
}
}
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
Size targetSize = null;
double refRatio = (double) Math.min(w, h) / Math.max(w, h);
double diff = 1;
double tmpDiff = 0;
for (Size size : sizes) {
tmpDiff = Math.abs(refRatio - ((double) Math.min(size.width, size.height) / Math.max(size.width, size.height)));
if (tmpDiff < diff) {
diff = tmpDiff;
targetSize = size;
}
}
return targetSize;
}
private void setCameraParam() {
Camera.Parameters param = mCamera.getParameters();
param.setPreviewFormat(ImageFormat.NV21);
int range[] = new int[2];
param.getPreviewFpsRange(range);
// detect device type for s5
if (android.os.Build.MODEL.equals("SM-G9006V")) {
param.setPreviewFpsRange(25000, range[1]);
} else {
param.setPreviewFpsRange(range[1], range[1]);
}
Log.d(TAG, "camera fps: " + range[0] + ", " + range[1]);
mPreviewSize.width = 640;
mPreviewSize.height = 480;
param.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
try {
mCamera.setParameters(param);
} catch (Exception e) {
Log.d(TAG, "set camera parameter error:" + e);
param.setPreviewFpsRange(range[0], range[1]);
try {
mCamera.setParameters(param);
} catch (Exception e2) {
Log.e(TAG, "set camera parameter error:" + e2);
}
}
}
private PreviewCallback mPreviewCallback = new PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (null != data) {
//process NV21 pixel format image here
mCamera.addCallbackBuffer(data);
}
}
private void turnRotation(int width, int height, byte[] src, byte[] dest) {
final int SUM = width * height;
int h = SUM;
// Y
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
dest[--h] = src[j * width + i];
}
}
h = SUM * 3 / 2;
// UV
for (int i = 0; i < width; i += 2) {
for (int j = 0; j < height / 2; j++) {
dest[--h] = src[SUM + j * width + i + 1];
dest[--h] = src[SUM + j * width + i];
}
}
}
private void turnRotationBack(int width, int height, byte[] src, byte[] dest) {
final int SUM = width * height;
int h = SUM;
// Y
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
dest[j * width + i] = src[--h];
}
}
h = SUM * 3 / 2;
int x = SUM * 3 / 2;
// UV
for (int i = 0; i < width; i += 2) {
for (int j = 0; j < height / 2; j++) {
dest[--x] = src[--h];
dest[--x] = src[--h];
}
}
}
};
}
补充1:NV21(yuv420sp)旋转90度
参考网址: yuv420sp旋转90度
对比YUV420sp和YUV420p数据格式
Android摄像头采集的YUV420sp数据,旋转90度的算法
具体旋转算法待详细总结(包括顺时针和逆时针旋转90度)。。。
补充2:NV21转Bitmap
参考网址: Android摄像头开发:实时摄像头视频预览帧的编码问题(二)
...
camera.addCallbackBuffer(...);
camera.setPreviewCallbackWithBuffer(callback);
...
private PreviewCallback callback = new PreviewCallback() {
public void onPreviewFrame(byte[] mData, Camera camera) {
...
final YuvImage image = new YuvImage(mData, ImageFormat.NV21, w, h, null);
ByteArrayOutputStream os = new ByteArrayOutputStream(mData.length);
if(!image.compressToJpeg(new Rect(0, 0, w, h), 100, os)){
return null;
}
byte[] tmp = os.toByteArray();
Bitmap bmp = BitmapFactory.decodeByteArray(tmp, 0, tmp.length);
...
}
}
具体细节有待总结。。。
相关网址: