Camera使用指南(一)

这篇文章本来上个月就要写的,后来一直没写,正好在新年写下,对之前的工作进行总结。

之前工作中需要自定义相机,网上看了很多介绍Camera组件的文章,写的都不够全,Camera调用这一块总是出很多问题,后来跟着博客看了zxing的源码,这是Google开源的二维码生成和识别的开源库,里面有Camera组件的调用,把zxing里相机调用的部分抽取出来,修改了一下,有了现在的这个版本,可以拍照,然后保存图片到相册。

  1. 与Camera组件相关的几个类
  2. 如何调用相机,应该注意什么
  3. 跟着zxing的架构,修改CameraDemo的结构(下一篇再介绍,这篇没有贴什么代码,如果想直接看代码,可以跳过呐~)

1. 与Camera组件相关的几个类

自定义相机,或实现扫码功能的时候,都需要调用Camera组件,由Camera可以获取摄像头捕获的帧数据,方便我们对帧数据进行加工。这里主要介绍的自定义相机的过程,中间也会介绍一点扫码相关的功能,如果需要详细了解扫码相关的功能,可以看zxing中扫码相关的代码。下面是与Camera相关的几个类:
Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback

Surface

关于Surface,sdk的介绍是这样的 "Handle onto a raw buffer that is being managed by the screen compositor.",翻译成中文是,“由屏幕内容合成器管理的原生缓冲区的句柄”,这句话包含一下两个方面的内容:
1.通过Surface(因为Surface是句柄)就可以获得原生缓冲器以及其中的内容。就像在C语言中,可以通过一个文件的句柄,就可以获得文件的内容一样;
2.原生缓冲器(rawbuffer)是用于保存当前窗口的像素数据的。
(以上介绍引用自Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之间的关系,顺便说一下,这篇博客写的很好,我这里介绍的Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback不如这篇详细,如果觉得还是没看懂,可以看一下)。

目前为止,知道可以从Surface获取屏幕像素数据就可以了,在自定义相机的过程中,我们不需要显示调用Surface获取像素数据,因为SurfaceView和SurfaceHolder已经帮我们封装好了Surface。

SurfaceView

使用Camera时,SurfaceView是很关键的一个类,它用来显示Camera组件捕获到的像素数据,如何显示呢,其实是通过Surface对象,SurfaceView中持有Surface对象,在Camera预览的时候,Camera为Surface提供像素数据,然后由SurfaceView显示在视图窗口,类似的显示像素数据的view还有SurfaceTexture。
总结:SurfaceView是View的子类,其实就是就是Surface的View,我们可以理解为Surface持有像素数据,通过SurfaceView显示到视图窗口。

SurfaceHolder

SurfaceHolder是一个接口,相当于一个管理器,提供控制和访问SurfaceView背后的Surface的方法,它内部有一个Callback类——SurfaceHolder.Callback,提供3个回调方法,帮助我们感知Surface创建,改变和销毁,Camera的所有操作,预览,对焦,拍照等操作,都必须在Surface的创建之后,销毁之前进行。 SurfaceView中有一个getHolder()方法,可以很方便的获取SurfaceView中的Surface对应的holder,SurfaceHolder又有一个getSurface()方法,用户获取Surface。

除了SurfaceHolder.Callback以外,SurfaceHolder还有几个重要的方法:

  1. public void addCallback(Callback callback),为SurfaceHolder添加一个SurfaceHolder.Callback回调接口。
  2. public void removeCallback(Callback callback),移除之前添加的回调接口,这个方法一般在相机界面onPause的时候调用。
  3. public boolean isCreating(),该方法用来判断在Callback方法调用之后,Surface是否已经被创建了,需要注意的,Surface被创建,不代表Surface就可用,只有surface.isValid()方法返回true,才代表该Surface是可用的,有时判断需要用到surface.isValid()方法。
  4. public void setType(int type),设置Surface的类型,接收如下的参数:
    SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
    SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
    SURFACE_TYPE_GPU:适用于GPU加速的Surface
    SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。需要注意的是,在高版本的Android SDK中,setType这个方法已经被depreciated了。

其实我们会发现,SurfaceView内部持有Surface对象,但是要获取Surface,又需要先获取SurfaceHolder,通过holder获取Surface。

从设计模式的高度来看,Surface、SurfaceView和SurfaceHolder实质上就是广为人知的MVC,即Model-View-Controller。Model就是模型的意思,或者说是数据模型,或者更简单地说就是数据,也就是这里的Surface;View即视图,代表用户交互界面,也就是这里的SurfaceView;SurfaceHolder很明显可以理解为MVC中的Controller(控制器)。这样看起来三者之间的关系就清楚了很多。

SurfaceHolder.Callback

SurfaceHolder.Callback是SurfaceHolder内部接口,提供3个回调方法,帮助我们感知Surface的创建,变化和销毁,在自定义相机的时候,我们需要在相机Activity实现这个接口,并调用SurfaceHolder.addCallback()方法,添加回调接口。

  1. public void surfaceCreated(SurfaceHolder holder):在Surface第一次被创建的时候,这个方法会被调用,我们需要在这里初始化相机,保证Surface可用。
  2. public void surfaceChanged(SurfaceHolder holder, int format, int width, int height):当Surface发生结构性变化的时候(格式或者大小),该方法会被调用。
  3. public void surfaceDestroyed(SurfaceHolder holder):当Surface被销毁之后,该方法会立即被调用,一旦该方法执行,就不应该再访问Surface。

2. 如何调用相机,应该注意什么

理清了与Camera相关的及各类,现在介绍一下如何调用。调用相机主要的两个步骤,开启和释放,分别对应两个方法,Camera.open()Camera.release()

  1. Camera.open():Camera的静态方法,创建一个Camera对象,用于访问物理硬件Camera,默认打开的是后摄像头,也可以通过指定参数0/1来指定打开对应的摄像头,0-后置主摄像头,1-前置主摄像头。需要注意是,如果指定的摄像头被占用,该方法会抛出RuntimeException,代码中需要对该异常进行处理,比如先查询摄像头是否被占用,适当等待,或者发现占用,弹出占用提醒。
  2. Camera.release(),释放摄像头,应用主动断开与硬件摄像头的连接,一旦Camera.open()创建了摄像头,就要保证在不使用Camera的时候释放,否则其他调用相机的应用就无法连接相机硬件了。

注意事项:

  1. 设置预览Surface:调用Camera.open()连接相机之后,需要调用camera.setPreviewDisplay(holder),设置预览的Surface,这时,Camera会从Holder中取出Surface,将像素数据提供给Surface,最后由SurfaceView显示到屏幕。camera.setPreviewDisplay(holder)必须在Surface的生命周期中调用,这就用到了上面提到的SurfaceHolder.CallbackSurfaceHolder.Callbackpublic void surfaceCreated(SurfaceHolder holder)方法中初始化相机,开启相机预览

有时调用相机时会出现相机预览界面黑屏,输出log Camera: app passed NULL ,这种情况就是camera.setPreviewDisplay(holder)方法传入的Surface不可用,可以在public void surfaceCreated(SurfaceHolder holder)方法中,设置预览Surface。

  1. 设置相机预览参数:由于SurfaceView大小不同,预览画面大小也不同,在调用camera.setPreviewDisplay(holder)时,需要设置预览参数,保证开启预览的时候,相机捕获到图像能正常显示在视图窗口,通过camera.getParameters()获取到Camera.Parameters进行设置,这里主要有两个函数:
    parameters.setPreviewSize(int width, int height),设置Camera的预览尺寸,自定义相机时发现预览画面被拉伸或者压缩,就是因为这个参数设置错误,最后再调用camera.setParameters(parameters)方法设置参数。
    parameters.setPictureSize(int width, int height),设置picture尺寸,该picture是指拍照获取到的照片。
    一般情况,需保证setPreviewDisplay和setPreviewSize设置的尺寸相近或相同。
    previewSize和pictureSize的值不能随意设置,错误的值报错,如何设置?
    a. 通过parameters.getSupportedPreviewSizes()/parameters.getSupportedPictureSizes()获取该相机支持的预览画面尺寸/照片尺寸,选择与SurfaceView尺寸最接近的值,如果预览画面是整个屏幕,那就用屏幕尺寸和备选尺寸进行比较。
    b. parameters.getSupportedPreviewSizes()/parameters.getSupportedPictureSizes()会返回List<Camera.Size>类型的数据,需要我们自己写对比代码,网上有很多如何找到最合适的预览尺寸的方法,这里提供一种,计算宽高比,选择宽高比最接近的,如果宽高比相同,选择宽高值和原预览画面尺寸最接近的。
  2. 开启预览和停止预览:分别对应camera.startPreview()camera.stopPreview(),在连接相机,设置好参数之后调用camera.startPreview(),在界面onPause之后调用camera.stopPreview(),然后释放相机资源。
  3. 异常处理:这应该是调用Camera最需要注意的一点,在频繁调用相机,打开,释放这些操作时,Camera.open(),camera.setPreviewDisplay(holder),camera.getParameters(),camera.setParameters(parameters),camera.startPreview(),camera.startPreview()这些操作都很容易抛出RuntimeException,调用的时候需要注意异常处理,简单的try{}catch{}就可以。

调用顺序

结合上面的内容,总结一下调用步骤:

  1. 初始化SurfaceView,获取SurfaceHolder,设置SurfaceHolder.Callback
  2. 在Surface创建成功之后,即SurfaceHolder.CallbacksurfaceCreated(SurfaceHolder holder)方法中,初始化相机(包括开启相机,设置预览的Surface,设置参数)。
  3. 开启预览。
  4. 界面onPause,停止预览,释放相机。

总结

这篇主要介绍一下自定义相机需要注意的一些内容,没有代码,下一篇参照zxing的结构修改demo,主要是代码,感兴趣的可以看一下。

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

推荐阅读更多精彩内容