【转】Android Camera 相机开发详解

在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架构上做了巨大的变动,
但是基于众所周知的原因,我们还必须基于 Android 4.+ 系统进行开发。本文介绍的是Camera接口开发及其使用方法,通过本文章,你将全面地学会Camera接口的开发流程。

本图文与GitHubPages原文均为本人原创

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,则第一个摄像头的cameraId0,通常对应手机背后那个大摄像头;第二个摄像头的cameraId1,通常对应着手机的前置自拍摄像头;

相机是一个硬件设备资源,在使用设备资源前需要将它打开,可以通过接口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_PORTRAITORIENTATION_LANDSCAPE。但相机设备的方向却有些特别,设置预览方向的接口Camera.setDisplayOrientaion(int)的参数是以角度为单位的,而且只能是0,90,180,270其中之一,默认为0,是指手机的左侧为摄像头顶部画面。记得只能是[0、90、180、270]其中之一,输入其它角度数值会报错。

如果你想让相机跟随设备的方向,预览界面顶部一直保持正上方,以下代码供参考:

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来开发视频拍摄功能

抱歉,这个我真没研究过。

提供一个链接地址供你参考:Camera开发视频拍摄

关于Camera2

后续再更新Camera2的开发教程


作者:陈小锅链接:https://www.jianshu.com/p/7dd2191b4537來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/exmyth/p/8415436.html

时间: 2024-11-08 17:35:07

【转】Android Camera 相机开发详解的相关文章

Android相机开发详解(一)

Android相机开发详解(一) 请支持原创,尊重原创,转载请注明出处:http://blog.csdn.net/kangweijian(来自kangweijian的csdn博客) Android相机开发能够实现打开相机,前后摄像头切换,摄像预览,保存图片,浏览已拍照图片等相机功能. Android相机开发详解(一)主要实现打开相机,摄像预览,前后置摄像头切换,保存图片等四个功能. Android相机开发详解(二)主要实现翻页浏览相片,触控缩放浏览图片,删除图片,发送图片等四个功能. Andro

Android网页浏览器开发详解(一)

Android网页浏览器开发详解(一) 请支持原创,尊重原创,转载请注明出处:http://blog.csdn.net/kangweijian(来自kangweijian的csdn博客) Android 网页浏览器开发器开发详解(一),主要通过WebView类实现载入网页,刷新网页,向前载入历史网页,向后载入历史网页和缩放网页等五个功能. Android 网页浏览器开发器开发详解(二),主要实现书签和历史记录的保存,删除,编辑等功能. Android 网页浏览器开发器开发详解(三),主要通过As

Android 相机开发详解

在android中应用相机功能,一般有两种:一种是直接调用系统相机,一种自己写的相机.我将分别演示两种方式的使用:第一种:是使用Intent跳转到系统相机,action为:android.media.action.STILL_IMAGE_CAMERA关键代码: Intent intent = new Intent(); //调用照相机 intent.setAction("android.media.action.STILL_IMAGE_CAMERA"); startActivity(i

(转)FS_S5PC100平台上Linux Camera驱动开发详解(一) .

平台linuxstructlinux内核videocam 说明:        理解摄像头驱动需要四个前提:        1)摄像头基本的工作原理和S5PC100集成的Camera控制器的工作原理        2)platform_device和platform_driver工作原理        3)Linux内核V4L2驱动架构        4)Linux内核I2C驱动架构 1. 摄像头工作原理 OV9650/9655是CMOS接口的图像传感器芯片,可以感知外部的视觉信号并将其转换为数

Android高德地图开发详解

这段时间开发的时候用到了高德地图,对高德地图开发有心得体会,现在分享给大家,对我开发过百度地图的我来说,总体来说高德地图Demo,没有百度讲解的详细 个人更偏向于使用百度地图,但是没办发,项目需要使用高德地图,我开发的是定位,更具经纬度添加标记,标记点击事件,以及路线规划废话不多说,上代代码 那么首先导入高德给的jar,包,我开发的是2d地图, 这个包结构图,高德题图api也提供了步骤,就不多说了 下面添加权限,设置key <uses-permission android:name="an

(转)FS_S5PC100平台上Linux Camera驱动开发详解(二)

4-3 摄像头的初始化流程及v4l2子设备驱动 这个问题弄清楚了以后下面就来看获得Camera信息以后如何做后续的处理: 在fimc_init_global调用结束之后我们获得了OV9650的信息,之后在probe函数里面就会继续调用一个函数:fimc_configure_subdev(). 这个函数的实现如下: /*        * Assign v4l2 device and subdev to fimc        * it is called per every fimc ctrl

Android Notification通知栏开发详解

Notification是在你的应用常规界面之外展示的消息.当app让系统发送一个消息的时候,消息首先以图表的形式显示在通知栏.要查看消息的详情需要进入通知抽屉(notificationdrawer)中查看.通知栏和通知抽屉(notificationdrawer)都是系统层面控制的,你可以随时查看,不限制于app. 图 1.通知栏的通知 图 2. notificationdrawer中的通知. Notification 的设计 作为android UI中很重要的组成部分,notification

Android开发经典书籍下载——《Android 4高级编程》《疯狂Android讲义》《Android应用开发详解(郭宏志)》《Android应用案例开发大全》《Android 3D游戏开发技术》

这是我收集的关于android开发方面的经典书籍,高清PDF电子版,可以在我的百度网盘免费下载,希望对需要的朋友有帮助. 目录: <Android 4高级编程>(附完整源代码) <疯狂Android讲义> <Android应用开发详解(郭宏志)> <Android应用案例开发大全> <Android 3D游戏开发技术> <Android内核剖析 柯元旦> <深入理解Android  卷1> <深入理解Android

Android Widget 开发详解(二)

转载请标明出处:http://blog.csdn.net/sk719887916/article/details/47027263 不少开发项目中都会有widget功能,别小瞧了它,他也是android的七大组件之一,对widget陌生的朋友可以阅读下我的上篇文章< Android Widget工作原理详解(一)> 今天我们就实现一个可以滑动的widet,熟悉下一个普通widget的开发. 一 创建AppWidgetProvider 此类是widget的控制核心,主要控制添加,删除,更新等.他