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()
方法停止录制。如此,便可完成视频的录制流程。