在 Android 5.0(SDK 21)中, Google 使用 Camera2 替代了 Camera 接口 Camera2 在接口和架构上做了巨大的变动,
但是基于众所周知的原因, 我们还必须基于 Android 4.+ 系统进行开发本文介绍的是 Camera 接口开发及其使用方法, 通过本文章, 你将全面地学会 Camera 接口的开发流程
Paste_Image.png
调用系统相机 / 其它 App 完成拍摄操作
如果你的 App 的需求只是
调用摄像头拍照并拿到照片
, 老司机的建议是别自己实现拍照模块, 这里面坑多水深你完全可以使用 Intent 来调用系统相机或第三方具备拍照功能的 App 来拍照并获取返回照片数据
创建一个 Intent, 指定两个拍摄类型之一:
MediaStore.ACTION_IMAGE_CAPTURE
拍摄照片;
MediaStore.ACTION_VIDEO_CAPTURE
拍摄视频;
Intent intent = new Intent(MediaStore.ACTION_IMAGE/VIDEO_CAPTURE);
通用流程
startActivityForResult()
和 onActivityResult()就不表述了说说拍摄照片的 Intent 参数吧
首先是设置拍摄后返回数据的地址:
- intent.putExtra(MediaStore.EXTRA_OUTPUT, your - store - uri);
- MediaStore.EXTRA_OUTPUT
参数用于指定拍摄完成后的照片 / 视频的储存路径你可以使用 Android 默认的储存照片目录来保存:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE)
也可以是其它任意你喜欢的储存目录如果你使用了 App 内部目录, 某些临时文件如拍摄并上传的头像文件, 在处理完成后, 要记得将它删除这样做的好处是减少 App 占用储存空间, 手机用户特别喜欢对占用大储存空间的 App 下重手删除和清理空间如果你必须保存大体积的文件, 可以使用公共空间来储存, 把包袱丢出去, 私有空间仅保存应用配置数据
相机其它设置, 如指定拍摄照片的尺寸大小, 照片质量等, 待以后文章更新吧
// TODO 是程序界最大的谎言
使用 Camera 开发照相功能
使用 Camera API 来开发拍照模块需要费一番大功夫下面是介绍我在开发 NextQRCode 项目中使用 Camera API 的方法和流程
1. 在 Android Manifest.xml 中声明相机权限
开发第一步是在 Android Manifest.xml 文件中声明使用相机的权限:
<uses-permission android:name="android.permission.CAMERA" />
有些同学在开发时忘了声明权限, 运行时应用可能会崩溃掉另外也要增加以下两个特性声明:
- <uses-feature android:name="android.hardware.camera" android:required="true"/>
- <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
required 属性是说明这个特性是否必须满足比方说示例的设置就是要求必须拥有相机设备但可以没有自动对焦功能
这两个声明是可选的, 它们用于应用商店 (Google Play) 过滤不支持相机和不支持自动对焦的设备
另外在保存照片时需要写入储存器的权限, 也需要加上读写储存器的权限声明:
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
2. 打开相机设备
现在市面上销售的手机 / 平板等消费产品基本标配两个摄像头如华为 P9, 更有前置双摄像头讲真, 我很好奇开发双摄像头的 App 是怎样的体验在打开相机设备前, 先获取当前设备有多少个相机设备如果你的开发需求里包含切换前后摄像头功能, 可以获取摄像头数量来判断是否存在后置摄像头
int cameras = Camera.getNumberOfCameras();
这个接口返回值为摄像头的数量: 非负整数对应地, 摄像头序号为: cameras - 1 例如在拥有前后摄像头的手机设备上, 其返回结果是 2, 则第一个摄像头的 cameraId 是 0, 通常对应手机背后那个大摄像头; 第二个摄像头的 cameraId 是 1, 通常对应着手机的前置自拍摄像头;
相机是一个硬件设备资源, 在使用设备资源前需要将它打开, 可以通过接口
Camera.open(cameraId)
来打开参考以下代码:
- public static Camera openCamera(int cameraId) {
- try {
- return Camera.open(cameraId);
- } catch(Exception e) {
- return null;
- }
- }
注意
打开相机设备可能会失败, 你一定要检查打开操作是否成功打开失败的可能原因有两种: 一是安装 App 的设备上根本没有摄像头, 例如某些平板或特殊 Android 设备; 二是 cameraId 对应的摄像头正被使用, 可能某个 App 正在后台使用它录制视频
3. 配置相机参数
在打开相机设备后, 你将获得一个 Camera 对象, 并独占相机设备资源
通过
Camera.getParameters()
接口可以获取当前相机设备的默认配置参数下面列举一些我能理解的参数:
闪光灯配置参数, 可以通过
Parameters.getFlashMode()
接口获取当前相机的闪光灯配置参数:
Camera.Parameters.FLASH_MODE_AUTO
自动模式, 当光线较暗时自动打开闪光灯;
Camera.Parameters.FLASH_MODE_OFF
关闭闪光灯;
Camera.Parameters.FLASH_MODE_ON
拍照时闪光灯;
Camera.Parameters.FLASH_MODE_RED_EYE
闪光灯参数, 防红眼模式, 科普一下: 防红眼;
对焦模式配置参数, 可以通过
Parameters.getFocusMode()
接口获取:
Camera.Parameters.FOCUS_MODE_AUTO
自动对焦模式, 摄影小白专用模式;
Camera.Parameters.FOCUS_MODE_FIXED
固定焦距模式, 拍摄老司机模式;
Camera.Parameters.FOCUS_MODE_EDOF
景深模式, 文艺女青年最喜欢的模式;
Camera.Parameters.FOCUS_MODE_INFINITY
远景模式, 拍风景大场面的模式;
Camera.Parameters.FOCUS_MODE_MACRO
微焦模式, 拍摄小花小草小蚂蚁专用模式;
场景模式配置参数, 可以通过
Parameters.getSceneMode()
接口获取:
Camera.Parameters.SCENE_MODE_BARCODE
扫描条码场景, NextQRCode 项目会判断并设置为这个场景;
Camera.Parameters.SCENE_MODE_ACTION
动作场景, 就是抓拍跑得飞快的运动员汽车等场景用的;
Camera.Parameters.SCENE_MODE_AUTO
自动选择场景;
Camera.Parameters.SCENE_MODE_HDR
高动态对比度场景, 通常用于拍摄晚霞等明暗分明的照片;
Camera.Parameters.SCENE_MODE_NIGHT
夜间场景;
Camera API 提供了非常多的参数接口供开发者设置, 有必要的话, 可以翻阅相关 API 文档
在 NextQRCode 项目中, 需要使用到自动对焦的特性在一些机型上可能是没有的自动对焦(虽然比较少见), 需要对这种情况进行处理
4. 设置相机预览方向
相机预览图需要设置正确的预览方向才能正常地显示预览画面, 否则预览画面会被挤压得很惨
在通常情况下, 如果我们需要知道设备的屏幕方向, 可以通过
Resources.Configuration.orientation
来获取 Android 屏幕方向有竖屏和横屏两种, 对应的值分别是
ORIENTATION_PORTRAIT
和
ORIENTATION_LANDSCAPE
但相机设备的方向却有些特别, 设置预览方向的接口
Camera.setDisplayOrientaion(int)
的参数是以角度为单位的, 而且只能是 0,90,180,270 其中之一, 默认为 0, 是指手机的左侧为摄像头顶部画面记得只能是 [090180270] 其中之一, 输入其它角度数值会报错
如果你想让相机跟随设备的方向, 预览界面顶部一直保持正上方, 以下代码供参考:
- public static void followScreenOrientation(Context context, Camera camera) {
- final int orientation = context.getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- camera.setDisplayOrientation(180);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- camera.setDisplayOrientation(90);
- }
- }
5. 预览 View 与拍照
我们一般使用 SurfaceView 作为相机预览 View, 你也可以使用 Texture 在 SurfaceView 中获取得 SurfaceHolder, 并通过 setPreviewDisplay()接口设置预览在设置预览 View 后, 一定要记得以下两点:
调用 startPreview()方法启动预览, 否则预览 View 不会显示任何内容;
拍照操作需要在 startPreview()方法执行之后调用;
每次拍照后, 预览 View 会停止预览所以连续拍照, 需要重新调用 startPreview()来恢复预览;
Camera 接受一个 SurfaceHolder 接口, 这个接口可以通过 SurfaceHolder.Callback 获得我们可以通过继承 SurfaceView 来实现相机预览效果在 NextQRCode 项目中, 实现了 LiveCameraView 类, 它内部已实现了相机预览所需要的处理过程, 很简洁的类, 以下是它的全部源码:
- public class LiveCameraView extends SurfaceView implements SurfaceHolder.Callback {
- private final static String TAG = LiveCameraView.class.getSimpleName();
- private Camera mCamera;
- private SurfaceHolder mSurfaceHolder;
- public LiveCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mSurfaceHolder = this.getHolder();
- mSurfaceHolder.addCallback(this);
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- Log.d(TAG, "Start preview display[SURFACE-CREATED]");
- startPreviewDisplay(holder);
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- if (mSurfaceHolder.getSurface() == null){
- return;
- }
- Cameras.followScreenOrientation(getContext(), mCamera);
- Log.d(TAG, "Restart preview display[SURFACE-CHANGED]");
- stopPreviewDisplay();
- startPreviewDisplay(mSurfaceHolder);
- }
- public void setCamera(Camera camera) {
- mCamera = camera;
- final Camera.Parameters params = mCamera.getParameters();
- params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
- params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE);
- }
- private void startPreviewDisplay(SurfaceHolder holder){
- checkCamera();
- try {
- mCamera.setPreviewDisplay(holder);
- mCamera.startPreview();
- } catch (IOException e) {
- Log.e(TAG, "Error while START preview for camera", e);
- }
- }
- private void stopPreviewDisplay(){
- checkCamera();
- try {
- mCamera.stopPreview();
- } catch (Exception e){
- Log.e(TAG, "Error while STOP preview for camera", e);
- }
- }
- private void checkCamera(){
- if(mCamera == null) {
- throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set");
- }
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- Log.d(TAG, "Stop preview display[SURFACE-DESTROYED]");
- stopPreviewDisplay();
- }
- }
从上面代码可以看出 LiveCameraView 的核心代码是
SurfaceHolder.Callback
的回调: 在创建 / 销毁时启动 / 停止预览动作在 LiveCameraView 类中, 我们利用了 View 的生命周期回调来实现自动管理预览生命周期控制:
当 SurfaceView 被创建后自动开启预览;
当 SurfaceView 被销毁时关闭预览;
当 SurfaceView 尺寸被改变时重置预览;
预览 View 需要注意预览输出画面的尺寸相机输出画面只支持部分尺寸关于尺寸部分, 后面再更新
在启用预览 View 后, 就可以通过
Camera.takePicture()
方法拍摄一张照片, 返回的照片数据通过 Callback 接口获取 takePicture()接口可以获取三个类型的照片:
第一个, ShutterCallback 接口, 在拍摄瞬间瞬间被回调, 通常用于播放咔嚓这样的音效;
第二个, PictureCallback 接口, 返回未经压缩的 RAW 类型照片;
第三个, PictureCallback 接口, 返回经过压缩的 JPEG 类型照片;
我们使用第三个参数, JPEG 类型的照片的图片精度即可满足识别二维码的需求在 NextQRCode 项目中, ZXing 识别二维码的数据格式为 Bitmap, 通过 BitmapFactory 可以很方便方便地将 byte 数组转换成 Bitmap
- public abstract class BitmapCallback implements Camera.PictureCallback {
- @Override
- public void onPictureTaken(byte[] data, Camera camera) {
- onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length));
- }
- public abstract void onPictureTaken(Bitmap bitmap);
- }
详细关于 Android 中 Bitmap 的说明, 请参见文章 Android: Bitmap 与 Drawable 这件小事
如果你需要将照片保存为文件, 可以参考这个类的实现: FilePhotoCallback.java
6. 释放相机设备
在打开一个相机设备后, 意味着你的 App 就独占了这个设备, 其它 App 将无法使用它因此在你不需要相机设备时, 记得调用 release()方法释放设备, 再使用时可以重新打开, 这并不需要多大的成本可以选择在 stopPreview()后即释放相机设备
附加工具性代码实现
1 - 判断手机设备是否有相机设备
- public static boolean hasCameraDevice(Context ctx) {
- return ctx.getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_CAMERA);
- }
2 - 判断是否支持自动对焦
- public static boolean isAutoFocusSupported(Camera.Parameters params) {
- List < String > modes = params.getSupportedFocusModes();
- return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
- }
如何正确地使用 Camera 来开发视频拍摄功能
抱歉, 这个我真没研究过
来源: http://www.bubuko.com/infodetail-2487713.html