Android系统编程入门系列之硬件交互——多媒体摄像头
多媒体系列硬件
多媒体包括图片、动画、音频、视频,这些多媒体素材的采集(输入)主要依靠摄像头和麦克风等硬件设备转化为基础数据,而他们的播放渲染(输出),则需要依靠具有相关功能的编解码软件。当然随着硬件集成度越来越高,也有些基础功能内置到硬件中解码,以此减少软件解码过程中的CPU耗时操作,这种方式称为硬件加速。由于多媒体的播放渲染(输出)是由系统主动向用户发出的,通常不需要向用户申请权限。系统将数据直接发给应用程序,进而在应用程序内编程实现相关数据的解码播放渲染(输出)操作。故文章重点介绍在多媒体采集(输入)过程中可能用到的硬件及相关使用流程。
摄像头及相关硬件
摄像头作为移动手机设备的重要硬件之一,从最初的单一摄像头,到最新的浴霸式四孔摄像头,不管是数量,还是焦距性能上,在不同设备上都有不同的区别。与传感器系列硬件交互一文相似的是,这些繁杂的类型,都由系统适配完成。而应用程序只需要使用系统提供的相关类即可。
对于摄像头硬件的使用,在Android5.0即API级别21以下的系统版本中,可以使用android.hardware.Camera摄像头类的相关方法来获取摄像头数据,以用来实时预览摄像头采集的数据、拍照保存某一时刻的数据、或录制视频保存一段时刻内的数据,但是从Android5.0开始,上述类由于过于臃肿而废弃,进而使用android.hardware.camra2. 包下的相关类开发更定制化的应用。
权限声明
对于使用摄像头硬件的应用程序,都需要声明权限为Manifest.permissions.CAMERA="android.permission.CAMERA"。
同样也可以在应用程序清单文件中声明需要摄像头硬件的设备支持,也可以增加标签信息<uses-feature android:name="android.hardware.camera"/>
。
另外,如果在使用摄像头拍照时,需要在照片中保存位置信息,应用程序需要申请位置权限;而想将照片存储到外部存储设备,还需要应用程序申请读写外部存储的相关权限;如果是使用摄像头录制有声视频,应用程序还需要申请麦克风权限。
使用流程
目标版本为API 21以下
在使用前首先检测摄像头硬件,在能获取到Context
上下文环境对象的位置,调用上下文环境对象的getPackageManager()
方法获取android.content.pm.PackageManager包管理类的实例化对象,进而通过该对象的hasSystemFeature(String featureName)
方法,使参数 featureName 值为PackageManager.FEATURE_CAMERA
代表摄像头功能,来判断当前系统是否有摄像头硬件的支持。
对于有摄像头硬件支持的设备,可以使用Camera.getNumberOfCameras()
静态方法获取当前设备的所有可用摄像头数量,而每个摄像头硬件都对应一个int
类型的 cameraId 属性编号,其值大于等于0,且小于静态方法获取可用摄像头数量,在下面获取摄像头信息和打开指定摄像头时均是根据 cameraId 属性值确定的。
对于每一个具有 cameraId 属性值的摄像头,都可以调用Camera.open(int cameraId)
方法获取到对应的Camera
摄像头类的实例化对象。参数 cameraId 即上文提到的摄像头硬件编号,该参数默认值为0
;如果编号参数对应的摄像头硬件不存在时,该方法则返回空指针。
在得到Camera
实例化对象后,可以查看该摄像头硬件的详细信息。调用该对象的getParameters()
方法,得到返回值为android.hardware.Camera.Parameters摄像头参数类型的对象。在Camera.Parameters
参数类型的对象中,可以使用getX
系列方法获取包括闪光灯、聚焦、分辨率等系列信息;同时也可以使用setX
系列方法重新调整设置包括闪光灯、聚焦、分辨率等系列信息。如果修改摄像头硬件的参数对象后,可以调用Camera
摄像头对象的setParameters(Camera.Parameters params)
方法,将修改后的参数应用到对应的摄像头硬件中。
预览
要实现Camera
摄像头的预览功能,只需要借助自定义控件类,该类继承自系统控件android.view.SurfaceView类。
在自定义控件类的构造方法中,传入上文获取的Camera
摄像头对象作为该类的全局变量,以供在预览功能开启或关闭时调用摄像头对象的相关方法。
之后可以在自定义控件类内部调用自己的getHolder()
方法,返回android.view.SurfaceHolder类型的对象,该对象是绑定当前SurfaceView
控件与其中的控制信息的。可以调用该对象的setX
系列方法设置当前自定义的SurfaceView
中的显示信息,同时调用该对象的addCallback(SurfaceHolder.Callback callback)
方法为当前自定义SurfaceView
增加界面更新的回调,参数 callback 为回调接口android.view.SurfaceHolder.Callback实现的实例化对象。
在SurfaceHolder.Callback
接口的实例化对象中,分别实现surfaceCreated(SurfaceHolder holder)
在自定义控件创建时回调的方法,通常在该方法中调用当前类的全局变量Camera
对象的setPreviewDisplay(holder)
方法将摄像头与当前控件绑定,之后调用Camera
对象的startPreview()
方法启动摄像头的预览,这样摄像头采集的数据就会实时展示在当前自定义SurfaceView
控件中了;surfaceChanged(SurfaceHolder holder, int format, int width, int height)
在自定义控件包括后边三个参数所代表的信息发生改变时回调的方法,此时一般要先调用全局变量Camera
对象的stopPreview()
停止预览,之后完成该控件内部的一些更新信息,最后再重新调用Camera
对象的setPreviewDisplay(holder)
和startPreview()
方法重新绑定并启动预览;surfaceDestroyed(SurfaceHolder holder)
在自定义控件被销毁时回调的方法,通常在刚方法中会调用Camera
对象的stopPreview()
停止预览,最后调用release()
方法直接释放相关资源,这样该Camera
对象的相关数据便都清空了。
拍照
要实现摄像头的拍照功能,只需要调用Camera
对象的takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg)
方法。其中参数 shutter 是拍照那一刻回调的android.hardware.Camera.ShutterCallback接口对象,在拍摄照片时,会回调该对象的唯一方法onShutter()
,因此如果想在拍照时搞些小动作,可以在该对象的onShutter()
中添加代码,通常该参数 shutter 为 null
;
参数 raw 、 postview 、 jpeg 三个都是android.hardware.Camera.PictureCallback图片回调接口的实例化对象,在该对象中实现了onPictureTaken(byte[] data, Camera camera)
方法,是摄像头采集到拍摄的数据后回调该方法,其中的 data 参数便是具体的照片数据,而 camera 则是对应的摄像头对象;三个参数不同的是,参数 raw 是返回的原始数据、参数 postview 是返回的是缩略图数据、参数 jpeg 则是返回的经过jpeg编码的压缩数据。
视频录制
如果想实现实现摄像头的录制视频功能,在调用Camera
对象的startPreview()
方法开启预览后,还要调用其unlock()
方法将该摄像头对象从当前进程解锁,以便之后将该摄像头对象配置到android.media.MediaRecorder多媒体录制类中,在多媒体录制类结束录制并关闭释放相关资源后,调用Camera
对象的reconnect ()
方法重新将该摄像头与当前进程锁定。这样便可以在当前进程中继续使用该摄像头对象了。
关于使用MediaRecorder
多媒体录制类的相关流程,将在后续文章中详细讲解。
目标版本为API 21及以上
从Android5.0版本系统开始,可以在应用程序项目配置文件中增加androidx.camera:camera-core
和androidx.camera:camera-camera2
等官方提供的CameraX框架的依赖库。该库将摄像头的功能分别作为单独的类处理,而不是继续使用低版本将功能都添加到同一个Camera
类中。
首先是检测设备是否支持摄像头硬件,同样是在能获取Context
上下文环境对象的地方,借助androidx.camera.lifecycle.ProcessCameraProvider摄像头提供者类的静态方法getInstance(Context context)
获取提供者的进程间唯一的单例对象,返回的是ListenableFuture<ProcessCameraProvider>
类型的结果,这里的ListenableFuture
是谷歌提供的 guava 框架下com.google.common.util
包中的异步任务,简单来说就是该类型的对象可以调用addListener(Runnable runnable, Executor executor)
监听方法,在该对象所绑定的异步任务完成后会回调监听方法中的参数 runnable 运行,而参数 executor 则指定了运行 runnable 所在的线程,通过使用ContextCompat.getMainExecutor(Context context)
方法获取UI主线程的Executor
对象。而这里通过摄像头提供者类的静态方法获取的单例对象,就是对应的异步任务,在返回ListenableFuture<ProcessCameraProvider>
对象后,为该对象增加监听方法,在异步任务完成后才会调用监听方法中的内容。
在参数 runnable 定义的运行过程中,便可以直接使用之前的ListenableFuture<ProcessCameraProvider>
对象的get()
方法,返回ProcessCameraProvider
类型的单例对象以实现摄像头功能。
同样可以调用ProcessCameraProvider
对象的getAvailableCameraInfos()
方法获取可以访问的摄像头详细信息,得到androidx.camera.core.CameraInfo摄像头信息对象组成的列表。
预览
实现预览功能,主要依靠androidx.camera.view.PreviewView预览视图类作为系统控件来实时展示摄像头采集的数据。最终在代码中调用PreviewView
对象的getSurfaceProvider()
方法,可以获取androidx.camera.core.Preview.SurfaceProvider预览提供者类型的对象,为之后将该控件与androidx.camera.core.Preview预览类绑定。
之后需要创建androidx.camera.core.Preview预览类,其创建方式遵循建造者模式,构造androidx.camera.core.Preview.Builder建造者对象,使用该对象的setX
系列方法可以配置预览信息,最终调用建造者对象的build()
方法返回创建Preview
预览类对象。
得到Preview
对象后,调用setSurfaceProvider(Preview.SurfaceProvider surfaceProvider)
方法绑定预览视图控件,参数 surfaceProvider 即上文预览视图控件对象中的Preview.SurfaceProvider
类型的预览提供者对象。
最终,只需将该Preview
对象绑定到ProcessCameraProvider
摄像头提供者对象中,在上文获取到摄像头提供者的异步任务完成监听中,调用ProcessCameraProvider
对象的bindToLifecycle (LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase... useCases)
方法将摄像头、预览、分别与当前界面生命周期绑定即可。其中参数 lifecycleOwner 为当前界面Activity
对象;
参数 cameraSelector 是通过建造者模式创建的androidx.camera.core.CameraSelector摄像头选择器对象,通过先构造androidx.camera.core.CameraSelector.Builder建造者对象,使用该对象的requireLensFacing(int lensFacing)
方法来选择要使用的摄像头类型,其参数 lensFacing 值只能为前置摄像头的CameraSelector.LENS_FACING_FRONT=0
或后置摄像头的CameraSelector.LENS_FACING_BACK=1
,之后同样调用build()
方法返回创建的CameraSelector
对象;
可变参数 useCases 即包括上文中的Preview
对象和下文的其他功能对应的案例对象。
拍照
实现拍照功能,主要依靠androidx.camera.core.ImageCapture图片捕获类。该类同样使用建造者模式创建,首先构造androidx.camera.core.ImageCapture.Builder建造者对象,调用该对象的setX
系列方法,可以设置拍照时的参数信息,最终调用该对象的build()
方法,返回创建的图片捕获对象。
在得到ImageCapture
图片拍摄类对象后,同样需要调用ProcessCameraProvider
摄像头提供者对象的bindToLifecycle (LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase... useCases)
方法将摄像头与当前拍照对象绑定,参数 lifecycleOwner 和 cameraSelector 与上文使用相同,而参数 useCases 则是这里的ImageCapture
图片拍摄类对象。
最终在需要拍照的时刻,调用ImageCapture
图片拍摄类对象的takePicture(ImageCapture.OutputFileOptions outputFileOptions, Executor executor, ImageCapture.OnImageSavedCallback imageSavedCallback)
方法即可。其中,
参数 outputFileOptions 是用建造者模式的输出文件选项,同样是通过构造androidx.camera.core.ImageCapture.OutputFileOptions.Builder建造者,设置要保存的文件路径,最终建造返回androidx.camera.core.ImageCapture.OutputFileOptions类型对象使用即可;
参数 executor 是下一个参数 imageSavedCallback 回调方法被运行时所在的线程;
参数 imageSavedCallback 是androidx.camera.core.ImageCapture.OnImageCapturedCallback照片捕获后回调接口的实例化对象,在该对象中需要实现onCaptureSuccess(ImageProxy image)
在图片拍摄成功时的回调方法,和onError(ImageCaptureException exception)
在图片拍摄出错时的回调方法。
视频录制
实现视频录制功能,主要依靠androidx.camera.video.VideoCapture视频捕获类。
这里的VideoCapture
视频捕获类可就不是建造者模式创建的了,而是使用其静态方法withOutput(T videoOutput)
,传入参数 videoOutput 为视频输出流,返回VideoCapture
的实例化对象。这里的视频输出流通常为androidx.camera.video.Recorder视频录制类型,Recorder
视频录制类的对象是后面的操作对象,因此他才是用建造者模式创建的。
先构造androidx.camera.video.Recorder.Builder建造者,通过建造者对象的setQualitySelector(QualitySelector qualitySelector)
方法为录制的视频流设置压缩质量,该方法的参数 qualitySelector 通常是由androidx.camera.video.QualitySelector质量选择器的静态方法getSupportedQualities(CameraInfo cameraInfo)
获取支持的压缩质量列表,其值包括最低分辨率的QualitySelector.QUALITY_LOWEST=0
、最高分辨率的QualitySelector.QUALITY_HIGHEST=2
、480P分辨率的QualitySelector。QUALITY_SD=4
、720P的QualitySelector.QUALITY_HD=5
、1080P的QualitySelector.QUALITY_FHD=6
、2160P的QualitySelector.QUALITY_UHD=8
。在获取质量列表的静态方法中,参数 canmeraInfo 是录制使用的摄像头信息对象。最终调用建造者的build()
方法,返回创建的Recorder
录制视频流对象。
在得到VideoCapture
视频捕获类对象后,同样需要调用ProcessCameraProvider
摄像头提供者对象的bindToLifecycle (LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase... useCases)
方法将摄像头与当前视频捕获对象绑定,在参数 useCases 中增加VideoCapture
对象即可。
在VideoCapture
视频捕获类对象绑定之后,通过调用其getOutput()
方法返回其设置的Recorder
视频录制对象。
通过调用Recorder
对象的prepareRecording(Context context, FileOutputOptions fileOutputOptions)
方法准备录制视频,其参数 context 为上下文环境对象,参数 fileOutputOptions 与拍摄照片时类似的使用androidx.camera.video.FileOutputOptions输出文件选项对象,用以设置录制视频的保存路径。该方法返回androidx.camera.video.PendingRecording预备录制类型的对象。
在得到的PendingRecording
对象中,可以调用start()
方法,启动视频录制,返回androidx.camera.video.ActiveRecording活动录制类型对象。
在得到的ActiveRecording
对象中,可以调用pause()
方法暂停录制,resume()
方法继续录制,stop()
方法停止录制。如此,便可完成视频的录制流程。
Android系统编程入门系列之硬件交互——多媒体摄像头的更多相关文章
- Android系统编程入门系列之硬件交互——多媒体麦克风
在多媒体摄像头及相关硬件文章中,对摄像头的使用方式需要区分应用程序的目标版本以使用不同的代码流程,而与之相比,麦克风硬件的使用就简单多了. 麦克风及相关硬件 麦克风硬件在移动设备上作为音频的采集设备, ...
- Android系统编程入门系列之硬件交互——多媒体展示
前两篇文章通过麦克风硬件和摄像头硬件分别采集音频和视频的多媒体数据,在得到的多媒体数据通常是以编码文件的格式存储,在用户需要展示时,可通过设备的内置扩音器或蓝牙耳机等硬件播放音频,通过设备的显示屏或外 ...
- Android系统编程入门系列之硬件交互——传感器
到目前为止,关于应用程序与用户之间的相关内容便比较肤浅的大致介绍完毕.而在整个系统架构中,应用程序与用户之间的交互,犹如参天大树上的枝干和树叶,交互起来五彩缤纷,但使整个生态系统保持生命力的核心,在于 ...
- Android系统编程入门系列之硬件交互——通信硬件USB
在硬件交互的首篇对设备硬件的分类中,互联通信系列硬件主要用来与其他设备进行数据交互.从本文开始,将重点介绍该系列相关硬件. 互联通信系列硬件 根据硬件的可通信距离,由近及远分为USB.NFC.蓝牙.W ...
- Android系统编程入门系列之硬件交互——通信硬件Bluetooth
通信硬件NFC的文章,虽然可以在Android系统中通过非直接接触的形式与支持NFC硬件的设备通信,但是也只能交互一些简短的标签内容,对大量的持续性数据,却并不能很好的支持.因此针对这个弊端,可以考虑 ...
- Android系统编程入门系列之硬件交互——通信硬件NFC
在上篇文章介绍了接入式USB硬件的简单使用,接下来将介绍不依赖物理连接的硬件通信了.本文的重点是近距离通信的硬件NFC. NFC硬件 应用程序中可以通过NFC硬件读取或发送指定协议的技术实现,在And ...
- Android系统编程入门系列之硬件交互——无线通信WLAN
Android系统的移动设备大多支持无线WLAN技术.利用该技术,不仅能实现互联网通信,还能实现无线定位,热点共享等远程通信功能.针对使用WLAN的不同功能,可能需要分别申请不同的权限声明,同时调用不 ...
- Android系统编程入门系列之硬件交互——通信硬件电信SIM卡
现在的SIM卡通常具备基站定位.语音通话.短信消息.网络流量这四大功能,而在移动端是无法对SIM卡使用基站定位功能的,所以这里只介绍移动端如何使用SIM卡实现语音通话.短信消息.数据流量三个功能. 语 ...
- Android系统编程入门系列之加载界面Activity
上回说到应用初始化加载及其生命周期,在Android系统调用Applicaiton.onCreate()之后,继续创建并加载清单文件中注册的首个界面即主Activity,也可称之为入口界面.主Acti ...
随机推荐
- Probius+Prometheus通过API集成POD监控
上一篇文章Probius+Kubernetes任务系统如虎添翼讲了我们把Kubernetes集成进了任务系统Probius,上线后小伙伴反馈虽然摆脱了Kubernetes-Dashboard,但还是得 ...
- Pytorch——torch.nn.Sequential()详解
参考:官方文档 源码 官方文档 nn.Sequential A sequential container. Modules will be added to it in the order th ...
- VS2015+OpenCV+Qt
VS2015+OpenCV+Qt 01.OpenCV 下载 进入官网链接: https://opencv.org,下载所需要的版本: 下载完成后直接双击,选择解压路径,解压到响应的文件夹中: 若之后需 ...
- eureka服务端的高可用
eureka client的高可用这个很简单,只需要向eureka服务端上多注册几个实例即可,那么eureka server端如何实现高可用呢?其实eureka server 端也是可以做为一个客户端 ...
- 人人都写过的5个Bug!
大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...
- MySQL 的架构与组件
MySQL 的逻辑架构图设计图 连接/线程处理:管理客户端连接/会话[mysql threads] 解析器:通过检查SQL查询中的每个字符来检查SQL语法,并为每个SQL查询生成 SQL_ID. 此 ...
- .NET Core TLS 协议指定被我钻了空子~~~
前言 此前,测试小伙伴通过工具扫描,平台TLS SSL协议支持TLS v1.1,这不安全,TLS SSL协议至少是v1.2以上才行,想到我们早已将其协议仅支持v1.3,那应该非我们平台问题.我依然自信 ...
- fatal error: sqlite3.h: No such file or directory
编译带有sqlite3的数据库c语言程序时,出现fatal error: sqlite3.h: No such file or directory,找不到头文件的问题.应该是是系统没有安装函数库. 在 ...
- 原串反转 牛客网 程序员面试金典 C++ Python
原串反转 牛客网 程序员面试金典 C++ Python 题目描述 请实现一个算法,在不使用额外数据结构和储存空间的情况下,翻转一个给定的字符串(可以使用单个过程变量). 给定一个string iniS ...
- CF #749
A 题意 有个长度为n的序列, 每个数互不相同, 求总和最大的最长子序列, 并输出每个i: 题解 emmmmmm, 刚开始看到这个数据和题解被迷惑了, 以为有什么顺序, 并且一直在想一些复杂度较高的算 ...