精华内容
下载资源
问答
  • Android相机开发

    热门讨论 2013-11-22 11:27:27
    Android相机开发,包括调用系统相机APP拍照、拍摄视频,和自定义相机拍照、拍摄视频。
  • android 相机开发

    2014-07-31 10:32:53
    基于Android 开发相机功能,可拍照存储。
  • Android 相机开发例程

    2014-03-12 06:47:29
    Android 相机开发例程,带有详细的注释说明和工具函数
  • Android相机开发01

    2019-06-01 15:30:00
    Android Camera Android相机开发01简要概述参考 在应用中开启Android设备的相机功能之前,应该考虑如下几个问题: 必须的相机硬件 - 应该在Mainfest文件中声明需要使用到相机的权限 使用已经存在的相机应用还是...

    Android Camera Android相机开发01

    在应用中开启Android设备的相机功能之前,应该考虑如下几个问题:

    1. 必须的相机硬件 - 应该在Mainfest文件中声明需要使用到相机的权限
    2. 使用已经存在的相机应用还是自定义相机
    3. 存储位置 - 生成的图片与视频是只对自己的应用可见还是其它相册Gallery类的应用也可以访问?即使自己的应用被卸载后也不能被其他应用访问吗?
    简要概述

    This class was deprecated in API level 21.
    We recommend using the new android.hardware.camera2 API for new applications.`

    在这里插入图片描述

    参考
    1. 基础概览篇
    展开全文
  • Android相机开发那些坑

    2016-03-07 09:47:35
    最近我负责开发了一个跟Android相机有关的需求,新功能允许用户使用手机...这篇文章总结了Android相机开发的相关知识、流程,以及容易遇到的坑,希望能帮助今后可能会接触Android相机开发的朋友快速上手,节省时间

            最近我负责开发了一个跟Android相机有关的需求,新功能允许用户使用手机摄像头,快速拍摄特定尺寸(1:1或3:4)的照片,并支持在拍摄出的照片上做贴纸相关的操作。由于之前没有接触过Android相机开发,所以在整个开发过程中踩了不少坑,费了不少时间和精力。这篇文章总结了Android相机开发的相关知识、流程,以及容易遇到的坑,希望能帮助今后可能会接触Android相机开发的朋友快速上手,节省时间,少走弯路。

    一.Android中开发相机应用的两种方式

            Android系统提供了两种使用手机相机资源实现拍摄功能的方法,一种是直接通过Intent调用系统相机组件,这种方法快速方便,适用于直接获得照片的场景,如上传相册,微博、朋友圈发照片等。另一种是使用相机API来定制自定义相机,这种方法适用于需要定制相机界面或者开发特殊相机功能的场景,如需要对照片做裁剪、滤镜处理,添加贴纸,表情,地点标签等。这篇文章主要是从如何使用相机API来定制自定义相机这个方向展开的。

    二.相机API中关键类解析

            通过相机API实现拍摄功能涉及以下几个关键类和接口:

            Camera:最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:

    • open():获取camera实例。

    • setPreviewDisplay(SurfaceHolder):绑定绘制预览图像的surface。surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。通过surfaceHolder可以将Camera和surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。

    • setPrameters设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。

    • startPreview():开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。

    • stopPreview():停止预览,关闭camra底层的帧数据传递以及surface上的绘制。

    • release():释放Camera实例

    • takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):这个是实现相机拍照的主要方法,包含了三个回调参数。shutter是快门按下时的回调,raw是获取拍照原始数据的回调,jpeg是获取经过压缩成jpg格式的图像数据的回调。

            SurfaceView:用于绘制相机预览图像的类,提供给用户实时的预览图像。普通的view以及派生类都是共享同一个surface的,所有的绘制都必须在UI线程中进行。而surfaceview是一种比较特殊的view,它并不与其他普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同时处理其他交互逻辑,因此对view的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。

            SurfaceHolder:surfaceholder是控制surface的一个抽象接口,它能够控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview通过getHolder()方法获得surfaceholder 实例,通过后者管理监听surface 的状态。

            SurfaceHolder.Callback接口:负责监听surface状态变化的接口,有三个方法:

    • surfaceCreated(SurfaceHolder holder):在surface创建后立即被调用。在开发自定义相机时,可以通过重载这个函数调用camera.open()、camera.setPreviewDisplay(),来实现获取相机资源、连接camera和surface等操作。

    • surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface发生format或size变化时调用。在开发自定义相机时,可以通过重载这个函数调用camera.startPreview来开启相机预览,使得camera预览帧数据可以传递给surface,从而实时显示相机预览图像。

    • surfaceDestroyed(SurfaceHolder holder):在surface销毁之前被调用。在开发自定义相机时,可以通过重载这个函数调用camera.stopPreview(),camera.release()来实现停止相机预览及释放相机资源等操作。

    三.自定义相机的开发过程

            定制一个自定义相机应用,通常需要完成以下步骤,其流程图如图1所示:

    • 检测并访问相机资源 检查手机是否存在相机资源,如果存在,请求访问相机资源。

    • 创建预览类 创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。此类能够显示相机的实时预览图像。

    • 建立预览布局 有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起。

    • 设置拍照监听器 给用户界面控件绑定监听器,使其能响应用户操作(如按下按钮), 开始拍照过程。

    • 拍照并保存文件 将拍摄获得的图像转换成位图文件,最终输出保存成各种常用格式的图片。

    • 释放相机资源 相机是一个共享资源,必须对其生命周期进行细心的管理。当相机使用完毕后,应用程序必须正确地将其释放,以免其它程序访问使用时,发生冲突。


    图1 定制自定义相机的过程

            对应到代码编写上可以分成三个步骤:

            第一步:在AndroidManifest.xml中添加Camera相关功能使用的权限,具体声明有以下这些:

           第二步:编写相机操作功能类CameraOperationHelper。采用单例模式来统一管理相机资源,封装相机API的直接调用,并提供用于跟自定义相机Activity做UI交互的回调接口,其功能函数如下,主要有创建\释放相机,连接\开始\关闭预览界面,拍照,自动对焦,切换前后摄像头,切换闪光灯模式等,具体实现可以参考官方API文档。


            第三步:编写自定义相机Activity,主要是定制相机界面,实现UI交互逻辑,如按钮点击事件处理,icon资源切换,镜头尺寸切换动画等。这里需要声明一个SurfaceView对象来实时显示相机预览画面。通过SurfaceHolder及其Callback接口来一同管理屏幕surface和相机资源的连接,相机预览图像的显示/关闭。

    四. 开发过程遇到的一些坑

            下面再讲讲我在开发自定义相机时踩过的一些坑:

    1. Activity设为竖屏时,SurfaceView预览图像颠倒90度。

            说明这个问题之前,先介绍下Android手机上几个方向的概念:

            屏幕方向:在Android系统中,屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。

            相机传感器方向:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,如下图2所示,坐标原点位于手机横放时的左上角,即与横屏应用的屏幕X方向一致。换句话说,与竖屏应用的屏幕X方向呈90度角。

    图2 相机传感器方向示意图 

            相机的预览方向:由于手机屏幕可以360度旋转,为了保证用户无论怎么旋转手机都能看到“正确”的预览画面(这个“正确”是指显示在UI预览界面的画面与人眼看到的眼前的画面是一致的),Android系统底层根据当前手机屏幕的方向对图像传感器采集到的数据进行了旋转处理,然后才送给显示系统,因此可以保证预览画面始终“正确”。在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器一致。因此对于横屏应用来说,由于屏幕方向和预览方向一致,预览图像不会颠倒90度。但是对于竖屏应用,屏幕方向和预览方向垂直,所以会出现颠倒90度现象。为了得到正确的预览画面,必须通过API将相机的预览方向旋转90,保持与屏幕方向一致,如图3所示。



    图3 相机预览方向示意图

    (红色箭头为预览方向,蓝色方向为屏幕方向)

            相机的拍照方向:当点击拍照按钮,拍摄的照片是由图像传感器采集到的数据直接存储到SDCard上产生的,因此,相机的拍照方向与传感器方向是一致的。

    2. SurfaceView预览图像、拍摄照片拉伸变形

            说明这个问题之前,同样先说一下几个跟相机有关的尺寸。

            SurfaceView尺寸:即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里surfaceview显示的预览图像暂且称作手机预览图像。

            Previewsize:相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。

            Picturesize:相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。图4说明了以上几种图像及照片之间的关系。手机预览图像是直接提供给用户看的图像,它由相机预览图像生成,拍摄照片的数据则来自于相机拍摄图像。

    图4 几种图像之间的关系

    下面说下我在开发过程中遇到的三种拉伸变形现象: 

    1、手机预览画面中物体被拉伸变形。

    2、拍摄照片中物体被拉伸变形。

    3、点击拍照瞬间,手机预览画面会停顿下,此时的图像是拉伸变形的,然后预览画面恢复后图像又正常了。

            现象1的原因是SurfaceView和Previewsize的长宽比率不一致。因为手机预览视图的图像是由相机预览图像根据SurfaceView大小缩放得来的,当长宽比不一致时必然会导致图像变形。后两个现象的原因则是Previewsize和Picturesize的长宽比率不一致所致,查了相关的资料,发现其具体原因跟某些手机相机硬件的底层实现有关。总之为了避免以上几种变形现象的发生,在开发时最好将SurfaceView、PreviewSize、PictureSize三个尺寸保证长宽比例一致。具体实现可以先通过camera.getSupportedPreviewSizes()和camera.getSupportedPictureSizes()获得相机硬件支持的所有预览和拍摄尺寸,然后在里面筛选出和SurfaceView的长宽比一致并且大小合适的尺寸,通过camera.setPrameters来更新设置。注意:市场上手机相机硬件支持的尺寸一般都是主流的4:3或者16:9,所以SurfaceView尺寸不能太奇葩,最好也设置成这样的长宽比。

    3. 各种crash




            前两个Crash的原因是:相机硬件在聚焦和拍照前必须要保证已经连接到surface,并且开启相机预览,surface有收到预览数据。如果在还没有执行camera. setPreviewDisplay或者未调用camera. startPreview之前,就调用camera.autofocus或camera.takepicture,就会出现这个运行时异常。对应到自定义相机的代码中,要注意在拍照按钮事件响应中执行camera.autofocus或camera.takepicture前,一定要检验camera有没有设置预览Surfaceview并开启了相机预览。这里有个方法可以判断预览状态:Camera.setPreviewCallback是预览帧数据的回调函数,它会在SurfaceView收到相机的预览帧数据时被调用,因此在里面可以设置是否允许对焦和拍照的标志位。


            还有一点要注意,camera.takePicture()在执行过程中会执行camera.stopPreview来获取拍摄帧数据,表现为预览画面卡住,而如果此时用户点击了按钮的话,也就是调用camera.takepicture,也会出现上面的crash,因此在开发时,可能还需要屏蔽拍照按钮的连续点击。

            第三个crash则涉及图像的裁剪,由于要支持1:1或者4:3尺寸镜头,所以会需要对预览视图进行裁剪,由于是竖屏应用,所以裁剪区域的坐标系跟相机传感器方向是成90度角的,表现在裁剪里就是,屏幕上的x方向,对应在拍摄图像上是高度方向,而屏幕上的y方向,对应到拍摄图像上则是宽度方向。因此在计算时要一定注意坐标系的转换以及越界保护。


    4. 前置摄像头的镜像效果

            Android相机硬件有个特殊设定,就是对于前置摄像头,在展示预览视图时采用类似镜面的效果,显示的是摄像头成像的镜像。而拍摄出的照片则仍采用摄像头成像。看到这里,大家可能会有些怀疑,不妨现在就试试自己Android手机上的前置摄像头,对比下预览图像和拍摄出照片的区别。这是由于底层相机在传递前置摄像头预览数据时做了水平翻转变换,即将x方向镜像翻转180度。这个变化对之前竖屏预览的方向也会造成影响,本来对于后置摄像头旋转90度即可使预览视图正确,而对前置摄像头,如果也旋转90度的话,看到的预览图像则是上下颠倒的(因为x方向翻转了180度),因此必须再旋转180度,才能显示正确,如图5所示,大家可以结合之前相机预览方向的示意图一起理解。


    图5 前置摄像头的预览方向示意图 

            此外,由于拍摄图像并没有做水平翻转,所以对于前置摄像头拍出来的照片,用户会发现跟预览时所见的是左右翻转的。这个在一定程度上会影响用户体验。为了解决这个问题,可以对前置摄像头拍摄的图像在生成位图文件时增加一个水平翻转矩阵变换。

    5. 锁屏下相机资源的释放问题

            为了节省手机电量,不浪费相机资源,在开发的自定义相机里,如果预览图像已不需要显示,如按Home键盘切换后台或者锁屏后,此时就应该关闭预览并把相机资源释放掉。参考官方API文档,当surfaceView变成可见时,会创建surface并触发surfaceHolder.callback接口中surfaceCreated回调函数。而surfaceview变成不可见时,则会销毁surface,并触发surfacedestroyed回调函数。我们可以在对应的回调函数里,处理相机的相关操作,如连接surface、开启/关闭预览。 至于相机资源释放,则可以放在Acticity的onpause里执行。相应的,要重新恢复预览图像时,可以把相机资源申请和初始化放在Acticity的onResume里执行,然后通过创建surfaceview,将camera和surface相连并开启预览。


            但是在开发过程中发现,对于按HOME键切后台场景,程序可以正常运行。对于锁屏场景,则在重新申请相机资源时会发生crash,说相机资源访问失败。那么原因是什么呢?我在代码里增加了调试log, 检查了代码的执行顺序,结果如下: 

            在自定义相机页面按HOME键时的执行流程:

    程序运行->按HOME键 
    Activity调用的顺序是onPause->onStop 
    SurfaceView调用了surfaceDestroyed方法 
    然后再切回程序 
    Activity调用的顺序是onRestart->onStart->onResume 
    SurfaceView调用了surfaceCreated->surfaceChanged方法 
    而对于锁屏,其执行流程则是:
    Activity只调用onPause方法 
    解锁后Activity调用onResume方法 
    SurfaceView中surfaceholder.callback的所有方法都没有执行 

            问题找到了,由于锁屏时,callback的回调方法没有执行,导致相机和预览的连接还没有断开,相机资源就被释放了,所以导致在重新申请相机资源时,系统报crash。根据上面的文档,推测是锁屏下系统并没有改变surfaceview的可见性,于是我尝试在onPause和onResume时通过手动设置surfaceview的visibile属性,结果发现可以正常触发回调函数了。由于在切后台或者锁屏时,用户本来就应该看不到surfaceview,因此这种手动更改surfaceview的可见性的方法,并不会对用户的体验造成影响。

    展开全文
  • Android相机开发实战

    万次阅读 多人点赞 2015-09-07 11:28:03
    开源分享二(Android相机开发实战教程) 上篇博文给大家分享了两个非常实用的项目功能模块,不知道大伙感觉如何?有木有一种臭袜子味扑鼻,酸爽的赶脚!!!贱笑贱笑了~ ~ OK!不扯淡了,言归正传。本文将主要为...

    开源分享二(Android相机开发实战)



    开源分享 一(StickerCamera + 仿微信多图选择)

    开源分享三(炫酷的Android Loading动画)


    前言


    上篇博文给大家分享了两个非常实用的项目功能模块,不知道大伙感觉如何?有木有一种臭袜子味扑鼻,酸爽的赶脚!!!贱笑贱笑了~ ~

    OK!不扯淡了,言归正传。本文将主要为大家介绍Android中自定义相机的开发,做Android应用的童鞋应该都知道,在应用中使用相机功能有两种方式:

    • 调用Camera API 自定义相机
    • 调用系统相机

    由于需求不同,所以选择的方案固然也不同,至于第二种调用系统相机,这里就不过多讲解了,使用Intent对象设置一个Action动作即可,跳转时使用startActivityForResult,然后在onActivityResult处理相关数据便可,关键代码:

    intent.setAction("android.media.action.STILL_IMAGE_CAMERA");

    至于使用,较常见的一般是应用中用户上传头像的时候调用,然后返回处理图像数据。


    而第一种自定义相机的方式使用也十分普遍,但是要做好这个模块,相对来说还是有一定难度的,之前分享过一个Github上的开源相机的项目,项目由美国的一个团队开发,集 拍照、摄影、各种特效动画 等功能与一身,本人之前研究了下,发现功能比较全面也很强大,抠出来单独拍照那一个模块,我滴妈呀,真TM费劲!相机不管是预览还是拍摄图像都还是很清晰的,自己当时也写了一个,比较操蛋,只能怪自己对这一块的优化了解浅显吧!特别是预览的时候,聚焦完成后,焦点周边会出现很多白色的噪点,密密麻麻,特别严重,头疼的很。不过也总算解决了,灰常感谢USA的那个什么什么团队的开源相机程序。经过自己改造后的预览效果图:




    下面看下这个项目的效果图,我也把地址甩底,大伙感兴趣的自行Clone研究(或者闲的蛋疼也可以抽时间剥离开每一个模块学习,作为日后的知识储备),里面也用到了这个Android中读取图片EXIF元数据之metadata-extractor的使用


    GitHub:https://github.com/xplodwild/android_packages_apps_Focal



    相机开发简介

    下面说说在Android中调用Camera来定义相机的最基本步骤:

    1. 打开相机 —— 调用Camera的open()方法。
    2. 获取拍照参数 —— 调用Camera的getParameters()方法,返回Camera.Parameters对象。
    3. 拍照参数设置 —— 调用Camera.Parameters对象。
    4. 拍照参数控制 —— 调用Camera的setParameters(),并将Camera.Parameters对象作为参数传入。注:Android2.3.3之后不用设置。
    5. 预览取景 —— 调用Camera的startPreview()方法,在之前注意调用Camera的setPreviewDisplay(SurfaceHolder holder)设置使用哪个SurfaceView来显示取得的图片。
    6. 拍照 —— 调用Camera的takePicture()
    7. 停止预览 —— 调用Camera的stopPreview()方法
    8. 资源释放 —— Camera.release()

    开启和关闭预览的联系如下:Camera ---- SurfaceHolder ------ SurfaceView

    关于SurfaceHolder.Callback必须实现的3个方法:

    surfaceCreated() 该方法在surfaceView被Create时调用
    surfaceChanged() 该方法是当surfaceView发生改变后调用
    surfaceDestroyed() 这个不用说了,销毁时调用

    surfaceHolder通过addCallBack()方法将响应的接口绑定


    注:必要Camera权限,例如:

    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    
    <uses-permission android:name="android.permission.CAMERA"/>
    
    <uses-feature android:name="android.hardware.camera" />
    
    <uses-permission android:name="android.hardware.camera.autofocus" />
    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    


    关于Camera下的Parameters类,其中封装了我们需要的大部分功能,下面做个简单介绍:

    1. setPictureFormat() 方法用于设置相机照片的格式,其参数是一个字符型参数,位于PixelFormat类中,如:PixelFormat.JPEG。
    2. setSceneMode() 方法用于设置相机场景类型,其参是是一个字符型参数,位于Parameters类中,以SCENE_MODE_开头。
    3. setZoom() 方法用于设置相机焦距,其参数是一个整型的参数,该参数的范围是0到Camera.getParameters().getMaxZoom()。
    4. setPictureSize() 方法用于设置相机照片的大小,参数为整型。
    5. setWhiteBalance() 方法用于设置相机照片白平衡,其参数是一个字符型,位于Parameters类中,以WHITE_BALANCE开头。
    6. setJpegQuality() 方法用于设置相机照片的质量,其参数是一个整型参数,取值范围为1到100。
    7. setFlashMode() 方法用于设置闪光灯的类型,其参数是一个字符型参数,位于Parameters类中,以FLASH_MODE_开头。
    8. setColorEffect() 方法用于设置照片颜色特效的类型,其参数是一个字符型参数,位于Parameters类中,以EFFECT_开头。


    本程序模块效果图及示例


    下面分享本篇Blog的示例相机模块,此功能模块并非上面开源项目中的剥离出来的,看下效果图咯:

             


             



    效果看着还可以吧(不点赞也太不给面子了吧  - . - ),下面个出主界面的布局代码:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <!-- 预览画布 -->
        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <!-- 闪光灯、前置摄像头、后置摄像头、聚焦 -->
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
    
            <org.gaochun.camera.CameraGrid
                android:id="@+id/camera_grid"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_alignParentTop="true" />
    
            <View
                android:id="@+id/focus_index"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/camera_focus"
                android:visibility="invisible" />
    
            <ImageView
                android:id="@+id/flash_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:onClick="onClick"
                android:padding="15dp"
                android:scaleType="centerCrop"
                android:src="@drawable/camera_flash_off" />
    
            <ImageView
                android:id="@+id/camera_flip_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:onClick="onClick"
                android:padding="15dp"
                android:scaleType="centerCrop"
                android:src="@drawable/camera_flip" />
    
            <!-- 底部按钮 -->
    
            <RelativeLayout
                android:layout_width="fill_parent"
                android:layout_height="70dp"
                android:layout_alignParentBottom="true"
                android:background="#a0000000"
                android:padding="5dp" >
    
                <Button
                    android:id="@+id/search"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="30dp"
                    android:background="@null"
                    android:drawablePadding="3dp"
                    android:drawableTop="@drawable/ic_search_selector"
                    android:onClick="onClick"
                    android:text="搜图"
                    android:textColor="@drawable/row_selector_text" />
    
                <ImageView
                    android:id="@+id/action_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:clickable="true"
                    android:onClick="onClick"
                    android:src="@drawable/btn_shutter_photo" />
    
                <Button
                    android:id="@+id/takephoto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_marginRight="30dp"
                    android:background="@null"
                    android:drawablePadding="3dp"
                    android:drawableTop="@drawable/ic_takephoto_selector"
                    android:onClick="onClick"
                    android:text="拍照"
                    android:textColor="@drawable/row_selector_text" />
            </RelativeLayout>
        </RelativeLayout>
    
    </FrameLayout>


    下面是核心模块 CameraPreview 类:

    public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback, Camera.AutoFocusCallback {
    
    	private SurfaceView mSurfaceView;
    	private SurfaceHolder mHolder;
    	private Size mPreviewSize;
    	private Size adapterSize;
    	//private List<Size> mSupportedPreviewSizes;
    	private Camera mCamera;
    	private boolean isSupportAutoFocus = false;
    	private Camera.Parameters parameters = null;
    	private Context mContext;
    	//private int mCurrentCameraId = 0;
    	private int screenWidth;
    	private int screenHeight;
    
    	CameraPreview(Context context, SurfaceView sv) {
    		super(context);
    		mContext = context;
    		mSurfaceView = sv;
    		mHolder = mSurfaceView.getHolder();
    		mHolder.addCallback(this);
    		mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    		mHolder.setKeepScreenOn(true);
    		isSupportAutoFocus = context.getPackageManager().hasSystemFeature(
    				PackageManager.FEATURE_CAMERA_AUTOFOCUS);
    		DisplayMetrics dm = new DisplayMetrics();
    		((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(dm);
    		screenWidth = dm.widthPixels;
    		screenHeight = dm.heightPixels;
    	}
    
    	public void setCamera(Camera camera) {
    		mCamera = camera;
    		initCamera();
    	}
    
    	public void initCamera() {
    		if (mCamera != null) {
    			Camera.Parameters params = mCamera.getParameters();
    			//mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
    			requestLayout();
    			if (mPreviewSize == null) {
    				mPreviewSize = findBestPreviewResolution();
    			}
    			if (adapterSize == null) {
    				adapterSize = findBestPictureResolution();
    			}
    			if (adapterSize != null) {
    				params.setPictureSize(adapterSize.width, adapterSize.height);
    			}
    			if (mPreviewSize != null) {
    				params.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    			}
    			params.setPictureFormat(PixelFormat.JPEG);
    			List<String> focusModes = params.getSupportedFocusModes();
    			if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
    				// set the focus mode
    				params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
    				// set Camera parameters
    				mCamera.setParameters(params);
    			}
    			setDispaly(params, mCamera);
    			//setCameraDisplayOrientation((Activity) mContext, mCurrentCameraId, mCamera);
    			mCamera.setParameters(params);
    		}
    	}
    
    	//控制图像的正确显示方向
    	private void setDispaly(Camera.Parameters parameters, Camera camera) {
    		if (Build.VERSION.SDK_INT >= 8) {
    			setDisplayOrientation(camera, 90);
    		} else {
    			parameters.setRotation(90);
    		}
    	}
    
    	//实现的图像的正确显示
    	private void setDisplayOrientation(Camera camera, int i) {
    		Method downPolymorphic;
    		try {
    			downPolymorphic = camera.getClass().getMethod("setDisplayOrientation",
    					new Class[]{int.class});
    			if (downPolymorphic != null) {
    				downPolymorphic.invoke(camera, new Object[]{i});
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void setCameraDisplayOrientation(Activity activity,
    			int cameraId, android.hardware.Camera camera) {
    		android.hardware.Camera.CameraInfo info =
    				new android.hardware.Camera.CameraInfo();
    		android.hardware.Camera.getCameraInfo(cameraId, info);
    		int rotation = activity.getWindowManager().getDefaultDisplay()
    				.getRotation();
    		int degrees = 0;
    		switch (rotation) {
    		case Surface.ROTATION_0:
    			degrees = 0;
    			break;
    		case Surface.ROTATION_90:
    			degrees = 90;
    			break;
    		case Surface.ROTATION_180:
    			degrees = 180;
    			break;
    		case Surface.ROTATION_270:
    			degrees = 270;
    			break;
    		}
    
    		int result;
    		if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    			result = (info.orientation + degrees) % 360;
    			result = (360 - result) % 360;  // compensate the mirror
    		} else {  // back-facing
    			result = (info.orientation - degrees + 360) % 360;
    		}
    		camera.setDisplayOrientation(result);
    	}
    
    	@Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    		final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    		final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    		setMeasuredDimension(width, height);
    		//        if (mSupportedPreviewSizes != null) {
    		//             mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
    		//        }
    	}
    
    	@Override
    	protected void onLayout(boolean changed, int l, int t, int r, int b) {
    		if (changed && getChildCount() > 0) {
    			final View child = getChildAt(0);
    
    			final int width = r - l;
    			final int height = b - t;
    
    			int previewWidth = width;
    			int previewHeight = height;
    			if (mPreviewSize != null) {
    				previewWidth = mPreviewSize.width;
    				previewHeight = mPreviewSize.height;
    			}
    
    			// Center the child SurfaceView within the parent.
    			if (width * previewHeight > height * previewWidth) {
    				final int scaledChildWidth = previewWidth * height / previewHeight;
    				child.layout((width - scaledChildWidth) / 2, 0,
    						(width + scaledChildWidth) / 2, height);
    			} else {
    				final int scaledChildHeight = previewHeight * width / previewWidth;
    				child.layout(0, (height - scaledChildHeight) / 2,
    						width, (height + scaledChildHeight) / 2);
    			}
    		}
    	}
    
    	public void surfaceCreated(SurfaceHolder holder) {
    		// The Surface has been created, acquire the camera and tell it where
    		// to draw.
    		try {
    			if (mCamera != null) {
    				mCamera.setPreviewDisplay(holder);
    			}
    		} catch (IOException e) {
    			if (null != mCamera) {
    				mCamera.release();
    				mCamera = null;
    
    			}
    			e.printStackTrace();
    		}
    	}
    
    	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    		if (holder.getSurface() == null) {
    			return;
    		}
    		if (mCamera != null) {
    			Camera.Parameters parameters = mCamera.getParameters();
    			parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    			mCamera.setParameters(parameters);
    			try {
    				mCamera.setPreviewDisplay(holder);
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    			mCamera.startPreview();
    			reAutoFocus();
    		}
    	}
    
    	public void surfaceDestroyed(SurfaceHolder holder) {
    		// Surface will be destroyed when we return, so stop the preview.
    		if (mCamera != null) {
    			mCamera.stopPreview();
    		}
    	}
    
    	/**
    	 * 最小预览界面的分辨率
    	 */
    	private static final int MIN_PREVIEW_PIXELS = 480 * 320;
    	/**
    	 * 最大宽高比差
    	 */
    	private static final double MAX_ASPECT_DISTORTION = 0.15;
    
    	/**
    	 * 找出最适合的预览界面分辨率
    	 *
    	 * @return
    	 */
    	private Camera.Size findBestPreviewResolution() {
    		Camera.Parameters cameraParameters = mCamera.getParameters();
    		Camera.Size defaultPreviewResolution = cameraParameters.getPreviewSize();
    
    		List<Camera.Size> rawSupportedSizes = cameraParameters.getSupportedPreviewSizes();
    		if (rawSupportedSizes == null) {
    			return defaultPreviewResolution;
    		}
    
    		// 按照分辨率从大到小排序
    		List<Camera.Size> supportedPreviewResolutions = new ArrayList<Camera.Size>(rawSupportedSizes);
    		Collections.sort(supportedPreviewResolutions, new Comparator<Size>() {
    			@Override
    			public int compare(Camera.Size a, Camera.Size b) {
    				int aPixels = a.height * a.width;
    				int bPixels = b.height * b.width;
    				if (bPixels < aPixels) {
    					return -1;
    				}
    				if (bPixels > aPixels) {
    					return 1;
    				}
    				return 0;
    			}
    		});
    
    		StringBuilder previewResolutionSb = new StringBuilder();
    		for (Camera.Size supportedPreviewResolution : supportedPreviewResolutions) {
    			previewResolutionSb.append(supportedPreviewResolution.width).append('x').append(supportedPreviewResolution.height)
    			.append(' ');
    		}
    
    
    		// 移除不符合条件的分辨率
    		double screenAspectRatio = (double) screenWidth
    		/ screenHeight;
    		Iterator<Size> it = supportedPreviewResolutions.iterator();
    		while (it.hasNext()) {
    			Camera.Size supportedPreviewResolution = it.next();
    			int width = supportedPreviewResolution.width;
    			int height = supportedPreviewResolution.height;
    
    			// 移除低于下限的分辨率,尽可能取高分辨率
    			if (width * height < MIN_PREVIEW_PIXELS) {
    				it.remove();
    				continue;
    			}
    
    			// 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
    			// 由于camera的分辨率是width>height,我们设置的portrait模式中,width<height
    			// 因此这里要先交换然preview宽高比后在比较
    			boolean isCandidatePortrait = width > height;
    			int maybeFlippedWidth = isCandidatePortrait ? height : width;
    			int maybeFlippedHeight = isCandidatePortrait ? width : height;
    			double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
    			double distortion = Math.abs(aspectRatio - screenAspectRatio);
    			if (distortion > MAX_ASPECT_DISTORTION) {
    				it.remove();
    				continue;
    			}
    
    			// 找到与屏幕分辨率完全匹配的预览界面分辨率直接返回
    			if (maybeFlippedWidth == screenWidth
    					&& maybeFlippedHeight == screenHeight) {
    				return supportedPreviewResolution;
    			}
    		}
    
    
    		// 如果没有找到合适的,并且还有候选的像素,则设置其中最大比例的,对于配置比较低的机器不太合适
    		if (!supportedPreviewResolutions.isEmpty()) {
    			Camera.Size largestPreview = supportedPreviewResolutions.get(0);
    			return largestPreview;
    		}
    
    
    		// 没有找到合适的,就返回默认的
    
    		return defaultPreviewResolution;
    	}
    
    
    	private Camera.Size findBestPictureResolution() {
    		Camera.Parameters cameraParameters = mCamera.getParameters();
    		List<Camera.Size> supportedPicResolutions = cameraParameters.getSupportedPictureSizes(); // 至少会返回一个值
    
    		StringBuilder picResolutionSb = new StringBuilder();
    		for (Camera.Size supportedPicResolution : supportedPicResolutions) {
    			picResolutionSb.append(supportedPicResolution.width).append('x')
    			.append(supportedPicResolution.height).append(" ");
    		}
    
    		Camera.Size defaultPictureResolution = cameraParameters.getPictureSize();
    
    		// 排序
    		List<Camera.Size> sortedSupportedPicResolutions = new ArrayList<Camera.Size>(
    				supportedPicResolutions);
    		Collections.sort(sortedSupportedPicResolutions, new Comparator<Camera.Size>() {
    			@Override
    			public int compare(Camera.Size a, Camera.Size b) {
    				int aPixels = a.height * a.width;
    				int bPixels = b.height * b.width;
    				if (bPixels < aPixels) {
    					return -1;
    				}
    				if (bPixels > aPixels) {
    					return 1;
    				}
    				return 0;
    			}
    		});
    
    
    		// 移除不符合条件的分辨率
    		double screenAspectRatio = screenWidth
    		/ (double) screenHeight;
    		Iterator<Camera.Size> it = sortedSupportedPicResolutions.iterator();
    		while (it.hasNext()) {
    			Camera.Size supportedPreviewResolution = it.next();
    			int width = supportedPreviewResolution.width;
    			int height = supportedPreviewResolution.height;
    
    			// 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
    			// 由于camera的分辨率是width>height,我们设置的portrait模式中,width<height
    			// 因此这里要先交换然后在比较宽高比
    			boolean isCandidatePortrait = width > height;
    			int maybeFlippedWidth = isCandidatePortrait ? height : width;
    			int maybeFlippedHeight = isCandidatePortrait ? width : height;
    			double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
    			double distortion = Math.abs(aspectRatio - screenAspectRatio);
    			if (distortion > MAX_ASPECT_DISTORTION) {
    				it.remove();
    				continue;
    			}
    		}
    
    		// 如果没有找到合适的,并且还有候选的像素,对于照片,则取其中最大比例的,而不是选择与屏幕分辨率相同的
    		if (!sortedSupportedPicResolutions.isEmpty()) {
    			return sortedSupportedPicResolutions.get(0);
    		}
    
    		// 没有找到合适的,就返回默认的
    		return defaultPictureResolution;
    	}
    
    	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
    		final double ASPECT_TOLERANCE = 0.1;
    		double targetRatio = (double) w / h;
    		if (sizes == null)
    			return null;
    
    		Size optimalSize = null;
    		double minDiff = Double.MAX_VALUE;
    
    		int targetHeight = h;
    
    		// Try to find an size match aspect ratio and size
    		for (Size size : sizes) {
    			double ratio = (double) size.width / size.height;
    			if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
    				continue;
    			if (Math.abs(size.height - targetHeight) < minDiff) {
    				optimalSize = size;
    				minDiff = Math.abs(size.height - targetHeight);
    			}
    		}
    
    		// Cannot find the one match the aspect ratio, ignore the requirement
    		if (optimalSize == null) {
    			minDiff = Double.MAX_VALUE;
    			for (Size size : sizes) {
    				if (Math.abs(size.height - targetHeight) < minDiff) {
    					optimalSize = size;
    					minDiff = Math.abs(size.height - targetHeight);
    				}
    			}
    		}
    		return optimalSize;
    	}
    
    
    	public void reAutoFocus() {
    		if (isSupportAutoFocus) {
    			mCamera.autoFocus(new Camera.AutoFocusCallback() {
    				@Override
    				public void onAutoFocus(boolean success, Camera camera) {
    				}
    			});
    		}
    	}
    
    	public List<Size> getResolutionList() {
    		return mCamera.getParameters().getSupportedPreviewSizes();
    	}
    
    	public Camera.Size getResolution() {
    		Camera.Parameters params = mCamera.getParameters();
    		Camera.Size s = params.getPreviewSize();
    		return s;
    	}
    
    	/*public void setCurrentCameraId(int current) {
    		mCurrentCameraId = current;
    	}*/
    
    	//定点对焦的代码
    	public void pointFocus(MotionEvent event) {
    		mCamera.cancelAutoFocus();
    		parameters = mCamera.getParameters();
    		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    			//showPoint(x, y);
    			focusOnTouch(event);
    		}
    		mCamera.setParameters(parameters);
    		autoFocus();
    	}
    
    	//实现自动对焦
    	public void autoFocus() {
    		new Thread() {
    			@Override
    			public void run() {
    				try {
    					sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				if (mCamera == null) {
    					return;
    				}
    				mCamera.autoFocus(new Camera.AutoFocusCallback() {
    					@Override
    					public void onAutoFocus(boolean success, Camera camera) {
    						if (success) {
    							initCamera();//实现相机的参数初始化
    						}
    					}
    				});
    			}
    		};
    	}
    
    	@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    	private void showPoint(int x, int y) {
    		if (parameters.getMaxNumMeteringAreas() > 0) {
    			List<Camera.Area> areas = new ArrayList<Camera.Area>();
    			WindowManager wm = (WindowManager) getContext()
    					.getSystemService(Context.WINDOW_SERVICE);
    			//xy变换了
    			int rectY = -x * 2000 / wm.getDefaultDisplay().getWidth() + 1000;
    			int rectX = y * 2000 / wm.getDefaultDisplay().getHeight() - 1000;
    			int left = rectX < -900 ? -1000 : rectX - 100;
    			int top = rectY < -900 ? -1000 : rectY - 100;
    			int right = rectX > 900 ? 1000 : rectX + 100;
    			int bottom = rectY > 900 ? 1000 : rectY + 100;
    			Rect area1 = new Rect(left, top, right, bottom);
    			areas.add(new Camera.Area(area1, 800));
    			parameters.setMeteringAreas(areas);
    		}
    
    		parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
    	}
    
    	@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    	public void focusOnTouch(MotionEvent event) {
    		Rect focusRect = calculateTapArea(event.getRawX(), event.getRawY(), 1f);
    		Rect meteringRect = calculateTapArea(event.getRawX(), event.getRawY(), 1.5f);
    
    		Camera.Parameters parameters = mCamera.getParameters();
    		parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
    
    		if (parameters.getMaxNumFocusAreas() > 0) {
    			List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
    			focusAreas.add(new Camera.Area(focusRect, 1000));
    
    			parameters.setFocusAreas(focusAreas);
    		}
    
    		if (parameters.getMaxNumMeteringAreas() > 0) {
    			List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
    			meteringAreas.add(new Camera.Area(meteringRect, 1000));
    
    			parameters.setMeteringAreas(meteringAreas);
    		}
    		mCamera.setParameters(parameters);
    		mCamera.autoFocus(this);
    	}
    
    	/**
    	 * Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
    	 */
    	private Rect calculateTapArea(float x, float y, float coefficient) {
    		float focusAreaSize = 300;
    		int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
    
    		int centerX = (int) (x / getResolution().width * 2000 - 1000);
    		int centerY = (int) (y / getResolution().height * 2000 - 1000);
    
    		int left = clamp(centerX - areaSize / 2, -1000, 1000);
    		int right = clamp(left + areaSize, -1000, 1000);
    		int top = clamp(centerY - areaSize / 2, -1000, 1000);
    		int bottom = clamp(top + areaSize, -1000, 1000);
    
    		return new Rect(left, top, right, bottom);
    	}
    
    	private int clamp(int x, int min, int max) {
    		if (x > max) {
    			return max;
    		}
    		if (x < min) {
    			return min;
    		}
    		return x;
    	}
    
    	@Override
    	public void onAutoFocus(boolean success, Camera camera) {
    
    	}
    
    	public void setNull() {
    		adapterSize = null;
    		mPreviewSize = null;
    	}
    
    }
    


    以下是CameraActivity类:

    public class CameraActivity extends Activity implements View.OnTouchListener,OnClickListener {
    
    	public static final String CAMERA_PATH_VALUE1 = "PHOTO_PATH";
    	public static final String CAMERA_PATH_VALUE2 = "PATH";
    	public static final String CAMERA_TYPE = "CAMERA_TYPE";
    	public static final String CAMERA_RETURN_PATH = "return_path";
    
    	private int PHOTO_SIZE_W = 2000;
    	private int PHOTO_SIZE_H = 2000;
    	public static final int CAMERA_TYPE_1 = 1;
    	public static final int CAMERA_TYPE_2 = 2;
    	private final int PROCESS = 1;
    	private CameraPreview preview;
    	private Camera camera;
    	private Context mContext;
    	private View focusIndex;
    	private ImageView flashBtn;
    	private int mCurrentCameraId = 0; // 1是前置 0是后置
    	private SurfaceView mSurfaceView;
    	private CameraGrid mCameraGrid;
    
    	private int type = 1;	//引用的矩形框
    
    	private Button mBtnSearch;
    	private Button mBtnTakePhoto;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		mContext = this;
    
    		//requestWindowFeature(Window.FEATURE_NO_TITLE);
    		//getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏
    		//getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//拍照过程屏幕一直处于高亮
    		setContentView(R.layout.camera_home);
    		type = getIntent().getIntExtra(CAMERA_TYPE, CAMERA_TYPE_2);
    		initView();
    		InitData();
    
    	}
    
    	private void initView() {
    		focusIndex = (View) findViewById(R.id.focus_index);
    		flashBtn = (ImageView) findViewById(R.id.flash_view);
    		mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
    		mCameraGrid = (CameraGrid) findViewById(R.id.camera_grid);
    		mBtnSearch = (Button) findViewById(R.id.search);
    		mBtnTakePhoto = (Button) findViewById(R.id.takephoto);
    	}
    
    
    	private void InitData() {
    		preview = new CameraPreview(this, mSurfaceView);
    		preview.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
    				LayoutParams.MATCH_PARENT));
    		((FrameLayout) findViewById(R.id.layout)).addView(preview);
    		preview.setKeepScreenOn(true);
    		mSurfaceView.setOnTouchListener(this);
    		mCameraGrid.setType(type);
    	}
    
    
    
    
    	private Handler handler = new Handler();
    
    	private void takePhoto() {
    		try {
    
    			camera.takePicture(shutterCallback, rawCallback, jpegCallback);
    
    		} catch (Throwable t) {
    			t.printStackTrace();
    			Toast.makeText(getApplication(), "拍照失败,请重试!", Toast.LENGTH_LONG)
    			.show();
    			try {
    				camera.startPreview();
    			} catch (Throwable e) {
    
    			}
    		}
    	}
    
    
    
    	@Override
    	protected void onResume() {
    		super.onResume();
    		int numCams = Camera.getNumberOfCameras();
    		if (numCams > 0) {
    			try {
    				mCurrentCameraId = 0;
    				camera = Camera.open(mCurrentCameraId);
    				camera.startPreview();
    				preview.setCamera(camera);
    				preview.reAutoFocus();
    			} catch (RuntimeException ex) {
    				Toast.makeText(mContext, "未发现相机", Toast.LENGTH_LONG).show();
    			}
    		}
    
    	}
    
    
    
    	@Override
    	protected void onPause() {
    		if (camera != null) {
    			camera.stopPreview();
    			preview.setCamera(null);
    			camera.release();
    			camera = null;
    			preview.setNull();
    		}
    		super.onPause();
    
    	}
    
    
    	private void resetCam() {
    		camera.startPreview();
    		preview.setCamera(camera);
    	}
    
    
    	ShutterCallback shutterCallback = new ShutterCallback() {
    		public void onShutter() {
    		}
    	};
    
    
    	PictureCallback rawCallback = new PictureCallback() {
    		public void onPictureTaken(byte[] data, Camera camera) {
    		}
    	};
    
    
    	PictureCallback jpegCallback = new PictureCallback() {
    		public void onPictureTaken(byte[] data, Camera camera) {
    
    			new SaveImageTask(data).execute();
    			resetCam();
    		}
    	};
    
    
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		try {
    			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    				preview.pointFocus(event);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    		RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(
    				focusIndex.getLayoutParams());
    		layout.setMargins((int) event.getX() - 60, (int) event.getY() - 60, 0,0);
    
    		focusIndex.setLayoutParams(layout);
    		focusIndex.setVisibility(View.VISIBLE);
    
    		ScaleAnimation sa = new ScaleAnimation(3f, 1f, 3f, 1f,
    				ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
    				ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
    		sa.setDuration(800);
    		focusIndex.startAnimation(sa);
    		handler.postAtTime(new Runnable() {
    			@Override
    			public void run() {
    				focusIndex.setVisibility(View.INVISIBLE);
    			}
    		}, 800);
    		return false;
    	}
    
    
    	@Override
    	public void onClick(View v) {
    		switch (v.getId()) {
    
    		/*case R.id.camera_back:
    			setResult(0);
    			finish();
    			break;*/
    
    		case R.id.camera_flip_view:
    			switchCamera();
    			break;
    
    		case R.id.flash_view:
    			turnLight(camera);
    			break;
    
    		case R.id.action_button:
    			takePhoto();
    			break;
    
    		case R.id.search:	//处理选中状态
    			mBtnSearch.setSelected(true);
    			mBtnTakePhoto.setSelected(false);
    			break;
    
    		case R.id.takephoto:	//处理选中状态
    			mBtnTakePhoto.setSelected(true);
    			mBtnSearch.setSelected(false);
    			break;
    		}
    	}
    
    	private static String getCameraPath() {
    		Calendar calendar = Calendar.getInstance();
    		StringBuilder sb = new StringBuilder();
    		sb.append("IMG");
    		sb.append(calendar.get(Calendar.YEAR));
    		int month = calendar.get(Calendar.MONTH) + 1; // 0~11
    		sb.append(month < 10 ? "0" + month : month);
    		int day = calendar.get(Calendar.DATE);
    		sb.append(day < 10 ? "0" + day : day);
    		int hour = calendar.get(Calendar.HOUR_OF_DAY);
    		sb.append(hour < 10 ? "0" + hour : hour);
    		int minute = calendar.get(Calendar.MINUTE);
    		sb.append(minute < 10 ? "0" + minute : minute);
    		int second = calendar.get(Calendar.SECOND);
    		sb.append(second < 10 ? "0" + second : second);
    		if (!new File(sb.toString() + ".jpg").exists()) {
    			return sb.toString() + ".jpg";
    		}
    
    		StringBuilder tmpSb = new StringBuilder(sb);
    		int indexStart = sb.length();
    		for (int i = 1; i < Integer.MAX_VALUE; i++) {
    			tmpSb.append('(');
    			tmpSb.append(i);
    			tmpSb.append(')');
    			tmpSb.append(".jpg");
    			if (!new File(tmpSb.toString()).exists()) {
    				break;
    			}
    
    			tmpSb.delete(indexStart, tmpSb.length());
    		}
    
    		return tmpSb.toString();
    	}
    
    
    
    	//处理拍摄的照片
    	private class SaveImageTask extends AsyncTask<Void, Void, String> {
    		private byte[] data;
    
    		SaveImageTask(byte[] data) {
    			this.data = data;
    		}
    
    		@Override
    		protected String doInBackground(Void... params) {
    			// Write to SD Card
    			String path = "";
    			try {
    
    				showProgressDialog("处理中");
    				path = saveToSDCard(data);
    
    			} catch (FileNotFoundException e) {
    				e.printStackTrace();
    			} catch (IOException e) {
    				e.printStackTrace();
    			} finally {
    			}
    			return path;
    		}
    
    
    		@Override
    		protected void onPostExecute(String path) {
    			super.onPostExecute(path);
    
    			if (!TextUtils.isEmpty(path)) {
    
    				Log.d("DemoLog", "path=" + path);
    
    				dismissProgressDialog();
    				Intent intent = new Intent();
    				intent.setClass(CameraActivity.this, PhotoProcessActivity.class);
    				intent.putExtra(CAMERA_PATH_VALUE1, path);
    				startActivityForResult(intent, PROCESS);
    			} else {
    				Toast.makeText(getApplication(), "拍照失败,请稍后重试!",
    						Toast.LENGTH_LONG).show();
    			}
    		}
    	}
    
    	private AlertDialog mAlertDialog;
    
    	private void dismissProgressDialog() {
    		this.runOnUiThread(new Runnable() {
    			@Override
    			public void run() {
    				if (mAlertDialog != null && mAlertDialog.isShowing()
    						&& !CameraActivity.this.isFinishing()) {
    					mAlertDialog.dismiss();
    					mAlertDialog = null;
    				}
    			}
    		});
    	}
    
    	private void showProgressDialog(final String msg) {
    		this.runOnUiThread(new Runnable() {
    			@Override
    			public void run() {
    				if (mAlertDialog == null) {
    					mAlertDialog = new GenericProgressDialog(
    							CameraActivity.this);
    				}
    				mAlertDialog.setMessage(msg);
    				((GenericProgressDialog) mAlertDialog)
    				.setProgressVisiable(true);
    				mAlertDialog.setCancelable(false);
    				mAlertDialog.setOnCancelListener(null);
    				mAlertDialog.show();
    				mAlertDialog.setCanceledOnTouchOutside(false);
    			}
    		});
    	}
    
    
    	/**
    	 * 将拍下来的照片存放在SD卡中
    	 */
    	public String saveToSDCard(byte[] data) throws IOException {
    		Bitmap croppedImage;
    		// 获得图片大小
    		BitmapFactory.Options options = new BitmapFactory.Options();
    		options.inJustDecodeBounds = true;
    		BitmapFactory.decodeByteArray(data, 0, data.length, options);
    		// PHOTO_SIZE = options.outHeight > options.outWidth ? options.outWidth
    		// : options.outHeight;
    		PHOTO_SIZE_W = options.outWidth;
    		PHOTO_SIZE_H = options.outHeight;
    		options.inJustDecodeBounds = false;
    		Rect r = new Rect(0, 0, PHOTO_SIZE_W, PHOTO_SIZE_H);
    		try {
    			croppedImage = decodeRegionCrop(data, r);
    		} catch (Exception e) {
    			return null;
    		}
    		String imagePath = "";
    		try {
    			imagePath = saveToFile(croppedImage);
    		} catch (Exception e) {
    
    		}
    		croppedImage.recycle();
    		return imagePath;
    	}
    
    
    
    	private Bitmap decodeRegionCrop(byte[] data, Rect rect) {
    		InputStream is = null;
    		System.gc();
    		Bitmap croppedImage = null;
    		try {
    			is = new ByteArrayInputStream(data);
    			BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is,false);
    			try {
    				croppedImage = decoder.decodeRegion(rect,
    						new BitmapFactory.Options());
    			} catch (IllegalArgumentException e) {
    			}
    		} catch (Throwable e) {
    			e.printStackTrace();
    		} finally {
    
    		}
    		Matrix m = new Matrix();
    		m.setRotate(90, PHOTO_SIZE_W / 2, PHOTO_SIZE_H / 2);
    		if (mCurrentCameraId == 1) {
    			m.postScale(1, -1);
    		}
    		Bitmap rotatedImage = Bitmap.createBitmap(croppedImage, 0, 0,
    				PHOTO_SIZE_W, PHOTO_SIZE_H, m, true);
    		if (rotatedImage != croppedImage)
    			croppedImage.recycle();
    		return rotatedImage;
    	}
    
    
    
    	// 保存图片文件
    	public static String saveToFile(Bitmap croppedImage)
    			throws FileNotFoundException, IOException {
    		File sdCard = Environment.getExternalStorageDirectory();
    		File dir = new File(sdCard.getAbsolutePath() + "/DCIM/Camera/");
    		if (!dir.exists()) {
    			dir.mkdirs();
    		}
    		String fileName = getCameraPath();
    		File outFile = new File(dir, fileName);
    		FileOutputStream outputStream = new FileOutputStream(outFile); // 文件输出流
    		croppedImage.compress(Bitmap.CompressFormat.JPEG, 70, outputStream);
    		outputStream.flush();
    		outputStream.close();
    		return outFile.getAbsolutePath();
    	}
    
    
    	/**
    	 * 闪光灯开关 开->关->自动
    	 *
    	 * @param mCamera
    	 */
    	private void turnLight(Camera mCamera) {
    		if (mCamera == null || mCamera.getParameters() == null
    				|| mCamera.getParameters().getSupportedFlashModes() == null) {
    			return;
    		}
    		Camera.Parameters parameters = mCamera.getParameters();
    		String flashMode = mCamera.getParameters().getFlashMode();
    		List<String> supportedModes = mCamera.getParameters()
    				.getSupportedFlashModes();
    		if (Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)
    				&& supportedModes.contains(Camera.Parameters.FLASH_MODE_ON)) {// 关闭状态
    			parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
    			mCamera.setParameters(parameters);
    			flashBtn.setImageResource(R.drawable.camera_flash_on);
    		} else if (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)) {// 开启状态
    			if (supportedModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
    				parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
    				flashBtn.setImageResource(R.drawable.camera_flash_auto);
    				mCamera.setParameters(parameters);
    			} else if (supportedModes
    					.contains(Camera.Parameters.FLASH_MODE_OFF)) {
    				parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
    				flashBtn.setImageResource(R.drawable.camera_flash_off);
    				mCamera.setParameters(parameters);
    			}
    		} else if (Camera.Parameters.FLASH_MODE_AUTO.equals(flashMode)
    				&& supportedModes.contains(Camera.Parameters.FLASH_MODE_OFF)) {
    			parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
    			mCamera.setParameters(parameters);
    			flashBtn.setImageResource(R.drawable.camera_flash_off);
    		}
    	}
    
    
    	// 切换前后置摄像头
    	private void switchCamera() {
    		mCurrentCameraId = (mCurrentCameraId + 1) % Camera.getNumberOfCameras();
    		if (camera != null) {
    			camera.stopPreview();
    			preview.setCamera(null);
    			camera.setPreviewCallback(null);
    			camera.release();
    			camera = null;
    		}
    		try {
    			camera = Camera.open(mCurrentCameraId);
    			camera.setPreviewDisplay(mSurfaceView.getHolder());
    			preview.setCamera(camera);
    			camera.startPreview();
    		} catch (Exception e) {
    			Toast.makeText(mContext, "未发现相机", Toast.LENGTH_LONG).show();
    		}
    
    	}
    
    	@Override
    	public boolean onKeyDown(int keyCode, KeyEvent event) {
    		if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
    			setResult(0);
    			finish();
    			return true;
    		}
    		return super.onKeyDown(keyCode, event);
    	}
    
    
    	@Override
    	public void onActivityResult(int requestCode, int resultCode, Intent data) {
    		if (requestCode == PROCESS) {
    			if (resultCode == RESULT_OK) {
    				Intent intent = new Intent();
    				if (data != null) {
    					intent.putExtra(CAMERA_RETURN_PATH,
    							data.getStringExtra(CAMERA_PATH_VALUE2));
    				}
    				setResult(RESULT_OK, intent);
    				finish();
    			} else {
    				if (data != null) {
    					File dir = new File(data.getStringExtra(CAMERA_PATH_VALUE2));
    					if (dir != null) {
    						dir.delete();
    					}
    				}
    			}
    		}
    	}
    }
    


    总结

    1、网上有些示例代码,担心相机初始化及开启时间较长,将初始化及启动工作单独放在子线程中,偶尔出现黑屏的情况,但也不是经常出现。

    导致原因:由于单独开辟了线程去初始化启动相机,导致相机的初始化和开启工作已完成,而找不到画布控件。若出现此情况,可调试或者将线程睡眠500毫秒。


    2、按下home键后,再次进入时,为毛黑屏了,如何破?

    导致原因:在onCreate中find了SurfaceView,按下Home后程序再次进入时,找不到预览的画布了,可将find的工作放入onResume中,再就是别忘了在onPause中做如下操作:

    @Override
    	protected void onPause() {
    		if (camera != null) {
    			camera.stopPreview();
    			preview.setCamera(null);
    			camera.release();
    			camera = null;
    			preview.setNull();
    		}
    		super.onPause();
    
    	}

    本项目源码(Eclipse版):http://download.csdn.net/download/gao_chun/9084853

    注:测试机-------> 小米2A、红米、华为P8、华为荣耀3C,魅蓝note2


    附:有些小伙伴经常问手机Gif动画如何制作的,在此也分享下:

    动画制作小软件GifMaker:http://download.csdn.net/detail/gao_chun/9077023



    【转载注明gao_chun的Blog:http://blog.csdn.net/gao_chun/article/details/48246871】



    展开全文
  • :person_running: BGACamera-Android :person_running: Android相机开发学习笔记,参考
  • Android相机开发和遇到的坑

    万次阅读 2016-11-26 16:55:11
    Android相机开发那些坑Android开发实践:掌握Camera的预览方向和拍照方向Android 开发之解决相机预览上下颠倒问题【腾讯优测干货分享】Android 相机预览方向及其适配探索关于Android Camera几点须知在Android相机...

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/53350551 文章出自:薛瑄的博客

    在Android相机开发实际开发过程中遇到了不少问题,在网上找了这些资料,但是感觉如果没有经历过Android相机开发开发,直接看这些还是有点太抽象,建议参考一些代码来学习下面的内容

    由于之前没有接触过Android相机开发,所以在整个开发过程中踩了不少坑,费了不少时间和精力。这篇文章总结了Android相机开发的相关知识、流程,以及容易遇到的坑,希望能帮助今后可能会接触Android相机开发的朋友快速上手,节省时间,少走弯路。

    一.Android中开发相机应用的两种方式

    Android系统提供了两种使用手机相机资源实现拍摄功能的方法,

    • 一种是直接通过Intent调用系统相机组件,这种方法快速方便,适用于直接获得照片的场景,如上传相册,微博、朋友圈发照片等。
    • 另一种是使用相机API来定制自定义相机,这种方法适用于需要定制相机界面或者开发特殊相机功能的场景,如需要对照片做裁剪、滤镜处理,添加贴纸,表情,地点标签等。

    这篇文章主要是从如何使用相机API来定制自定义相机这个方向展开的

    二.相机API中关键类解析

    通过相机API实现拍摄功能涉及以下几个关键类和接口:

    2.1、SurfaceView:用于绘制相机预览图像的类,展现实时的预览图像。

    • 普通的view以及派生类都是共享同一个surface的,所有的绘制都必须在UI线程中进行。 surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。

    • surfaceview是一种比较特殊的view,它并不与其他普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同时处理其他交互逻辑,因此对view的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。

    2.2、SurfaceHolder:控制surface的一个抽象接口

    它能够控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview通过getHolder()方法获得surfaceholder 实例,通过后者管理监听surface 的状态。

    2.3、SurfaceHolder.Callback接口:负责监听surface状态变化的接口,

    有三个方法:

    • surfaceCreated(SurfaceHolder holder):在surface创建后立即被调用。在开发自定义相机时,可以通过重载这个函数调用camera.open()、camera.setPreviewDisplay(),来实现获取相机资源、连接camera和surface等操作。

    • surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface发生format或size变化时调用。在开发自定义相机时,可以通过重载这个函数调用camera.startPreview来开启相机预览,使得camera预览帧数据可以传递给surface,从而实时显示相机预览图像。

    • surfaceDestroyed(SurfaceHolder holder):在surface销毁之前被调用。在开发自定义相机时,可以通过重载这个函数调用camera.stopPreview(),camera.release()来实现停止相机预览及释放相机资源等操作。

    2.4、Camera:最主要的类,用于管理和操作camera资源。

    它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:

    • open():获取camera实例。

    • setPreviewDisplay(SurfaceHolder):通过surfaceHolder可以将Camera和surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。

    • setPrameters设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。

    • startPreview():开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。

    • stopPreview():停止预览,关闭camra底层的帧数据传递以及surface上的绘制。

    • release():释放Camera实例

    • takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):这个是实现相机拍照的主要方法,包含了三个回调参数。shutter是快门按下时的回调,raw是获取拍照原始数据的回调,jpeg是获取经过压缩成jpg格式的图像数据的回调。

    三.自定义相机的开发过程

    定制一个自定义相机应用,通常需要完成以下步骤,其流程图如图1所示:

    • 检测并访问相机资源 检查手机是否存在相机资源,如果存在,请求访问相机资源。
    • 创建预览类 创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。此类能够显示相机的实时预览图像。
    • 建立预览布局 有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起。
    • 设置拍照监听器 给用户界面控件绑定监听器,使其能响应用户操作(如按下按钮), 开始拍照过程。
    • 拍照并保存文件 将拍摄获得的图像转换成位图文件,最终输出保存成各种常用格式的图片。
    • 释放相机资源 相机是一个共享资源,必须对其生命周期进行细心的管理。当相机使用完毕后,应用程序必须正确地将其释放,以免其它程序访问使用时,发生冲突。
      这里写图片描述

    四. 开发过程遇到的一些坑

    下面再讲讲我在开发自定义相机时踩过的一些坑:

    1.相机预览方向适配问题的产生

    1.1、相机的安装方向如何获取?

    Android官方提供orientation这个属性:表示相机采集的图像顺时针旋转orientation度后才能与到设备自然方向一致。

    假设设备是竖屏显示。后置相机传感器是横屏安装的。

    这里写图片描述

    你面向屏幕时,如果后置相机传感器所采集的图像的上边是在设备自然方向的右边,则后置相机的orientation是90。

    如果前置相机传感器所采集的图像的上边是在设备自然方向的右边,则前置相机的orientation是270。

    这个值,不同的设备有所差异,但大多数都是这样的值。

    1.2、Activity设为竖屏时,SurfaceView预览图像为什么是逆时针旋转90度?

    说明这个问题之前,先介绍下Android手机上几个方向的概念:

    屏幕坐标:在Android系统中,屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。

    自然方向(natrual orientation)

    每个设备都有一个自然方向,手机和平板的自然方向不同。Android:screenOrientation的默认值unspecified即为自然方向。
    关于orientation的两个常见值是这样定义的:
    landscape(横屏):the display is wider than it is tall,正常拿着设备的时候,宽比高长,这是平板的自然方向。
    portrait(竖屏):the display is taller than it is wide,正常拿设备的时候,宽比高短,这是手机的自然方向。

    图像传感器(Image Sensor)方向:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,如下图2所示,坐标原点位于手机横放时的左上角,即与横屏应用的屏幕X方向一致。换句话说,与竖屏应用的屏幕X方向呈90度角。

    这里写图片描述

    相机的预览方向:将图像传感器捕获的图像,显示在屏幕上的方向。在默认情况下,与图像传感器方向一致。在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器方向一致。

    下面是Camera.setDisplayOrientation的注释文档:

    /**
     * Set the clockwise rotation of preview display in degrees. This affects
     * the preview frames and the picture displayed after snapshot. This method
     * is useful for portrait mode applications. Note that preview display of
     * front-facing cameras is flipped horizontally before the rotation, that
     * is, the image is reflected along the central vertical axis of the camera
     * sensor. So the users can see themselves as looking into a mirror.
     *
     * <p>This does not affect the order of byte array passed in {@link
     * PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This
     * method is not allowed to be called during preview.     
     */
    
    public native final void setDisplayOrientation(int degrees);

    注释中的第二段,描述了这个API修改的仅仅是Camera的预览方向而已,并不会影响到PreviewCallback回调、生成的JPEG图片和录像文件的方向,这些数据的方向依然会跟图像Sensor的方向一致。

    注意:设置预览方向并不会改变拍出照片的方向

    • 对于横屏(这个activity设置的是横屏显示)来说,由于屏幕方向和预览方向一致,预览图像和看到的实物方向一致。
    • 对于竖屏(这个activity设置的是竖屏显示),屏幕方向和预览方向垂直,所以会出现颠倒90度现象,无论怎么旋转手机,显示在UI预览界面的画面与人眼看到的实物始终成90度(UI预览界面逆时针转了90度)。为了得到一致的预览画面,需要将相机的预览方向旋转90(setDisplayOrientation(90)),保持与屏幕方向一致,

    如图3所示(红色箭头表示相机预览的x轴方向,蓝色箭头表示屏幕的x轴方向)。

    这里写图片描述

    看到这里,相信你肯定有疑问,按照这样旋转的话,看到的UI预览图应该是顺时针旋转90度才对啊,但为什么看到的是逆时针旋转90度

    举例

    下图是MI3手机的屏幕“自然”方向和后置相机的图像传感器方向。

    后置相机的orientation是90

    这里写图片描述

    1、图像传感器获得到图像后,就知道了这幅图像每个坐标的像素值,但是要显示到屏幕上,就要按照屏幕的坐标系来显示,于是就需要将图像传感器的坐标系逆时针旋转90度,才能显示到屏幕的坐标系上,
    2、于是看到的图像逆时针旋转了90度,因此需要顺时针旋转90度(setDisplayOrientation(90)),才能与手机的自然方向保持一致。

    这里写图片描述

    1.3、 前置摄像头的镜像效果

    Android相机硬件有个特殊设定,就是对于前置摄像头,

    • 在预览图像是真实场景的镜像

    • 拍出的照片和真实场景一样。

    以MI3为例

    通过程序调用取到的后置相机的orientation是90,前置相机的orientation是270。

    orientation可以理解为图像传感器方向顺时针旋转到屏幕自然方向的角度。

    下图所示是MI3手机的后置相机和前置相机对准同一个小人,后置前置相机采集到的图像、前置相机预览的图像。

    后置

    对于后置相机,相机预览的图像和相机采集到的图像是一样的。只需要旋转后置相机orientation度,即90度即可和屏幕方向保持一致;

    前置

    对于前置相机来说,相机预览的图像和相机采集到的图像是镜像关系, 注意下图小人头发在前置采集的图像和预览图像中头发是相反的。

    因此在MI3手机上做竖屏应用时,

    采集的图像:顺时针旋转270度后,与屏幕方向一致

    预览的图像:顺时针旋转90度后,与屏幕方向一致.(因为底层相机对采集的图像做了镜像处理,所以只需转90度)

    这里写图片描述

    1 、图像传感器获得到图像后,就知道了这幅图像每个坐标的像素值,但是要显示到屏幕上,就要按照屏幕的坐标系来显示,于是就需要将图像传感器的坐标系顺时针旋转90度,才能显示到屏幕的坐标系上,于是看到的图像顺时针旋转了90度

    2、也就是说前置摄像头获取到的图像(未经镜像处理)需要顺时针旋转270,才能和手机的自然方向一致(上图第二行)

    3、但是在预览的时候,做了镜像处理后,只需要顺时针旋转90度,就能和手机的自然方向一致。(如上图第三行):

    下图显示了,屏幕的坐标系和前置摄像头的坐标系

    这里写图片描述

    下图简单显示了,横屏和竖屏的情况

    这里写图片描述

    此外,由于拍摄图像并没有做水平翻转,所以对于前置摄像头拍出来的照片,用户会发现跟预览时所见的是左右翻转的。可以根据自己的需求进行处理。

    1.4、官方推荐的相机预览方向适配做法

    通过orientation属性的含义可以知道,我们可以用它和应用的方向来做相机预览方向的适配,下面代码是官方网站推荐的。

    但并不是所有手机的orientation值都靠谱,比如VIVO V1手机第一次获取后置相机的CameraInfo的orientation值是90,而当执行了mCamera = Camera.open();之后再获取CameraInfo的orientation值就是0,而且以后获取的都是 0 ,除非重启手机。无论是这款手机上的哪个应用,只要执行了一次Camera.open()之后,其他所有程序中获取CameraInfo的orientation都是是0。

    因此按照此方法做适配不能解决所有手机上的问题。

     public static void setCameraDisplayOrientation(Activity activity,
         int cameraId, android.hardware.Camera camera) {
         android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
         android.hardware.Camera.getCameraInfo(cameraId, info);
         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
         int degrees = 0;
         switch (rotation) {
             case Surface.ROTATION_0: degrees = 0; break;
             case Surface.ROTATION_90: degrees = 90; break;
             case Surface.ROTATION_180: degrees = 180; break;
             case Surface.ROTATION_270: degrees = 270; break;
         }
    
         int result;
         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
             result = (info.orientation + degrees) % 360;
             result = (360 - result) % 360;  // compensate the mirror
         } else {  // back-facing
             result = (info.orientation - degrees + 360) % 360;
         }
         camera.setDisplayOrientation(result);
     }

    2、Camera的拍照方向

    当你点击拍照按钮,得到的图片方向不一定与画面中预览的方向一致,这是因为拍摄的照片是将图像Sensor采集到的数据直接存储到SDCard上的,因此,Camera的拍照方向与上述的Camera的图像Sensor方向一致。

    为了演示这个问题,我用手机的Camera对同一个场景拍了两张照片,第一张是横着拿手机拍的,第二张是竖着拿手机拍的。然后用在电脑上打开得到的图片(实际场景中的杯子是竖着的),效果如下所示:

    这里写图片描述

    由此可见,如果横向拿手机拍照,由于正好与Camera的拍照方向一致,因此得到的照片是“正确”的;而竖着拿手机拍照的话,Camera的图像Sensor依然以上面描述的角度在采集图像并存储到SDCard上,所以得到的图片就是右图这样的,因为竖着拿手机正好与图像Sensor的方向相差了90度。由此,大家应该明白了为什么我们用手机拍出的照片经常需要旋转90度才能看到“正确”的画面了吧?

    3. SurfaceView预览图像、拍摄照片拉伸变形

    说明这个问题之前,同样先说一下几个跟相机有关的尺寸。

    • SurfaceView尺寸:即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里surfaceview显示的预览图像暂且称作手机预览图像。

    • Previewsize:相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。

    • Picturesize:相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。图4说明了以上几种图像及照片之间的关系。手机预览图像是直接提供给用户看的图像,它由相机预览图像生成,拍摄照片的数据则来自于相机拍

    这里写图片描述

    下面说下我在开发过程中遇到的三种拉伸变形现象:

    1. 手机预览画面中物体被拉伸变形。
    2. 拍摄照片中物体被拉伸变形。
    3. 点击拍照瞬间,手机预览画面会停顿下,此时的图像是拉伸变形的,然后预览画面恢复后图像又正常了。

    现象1的原因是SurfaceView和Previewsize的长宽比率不一致。因为手机预览视图的图像是由相机预览图像根据SurfaceView大小缩放得来的,当长宽比不一致时必然会导致图像变形。

    后两个现象的原因则是Previewsize和Picturesize的长宽比率不一致所致,查了相关的资料,发现其具体原因跟某些手机相机硬件的底层实现有关。

    总之为了避免以上几种变形现象的发生,在开发时最好将SurfaceView、PreviewSize、PictureSize三个尺寸保证长宽比例一致。具体实现可以先通过camera.getSupportedPreviewSizes()和camera.getSupportedPictureSizes()获得相机硬件支持的所有预览和拍摄尺寸,然后在里面筛选出和SurfaceView的长宽比一致并且大小合适的尺寸,通过camera.setPrameters来更新设置。注意:市场上手机相机硬件支持的尺寸一般都是主流的4:3或者16:9,所以SurfaceView尺寸不能太奇葩,最好也设置成这样的长宽比。

    4. 各种crash

    这里写图片描述

    这里写图片描述

    这里写图片描述

    前两个Crash的原因是:相机硬件在聚焦和拍照前必须要保证已经连接到surface,并且开启相机预览,surface有收到预览数据。如果在还没有执行camera. setPreviewDisplay或者未调用camera. startPreview之前,就调用camera.autofocus或camera.takepicture,就会出现这个运行时异常。

    对应到自定义相机的代码中,要注意在拍照按钮事件响应中执行camera.autofocus或camera.takepicture前,一定要检验camera有没有设置预览Surfaceview并开启了相机预览。这里有个方法可以判断预览状态:Camera.setPreviewCallback是预览帧数据的回调函数,它会在SurfaceView收到相机的预览帧数据时被调用,因此在里面可以设置是否允许对焦和拍照的标志位。

    这里写图片描述

    还有一点要注意,camera.takePicture()在执行过程中会执行camera.stopPreview来获取拍摄帧数据,表现为预览画面卡住,而如果此时用户点击了按钮的话,也就是调用camera.takepicture,也会出现上面的crash,因此在开发时,可能还需要屏蔽拍照按钮的连续点击。
    第三个crash则涉及图像的裁剪,由于要支持1:1或者4:3尺寸镜头,所以会需要对预览视图进行裁剪,由于是竖屏应用,所以裁剪区域的坐标系跟相机传感器方向是成90度角的,表现在裁剪里就是,屏幕上的x方向,对应在拍摄图像上是高度方向,而屏幕上的y方向,对应到拍摄图像上则是宽度方向。因此在计算时要一定注意坐标系的转换以及越界保护。

    5. 锁屏下相机资源的释放问题

    为了节省手机电量,不浪费相机资源,在开发的自定义相机里,如果预览图像已不需要显示,如按Home键盘切换后台或者锁屏后,此时就应该关闭预览并把相机资源释放掉。

    参考官方API文档,

    • 当surfaceView变成可见时,会创建surface并触发surfaceHolder.callback接口中surfaceCreated回调函数。

    • 而surfaceview变成不可见时,则会销毁surface,并触发surfacedestroyed回调函数。

    我们可以在对应的回调函数里,处理相机的相关操作,如连接surface、开启/关闭预览。 至于相机资源释放,则可以放在Acticity的onpause里执行。相应的,要重新恢复预览图像时,可以把相机资源申请和初始化放在Acticity的onResume里执行,然后通过创建surfaceview,将camera和surface相连并开启预览。
    这里写图片描述

    但是在开发过程中发现,对于按HOME键切后台场景,程序可以正常运行。对于锁屏场景,则在重新申请相机资源时会发生crash,说相机资源访问失败。那么原因是什么呢?我在代码里增加了调试log, 检查了代码的执行顺序,结果如下:

    在自定义相机页面按HOME键时的执行流程:

    • 程序运行->按HOME键
      Activity调用的顺序是onPause->onStop
      SurfaceView调用了surfaceDestroyed方法
      然后再切回程序
      Activity调用的顺序是onRestart->onStart->onResume
      SurfaceView调用了surfaceCreated->surfaceChanged方法

    • 而对于锁屏,其执行流程则是:
      Activity只调用onPause方法
      解锁后Activity调用onResume方法
      SurfaceView中surfaceholder.callback的所有方法都没有执行

      问题找到了,由于锁屏时,callback的回调方法没有执行,导致相机和预览的连接还没有断开,相机资源就被释放了,所以导致在重新申请相机资源时,系统报crash。根据上面的文档,推测是锁屏下系统并没有改变surfaceview的可见性,于是我尝试在onPause和onResume时通过手动设置surfaceview的visibile属性,结果发现可以正常触发回调函数了。由于在切后台或者锁屏时,用户本来就应该看不到surfaceview,因此这种手动更改surfaceview的可见性的方法,并不会对用户的体验造成影响。

    这里写图片描述

    参考:

    Android相机开发那些坑

    Android开发实践:掌握Camera的预览方向和拍照方向

    Android 开发之解决相机预览上下颠倒问题

    【腾讯优测干货分享】Android 相机预览方向及其适配探索

    关于Android Camera几点须知

    关注我的公众号,轻松了解和学习更多技术
    这里写图片描述

    展开全文
  • 非商业性使用-禁止演绎 4.0 国际》协议 https://blog.csdn.net/bluewindtalker/article/details/54563910相机开发现在有2个类,分别为android.hardware.camera2和android.hard...
  • Android相机开发代码

    热门讨论 2013-09-25 22:23:55
    简单的Android相机程序,可实现以下功能: 1、预览 2、对焦拍照保存 3、查看图片,并且能够更新按钮的图像缩略图
  • Android相机开发详解(一)

    千次阅读 2015-02-11 10:55:41
    Android相机开发详解(一) 主要实现打开相机,摄像预览,前后置摄像头切换,保存图片等四个功能。
  • 相机应用,电源键唤醒手机,划屏解锁直接进入相机,ok。 但是如果有需要图案或者密码解锁的时候,就会白屏不能进入。也不出现密码输入的界面。有人知道这是怎么回事么? 有人说是缺少什么权限之类的配置,到底是...
  • 开关相机分为以下几个步骤:申请授权-->选择相机-->打开相机-->关闭相机 1申请授权 在使用相机 API 之前,必须在 AndroidManifest.xml 注册相机权限 android.permission.CAMERA。 静态授权 <...
  • 本人入门Android相机开发不久,在此记录一下踩过的坑(以下总结局限于当时的个人认知以及基于当时相关版本的API)。 在Android里的相机开发里边,可能会遇到各种各样的问题,比如 在打开相机之前的权限申请 选择...
  • Android相机开发中遇到的坑(注意事项)相机预览界面方向的设定在默认情况下有些相机用户看到的预览界面和真实世界的情况是颠倒的,所以我们需要设定预览界面的方向,如下所示: public static int ...
  • android调用相机问题 解决三星相机调用BUG

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 30,489
精华内容 12,195
关键字:

安卓相机开发