在Api21中Camera类被废弃,取而代之的是camera2包。相对来说,camera2比Camera使用起来看似复杂了好多,但是在灵活性方面增加了很多。不过出于兼容性的考虑,加上当前Android设备5.0以下的占比还是比较大的,所以在相机开发的过程中,Camera类还是不得不掌握的。在本文中,讲解的是Camera的基本使用。而关于camera2的使用,在以后的文章中会单独再讲。
拍照步骤
- 添加相机相关权限
- 通过
Camera.open(int)
获得一个相机实例 - 利用
camera.getParameters()
得到相机实例的默认设置Camera.Parameters - 如果需要的话,修改Camera.Parameters并调用
camera.setParameters(Camera.Parameters)
来修改相机设置 - 调用
camera.setDisplayOrientation(int)
来设置正确的预览方向 - 调用
camera.setPreviewDisplay(SurfaceHolder)
来设置预览,如果没有这一步,相机是无法开始预览的 - 调用
camera.startPreview()
来开启预览,对于拍照,这一步是必须的 - 在需要的时候调用
camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)
来拍摄照片 - 拍摄照片后,相机会停止预览,如果需要再次拍摄多张照片,需要再次调用
camera.startPreview()
来重新开始预览 - 调用
camera.stopPreview()
来停止预览 - 一定要在onPause()的时候调用
camera.release()
来释放camera,在onResume中重新开始camera
相机权限
使用相机进行拍照,需要添加以下权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
拍摄的照片需要存储到内存卡的话,还需要内存卡读写的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
拍照注意事项
根据拍照步骤就可以使用相机进行拍照,但是在使用过程中,有诸多需要注意的地方。
开启相机
开启相机直接调用Camera.open(),理论上就可以得到一个相机实例。但是在开启相机前,最好check一下。虽然目前基本上所有的手机都是前后摄像头,但是也不排斥有些奇葩的手机,只有后摄像头,或者干脆没有摄像头的。所有在open一个camera前,先检查是否存在该id的camera。Camera.getNumberOfCameras()
可以获得当前设备的Camera的个数N,N为0表示不支持摄像头,否则对应的Camera的ID就是0—(N-1)。
相机设置
Camera的Parameters提供了诸多属性,可以对相机进行多种设置,包括预览大小及格式、照片大小及格式、预览频率、拍摄场景、颜色效果、对焦方式等等,具体设置可参考官方手册。
拍照尤其需要注意的是对预览大小、照片大小以及对焦方式的设置。
在对预览大小、照片大小及对焦方式设置时,设置的值必须是当前设备所支持的。否则,预览大小和照片大小设置会无效,对焦方式设置会导致崩溃。它们都有相应的方法,获取相应的支持的列表。对应的依次为getSupportedPictureSizes()
,getSupportedPictureSizes()
,getSupportedFocusModes()
。
相机预览方向
不对相机预览方向和应用方向设置,通常情况下得到的预览结果是无法接受的。一般应用设置的方向为固定竖向,预览设置旋转90度即可。严谨点来说,预览方向的设置是根据当前window的rotation来设置的,即((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()
的值。在Surface.ROTATION_0
和Surface.ROTATION_180
时,Camera设置displayOrientation为90,否则设置为0。
预览View的设置
相机预览前,必须调用camera.setPreviewDisplay(SurfaceHolder)
来设置预览的承载。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情况下,如果不对显示的View大小做合理的设置,预览中的场景都会被变形。
如何保证预览不变形呢?预览效果变形是因为设置的previewSize和预览View的长宽比例不同造成的,将预览View的长宽比设置的与Camera的previewSize相同(需要注意的是,竖屏下是预览View的长宽比,要设置的与Camera的previewSize的宽长比相同)即可解决变形的问题。
以全屏预览为例,假如手机屏幕分辨率为1280*720,相机支持的预览大小没有1280*720的,也没有这个比例的,但是有1280*960的,相机预览大小选的也是这个。这时候,将预览View的大小设置为1280*960即可,超出屏幕的部分不予理会。值得注意的是,如果预览View的父布局是RelativeLayout,设置宽度大于父布局是无效的。可以重写RelativeLayout的onMeasure来实现,或者将父布局改为FrameLayout。
拍照监听及图片处理
相机拍照时在预览时,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)
(或其重载方法)来实现拍照的,其中第一个参数表示图像捕获时刻的回调。可以看到此方法的后三个参数类型是一样的,都是图像回调。分别表示原始图数据回调、展示图像数据的回调、JPEG图像数据的回调。图像回调中得到byte数组decode为image后,图片的方向都是摄像头原始的图像方向。
可以通过parameters.setRotation(int)
来改变最后一个回调中图像数据的方向。个人推荐不设置,直接在回调中利用矩阵变换统一处理。因为利用parameters.setRotation(int)
来旋转图像,在不同手机上会有差异。
与预览View设置类似,pictureSize设置的值,影响了最后的拍照结果,处理时需要对拍照的结果进行裁剪,使图片结果和在可视区域预览的结果相同。前摄像头拍摄的结果还需要做对称变换,以保证“所见即所得”。
拍照示例
首先需要一个相机的控制类CameraKitKat,继承自ACamera,ACamera是一个抽象类,具有open(int),close()方法,控制相机的开启和关闭。这样做主要是为了后面扩展使用Camera2。CameraKitKat源码如下:
public class CameraKitKat extends ACamera{
private Camera camera;
private SurfaceHolder holder;
private float displayScale;
public CameraKitKat(SurfaceView surfaceView) {
super(surfaceView);
init();
}
private void init(){
holder=displayView.getHolder();
}
@Override
public void open(int type){
int rotation=((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getRotation();
if(!openCamera(type))return;
setParameters(camera,rotation);
setDisplayOrientation(camera,rotation);
setPreviewDisplay(camera,holder);
camera.startPreview();
}
@Override
public void close(){
camera.stopPreview();
camera.release();
}
//调整SurfaceView的大小
private void resizeDisplayView(){
Camera.Parameters parameters=camera.getParameters();
Camera.Size size=parameters.getPreviewSize();
FrameLayout.LayoutParams p= (FrameLayout.LayoutParams) displayView.getLayoutParams();
float scale=size.width/(float)size.height;
displayScale=displayView.getHeight()/(float)displayView.getWidth();
if(scale>displayScale){
p.height= (int) (scale*displayView.getWidth());
p.width=displayView.getWidth();
}else{
p.width= (int) (displayView.getHeight()/scale);
p.height=displayView.getHeight();
}
Log.e("wuwang","-->"+size.width+"/"+size.height);
Log.e("wuwang","--<"+p.height+"/"+p.width);
displayView.setLayoutParams(p);
displayView.invalidate();
}
private boolean checkCameraId(int cameraId){
return cameraId>=0&&cameraId<Camera.getNumberOfCameras();
}
//相机使用第一步,打开相机,获得相机实例
private boolean openCamera(int cameraId){
if(!checkCameraId(cameraId))return false;
camera=Camera.open(cameraId);
return true;
}
//相机使用第二步,设置相机实例参数
//TODO :里面还存在问题,需要修改
private void setParameters(Camera camera,int rotation){
Camera.Parameters parameters=camera.getParameters();
//PreviewSize设置为设备支持的最高分辨率
final Camera.Size size=Collections.max(camera.getParameters().getSupportedPreviewSizes(),new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return lhs.width*lhs.height-rhs.width*rhs.height;
}
});
parameters.setPreviewSize(size.width,size.height);
//PictureSize设置为和预览大小最近的
Camera.Size picSize=Collections.max(parameters.getSupportedPictureSizes(), new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return (int) (Math.sqrt(Math.pow(size.width-rhs.width,2)+Math.pow(size.height-rhs.height,2))-
Math.sqrt(Math.pow(size.width-lhs.width,2)+Math.pow(size.height-lhs.height,2)));
}
});
parameters.setPictureSize(picSize.width,picSize.height);
//如果相机支持自动聚焦,则设置相机自动聚焦,否则不设置
if(parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)){
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
//设置颜色效果
// parameters.setColorEffect(Camera.Parameters.EFFECT_MONO);
camera.setParameters(parameters);
resizeDisplayView();
}
//相机使用第三步,设置相机预览方向
private void setDisplayOrientation(Camera camera,int rotation){
if(rotation== Surface.ROTATION_0||rotation==Surface.ROTATION_180){
camera.setDisplayOrientation(90);
}else{
camera.setDisplayOrientation(0);
}
}
//相机使用第四步,设置相机预览载体SurfaceHolder
private void setPreviewDisplay(Camera camera,SurfaceHolder holder){
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void measureSize(int width, int height) {
super.measureSize(width, height);
}
@Override
public void takePicture() {
super.takePicture();
camera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
if(pictureCallback!=null){
pictureCallback.onPictureTaken(data,displayScale);
}
}
});
}
}
本例中,使用SurfaceView作为预览View,提供SurfaceHolder给Camera。考虑到预览变形问题,使用FrameView作为其父布局。CameraPreview源码如下:
public class CameraPreview extends FrameLayout implements SurfaceHolder.Callback{
private SurfaceView surfaceView;
private ACamera camera;
private boolean isCameraBack=false;
public CameraPreview(Context context) {
this(context,null);
}
public CameraPreview(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
addPreview();
//后续增加CameraLollipop,根据系统版本使用Camera或者Camera2
camera=new CameraKitKat(surfaceView);
setKeepScreenOn(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
camera.measureSize(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void addPreview(){
surfaceView=new SurfaceView(getContext());
surfaceView.getHolder().addCallback(this);
this.addView(surfaceView);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
camera.open(isCameraBack?0:1);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
setKeepScreenOn(false);
camera.close();
}
public boolean isCameraBack(){
return isCameraBack;
}
public void setOnPictureCallback(PictureCallback pictureCallback){
camera.setOnPictureCallback(pictureCallback);
}
public void takePicture(){
camera.takePicture();
}
}
在拍照的Activity中,置入CameraPreview,长宽都设置为match_parent,然后增加一个拍照的按钮。增加拍照监听,并在监听中对图像数据进行处理即可。Activity的源码如下:
public class MainActivity extends Activity implements View.OnClickListener{
private View btnTake;
private ImageView ivShower;
private CameraPreview cameraPreview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btnTake).setOnClickListener(this);
ivShower= (ImageView) findViewById(R.id.ivShower);
cameraPreview= (CameraPreview) findViewById(R.id.cameraView);
cameraPreview.setOnPictureCallback(new PictureCallback() {
@Override
public void onPictureTaken(byte[] data,float scale) {
ivShower.setVisibility(View.VISIBLE);
Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length);
Bitmap bitmap2=rotateAndCropBitmap(bitmap,cameraPreview.isCameraBack()?90:-90,scale);
saveBitmapToPath(bitmap2,Environment.getExternalStorageDirectory()+"/temp.jpeg");
ivShower.setImageBitmap(bitmap2);
bitmap.recycle();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
File file=new File(Environment.getExternalStorageDirectory()+"/temp.jpeg");
if(file.exists()){
file.delete();
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnTake:
cameraPreview.takePicture();
break;
default:
break;
}
}
private Bitmap rotateAndCropBitmap(Bitmap bm,int orientationDegree,float rate){
//TODO : 貌似有些问题
int width,height;
float bmScale=bm.getHeight()/(float)bm.getWidth();
if(rate==bmScale)return bm;
else if(rate>bmScale){
width=bm.getWidth();
height= (int) (width/rate);
}else{
height= bm.getHeight();
width= (int) (height*rate);
}
Matrix m = new Matrix();
if(orientationDegree==-90){ //前摄像头,则左右镜像
m.postScale(1,-1);
}
m.postRotate(orientationDegree);
return Bitmap.createBitmap(bm,0,bm.getHeight()-height,width,height,m,true);
}
private void saveBitmapToPath(Bitmap bitmap,String path){
File file=new File(path);
try {
FileOutputStream fos=new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,90,fos);
fos.flush();
fos.close();
Log.e("wuwang","filePath-->"+file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}