眼前多少难甘事,自古男儿当自强。这篇文章主要讲述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
拍照开发
流程
- 获取权限
- 初始化曲面视图View(SurfaceView或者TextureView)(用于显示相机预览图像)
- 初始化打开相机,选择前后摄像头
- 配置相机参数
- 拍照
- 处理照片返回数据,旋转照片与压缩照片
< !-- 相机相关 --> < 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() |
推荐阅读
|