【转】Android Camera 相机开发详解
在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架构上做了巨大的变动,
但是基于众所周知的原因,我们还必须基于 Android 4.+ 系统进行开发。本文介绍的是Camera接口开发及其使用方法,通过本文章,你将全面地学会Camera接口的开发流程。
本图文与GitHubPages原文均为本人原创
调用系统相机/其它App完成拍摄操作
如果你的App的需求只是调用摄像头拍照并拿到照片
,老司机的建议是别自己实现拍照模块,这里面坑多水深。你完全可以使用Intent来调用系统相机或第三方具备拍照功能的App来拍照并获取返回照片数据。
创建一个Intent,指定两个拍摄类型之一:
MediaStore.ACTION_IMAGE_CAPTURE
拍摄照片;MediaStore.ACTION_VIDEO_CAPTURE
拍摄视频;
Intent intent = new Intent(MediaStore.ACTION_IMAGE/VIDEO_CAPTURE);
通用流程startActivityForResult()
和onActivityResult()
就不表述了。说说拍摄照片的Intent参数吧。
首先是设置拍摄后返回数据的地址:
intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);
MediaStore.EXTRA_OUTPUT
参数用于指定拍摄完成后的照片/视频的储存路径。你可以使用Android默认的储存照片目录来保存:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE)
也可以是其它任意你喜欢的储存目录。如果你使用了App内部目录,某些临时文件如拍摄并上传的头像文件,在处理完成后,要记得将它删除。这样做的好处是减少App占用储存空间,手机用户特别喜欢对占用大储存空间的App下重手删除和清理空间。如果你必须保存大体积的文件,可以使用公共空间来储存,把包袱丢出去,私有空间仅保存应用配置数据。
相机其它设置,如指定拍摄照片的尺寸大小,照片质量等,待以后文章更新吧。
// TODO 是程序界最大的谎言
使用Camera开发照相功能
使用Camera API来开发拍照模块需要费一番大功夫。下面是介绍我在开发NextQRCode项目中使用Camera API的方法和流程。
1.在 Android Manifest.xml 中声明相机权限
开发第一步是在 Android Manifest.xml 文件中声明使用相机的权限:
<uses-permission android:name="android.permission.CAMERA" />
有些同学在开发时忘了声明权限,运行时应用可能会崩溃掉。另外也要增加以下两个特性声明:
<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
required
属性是说明这个特性是否必须满足。比方说示例的设置就是要求必须拥有相机设备但可以没有自动对焦功能。
这两个声明是可选的,它们用于应用商店(Google Play)过滤不支持相机和不支持自动对焦的设备。
另外在保存照片时需要写入储存器的权限,也需要加上读写储存器的权限声明:
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
2. 打开相机设备
现在市面上销售的手机/平板等消费产品基本标配两个摄像头。如华为P9,更有前置双摄像头。讲真,我很好奇开发双摄像头的App是怎样的体验。在打开相机设备前,先获取当前设备有多少个相机设备。如果你的开发需求里包含切换前后摄像头功能,可以获取摄像头数量来判断是否存在后置摄像头。
int cameras = Camera.getNumberOfCameras();
这个接口返回值为摄像头的数量:非负整数。对应地,摄像头序号为: cameras - 1
。例如在拥有前后摄像头的手机设备上,其返回结果是2
,则第一个摄像头的cameraId
是0
,通常对应手机背后那个大摄像头;第二个摄像头的cameraId
是1
,通常对应着手机的前置自拍摄像头;
相机是一个硬件设备资源,在使用设备资源前需要将它打开,可以通过接口Camera.open(cameraId)
来打开。参考以下代码:
public static Camera openCamera(int cameraId) {
try{
return Camera.open(cameraId);
}catch(Exception e) {
return null;
}
}
注意
打开相机设备可能会失败,你一定要检查打开操作是否成功。打开失败的可能原因有两种:一是安装App的设备上根本没有摄像头,例如某些平板或特殊Android设备;二是cameraId
对应的摄像头正被使用,可能某个App正在后台使用它录制视频。
3. 配置相机参数
在打开相机设备后,你将获得一个Camera对象,并独占
相机设备资源。
通过Camera.getParameters()
接口可以获取当前相机设备的默认配置参数。下面列举一些我能理解的参数:
闪光灯配置参数,可以通过Parameters.getFlashMode()
接口获取当前相机的闪光灯配置参数:
Camera.Parameters.FLASH_MODE_AUTO
自动模式,当光线较暗时自动打开闪光灯;Camera.Parameters.FLASH_MODE_OFF
关闭闪光灯;Camera.Parameters.FLASH_MODE_ON
拍照时闪光灯;Camera.Parameters.FLASH_MODE_RED_EYE
闪光灯参数,防红眼模式,科普一下:防红眼;
对焦模式配置参数,可以通过Parameters.getFocusMode()
接口获取:
Camera.Parameters.FOCUS_MODE_AUTO
自动对焦模式,摄影小白专用模式;Camera.Parameters.FOCUS_MODE_FIXED
固定焦距模式,拍摄老司机模式;Camera.Parameters.FOCUS_MODE_EDOF
景深模式,文艺女青年最喜欢的模式;Camera.Parameters.FOCUS_MODE_INFINITY
远景模式,拍风景大场面的模式;Camera.Parameters.FOCUS_MODE_MACRO
微焦模式,拍摄小花小草小蚂蚁专用模式;
场景模式配置参数,可以通过Parameters.getSceneMode()
接口获取:
Camera.Parameters.SCENE_MODE_BARCODE
扫描条码场景,NextQRCode项目会判断并设置为这个场景;Camera.Parameters.SCENE_MODE_ACTION
动作场景,就是抓拍跑得飞快的运动员、汽车等场景用的;Camera.Parameters.SCENE_MODE_AUTO
自动选择场景;Camera.Parameters.SCENE_MODE_HDR
高动态对比度场景,通常用于拍摄晚霞等明暗分明的照片;Camera.Parameters.SCENE_MODE_NIGHT
夜间场景;
Camera API提供了非常多的参数接口供开发者设置,有必要的话,可以翻阅相关API文档。
在NextQRCode项目中,需要使用到自动对焦的特性。在一些机型上可能是没有的自动对焦(虽然比较少见),需要对这种情况进行处理。
4. 设置相机预览方向
相机预览图需要设置正确的预览方向才能正常地显示预览画面,否则预览画面会被挤压得很惨。
在通常情况下,如果我们需要知道设备的屏幕方向,可以通过Resources.Configuration.orientation
来获取。Android屏幕方向有“竖屏”和“横屏”两种,对应的值分别是ORIENTATION_PORTRAIT
和ORIENTATION_LANDSCAPE
。但相机设备的方向却有些特别,设置预览方向的接口Camera.setDisplayOrientaion(int)
的参数是以角度为单位的,而且只能是0,90,180,270
其中之一,默认为0
,是指手机的左侧为摄像头顶部画面。记得只能是[0、90、180、270]其中之一,输入其它角度数值会报错。
如果你想让相机跟随设备的方向,预览界面顶部一直保持正上方,以下代码供参考:
public static void followScreenOrientation(Context context, Camera camera){
final int orientation = context.getResources().getConfiguration().orientation;
if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
camera.setDisplayOrientation(180);
}else if(orientation == Configuration.ORIENTATION_PORTRAIT) {
camera.setDisplayOrientation(90);
}
}
5. 预览View与拍照
我们一般使用SurfaceView
作为相机预览View,你也可以使用Texture。在SurfaceView中获取得SurfaceHolder,并通过setPreviewDisplay()
接口设置预览。在设置预览View后,一定要记得以下两点:
- 调用
startPreview()
方法启动预览,否则预览View不会显示任何内容; - 拍照操作需要在
startPreview()
方法执行之后调用; - 每次拍照后,预览View会停止预览。所以连续拍照,需要重新调用
startPreview()
来恢复预览;
Camera接受一个SurfaceHolder接口,这个接口可以通过SurfaceHolder.Callback获得。我们可以通过继承SurfaceView来实现相机预览效果。在NextQRCode项目中,实现了LiveCameraView
类,它内部已实现了相机预览所需要的处理过程,很简洁的类,以下是它的全部源码:
public class LiveCameraView extends SurfaceView implements SurfaceHolder.Callback {
private final static String TAG = LiveCameraView.class.getSimpleName();
private Camera mCamera;
private SurfaceHolder mSurfaceHolder;
public LiveCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSurfaceHolder = this.getHolder();
mSurfaceHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "Start preview display[SURFACE-CREATED]");
startPreviewDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mSurfaceHolder.getSurface() == null){
return;
}
Cameras.followScreenOrientation(getContext(), mCamera);
Log.d(TAG, "Restart preview display[SURFACE-CHANGED]");
stopPreviewDisplay();
startPreviewDisplay(mSurfaceHolder);
}
public void setCamera(Camera camera) {
mCamera = camera;
final Camera.Parameters params = mCamera.getParameters();
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE);
}
private void startPreviewDisplay(SurfaceHolder holder){
checkCamera();
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.e(TAG, "Error while START preview for camera", e);
}
}
private void stopPreviewDisplay(){
checkCamera();
try {
mCamera.stopPreview();
} catch (Exception e){
Log.e(TAG, "Error while STOP preview for camera", e);
}
}
private void checkCamera(){
if(mCamera == null) {
throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set");
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Stop preview display[SURFACE-DESTROYED]");
stopPreviewDisplay();
}
}
从上面代码可以看出LiveCameraView的核心代码是SurfaceHolder.Callback
的回调:在创建/销毁时启动/停止预览动作。在LiveCameraView类中,我们利用了View的生命周期回调来实现自动管理预览生命周期控制:
- 当SurfaceView被创建后自动开启预览;
- 当SurfaceView被销毁时关闭预览;
- 当SurfaceView尺寸被改变时重置预览;
预览View需要注意预览输出画面的尺寸。相机输出画面只支持部分尺寸。关于尺寸部分,后面再更新。
在启用预览View后,就可以通过Camera.takePicture()
方法拍摄一张照片,返回的照片数据通过Callback接口获取。takePicture()
接口可以获取三个类型的照片:
- 第一个,ShutterCallback接口,在拍摄瞬间瞬间被回调,通常用于播放“咔嚓”这样的音效;
- 第二个,PictureCallback接口,返回未经压缩的RAW类型照片;
- 第三个,PictureCallback接口,返回经过压缩的JPEG类型照片;
我们使用第三个参数,JPEG类型的照片的图片精度即可满足识别二维码的需求。在NextQRCode项目中,ZXing识别二维码的数据格式为Bitmap,通过BitmapFactory可以很方便方便地将byte数组转换成Bitmap。
public abstract class BitmapCallback implements Camera.PictureCallback {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length));
}
public abstract void onPictureTaken(Bitmap bitmap);
}
详细关于Android中Bitmap的说明,请参见文章Android: Bitmap与Drawable这件小事。
如果你需要将照片保存为文件,可以参考这个类的实现:FilePhotoCallback.java
6. 释放相机设备
在打开一个相机设备后,意味着你的App就独占
了这个设备,其它App将无法使用它。因此在你不需要相机设备时,记得调用release()
方法释放设备,再使用时可以重新打开,这并不需要多大的成本。可以选择在stopPreview()
后即释放相机设备。
附加工具性代码实现
1 - 判断手机设备是否有相机设备
public static boolean hasCameraDevice(Context ctx) {
return ctx.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
2 - 判断是否支持自动对焦
public static boolean isAutoFocusSupported(Camera.Parameters params) {
List<String> modes = params.getSupportedFocusModes();
return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
}
如何正确地使用Camera来开发视频拍摄功能
抱歉,这个我真没研究过。
提供一个链接地址供你参考:Camera开发视频拍摄
关于Camera2
后续再更新Camera2的开发教程
作者:陈小锅
链接:https://www.jianshu.com/p/7dd2191b4537
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【转】Android Camera 相机开发详解的更多相关文章
- (转)FS_S5PC100平台上Linux Camera驱动开发详解(一) .
平台linuxstructlinux内核videocam 说明: 理解摄像头驱动需要四个前提: 1)摄像头基本的工作原理和S5PC100集成的Camera控制器的工作原理 ...
- (转)FS_S5PC100平台上Linux Camera驱动开发详解(二)
4-3 摄像头的初始化流程及v4l2子设备驱动 这个问题弄清楚了以后下面就来看获得Camera信息以后如何做后续的处理: 在fimc_init_global调用结束之后我们获得了OV9650的信息,之 ...
- Android Widget 开发详解(二) +支持listView滑动的widget
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/47027263 不少开发项目中都会有widget功能,别小瞧了它,他也是androi ...
- Android WebView 开发详解
Android WebView 开发详解 参见 http://blog.csdn.net/typename/article/details/39030091
- JMessage Android 端开发详解
目前越来越多的应用会需要集成即时通讯功能,这里就为大家详细讲一下如何通过集成 JMessage 来为你的 App 增加即时通讯功能. 首先,一个最基础的 IM 应用会需要有哪些功能? 用户注册 / 登 ...
- Android USB 开发详解
Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...
- 《Android游戏开发详解》一1.7 控制流程第1部分——if和else语句
本节书摘来异步社区<Android游戏开发详解>一书中的第1章,第1.7节,译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.7 ...
- android Camera2 API使用详解
原文:android Camera2 API使用详解 由于最近需要使用相机拍照等功能,鉴于老旧的相机API问题多多,而且新的设备都是基于安卓5.0以上的,于是本人决定研究一下安卓5.0新引入的Came ...
- ANDROID L——Material Design详解(UI控件)
转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...
随机推荐
- 使用Hibernate Validator来帮你做数据校验
数据校验是贯穿所有应用程序层(从表示层到持久层)的常见任务.通常在每个层中实现相同的验证逻辑,这是耗时且容易出错的.这里我们可以使用Hibernate Validator来帮助我处理这项任务.对此,H ...
- 【Linux】 源码安装make命令详解,避免踩坑
正常的编译安装/卸载: 源码的安装一般由3个步骤组成:配置(configure).编译(make).安装(make install). configure文件是一个可执行的脚本文件,它有很多选项, ...
- SWT table性能改善 -- 使用VirtualTable
在SWT程序中使用table展示数据时,如果数据过多,执行起来会比较慢,不过,我们可以借助VirtualTable来解决这一问题. Eclipse官网中关于VirtualTable的说明见:http: ...
- Android - AssetManager
http://blog.csdn.net/luoshengyang/article/details/8791064
- 3.Decorator Pattern(装饰者模式)
装饰者模式: 动态地将责任附加到对象上.想要扩展功能,装饰者提供有别于继承的另一种选择. 举例: 不知道大家学校的食堂是什么点餐制度(或者大家就直接想成吃火锅,我们要火锅料 + 配菜),我们学校的点餐 ...
- Java_万年历(简单)
1.方法,需要一个年份,一个月份.然后在控制台输出日历 // 输入一个年份和一个月份显示日历 public static void printCalendar(int year, int month) ...
- CodeForces822A
A. I'm bored with life time limit per test 1 second memory limit per test 256 megabytes input standa ...
- jQuery复选框全选和全选取消
jQuery(".salaryIds").each(function(){ if(jQuery("#salaryIds").attr("checked ...
- memset初始化数组的问题
今天才搞清楚,memset用于初始化数组,仅能初始化为0值,而不能初始化一个特定的值,这怎么能模糊了呢??? 因此,如果对申请的一段存放数组的内存进行初始化,每个数组元素均初始化为特定的值,必须使用循 ...
- Python 基于Python实现邮件发送
基于Python实现邮件发送 by:授客 QQ:1033553122 测试环境: Python版本:Python 2.7 注:需要修改mimetypes.py文件(该文件可通过文章底部的网盘分 ...