查看: 10829|回复: 0
收起左侧

调用系统Camera App实现拍照和摄像功能

[复制链接]
发表于 2015-4-15 15:42:30 | 显示全部楼层 |阅读模式
     Android手机关于Camera的使用,一是拍照,二是摄像,由于Android提供了强大的组件功能,为此对于在Android手机系统上进行Camera的开发,我们可以使用两类方法:一是借助Intent和MediaStroe调用系统Camera App程序来实现拍照和摄像功能,二是根据Camera API自写Camera程序。由于自写Camera需要对Camera API了解很充分,而且对于通用的拍照和摄像应用只需要借助系统Camera App程序就能满足要求了,为此先从调用系统Camera App应用开始来对Android Camera做个简单的使用小结。- f, J% ]1 \# j- m

1 e/ _, Z! T& w. w0 D/ {' f调用系统Camera App实现拍照和摄像功能

8 n0 @9 [, Z* }    不是专门的Camera应用,一般用到Camera的需求就是获取照片或者视频,比如微博分享、随手记等,对于在Symbian系统上通过简单地调用系统自带的Camera APP来实现该功能是做不到的,但是Android系统强大的组件特性,使得应用开发者只需通过Intent就可以方便的打开系统自带的Camera APP,并通过MediaStroe方便地获取照片和视频的文件路径。具体我们还是用代码来说话吧:
3 g& }; ^3 l, K/ N  y1 K例1、 实现拍照 : [" I3 R' z' J/ q
在菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,具体如下:
1 d! x" W, l# q" timgPath = "/sdcard/test/img.jpg"; ; }5 V2 J( _  D" U  c( r' l
//必须确保文件夹路径存在,否则拍照后无法完成回调 . c5 }) o$ P' |& L5 V, B
File vFile = new File(imgPath);
2 ]3 A* b4 G8 W# n8 L& ~if(!vFile.exists()) 9 W) g; d( U* @$ |- ]) N
{
2 c2 @3 G! U; v0 S. m' PFile vDirPath = vFile.getParentFile(); //new File(vFile.getParent());
3 c1 Z$ G1 t2 A0 g( ^6 p7 \; f' L- gvDirPath.mkdirs();
! O  n  w1 l4 J. N} ) j1 M# T, v* n& b
Uri uri = Uri.fromFile(vFile); 6 D* V% M; |5 t4 p
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + j4 G6 Q; w, @3 |, O! X2 @: ]
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);// 9 C* {1 Q+ X" I. `, i3 I
startActivityForResult(intent, SystemCapture);
3 x4 T( b8 Q) o上面我们使用的是startActivityForResult,所以最好需要重载void onActivityResult(int requestCode, int resultCode, Intent data)函数,不过因为当传入文件路径的的情况下,data返回参数是null值,只要resultCode为RESULT_OK,则上述代码中/sdcard/test/img.jpg的图片文件就是最新的照片文件。所以我们在这里只需给出如下简单的代码,将其显示到ImageView中
% N( K4 M" `, E# F) m! |% `4 m- Vif (resultCode == RESULT_OK)  
: k0 ]$ N, f8 D4 U{
- \1 I1 [0 R5 G. v, z9 r" \) }iViewPic.setImageURI(Uri.fromFile(new File(imgPath)));
7 j; y% Z; O, E& E}
9 D) N$ T6 ~8 R7 q" e% t/ R假设不传参数MediaStore.EXTRA_OUTPUT的情况下,onActivityResult函数在resultCode为RESULT_OK的情况下,data返回的参数是经过实际拍摄照片经过缩放的图像数据,可以通过类似如下方法来打印缩放图像的尺寸 4 k, ~$ d3 g7 g; r
if (resultCode == RESULT_OK)  
# {( n2 P- M* u' R; K0 e: I{ ! K  o2 J+ o7 K1 j# w
Bitmap bmp = (Bitmap)data.getExtras().get("data");
, y" ~$ n  k. v+ aLog.d("Test", "bmp width:" + bmp.getWidth() + ", height:" + bmp.getHeight());
, O- r/ Q" f$ [$ k2 W& t% g/ P: p} : ]5 W, |; D) ?" p3 B( x, V
另外假如仅仅是调用系统照相机拍照,不关心拍照结果,则可以简单使用如下代码 ) Y' t  {* I" x) g' T2 c
Intent intent = new Intent(); //调用照相机
1 S  H8 A, B# w7 j; F, i7 W5 W- sintent.setAction("android.media.action.STILL_IMAGE_CAMERA");
) I% Q) M+ N" Q7 estartActivity(intent); ; m! Z# D! Y$ i- z) E4 H6 O
备注:上面设置MediaStore.EXTRA_OUTPUT的方法,经过手机实测除了我们设定的路径下有照片外,在手机存储卡上也会保存一份照片,默认目录为sdcard/dcim/camera下面,我曾经尝试着想如果每次返回可以取得sdcard/dcim/camera下面的路径就好了,但是目前看来没办法直接获得,可以借助MediaStroe每次去查询最后一条照片记录,应该也是可行的。
' J( o: E. w, w* Q1 s/ O. V7 M例2、 实现摄像
1 D$ Q4 Q" a/ O- `* l& @在摄像功能时,尝试着设置MediaStore.EXTRA_OUTPUT以传入类似拍照时的文件路径,结果在我的测试真机上,那个视频文件居然是一个0k的空文件,最后通过类似如下代码实现 ' H1 D& s* s) j- J, B! O
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
4 L+ x1 i$ I5 L  e& hintent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//参数设置可以省略
. T) b0 x, [3 O" b0 @/ M( p+ u& CstartActivityForResult(intent, SystemVideoRecord); 6 d( H2 V0 Y( i: A
在onActivityResult函数中进行如下代码调用 # _# y9 b. s- D& r, m
Uri videoUri = data.getData();
( o/ D7 S& @- p! t1 W+ m//String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.SIZE }; - E) W! _( G7 ^. O9 C' U- w
Cursor cursor = managedQuery(videoUri, null, null, null, null);   i# w. O' N9 g' |- F' C6 U8 h6 `$ \
cursor.moveToFirst();//这个必须加,否则下面读取会报错 * b0 |9 {" S$ B: ?# f1 Y* x
int num = cursor.getCount();
" ]- o5 h% C* c$ N+ YString recordedVideoFilePath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); * c% f% x5 `( P& s/ y' R7 i/ G/ H
int recordedVideoFileSize = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.SIZE));
: R2 x+ H/ S! R% r* d3 v7 x  z) y" KiResultText.setText(recordedVideoFilePath);
3 l( y( b" r- v) V. U( x: WLog.i("videoFilePath", recordedVideoFilePath); - m4 _; T( O4 H
Log.i("videoSize", ""+recordedVideoFileSize); 9 r8 L" I4 r$ T0 O& h) v
上面的返回参数data,也会因为用户是否设置MediaStore.EXTRA_OUTPUT参数而改变,假设没有通过EXTRA_OUTPUT设置路径,data.getData返回的Uri为content://media/external/video/media/*,*个数字,代表具体的记录号,通过managedQuery可以获取到路径,假如设置了EXTRA_OUTPUT的话(比如/sdcard/test.3gp),则data.getData返回的Uri则为file:///sdcard/test.3gp,但是该文件居然是空白内容(不知道是不是跟手机有关,也没有在其它手机上验证过)。
- K, \. W- q6 a3 |4 f6 [根据Camera API实现自己的拍照和摄像程序 通过上面对调用系统Camera App实现拍照和摄像功能的例子,我们发现虽然能够满足我们的需求,但是毕竟自由度降低了,而且拍照的界面就是系统的样子,现在很多拍照程序,比如火爆的Camera 360软件等,就需要根据SDK提供的Camera API来编写自己的程序。 0 Q& A& v6 j9 Q6 U* @( b1 I% Z' C5 q
准备工作 上面调用系统Camera App,我们压根不需要任何权限,但是这里用Camera API,就必须在manifest内声明使用权限,通常由以下三项
& d, V$ P' R5 V( z# z0 i- ~<uses-permission android:name = "android.permission.CAMERA" />
; C* d  p$ Z- V, F1 F' f1 ?. v4 o# a<uses-feature android:name = "android.hardware.camera" /> 9 {9 {! F7 X) L3 I/ p" ^, O2 P
<uses-feature android:name = "android.hardware.camera.autofocus" /> ; ], k5 \" f8 u3 t
一般拍照和摄像的时候需要写到sd卡上,所以还有一向权限声明如下
8 f" c0 ?; w4 {8 `4 n8 z7 Q<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
& o' q' C! Z4 J1 L真做摄像功能时,需要音频录制和视频录制功能,所以又需要下面两项权限声明 / a# t6 K, F0 M- \
<uses-permission android:name="android.permission.RECORD_VIDEO"/> ! ^) k; v' F) J8 K  Q
<uses-permission android:name="android.permission.RECORD_AUDIO"/> ) v. R4 b6 r8 ~- Y' S( \
另外使用Camera API拍照或摄像,都需要用到预览,预览就要用到SurfaceView,为此Activity的布局中必须有SurfaceView。
4 U1 M" d! W+ a  m
+ C5 X- v! W+ E5 ]: K- r% r* d

0 i6 x0 J% t4 _0 K. Q8 \" u拍照流程 上面简单介绍了下准备工作,下面结合拍照过程中的需要用到的API对拍照流程做下简单描述 9 e) n0 A& V+ R8 l* c
1、在Activity的OnCreate函数中设置好SurfaceView,包括设置SurfaceHolder.Callback对象和SurfaceHolder对象的类型,具体如下
+ `8 p' r. E( D8 u' e- @( v# r+ ^SurfaceView mpreview = (SurfaceView) this.findViewById(R.id.camera_preview);
+ ^6 e7 I; f2 Z6 nSurfaceHolder mSurfaceHolder = mpreview.getHolder();   U* F8 r6 g$ y0 f7 G: v' H5 k/ }
mSurfaceHolder.addCallback(this); 9 }( K1 Z( E# a; n3 {
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- t) [) P( _% t( {% C2、在SurfaceHolder.Callback的surfaceCreated函数中,使用Camera的Open函数开机摄像头硬件,这个API在SDK 2.3之前,是没有参数的,2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,再通过getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,假如摄像头开启成功则返回一个Camera对象,否则就抛出异常;
' K: ?* D0 m* J; z1 r  A3、开启成功的情况下,在SurfaceHolder.Callback的surfaceChanged函数中调用getParameters函数得到已打开的摄像头的配置参数Parameters对象,如果有需要就修改对象的参数,然后调用setParameters函数设置进去(SDK2.2以后,还可以通过Camera::setDisplayOrientation设置方向);
9 [: U' o& z, c0 I* T* H" ?4、同样在surfaceChanged函数中,通过Camera::setPreviewDisplay为摄像头设置SurfaceHolder对象,设置成功后调用Camera::startPreview函数开启预览功能,上面3,4两步的代码可以如下所示 + M& g" Q0 T* d( [
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) ' O+ D5 U4 T# c2 F- \  A. o
{
% `) D8 W2 f; M* @//已经获得Surface的width和height,设置Camera的参数 ( m8 x- f- l( c7 A
Camera.Parameters parameters = camera.getParameters();
) g6 n8 v2 n+ t4 zparameters.setPreviewSize(w, h);
6 h3 i9 L+ u% T& q$ EList<Size> vSizeList = parameters.getSupportedPictureSizes();
% K# ]( \# C" }0 L/ _) j: xfor(int num = 0; num < vSizeList.size(); num++) 7 I6 w' t. f* u) n, N
{ & q4 N- [: F# e# ?% W
Size vSize = vSizeList.get(num); 2 F8 |' b0 ~. ~, i# w2 p9 g# a
} ) o8 N- f5 ?4 [- d! V6 S. n* J4 R
if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) # y3 [$ e) ~( U' t
{ 6 Z9 K2 @  W% p. _( |
//如果是竖屏
- S# x* `: Z  @3 s. |& M" ^parameters.set("orientation", "portrait"); : V; t7 X, N. p
//在2.2以上可以使用 ! f; X/ _* ^+ [0 t
//camera.setDisplayOrientation(90); 8 z7 v+ I- Z$ a% Y
}
) y; i- d' ^, q3 K, Z% c3 a8 Belse
5 p6 I1 x; Q! l$ g  r; G/ t* F{
. v, V7 g) }% m7 O* e' Yparameters.set("orientation", "landscape");
$ A6 i; M% @7 A//在2.2以上可以使用
: ^- n' u$ d( ]8 ?3 p- N( s- E//camera.setDisplayOrientation(0); 9 g0 c! ?6 k3 l: t- _5 K
} 6 R, ~; x7 m- Y9 Z: t
camera.setParameters(parameters);
8 ~2 [8 v* |$ N1 t( qtry { - S, q( ?! A$ T9 ]1 d- _# h8 r
//设置显示 $ i. f0 F8 F8 C6 @6 z  H
camera.setPreviewDisplay(holder); 7 P& X+ n, O8 K. G+ s( O2 l
} catch (IOException exception) { 9 K: l$ F! f. g3 o) X' N1 w
camera.release();
# t* ~% ]$ ~! ^camera = null; % D. Y4 b6 A; [( K7 ?8 O
} 4 z* N6 k9 P4 x9 `
//开始预览
( b4 G) Y7 I2 v! t  @camera.startPreview(); ( S8 i. B) b! [& Q$ }1 W- U
}
, s" l: v7 t+ J& R, R: F7 i5、假设要支持自动对焦功能,则在需要的情况下,或者在上述surfaceChanged调用完startPreview函数后,可以调用Camera::autoFocus函数来设置自动对焦回调函数,该步是可选操作,有些设备可能不支持,可以通过Camera::getFocusMode函数查询。代码可以参考如下: 7 ^0 T$ I: }# a' V: K
// 自动对焦
. O; @8 X5 ~4 O0 wcamera.autoFocus(new AutoFocusCallback()
1 `/ @8 J2 }0 h8 b$ |{ * q" G8 G! [: [; ]- B2 G
@Override
! {  B" O* I) O6 U- U& Q1 \2 vpublic void onAutoFocus(boolean success, Camera camera) 8 f; K2 l* _+ r) A8 ]8 E
{ * T& F6 Y8 i) H3 y1 A* ~2 i0 d# `
if (success)
+ d0 Z$ b5 p8 _/ c" `. x{
6 ]5 p( o# M2 S( n// success为true表示对焦成功,改变对焦状态图像 " L! Z& X7 B; [/ M
ivFocus.setImageResource(R.drawable.focus2);
* r( e4 \- d) H' W7 ~}
. N  ~  z( X$ I5 r& |} ( w+ K2 p9 P& J9 b# B8 \/ j
}); ; W. L4 W7 w( r7 _  o
6、在需要拍照的时候,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)函数来完成拍照,这个函数中可以四个回调接口,ShutterCallback是快门按下的回调,在这里我们可以设置播放“咔嚓”声之类的操作,后面有三个PictureCallback接口,分别对应三份图像数据,分别是原始图像、缩放和压缩图像和JPG图像,图像数据可以在PictureCallback接口的void onPictureTaken(byte[] data, Camera camera)中获得,三份数据相应的三个回调正好按照参数顺序调用,通常我们只关心JPG图像数据,此时前面两个PictureCallback接口参数可以直接传null; 5 r. K1 ^& {" K
7、每次调用takePicture获取图像后,摄像头会停止预览,假如需要继续拍照,则我们需要在上面的PictureCallback的onPictureTaken函数末尾,再次掉哟更Camera::startPreview函数; " M" U' M+ |" o4 F/ x
8、在不需要拍照的时候,我们需要主动调用Camera::stopPreview函数停止预览功能,并且调用Camera::release函数释放Camera,以便其他应用程序调用。SDK中建议放在Activity的Pause函数中,但是我觉得放在surfaceDestroyed函数中更好,示例代码如下
* }8 u/ D& |5 V& A. k// 停止拍照时调用该方法 0 l. g' A. l& ?1 e8 M; `# T
public void surfaceDestroyed(SurfaceHolder holder)
& F" L0 e5 R, V  }. J. D0 h) d{
* `+ ~$ M2 y6 x4 r// 释放手机摄像头
: V  s2 t; j' O3 G$ Rcamera.release(); & ?/ L7 [% R4 [: T  V
}
& `9 H* S+ D) J2 L$ T/ D
7 m$ _8 B9 t' x2 y; W

. j: Y! K# Z6 y: G7 X& B) c) V% r$ T, {, \
以上就是自己实现拍照程序的的流程,一般还可以还可以获取预览帧的图像数据,可以分别通过Camera::setPreviewCallback和Camera::setOneShotPreviewCallback来设置每帧或下一帧图像数据的回调,这里就不做展开了。 , a( N* M8 m& @' K, s2 A
2 H6 z) m4 F- @  c
摄像流程 摄像流程也是需要预览的,而且流程上与拍照流程在起始的1~4步流程和结束的8流程是一样的,唯一不同的是6和7两个步骤,至于5自动对焦本身就是可选的,在摄像流程也没必要。
: o  ^, O8 b) t6 c3 ~. z/ t% `
; T$ J) }# l2 P- D4 j
6、开启视频录制,需要创建一个MediaRecorder对象,并调用Camera::unLock操作解锁摄像头,因为默认Camera都是锁定的,只有解锁后MediaRecorder等多媒体进程调用,并设置一些参数,然后调用MediaRecorder:: start开启录制具体可以参阅如下代码:
% ~6 b- X% z$ vMediaRecorder mMediaRecorder = new MediaRecorder();
  ~. p6 y* `" [// Unlock the camera object before passing it to media recorder.   O7 {, k& Y: t$ b+ u6 k
camera.unlock(); 0 @6 K/ \# A5 u5 f2 ~! Y
mMediaRecorder.setCamera(camera); + ?, L% }% P# |3 r! P: K7 n
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); + z+ J# X8 f! m& m& Q
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); # ]% }. e( V0 O) }1 N6 U) g) q
mMediaRecorder.setProfile(mProfile);
+ @' c4 t6 [- }: ^% fmMediaRecorder.setMaxDuration(100000);//ms为单位
8 n& F2 L' ~5 I$ N* H! x4 G8 qlong dateTaken = System.currentTimeMillis();
' E& X/ ], S5 `3 C: fDate date = new Date(dateTaken);   a. [0 e: H2 q! k" A
SimpleDateFormat dateFormat = new SimpleDateFormat(getString(R.string.video_file_name_format)); 3 H* G2 \- l, t2 K
String title = dateFormat.format(date); : G" L8 d8 O0 M8 x# E
String filename = title + ".3gp"; // Used when emailing.
3 s/ r' G* O+ r. I3 gString cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
9 w7 X0 T+ J; |. D7 l8 j# G2 wString filePath = cameraDirPath + "/" + filename;
8 b$ i7 @! k3 k& t( [File cameraDir = new File(cameraDirPath);
: t7 d- d" {1 s$ o- X0 lcameraDir.mkdirs(); " d) R  ~$ |/ Q8 e7 |" Y) Y( T. b
mMediaRecorder.setOutputFile(filePath);
+ z% W: e, `7 {' Y1 M' W* utry {
7 N/ p6 c' i4 _5 l: dmMediaRecorder.prepare();
) Y* |8 Z2 L2 X8 I2 EmMediaRecorder.start(); // Recording is now started
4 G( i9 l4 M/ t* f, x} catch (RuntimeException e) {
) Z6 L8 C2 I% C; r' w8 `) [Log.e(TAG, "Could not start media recorder. ", e); + \1 v" F- |/ J
return;
& \" g- l2 A# [* e}
) M2 Q1 ^/ B1 o% C2 ~; f; G7、上面设置了最大间隔为100s,当100是视频录制结束,录制就会被停止,如果没有设时长和文件大小限制,那么通常需要调用MediaRecorder:: stop函数主动停止视频的录制,并将Camera对象通过lock函数继续加锁,示例代码如下   z0 p, e8 U) T0 ^. J% ]
mMediaRecorder.stop(); ' c# q9 k0 j  p: {* }* g# k9 e
mMediaRecorder.reset();
2 ?0 h- f" S  L& g9 jmMediaRecorder.release(); $ u7 C9 ^( l+ b8 M2 t1 R- y1 Q
mMediaRecorder = null;
" x5 a4 ^- S  S1 M: n0 Xif(camera != null)  + m/ K2 q% h- i1 A, T% X
camera.lock();

9 C1 S( p7 f1 q5 n7 g" [2 k: U9 `
之后的操作根据交互要么重新录制要么就释放Camera对象回到拍照流程的8步骤了。在这里就不做赘述了。

; o" R, t7 M+ O0 \- n; c% x1 T# l6 T( J! N& P

! b. ~! E; R' V3 l' q) l
( I0 H2 v, V- A( g  D摄像头模组论坛网,行业交流,分享,学习...- m: M1 Y. ?* z. S2 q3 e
www.ccm99.com
$ A9 @; _$ q) k% @0 ?: f6 P# r
高级模式
B Color Image Link Quote Code Smilies @朋友 |上传

本版积分规则

在线客服

客服电话

欢迎来电咨询

188-9985 8350

微信关注

手机APP程序:
扫码下载访问

微信公众平台:
摄像头之家公众号

微信小程序:
摄像头小程序

返回顶部

QQ|站点统计|小黑屋|手机版|Archiver|摄像头模组论坛网-摄像头方案网CCM99 ( 粤ICP备18155214号 )

Powered by Discuz! X3.4 Licensed© 2001-2013 Comsenz Inc.