查看: 10606|回复: 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做个简单的使用小结。
7 O( ]9 p# a" n  w9 | / k, \- E% P$ M% k9 \2 |
调用系统Camera App实现拍照和摄像功能

  B8 V1 j6 H" Q4 M. ~    不是专门的Camera应用,一般用到Camera的需求就是获取照片或者视频,比如微博分享、随手记等,对于在Symbian系统上通过简单地调用系统自带的Camera APP来实现该功能是做不到的,但是Android系统强大的组件特性,使得应用开发者只需通过Intent就可以方便的打开系统自带的Camera APP,并通过MediaStroe方便地获取照片和视频的文件路径。具体我们还是用代码来说话吧:
* K( |' A4 z# s* u/ ~例1、 实现拍照 ! n+ ~* r# H& \0 `4 ]
在菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,具体如下:% [; c  Z# v+ ~; Q$ Z% E
imgPath = "/sdcard/test/img.jpg"; ! `! v0 `+ T" Z1 O! X0 M
//必须确保文件夹路径存在,否则拍照后无法完成回调 9 z. z+ A5 `/ G( s; `7 n
File vFile = new File(imgPath);   L/ P) _) r8 [) X
if(!vFile.exists()) * [/ i7 S4 z, ~" i
{ 2 k  |) H  t/ n4 L, K: s, k5 j
File vDirPath = vFile.getParentFile(); //new File(vFile.getParent());
, S3 }7 r- t) c3 C- N5 yvDirPath.mkdirs();
; k% q- u/ d. n2 z- l8 a}
3 c- @/ }8 Q$ h7 N3 b/ JUri uri = Uri.fromFile(vFile);
- l: L( G4 a1 L; ]2 `$ c6 OIntent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  ]& |- Z' X; \+ }! Qintent.putExtra(MediaStore.EXTRA_OUTPUT, uri);//
% d, ]5 e: A7 R# `startActivityForResult(intent, SystemCapture);
* u8 p* C7 v4 i1 X上面我们使用的是startActivityForResult,所以最好需要重载void onActivityResult(int requestCode, int resultCode, Intent data)函数,不过因为当传入文件路径的的情况下,data返回参数是null值,只要resultCode为RESULT_OK,则上述代码中/sdcard/test/img.jpg的图片文件就是最新的照片文件。所以我们在这里只需给出如下简单的代码,将其显示到ImageView中 ' U% \- c9 }9 I7 ]' q1 @+ w
if (resultCode == RESULT_OK)  9 f2 u* L5 ^8 B1 ~  H3 G4 p- m
{
' S6 B) K1 C" f& MiViewPic.setImageURI(Uri.fromFile(new File(imgPath))); ' o/ k# U( }# l7 {- U1 c' g
} ( S! S+ L, U9 F1 _5 \% p6 b* Z/ U
假设不传参数MediaStore.EXTRA_OUTPUT的情况下,onActivityResult函数在resultCode为RESULT_OK的情况下,data返回的参数是经过实际拍摄照片经过缩放的图像数据,可以通过类似如下方法来打印缩放图像的尺寸
+ |! I: ^! S& ?, K  }if (resultCode == RESULT_OK)  # l1 H6 c+ U9 ^# i# S
{
8 S5 B/ m- z* a* ~3 |Bitmap bmp = (Bitmap)data.getExtras().get("data");
9 Q% o: i, j/ x4 K6 W' Q6 OLog.d("Test", "bmp width:" + bmp.getWidth() + ", height:" + bmp.getHeight()); " r% j) N+ m  B
}
8 X7 t& j$ _1 O另外假如仅仅是调用系统照相机拍照,不关心拍照结果,则可以简单使用如下代码
1 x6 C3 I, U& t/ t& t, k8 \' ^( ]Intent intent = new Intent(); //调用照相机
3 K4 @$ t# C0 h! ]5 w- Xintent.setAction("android.media.action.STILL_IMAGE_CAMERA"); 8 ]5 @+ A' ^6 S" i+ V! ~. h
startActivity(intent); ) Q$ b9 u0 Z; Q" Q) @7 d0 y
备注:上面设置MediaStore.EXTRA_OUTPUT的方法,经过手机实测除了我们设定的路径下有照片外,在手机存储卡上也会保存一份照片,默认目录为sdcard/dcim/camera下面,我曾经尝试着想如果每次返回可以取得sdcard/dcim/camera下面的路径就好了,但是目前看来没办法直接获得,可以借助MediaStroe每次去查询最后一条照片记录,应该也是可行的。
4 \9 k5 d. w& P% U例2、 实现摄像 1 O2 M. L. }" ~+ `5 m8 y) d
在摄像功能时,尝试着设置MediaStore.EXTRA_OUTPUT以传入类似拍照时的文件路径,结果在我的测试真机上,那个视频文件居然是一个0k的空文件,最后通过类似如下代码实现
3 O% l* a4 ]) [6 X" SIntent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); * R* X" J3 a: q) z- j5 A
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//参数设置可以省略
+ R0 l( K* ^* V7 C, p( }; QstartActivityForResult(intent, SystemVideoRecord);
$ ~! H7 ?3 H4 p6 l8 V+ l% f9 _在onActivityResult函数中进行如下代码调用
8 |: L; v$ S+ h! O& f0 z/ nUri videoUri = data.getData();
& j  a& J' M5 K; X" t8 y9 z//String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.SIZE };
* X/ Y7 k6 T; V, r2 mCursor cursor = managedQuery(videoUri, null, null, null, null); ; R7 A% c& t9 H0 o! g
cursor.moveToFirst();//这个必须加,否则下面读取会报错
& ?& x& z7 l, v: t- B$ bint num = cursor.getCount(); 6 U/ j# b& O" w3 T# _, v8 C2 E$ X
String recordedVideoFilePath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); 3 ^' t) E2 U8 M# t4 N- _$ Q
int recordedVideoFileSize = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.SIZE)); 9 _7 h3 G' W' M3 K+ r6 w
iResultText.setText(recordedVideoFilePath);
. r, b! ^5 y+ F1 v, `" ]Log.i("videoFilePath", recordedVideoFilePath);
& V5 c0 k5 I% _. m! X) `6 P1 p" ~Log.i("videoSize", ""+recordedVideoFileSize); ! D9 C5 N* F: u& c2 A
上面的返回参数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,但是该文件居然是空白内容(不知道是不是跟手机有关,也没有在其它手机上验证过)。 9 k! h; }. o0 C0 S0 m" D
根据Camera API实现自己的拍照和摄像程序 通过上面对调用系统Camera App实现拍照和摄像功能的例子,我们发现虽然能够满足我们的需求,但是毕竟自由度降低了,而且拍照的界面就是系统的样子,现在很多拍照程序,比如火爆的Camera 360软件等,就需要根据SDK提供的Camera API来编写自己的程序。 ( `. T9 f7 f- ^) N" V8 v
准备工作 上面调用系统Camera App,我们压根不需要任何权限,但是这里用Camera API,就必须在manifest内声明使用权限,通常由以下三项
: M) t2 }, u0 G! \$ i# q<uses-permission android:name = "android.permission.CAMERA" />
+ b' @2 [: g0 h3 {3 H- T" T/ D<uses-feature android:name = "android.hardware.camera" />
+ O; \/ h5 g" _) y6 r' J<uses-feature android:name = "android.hardware.camera.autofocus" />
  X/ ^+ A1 t8 F3 Q" _/ Q( _6 d: z8 A一般拍照和摄像的时候需要写到sd卡上,所以还有一向权限声明如下
4 a6 b7 R7 Y% c  C- Y1 Q<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 9 W1 e& e1 b5 J+ [2 U
真做摄像功能时,需要音频录制和视频录制功能,所以又需要下面两项权限声明
8 v) C: K0 e! Q( P% e<uses-permission android:name="android.permission.RECORD_VIDEO"/>
! _8 ~$ l$ v5 x! S( A<uses-permission android:name="android.permission.RECORD_AUDIO"/> 2 t6 `$ Q. E5 R; h8 E. m5 `
另外使用Camera API拍照或摄像,都需要用到预览,预览就要用到SurfaceView,为此Activity的布局中必须有SurfaceView。

% o+ Y& e7 z" W5 H- z2 v7 V4 e" a* t8 k. v% P

" @- Q4 l! c+ G/ D( ?4 Y9 y拍照流程 上面简单介绍了下准备工作,下面结合拍照过程中的需要用到的API对拍照流程做下简单描述
" F- [. Z3 t) ^  U1、在Activity的OnCreate函数中设置好SurfaceView,包括设置SurfaceHolder.Callback对象和SurfaceHolder对象的类型,具体如下 / v' C( ^, z/ n* I
SurfaceView mpreview = (SurfaceView) this.findViewById(R.id.camera_preview);
% T, N8 x6 T* W, f. SSurfaceHolder mSurfaceHolder = mpreview.getHolder(); , l9 x# ^% E6 M: m* |% N$ r. O
mSurfaceHolder.addCallback(this); 2 \5 k0 ?; ]/ Z! ^% b
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
! d4 O, z# T" F8 m2、在SurfaceHolder.Callback的surfaceCreated函数中,使用Camera的Open函数开机摄像头硬件,这个API在SDK 2.3之前,是没有参数的,2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,再通过getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,假如摄像头开启成功则返回一个Camera对象,否则就抛出异常; : w, ^2 s( E; C& x
3、开启成功的情况下,在SurfaceHolder.Callback的surfaceChanged函数中调用getParameters函数得到已打开的摄像头的配置参数Parameters对象,如果有需要就修改对象的参数,然后调用setParameters函数设置进去(SDK2.2以后,还可以通过Camera::setDisplayOrientation设置方向); : s6 _$ s0 O4 m3 P! o' I8 a+ w
4、同样在surfaceChanged函数中,通过Camera::setPreviewDisplay为摄像头设置SurfaceHolder对象,设置成功后调用Camera::startPreview函数开启预览功能,上面3,4两步的代码可以如下所示 ( s: p% z7 [" r, b
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
$ s7 ?3 ~; O+ q{
0 {% o* T) i* n: P//已经获得Surface的width和height,设置Camera的参数 7 T! J- e- U* v/ O
Camera.Parameters parameters = camera.getParameters(); 6 C& V3 s! Z% l2 {
parameters.setPreviewSize(w, h);
  y6 q3 @$ }& K  s6 KList<Size> vSizeList = parameters.getSupportedPictureSizes(); . B) e& k( I7 o$ L. Q
for(int num = 0; num < vSizeList.size(); num++) ; ~+ b& \4 X0 Y' j
{
: O2 A/ Q4 W9 E# j; }Size vSize = vSizeList.get(num); 7 \! A; H' l6 E! h
} 5 S6 K8 @2 X2 P3 @) y3 v8 p: z8 D
if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
; o! n; ~/ Z- t* C+ L{
6 r! b0 S6 Y: r7 y//如果是竖屏 8 A) W1 Y& X& q
parameters.set("orientation", "portrait");   E2 v' A# T/ Z2 ~0 c
//在2.2以上可以使用 . F( `( k9 Y: V4 \+ p
//camera.setDisplayOrientation(90); 3 k4 B9 [) \0 P8 x8 z
} 6 w$ R! |, F5 R/ B9 r
else
8 w2 v( E1 |; r( f/ I: \- i{ / \" ^6 N1 W8 q  G
parameters.set("orientation", "landscape");
1 K) Q9 e4 E# Z1 J' r; S  u0 J  _* L3 I//在2.2以上可以使用 % }& \% j9 F# N( o9 a8 i! S
//camera.setDisplayOrientation(0);
! ?5 S3 E4 G$ X+ P; g# G- g( v& E} ; }# E, U' V! [* r1 G. H
camera.setParameters(parameters);
1 L. t# w* Z8 g; r6 M5 _# Jtry { & c+ j7 x7 L; }; K2 |
//设置显示 ( H; s" }0 A+ D
camera.setPreviewDisplay(holder);
4 p0 V  k; ^, o  S} catch (IOException exception) { 6 F6 e7 W. _( `8 O; V
camera.release(); 1 A  ~6 ?* `" h2 S2 b4 Q* N8 y
camera = null;
: P5 |) p. I3 Q' H4 a}
& K% s  I, y6 A* \! a//开始预览 6 ]; o, P% E. c6 Q! l" Z4 y* G
camera.startPreview(); 1 Y: y! P" I0 |8 n& G
} 3 I$ a- [; f! B6 s9 j) E4 h6 q
5、假设要支持自动对焦功能,则在需要的情况下,或者在上述surfaceChanged调用完startPreview函数后,可以调用Camera::autoFocus函数来设置自动对焦回调函数,该步是可选操作,有些设备可能不支持,可以通过Camera::getFocusMode函数查询。代码可以参考如下:
5 f' H, [$ K( {. y// 自动对焦 % `0 w/ [: u( e
camera.autoFocus(new AutoFocusCallback() 8 D8 T6 s! S* P) G5 x# l: O
{
& n4 z4 [8 a) r; s" J0 v@Override & q. v- i2 Z+ u1 T, b
public void onAutoFocus(boolean success, Camera camera) ' d, j- b- z, T
{
2 J' a4 _% q# N; B, ~5 R2 }/ Gif (success) 3 ^" [4 ^% a9 M' t8 f
{ ( _6 F; Y6 h- M; i* N
// success为true表示对焦成功,改变对焦状态图像 9 l- W# ?, @# s% P
ivFocus.setImageResource(R.drawable.focus2);
; S' q; r8 D, ~( `( l}
2 a* O" t9 r, a: V} % h0 f+ B* b# d) l6 l
});
% w% i( C7 o& N& \5 U1 l6、在需要拍照的时候,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)函数来完成拍照,这个函数中可以四个回调接口,ShutterCallback是快门按下的回调,在这里我们可以设置播放“咔嚓”声之类的操作,后面有三个PictureCallback接口,分别对应三份图像数据,分别是原始图像、缩放和压缩图像和JPG图像,图像数据可以在PictureCallback接口的void onPictureTaken(byte[] data, Camera camera)中获得,三份数据相应的三个回调正好按照参数顺序调用,通常我们只关心JPG图像数据,此时前面两个PictureCallback接口参数可以直接传null;
7 y4 s+ _" C9 i+ \: i7、每次调用takePicture获取图像后,摄像头会停止预览,假如需要继续拍照,则我们需要在上面的PictureCallback的onPictureTaken函数末尾,再次掉哟更Camera::startPreview函数; / w- X6 i( Z5 ?, t  l
8、在不需要拍照的时候,我们需要主动调用Camera::stopPreview函数停止预览功能,并且调用Camera::release函数释放Camera,以便其他应用程序调用。SDK中建议放在Activity的Pause函数中,但是我觉得放在surfaceDestroyed函数中更好,示例代码如下 % p1 P: z) x. H
// 停止拍照时调用该方法 ' V& Q3 A( b5 t8 H; d
public void surfaceDestroyed(SurfaceHolder holder) . l7 G6 T6 E1 D: Q6 J$ K; d# P
{ ' v1 W6 |  {+ n1 T' y% z- [
// 释放手机摄像头 + c/ ^4 v7 B9 W. P4 q( `
camera.release();
/ Z4 K  w2 M0 w7 b}
. a; B6 A) g# d) d. x, o, |
7 z3 {8 B" q) D, Z# G* `9 _+ h

9 H( K! s- g, N, N9 Y7 p
! G8 v' i7 {6 T! A+ }3 ?8 r以上就是自己实现拍照程序的的流程,一般还可以还可以获取预览帧的图像数据,可以分别通过Camera::setPreviewCallback和Camera::setOneShotPreviewCallback来设置每帧或下一帧图像数据的回调,这里就不做展开了。
: R! m6 \+ C; R" Z
# q- S( i5 g# N. w摄像流程 摄像流程也是需要预览的,而且流程上与拍照流程在起始的1~4步流程和结束的8流程是一样的,唯一不同的是6和7两个步骤,至于5自动对焦本身就是可选的,在摄像流程也没必要。

, r, \$ v' m* u7 d9 s" ~/ w$ F1 z2 s9 Z% P) V! A
6、开启视频录制,需要创建一个MediaRecorder对象,并调用Camera::unLock操作解锁摄像头,因为默认Camera都是锁定的,只有解锁后MediaRecorder等多媒体进程调用,并设置一些参数,然后调用MediaRecorder:: start开启录制具体可以参阅如下代码:
6 B. {, Q3 Q; u6 R6 p8 yMediaRecorder mMediaRecorder = new MediaRecorder();
. f- o: w) u7 H+ S6 K) w% b$ _// Unlock the camera object before passing it to media recorder.
# `# l& K& d9 e0 Xcamera.unlock(); , _, E; y9 j4 L9 N$ Z& S( |5 ]
mMediaRecorder.setCamera(camera); 7 z/ C+ ~5 C8 e/ R3 ?8 l
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
" y  K7 @' N$ Y0 l' E: e1 HmMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); : _6 I$ l9 z$ K( a; B+ Y8 a7 e
mMediaRecorder.setProfile(mProfile);
- F' J/ @4 h7 J, umMediaRecorder.setMaxDuration(100000);//ms为单位
: Q2 k  ?) }& a8 J  B# l' U. [long dateTaken = System.currentTimeMillis();
- m+ L! D/ I2 ~& P) P3 T) H" @Date date = new Date(dateTaken);
- G/ I" N+ v& t3 HSimpleDateFormat dateFormat = new SimpleDateFormat(getString(R.string.video_file_name_format));
& f. e: E7 v+ mString title = dateFormat.format(date); 7 ^5 e7 T% a2 ]1 e1 h7 F0 E) C
String filename = title + ".3gp"; // Used when emailing.
7 S& W0 Q3 v( q( k/ UString cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
, n5 h; ^' z% C2 X8 H4 SString filePath = cameraDirPath + "/" + filename;
6 N, G0 q$ |: ]& k9 ]% C# [File cameraDir = new File(cameraDirPath);
$ x5 T0 u7 A0 \! [: s7 FcameraDir.mkdirs(); , V; y# c6 q2 G" m
mMediaRecorder.setOutputFile(filePath); 2 X# v9 P7 Q' g" b+ g& t
try {
4 }1 w5 ]! ?$ T. Q. X5 HmMediaRecorder.prepare(); # |! o" f6 v8 r# f; U5 E" d  [
mMediaRecorder.start(); // Recording is now started % J( L. f; P* u+ C- A& p* y$ M. u; R
} catch (RuntimeException e) {
2 L- s% t$ C+ c, y4 C# h2 MLog.e(TAG, "Could not start media recorder. ", e); ! q7 D/ e1 K- x
return;
& h& g: A  M$ w$ b0 [& m1 k1 q  m}
4 t2 M1 U! p+ B3 u7、上面设置了最大间隔为100s,当100是视频录制结束,录制就会被停止,如果没有设时长和文件大小限制,那么通常需要调用MediaRecorder:: stop函数主动停止视频的录制,并将Camera对象通过lock函数继续加锁,示例代码如下
0 o: x2 I  ^0 H4 imMediaRecorder.stop();
5 d1 b  K  L) t8 ~" j4 }. u1 t5 YmMediaRecorder.reset(); 0 e1 g4 ]/ T9 J
mMediaRecorder.release();
  ?9 O6 U) B9 E7 z; |mMediaRecorder = null;
/ ]& n) _. B* w" tif(camera != null)  
- ?; j- {* E& W- h6 fcamera.lock();

8 {# {0 W9 X! @
' m& j8 q9 c: _9 h2 Q- s- R  T9 \7 a之后的操作根据交互要么重新录制要么就释放Camera对象回到拍照流程的8步骤了。在这里就不做赘述了。

; _6 U2 G4 O1 a2 a9 |  k3 `
% x1 ^2 {1 O9 ?6 |, G- \5 Q7 K# W. L( I% k. y/ v- u' P2 H" o
7 }  i9 q6 s) l; Q( B( ?
摄像头模组论坛网,行业交流,分享,学习...3 ^% F" K( z# V% c3 x. \
www.ccm99.com
2 I# C( A5 A0 j" v
高级模式
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.