Android 开发 Camera类的拍照与录像

眼前多少难甘事,自古男儿当自强。这篇文章主要讲述Android 开发 Camera类的拍照与录像相关的知识,希望能为你提供帮助。
前言在开发android应用的时候,如果需要调用摄像头拍照或者录像,除了通过Intent调用系统现有相机应用进行拍照录像之外,还可以通过直接调用Camera硬件去去获取摄像头进行拍照录像的操作。本篇博客将讲解如何在Android应用中通过Camera拍照功能.
录像功能因为需要与MediaRecorder配使用,反而是更偏向操作MediaRecorder,所以我把录像功能放到了音视频开发篇幅里,请参考以下博客了解录像功能开发.
MediaRecorder视频录制入门:https://www.cnblogs.com/guanxinjing/p/10980906.html
MediaRecorder与Camera1配合使用:https://www.cnblogs.com/guanxinjing/p/10986766.html
拍照开发 流程

  1. 获取权限
  2. 初始化曲面视图View(SurfaceView或者TextureView)(用于显示相机预览图像)
  3. 初始化打开相机,选择前后摄像头
  4. 配置相机参数
  5. 拍照
  6. 处理照片返回数据,旋转照片与压缩照片
获取权限
< !-- 相机相关 --> < uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> < uses-permission android:name="android.permission.CAMERA" /> < uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> < uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

拍照TextureView例子其实目前最适合相机预览图像显示使用的是TextureView,因为它是真正独立刷新帧数的View,可以让相机预览减少卡顿问题
而使用TextureView需要开启硬件加速功能.开启硬件加速方法如下:
在AndroidManifest.xml 清单文件里,你需要实现相机功能的activity添加 android:hardwareAccelerated="true"
< activity android:name=".work.share.FaceCameraActivity" android:hardwareAccelerated="true"> < /activity>

以下是代码部分:
 
public class FaceCameraActivity extends BaseActivity implements TextureView.SurfaceTextureListener,Camera.PictureCallback , View.OnClickListener{ private static final float PICTURE_SIZE_PROPORTION = 1.1f; //目标分辨率尺寸 private TextureView mTextureView; private Camera mCamera; private Button mBtnCamera; private boolean mMoveTakingPhotos = false; //用于防止连续点击拍照多次引起报错的问题@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initCamera(); initCameraParameters(); }@Override public int getLayout() { return R.layout.activity_face_camera; }@Override public void initView() { mTextureView = findViewById(R.id.texture_view); mBtnCamera = findViewById(R.id.btn_camera); mBtnCamera.setOnClickListener(this); mTextureView.setSurfaceTextureListener(this); //添加监听,用于监听TextureView的创建/变化/销毁}@Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_camera: if(!mMoveTakingPhotos){ mMoveTakingPhotos = true; mCamera.takePicture(null,null,this); //拍摄拍照 参数为快门图片回调/原始图片回调(未压缩)/jpeg图片回调 } break; default: break; }}/** * 初始化打开相机 */ private void initCamera(){ if (mCamera == null){ //大多数情况下:0是后置摄像头 1是前置摄像头 ,这里demo就不弄这么复杂,在下面的会提供选择前后摄像头的方法 mCamera = Camera.open(1); }}/** * 初始化相机参数 */ private void initCameraParameters(){ Camera.Parameters parameters = mCamera.getParameters(); //parameters.getPreviewSize(); //当前预览尺寸 //parameters.getPictureSize(); //当前分辨率尺寸 //parameters.getJpegThumbnailSize(); //返回当前jpeg图片中exif缩略图的尺寸//parameters.getSupportedPreviewSizes(); //预览尺寸List //parameters.getSupportedPictureSizes(); //分辨率尺寸List //parameters.getSupportedJpegThumbnailSizes(); //返回当前jpeg图片中exif缩略图的尺寸ListDisplayMetrics displayMetrics = getResources().getDisplayMetrics(); Camera.Size previewSize = getpreviewSize(parameters, displayMetrics.heightPixels, displayMetrics.widthPixels); //获取最接近屏幕分辨率的的预览尺寸 Camera.Size pictureSize = getPictureSize(parameters, PICTURE_SIZE_PROPORTION); //获取对应比例的最大分辨率 parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); //设置关闭闪光灯 parameters.setFocusMode(Camera.Parameters.FLASH_MODE_AUTO); //对焦设置为自动 parameters.setPictureFormat(PixelFormat.JPEG); //拍照格式 parameters.setPreviewSize(previewSize.width, previewSize.height); //设置预览尺寸 parameters.setPictureSize(pictureSize.width, pictureSize.height); //分辨率尺寸 parameters.set("orientation", "portrait"); //相片方向 parameters.set("rotation", 90); //相片镜头角度转90度(默认摄像头是横拍) mCamera.setParameters(parameters); //添加参数 mCamera.setDisplayOrientation(90); //设置显示方向 }/** * 计算获得最接近比例的预览尺寸 * @param parameters * @param height * @param width * @return */ private Camera.Size getpreviewSize(Camera.Parameters parameters, int height, int width){ List< Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes(); Camera.Size selectPreviewSize = null; //缓存当前最准确比例的Size float currentDifference = 0; //缓存当前最小的差值 /** * 下面这行代码是求传入高度和宽度的高宽比例,这里可以发现一个细节我下面的预览尺寸求的的宽高比例. * 是的他们一个是高宽比一个是宽高比,说明为什么这样,因为如果按照2个都是高宽比来获得预览尺寸你会发现,获得的尺寸怎么都有可能会拉伸变形(除非狗屎运尺寸完美刚好) * 最好的办法就是,不求最合适目标尺寸的长方形比例,而求一个最适合目标尺寸的正方形比例,这样拉伸变形就不会出现了 */ float proportion = (float)height/(float)width; for (int i = 0; i < previewSizeList.size(); i++){ Camera.Size size = previewSizeList.get(i); float previewSizeProportion = ((float)size.width)/((float)size.height); //计算当前预览尺寸的宽高比例 float tempDifference = Math.abs(previewSizeProportion - proportion); //相减求绝对值的差值 if(i == 0){ selectPreviewSize = size; currentDifference = tempDifference; continue; } if (tempDifference < = currentDifference){ //获得最小差值 if (tempDifference == currentDifference){ //如果差值一样 if ((selectPreviewSize.width + selectPreviewSize.height) < (size.width+size.height)){ //判断那个尺寸大保留那个 selectPreviewSize = size; currentDifference = tempDifference; }}else { //如果差值更小更准确 selectPreviewSize = size; currentDifference = tempDifference; } } L.e("currentDifference="+currentDifference +"width="+selectPreviewSize.width+"height="+selectPreviewSize.height); } return selectPreviewSize; }/** * 计算获得最接近比例的分辨率 * @param parameters * @param targetProportion * @return */ private Camera.Size getPictureSize(Camera.Parameters parameters, float targetProportion) { List< Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes(); Camera.Size selectPreviewSize = null; float currentDifference = 0; for (int i = 0; i < pictureSizeList.size(); i++) { Camera.Size size = pictureSizeList.get(i); L.e("分辨率列表_" + i + "_width=" + size.width + "height=" + size.height); float pictureSizeProportion = ((float) size.width) / ((float) size.height); L.e("分辨率列表_" + i + "_比例=" + pictureSizeProportion); float tempDifference = Math.abs(pictureSizeProportion - targetProportion); if (i == 0) { selectPreviewSize = size; currentDifference = tempDifference; continue; } if (tempDifference < = currentDifference) { if (tempDifference == currentDifference) { if ((selectPreviewSize.width + selectPreviewSize.height) < (size.width + size.height)) { //判断那个尺寸大保留那个 selectPreviewSize = size; currentDifference = tempDifference; } } else { //如果差值更小更准确 selectPreviewSize = size; currentDifference = tempDifference; } }} L.e("当前选择分辨率width=" + selectPreviewSize.width + "height=" + selectPreviewSize.height); return selectPreviewSize; }/** * 照片拍照完成后的回调方法 * @param data * @param camera */ @Override public void onPictureTaken(final byte[] data, Camera camera) { //注意!此处返回是主线程,而处理图片是耗时操作需要放到子线程里处理 handlerImageWaitDialog().show(); new Thread(new Runnable() { @Override public void run() { try { //这里有一个坑,如果你想要读取照片的角度信息,那么就需要直接吧byte[] data的照片数据先保存成图片文件在从文件读成Bitmap //因为如果你先处理压缩图片或者裁剪图片,只要是Bitmap.createBitmap处理过就都有可能丢失这些照片信息到时候你怎么获取角度都是0 FilePathSession.deleteFaceImageFile(); FileOutputStream fileOutputStream = new FileOutputStream(FilePathSession.getFaceImagePath()); fileOutputStream.write(data,0,data.length); fileOutputStream.flush(); fileOutputStream.close(); int angle = readPictureDegree(FilePathSession.getFaceImagePath().toString()); //获取角度 Bitmap bitmap = BitmapFactory.decodeFile(FilePathSession.getFaceImagePath().toString()); //重新在文件里获取图片 Matrix matrix = new Matrix(); //创建矩阵配置类,用与设置旋转角度和旋转位置 matrix.setRotate(angle, bitmap.getWidth(), bitmap.getHeight()); //设置旋转角度和旋转位置 Bitmap handlerAngleBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true); ImageHandle.bitmapImageConfig(handlerAngleBitmap)//这个是个人写的压缩图片工具类 .setTargetKB(200) .setSize(1080f,1920f) .setHandleListener(new BitmapImageHandleListener() { @Override public boolean onReady(Bitmap inpBitmap) { return true; }@Override public void onSuccess(Bitmap outBitmap) { try { FileOutputStream fileOutputStream = new FileOutputStream(FilePathSession.getFaceImagePath()); outBitmap.compress(Bitmap.CompressFormat.JPEG,90,fileOutputStream); fileOutputStream.flush(); fileOutputStream.close(); outBitmap.recycle(); runOnUiThread(new Runnable() { @Override public void run() { handlerImageWaitDialog().dismiss(); Intent startFaceConfirm = new Intent(FaceCameraActivity.this, FaceConfirmActivity.class); startActivity(startFaceConfirm); FaceCameraActivity.this.finish(); } }); } catch (IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { mMoveTakingPhotos = false; handlerImageWaitDialog().dismiss(); Toast.makeText(FaceCameraActivity.this, "图像压缩处理失败", Toast.LENGTH_SHORT).show(); } }); }}@Override public void onFailure(String text) { runOnUiThread(new Runnable() { @Override public void run() { mMoveTakingPhotos = false; handlerImageWaitDialog().dismiss(); Toast.makeText(FaceCameraActivity.this, "图像压缩处理失败", Toast.LENGTH_SHORT).show(); } }); }@Override public void onError(Exception e) { runOnUiThread(new Runnable() { @Override public void run() { mMoveTakingPhotos = false; handlerImageWaitDialog().dismiss(); Toast.makeText(FaceCameraActivity.this, "图像压缩处理失败", Toast.LENGTH_SHORT).show(); } }); } }).build(); } catch (FileNotFoundException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { mMoveTakingPhotos = false; handlerImageWaitDialog().dismiss(); Toast.makeText(FaceCameraActivity.this, "图像压缩处理失败", Toast.LENGTH_SHORT).show(); } }); } catch (IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { mMoveTakingPhotos = false; handlerImageWaitDialog().dismiss(); Toast.makeText(FaceCameraActivity.this, "图像压缩处理失败", Toast.LENGTH_SHORT).show(); } }); } } }).start(); }/** * TextureView的创建完成后的可用状态回调 */ @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { //可用 try { mCamera.setPreviewTexture(surface); //给相机添加预览图像的曲面View mCamera.startPreview(); //启动图像预览 } catch (IOException e) { e.printStackTrace(); }}@Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { //尺寸变化}@Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { //销毁 return false; }@Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { //更新}/** * 读取照片旋转角度 * * @param path 照片路径 * @return 角度 */ public int readPictureDegree(String path) { int degree = 0; try { ExifInterface exifInterface = new ExifInterface(path); int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: L.e("触发 90度"); degree = 90; degree = degree + 90; //这里我是直接处理要增加或者减少的角度,让图片竖起来 break; case ExifInterface.ORIENTATION_ROTATE_180: L.e("触发 180度"); degree = 180; degree = degree + 0; break; case ExifInterface.ORIENTATION_ROTATE_270: L.e("触发 270度"); degree = 270; degree = degree - 90; break; default: L.e("触发 0度"); degree = 0; degree = degree + 180; break; } } catch (IOException e) { e.printStackTrace(); } return degree; }@Override protected void onResume() { super.onResume(); //try { //mCamera.reconnect(); //相机重连接 //mCamera.startPreview(); //} catch (IOException e) { //e.printStackTrace(); //} }@Override protected void onStop() { super.onStop(); //mCamera.stopPreview(); 暂停预览 }@Override protected void onDestroy() { super.onDestroy(); if (mCamera != null){ mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } }

  选择前后摄像头的代码
/** * 选择摄像头 * @param isFacing true=前摄像头 false=后摄像头 * @return 摄像id */ private Integer selectCamera(boolean isFacing){ int cameraCount = Camera.getNumberOfCameras(); //CameraInfo.CAMERA_FACING_BACK 后摄像头 //CameraInfo.CAMERA_FACING_FRONT前摄像头 int facing = isFacing ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK; Log.e(TAG, "selectCamera: cameraCount="+cameraCount); if (cameraCount == 0){ Log.e(TAG, "selectCamera: The device does not have a camera "); return null; } Camera.CameraInfo info = new Camera.CameraInfo(); for (int i=0; i < cameraCount; i++){ Camera.getCameraInfo(i,info); if (info.facing == facing){ return i; }} return null; }

  如果你需要切换摄像头
mCamera.stopPreview(); //暂停预览 mCamera.release(); //释放摄像头 这个是关键 openCamera(selectCamera(mCurrentCameraFacing)); //重新选择摄像头并且打开 configCameraParameters(); //重新配置摄像头参数 startPreview(mSurfaceTexture); //开启预览

拍照SurfaceView例子
package com.demo; import androidx.appcompat.app.AppCompatActivity; import android.graphics.PixelFormat; import android.hardware.Camera; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import com.example.user.demo.R; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class TakePictureActivity extends AppCompatActivity implements SurfaceHolder.Callback,Camera.PictureCallback { private static final String TAG = "TakePictureActivity"; private Button mBtnTake; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private Camera mCamera; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_take_picture); mBtnTake = (Button)findViewById(R.id.take); mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView); init(); mBtnTake.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mCamera != null){ mCamera.takePicture(null,null,TakePictureActivity.this); } } }); }@Override protected void onDestroy() { super.onDestroy(); mSurfaceHolder.removeCallback(this); mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); }private void init(){ mSurfaceHolder = mSurfaceView.getHolder(); mCamera = Camera.open(); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this); Camera.Parameters parameters = mCamera.getParameters(); parameters.setFlashMode("off"); parameters.setPictureFormat(PixelFormat.JPEG); //设定相片格式为JPEG,默认为NV21 parameters.setPreviewSize(640, 480); parameters.set("orientation", "portrait"); //相片方向 parameters.set("rotation", 90); //相片镜头角度转90度(默认摄像头是横拍) mCamera.setParameters(parameters); mCamera.setDisplayOrientation(90); }@Override public void surfaceCreated(SurfaceHolder holder) { Log.e(TAG,"悬浮窗口生成"); try { mCamera.setPreviewDisplay(mSurfaceHolder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); }}@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.e(TAG,"悬浮窗口变化"); }@Override public void surfaceDestroyed(SurfaceHolder holder) { Log.e(TAG,"悬浮窗口销毁"); }@Override public void onPictureTaken(final byte[] data, Camera camera) { Log.e(TAG,"拍照结果处理"); File file = getExternalFilesDir("takePiceture"); if (!file.exists()){ file.mkdirs(); } final File fileName = new File(file,System.currentTimeMillis()+".jpg"); new Thread(new Runnable() { @Override public void run() { try { FileOutputStream fileOutputStream = new FileOutputStream(fileName); fileOutputStream.write(data); fileOutputStream.close(); mCamera.startPreview(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }).start(); }}

Camera api 说明

Camera是Android摄像头硬件的相机类,位于硬件包"android.hardware.Camera"下。它主要用于摄像头捕获图片、启动/停止预览图片、拍照、获取视频帧等,它是设备本地的服务,负责管理设备上的摄像头硬件。
【Android 开发 Camera类的拍照与录像】Camera既然用于管理设备上的摄像头硬件,那么它也为开发人员提供了相应的方法,并且这些方法大部分都是native的,用C++在底层实现,下面简单介绍一下Camera的一些方法:
api 说明
open() 打开Camera,返回一个Camera实例。
open(int cameraId) 根据cameraId打开一个Camera,返回一个Camera实例。
release() 释放掉Camera的资源。
getNumberOfCameras() 获取当前设备支持的Camera硬件个数。
getParameters() 获取Camera的各项参数设置类。
setParameters(Camera.Parameters params) 通过params把Camera的各项参数写入到Camera中。
setDisplayOrientation(int degrees) 摄像预览的旋转度。
setPreviewDisplay(SurfaceHolder holder) 设置Camera预览的SurfaceHolder。
starPreview() 开始Camera的预览。
stopPreview() 停止Camera的预览
setPreviewCallback() 设置预览回调
reconnect() 重新连接
autoFocus(Camera.AutoFocusCallback cb) 自动对焦
cancelAutoFocus() 取消启动对焦
setAutoFocusMoveCallback() 自动对焦移动回调
takePicture(Camera.ShutterCallback shutter,Camera.PictureCallback raw,Camera.PictureCallback jpeg) 拍照。
enableShutterSound() 启用快门声音
lock() 锁定Camera硬件,使其他应用无法访问。
unlock() 解锁Camera硬件,使其他应用可以访问。
startFaceDetection() 启动人脸识别
stopFaceDetection() 停止人脸识别
setFaceDetectionListener() 人脸识别监听回调
setPreviewCallback() 设置预览回调
setPreviewCallbackWithBuffer()

    推荐阅读