6.android 自定义相机详解(markdown版)

情景

开发过程中,经常会遇到自定义开发相机。下面详细介绍:

Android中开发相机的两种方式

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

1.调用系统自带相机

关于系统自带相机的调用非常简单,这里我就不过多叙述了,具体可以参考谷歌的Training。我只说容易被大家忽视的几个点:

如果我们的应用使用相机,但相机并不是应用的正常运行所必不可少的组件,可以将权限声明中的android:required设置为”false”。这样的话,Google Play 也会允许没有相机的设备下载该应用。当然我们有必要在使用相机之前通过调用hasSystemFeature(PackageManager.FEATURE_CAMERA)方法来检查设备上是否有相机。如果没有,我们应该禁用和相机相关的功能!

在调用startActivityForResult()方法之前,先调用resolveActivity(),这个方法会返回能处理该Intent的第一个Activity(译注:即检查有没有能处理这个Intent的Activity)。执行这个检查非常重要,因为如果在调用startActivityForResult()时,没有应用能处理你的Intent,应用将会崩溃。所以只要返回结果不为null,使用该Intent就是安全的。

使用Android框架所提供的API来直接控制相机硬件

使用API来控制相机我们需要用到关键类和接口

使用Camera对象来控制相机

使用SurfaceView来展现照相机采集的图像

通过surfaceholder来控制surfac的尺寸和格式,修改surface的像素,监视surface的变化等等

通过SurfaceHolder.Callback 接口,监听surface状态变化

接下来我们分为以下三部分来介绍:关键类以及接口的作用和方法,Camera控制拍照步骤,自定义相机容易踩到的坑以及解决办法。

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()来实现停止相机预览及释放相机资源等操作。

Camera控制拍照的过程

调用Camera的open()方法打开相机。 调用Camera的getParameters()获取拍照参数,该方法返回一个Cmera.Parameters对象。 调用Camera.Parameters对象对照相的参数进行设置。 调用Camera的setParameters(),并将Camera.Parameters对象作为参数传入,这样就可以对拍照进行参数控制,Android2.3.3以后不用设置。 调用Camerade的startPreview()的方法开始预览取景,在之前需要调用Camera的setPreviewDisplay(SurfaceHolder holder)设置使用哪个SurfaceView来显示取得的图片。 调用Camera的takePicture()方法进行拍照。 程序结束时,要调用Camera的stopPreview()方法停止预览,并且通过Camera.release()来释放资源。

预览方向

先看下官方文档的说明 Most camera applications lock the display into landscape mode because that is the natural orientation of the camera sensor. This setting does not prevent you from taking portrait-mode photos, because the orientation of the device is recorded in the EXIF header. The setCameraDisplayOrientation() method lets you change how the preview is displayed without affecting how the image is recorded. However, in Android prior to API level 14, you must stop your preview before changing the orientation and then restart it.
大多数相机程序会锁定预览为横屏状态,因为该方向是相机传感器的自然方向。当然这一设定并不会阻止我们去拍竖屏的照片,因为设备的方向信息会被记录在EXIF头中。setCameraDisplayOrientation()方法可以让你在不影响照片拍摄过程的情况下,改变预览的方向。然而,对于Android API Level 14及以下版本的系统,在改变方向之前,我们必须先停止预览,然后再去重启它。

特别提示

SurfaceView预览图像拉伸变形,拍摄照片尺寸不对 说明这个问题之前,同样先说一下几个跟相机有关的尺寸。 SurfaceView尺寸 :即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里surfaceview显示的预览图像暂且称作手机预览图像。 Previewsize :相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。 Picturesize :相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。图4说明了以上几种图像及照片之间的关系。手机预览图像是直接提供给用户看的图像,它由相机预览图像生成,拍摄照片的数据则来自于相机拍摄图像。 原因是没有正确设置比例 parameter.setPictureSize(width,height),这个比例不是你决定的,要先通过camera.getParameters().getSupportedPictureSizes()获得手机支持的尺寸。

    /*** 设置照片格式*/
    private void setParameter() {

        Camera.Parameters parameters = camera.getParameters(); // 获取各项参数

        parameters.setPictureFormat(PixelFormat.JPEG); // 设置图片格式

        parameters.setJpegQuality(100); // 设置照片质量//获得相机支持的照片尺寸,选择合适的尺寸

        List sizes = parameters.getSupportedPictureSizes();

        int maxSize = Math.max(display.getWidth(), display.getHeight());

        int length = sizes.size();

        if (maxSize > 0) {

            for (int i = 0; i < length; i++) {

                if (maxSize <= Math.max(sizes.get(i).width, sizes.get(i).height)) {

                    parameters.setPictureSize(sizes.get(i).width, sizes.get(i).height);

                    break;

                }

            }

        }


        List ShowSizes = parameters.getSupportedPreviewSizes();

        int showLength = ShowSizes.size();

        if (maxSize > 0) {
            for (int i = 0; i < showLength; i++) {

                if (maxSize <= Math.max(ShowSizes.get(i).width, ShowSizes.get(i).height)) {
                    parameters.setPreviewSize(ShowSizes.get(i).width, ShowSizes.get(i).height);

                    break;

                }

            }

        }

        camera.setParameters(parameters);

    }

前置摄像头的镜像效果

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

解决方案:在保存图片的时候根据选择的摄像头做对应的翻转。

 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

    Matrix matrix = new Matrix();
    
    switch(cameraPosition){
        case 0://前
             matrix.preRotate(270);
            break;
        case 1:
            matrix.preRotate(90);
            break;
        
    }

    bitmap =Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

同时在开发的过程中发现了一个有趣的东西,我们用前置摄像头拍出来的照片其实是左右翻转的。但我用小米自带的相机测试发现,当摄像头中有人脸出现的时候,相机会做左右翻转的操作,以给用户更好的体验。

SurfaceView预览图像拉伸变形,拍摄照片尺寸不对

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

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

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

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

原因是没有正确设置比例 parameter.setPictureSize(width,height),这个比例不是你决定的,要先通过camera.getParameters().getSupportedPictureSizes()获得手机支持的尺寸。

前置摄像头的镜像效果

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

解决方案,在保存图片的时候根据选择的摄像头做对应的翻转。


更多了解,可关注公众号:人人懂编程


微信公众号:人人懂编程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容