查看: 10027|回复: 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做个简单的使用小结。
+ b( l* `, i4 S3 Y
. Y3 M! {: ]! K调用系统Camera App实现拍照和摄像功能

5 P  n1 \& m5 k& C    不是专门的Camera应用,一般用到Camera的需求就是获取照片或者视频,比如微博分享、随手记等,对于在Symbian系统上通过简单地调用系统自带的Camera APP来实现该功能是做不到的,但是Android系统强大的组件特性,使得应用开发者只需通过Intent就可以方便的打开系统自带的Camera APP,并通过MediaStroe方便地获取照片和视频的文件路径。具体我们还是用代码来说话吧:
" d6 Z8 E! C& v2 s/ e2 X) o例1、 实现拍照 ' e. t' ^# [9 e; G/ |
在菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,具体如下:
; l2 x' \, [) z+ F  G; `imgPath = "/sdcard/test/img.jpg";
  S3 h4 D9 t1 Q  D//必须确保文件夹路径存在,否则拍照后无法完成回调
% w, }% b( l9 a) {. `3 K& _' Z9 NFile vFile = new File(imgPath); ' m- p8 E# B- y1 y( H& O% E
if(!vFile.exists())
7 M* e8 C4 [3 H5 \# l3 B$ O# B{
, ^* Q; @7 A4 u0 P7 xFile vDirPath = vFile.getParentFile(); //new File(vFile.getParent()); ! V) n- M! ^- y& N
vDirPath.mkdirs();
& |) u! V4 n6 v}
& G$ F7 g! j1 dUri uri = Uri.fromFile(vFile);
" e& y) h& w& r" ]: j# BIntent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
* M1 t2 U- c& f5 Mintent.putExtra(MediaStore.EXTRA_OUTPUT, uri);// # x$ {, h& \. E4 _8 d
startActivityForResult(intent, SystemCapture);
* I0 R$ ]7 i7 [* }' }上面我们使用的是startActivityForResult,所以最好需要重载void onActivityResult(int requestCode, int resultCode, Intent data)函数,不过因为当传入文件路径的的情况下,data返回参数是null值,只要resultCode为RESULT_OK,则上述代码中/sdcard/test/img.jpg的图片文件就是最新的照片文件。所以我们在这里只需给出如下简单的代码,将其显示到ImageView中 8 J" Z& J: a' `
if (resultCode == RESULT_OK)  
9 I2 R% i* U1 M% w{ 6 v, A) V* s' E) u  ~- @$ r
iViewPic.setImageURI(Uri.fromFile(new File(imgPath))); $ L/ z" V" u7 l
}
) F+ r8 ?$ z' V2 s$ v2 n: d假设不传参数MediaStore.EXTRA_OUTPUT的情况下,onActivityResult函数在resultCode为RESULT_OK的情况下,data返回的参数是经过实际拍摄照片经过缩放的图像数据,可以通过类似如下方法来打印缩放图像的尺寸
( `( @6 Z. B8 {2 [% W# {" Nif (resultCode == RESULT_OK)  ) ?* \. W9 N" n4 T* Z1 o* r) _
{
" J7 f1 @, ^! CBitmap bmp = (Bitmap)data.getExtras().get("data"); 3 F! t% e0 _: _) s, u
Log.d("Test", "bmp width:" + bmp.getWidth() + ", height:" + bmp.getHeight());
% y7 f9 L+ ~/ G$ Q, c$ i} 6 R4 W1 i* G, t% b$ u% s0 v( e
另外假如仅仅是调用系统照相机拍照,不关心拍照结果,则可以简单使用如下代码
: ^& o& a9 J4 P! dIntent intent = new Intent(); //调用照相机
' y. C2 `+ C& I" N6 O% Vintent.setAction("android.media.action.STILL_IMAGE_CAMERA");
' y4 h7 ?  N% i1 EstartActivity(intent);
2 g/ ?) h1 i8 u% @备注:上面设置MediaStore.EXTRA_OUTPUT的方法,经过手机实测除了我们设定的路径下有照片外,在手机存储卡上也会保存一份照片,默认目录为sdcard/dcim/camera下面,我曾经尝试着想如果每次返回可以取得sdcard/dcim/camera下面的路径就好了,但是目前看来没办法直接获得,可以借助MediaStroe每次去查询最后一条照片记录,应该也是可行的。 * o8 Y! I1 o  p  R9 {8 d
例2、 实现摄像
& W- k( S! |# a( Q! X在摄像功能时,尝试着设置MediaStore.EXTRA_OUTPUT以传入类似拍照时的文件路径,结果在我的测试真机上,那个视频文件居然是一个0k的空文件,最后通过类似如下代码实现
9 F, M( s2 J+ |5 J3 I( NIntent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 2 x8 e6 l4 m$ C$ n
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//参数设置可以省略
* R' @9 k( n& ~! n% astartActivityForResult(intent, SystemVideoRecord); , P+ W* m9 ~# K' m
在onActivityResult函数中进行如下代码调用
. k% w- z; Q) aUri videoUri = data.getData(); % R3 `8 J2 F3 D/ _) i; j1 y
//String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.SIZE };
7 o& [' K; U' S1 OCursor cursor = managedQuery(videoUri, null, null, null, null); " l$ l+ ^. S# i, T
cursor.moveToFirst();//这个必须加,否则下面读取会报错 : A5 _  u9 t# D) P6 T( `- U
int num = cursor.getCount();
" U+ @: n4 ~( ]" w, N2 n4 w9 Y3 S5 ?String recordedVideoFilePath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); 5 z* C  [  k  o, n# f
int recordedVideoFileSize = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.SIZE));
, {( ]0 [3 D$ M: ^7 ]# KiResultText.setText(recordedVideoFilePath); 4 k. s9 B( U9 ?. U& I
Log.i("videoFilePath", recordedVideoFilePath); 4 ^9 v3 ?$ Q3 Q) t; n. A
Log.i("videoSize", ""+recordedVideoFileSize); ) |% n; L$ i# P( M! X+ H" [
上面的返回参数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,但是该文件居然是空白内容(不知道是不是跟手机有关,也没有在其它手机上验证过)。 % @/ l) b* i. F0 F3 R; h, `
根据Camera API实现自己的拍照和摄像程序 通过上面对调用系统Camera App实现拍照和摄像功能的例子,我们发现虽然能够满足我们的需求,但是毕竟自由度降低了,而且拍照的界面就是系统的样子,现在很多拍照程序,比如火爆的Camera 360软件等,就需要根据SDK提供的Camera API来编写自己的程序。
" U% o; q4 L! |5 x准备工作 上面调用系统Camera App,我们压根不需要任何权限,但是这里用Camera API,就必须在manifest内声明使用权限,通常由以下三项 - g7 T: ~* J: B1 ^6 n- B
<uses-permission android:name = "android.permission.CAMERA" />
9 u/ F$ m7 y. I% N0 b( Z5 }<uses-feature android:name = "android.hardware.camera" />
% F* M, Y6 L6 t( `<uses-feature android:name = "android.hardware.camera.autofocus" /> 4 o, O7 n. H0 _9 c* j' J0 _# c% ~
一般拍照和摄像的时候需要写到sd卡上,所以还有一向权限声明如下
0 ]+ U4 u  ~; ^; D; u<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> # q& }8 ]# T/ T% o: j
真做摄像功能时,需要音频录制和视频录制功能,所以又需要下面两项权限声明 # l2 j9 B. V# P, f" z6 v
<uses-permission android:name="android.permission.RECORD_VIDEO"/> ! Q6 U( ^7 {0 i& q+ k
<uses-permission android:name="android.permission.RECORD_AUDIO"/> , W  y9 P) U$ D  R- ^
另外使用Camera API拍照或摄像,都需要用到预览,预览就要用到SurfaceView,为此Activity的布局中必须有SurfaceView。

" L5 e* }' j1 n3 H/ _$ a* K
0 E' L( y# v2 p; C
( U/ O" M9 m6 I- ]1 {3 j. B! r, R拍照流程 上面简单介绍了下准备工作,下面结合拍照过程中的需要用到的API对拍照流程做下简单描述
& T& W3 k% i8 ]# {  w1、在Activity的OnCreate函数中设置好SurfaceView,包括设置SurfaceHolder.Callback对象和SurfaceHolder对象的类型,具体如下
8 Z: l- ?  ~  y, E8 gSurfaceView mpreview = (SurfaceView) this.findViewById(R.id.camera_preview);
1 G1 f2 S/ b- S% T, pSurfaceHolder mSurfaceHolder = mpreview.getHolder(); " p. S5 T! n+ b; c
mSurfaceHolder.addCallback(this);
* H! d2 U2 l, I7 bmSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 4 }( \( C! s% A# B7 z& x
2、在SurfaceHolder.Callback的surfaceCreated函数中,使用Camera的Open函数开机摄像头硬件,这个API在SDK 2.3之前,是没有参数的,2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,再通过getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,假如摄像头开启成功则返回一个Camera对象,否则就抛出异常;
1 G$ n, v4 o( @% S/ ?' K& q) P0 [% w3、开启成功的情况下,在SurfaceHolder.Callback的surfaceChanged函数中调用getParameters函数得到已打开的摄像头的配置参数Parameters对象,如果有需要就修改对象的参数,然后调用setParameters函数设置进去(SDK2.2以后,还可以通过Camera::setDisplayOrientation设置方向);
, N- s0 m! Y1 V4、同样在surfaceChanged函数中,通过Camera::setPreviewDisplay为摄像头设置SurfaceHolder对象,设置成功后调用Camera::startPreview函数开启预览功能,上面3,4两步的代码可以如下所示
3 q% h3 V9 F6 d! n! t$ tpublic void surfaceChanged(SurfaceHolder holder, int format, int w, int h) " h; u8 `% {& _7 B/ d
{ : l8 I- h" e* j9 b: P) N2 [
//已经获得Surface的width和height,设置Camera的参数
$ m" O/ N7 F* T9 v7 w, [& Y3 TCamera.Parameters parameters = camera.getParameters(); ( S" t. d1 M, @! o0 f
parameters.setPreviewSize(w, h); . I4 N4 i. p% h; Q: ]
List<Size> vSizeList = parameters.getSupportedPictureSizes();
: q7 K$ [& R7 Q  x2 e+ M- c' ?for(int num = 0; num < vSizeList.size(); num++)
5 M  c, E$ s* v7 q& ~{ " y/ r7 k9 X  ^
Size vSize = vSizeList.get(num);
$ {; B  O$ y" u9 b, s6 V, W6 U} & e* b+ a! p8 X( `/ T3 i
if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
# H. a6 Y4 p) B& Q; I{
/ F- g3 X8 O+ r( A  y" y//如果是竖屏 : Y, b; h4 j- h- `
parameters.set("orientation", "portrait");
' u5 M8 Y0 Z" H. @+ i$ i* R//在2.2以上可以使用 1 }; L3 m( u% v5 u  l3 M& O. ~) o
//camera.setDisplayOrientation(90); # @! r4 Q9 P$ {  R
}
7 d% v2 }4 G! J% i: i/ U+ Felse
; C: \" w. y2 M* ~! i: h/ Q$ O{
* e. h, o$ K6 L4 k7 Tparameters.set("orientation", "landscape"); ( N) d0 ^6 h2 Y+ K$ f1 d
//在2.2以上可以使用
; Y9 o6 ~, ?6 C0 M//camera.setDisplayOrientation(0);
. ]" l: N* N# Q& N% C7 ?( q6 I, z}
3 C- E2 e. ?4 @" K' L) E1 ]) gcamera.setParameters(parameters); * F6 F) J9 A0 M9 I
try { : g+ Q9 ~- Z* m2 z  }$ k
//设置显示
! z! F8 O# t/ b0 f+ R$ ycamera.setPreviewDisplay(holder); " y' w3 [! u7 Y+ L- A% r/ ~1 W
} catch (IOException exception) { $ a4 m2 z5 U( w7 o" l
camera.release(); 7 D6 L3 c' i/ q$ O% V
camera = null; ! C$ ?& W5 h6 r5 I8 Y6 Z9 ?- Z; t
}
. m) G: q% R( o$ ?//开始预览 , O+ \) ?9 ]- f3 Y; k
camera.startPreview();
3 q( P; s, ?% v+ H/ I8 Z6 w* L; H7 }} & ?2 |) F8 N' @3 B4 X
5、假设要支持自动对焦功能,则在需要的情况下,或者在上述surfaceChanged调用完startPreview函数后,可以调用Camera::autoFocus函数来设置自动对焦回调函数,该步是可选操作,有些设备可能不支持,可以通过Camera::getFocusMode函数查询。代码可以参考如下:
% R# U1 \$ C5 Z, A// 自动对焦 / V* N) W( @6 f  j8 u6 t
camera.autoFocus(new AutoFocusCallback()
* n/ o& M9 e' j{
  u+ f. S" j; [4 I, J( a2 }4 g* H@Override " R- r- E/ J; z) @& o; O) Z' B
public void onAutoFocus(boolean success, Camera camera) 1 r/ w8 f7 X5 K8 P# o6 m
{
( r5 c( L( G0 V6 G4 oif (success)
8 u8 T& u9 e3 K# q1 u{ . o8 ~$ ~% }( L5 Y1 ~6 q$ {/ D5 z
// success为true表示对焦成功,改变对焦状态图像
+ k6 ^# v, ?5 n1 n7 aivFocus.setImageResource(R.drawable.focus2); 0 {; b$ E$ U+ k' Q/ `6 z# A- f
}
# J% n, m0 n' I0 N" [} 2 D$ r3 y+ o- Q. ?1 g) i
});
- U5 ~% v4 [- S4 L8 `6、在需要拍照的时候,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)函数来完成拍照,这个函数中可以四个回调接口,ShutterCallback是快门按下的回调,在这里我们可以设置播放“咔嚓”声之类的操作,后面有三个PictureCallback接口,分别对应三份图像数据,分别是原始图像、缩放和压缩图像和JPG图像,图像数据可以在PictureCallback接口的void onPictureTaken(byte[] data, Camera camera)中获得,三份数据相应的三个回调正好按照参数顺序调用,通常我们只关心JPG图像数据,此时前面两个PictureCallback接口参数可以直接传null; ' t3 A6 D; n. ^) n6 U
7、每次调用takePicture获取图像后,摄像头会停止预览,假如需要继续拍照,则我们需要在上面的PictureCallback的onPictureTaken函数末尾,再次掉哟更Camera::startPreview函数;
0 V8 y' l0 s2 n8、在不需要拍照的时候,我们需要主动调用Camera::stopPreview函数停止预览功能,并且调用Camera::release函数释放Camera,以便其他应用程序调用。SDK中建议放在Activity的Pause函数中,但是我觉得放在surfaceDestroyed函数中更好,示例代码如下
' U2 H" e0 x4 |! S// 停止拍照时调用该方法 8 @# J9 ^( ^) y& b. \. K
public void surfaceDestroyed(SurfaceHolder holder) ( M9 D" d& T1 J0 F/ P
{
0 J1 r% Q0 q% ]) H7 ~/ I& P// 释放手机摄像头 + o5 U9 p2 ]% Y, o9 S
camera.release();
- d8 N$ l+ e$ S7 Y' W% l}
/ N  m( d& @+ p8 ^# H/ }
/ T# @& v+ v  P9 W0 L( o
/ n) q+ s$ g' z' }

% Q5 l0 h- h- e. W6 Y# F+ t以上就是自己实现拍照程序的的流程,一般还可以还可以获取预览帧的图像数据,可以分别通过Camera::setPreviewCallback和Camera::setOneShotPreviewCallback来设置每帧或下一帧图像数据的回调,这里就不做展开了。 & q8 _, D! X; j; P3 _  d& J

! G4 w: k9 z6 T9 u摄像流程 摄像流程也是需要预览的,而且流程上与拍照流程在起始的1~4步流程和结束的8流程是一样的,唯一不同的是6和7两个步骤,至于5自动对焦本身就是可选的,在摄像流程也没必要。
% k3 ~; \6 z' J1 R1 X0 `
3 T5 i6 O5 ]4 X
6、开启视频录制,需要创建一个MediaRecorder对象,并调用Camera::unLock操作解锁摄像头,因为默认Camera都是锁定的,只有解锁后MediaRecorder等多媒体进程调用,并设置一些参数,然后调用MediaRecorder:: start开启录制具体可以参阅如下代码: ) w3 V% b+ q. B' V6 o
MediaRecorder mMediaRecorder = new MediaRecorder();
" N* t- g3 A* k6 z) `: L% n// Unlock the camera object before passing it to media recorder.
2 E; f) G  l/ N0 \; Qcamera.unlock();
8 B) B+ ^9 g, c5 L) T% N2 [mMediaRecorder.setCamera(camera);
- a' H# c3 v8 gmMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
; J% I+ T1 N7 P8 S3 i% `) N, e+ VmMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
5 h# Y+ V) T% a2 umMediaRecorder.setProfile(mProfile);
( k0 ?( O$ m/ ImMediaRecorder.setMaxDuration(100000);//ms为单位 ) d9 k7 T8 T% ], Y
long dateTaken = System.currentTimeMillis(); 9 E7 p' k, R3 n3 r* c0 I
Date date = new Date(dateTaken);
# r, t9 Y, R. `/ E1 t( O; MSimpleDateFormat dateFormat = new SimpleDateFormat(getString(R.string.video_file_name_format));
6 h: L6 W6 @) r6 F: |! lString title = dateFormat.format(date);
. ~. N( y' J* s6 h' \: }' m* |String filename = title + ".3gp"; // Used when emailing. + {/ R7 e! d0 p* s
String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; " T# _- E5 Q/ Z* _
String filePath = cameraDirPath + "/" + filename; * X4 I, q  X) l4 }
File cameraDir = new File(cameraDirPath); . r" r. C9 o8 W7 {/ S; S
cameraDir.mkdirs();   p" |$ l5 T( `/ V
mMediaRecorder.setOutputFile(filePath); 5 J- A7 u- A0 k: L2 `2 b
try {
: H8 R" P7 Y9 f& }mMediaRecorder.prepare();
/ P4 f# @" i9 A! l9 @; wmMediaRecorder.start(); // Recording is now started ; F! e3 Y  ]6 E: |) D4 q& [
} catch (RuntimeException e) {
3 ~2 R+ ]0 y3 [) l9 P- x/ l5 E! k" eLog.e(TAG, "Could not start media recorder. ", e); 6 t) S: v: F: ]# K" U- G1 C
return; 5 b3 E2 z$ q4 p7 }. k; Z
}
& w! E! h: `4 f" R6 v& v6 P+ O/ F7、上面设置了最大间隔为100s,当100是视频录制结束,录制就会被停止,如果没有设时长和文件大小限制,那么通常需要调用MediaRecorder:: stop函数主动停止视频的录制,并将Camera对象通过lock函数继续加锁,示例代码如下 ( o/ B, s& `1 k! y7 R
mMediaRecorder.stop(); ) |: Q+ L* T5 C7 X0 z! p- q. A) P
mMediaRecorder.reset(); 7 {8 _, U  q0 L' ^
mMediaRecorder.release(); 0 I2 H4 J8 J0 _: p
mMediaRecorder = null; ( ~9 d5 S, e8 t1 x
if(camera != null)  
! q7 T5 i5 }& A4 Q6 b9 P2 qcamera.lock();

2 X2 o; F% R. r- a& X  }, S: E2 g2 u$ L1 x8 c& s, N" Z) {
之后的操作根据交互要么重新录制要么就释放Camera对象回到拍照流程的8步骤了。在这里就不做赘述了。
5 c9 Q3 b' ?! k7 U/ v; Z

* S. z3 T9 W' u* p5 X# c
4 _1 E4 ?' }; q/ F' a( ~; e1 b- L# u1 w  `1 c: @( k
摄像头模组论坛网,行业交流,分享,学习...
+ `  j" G. e$ ~5 Pwww.ccm99.com
+ [3 G* o. J! _" N0 \0 |
高级模式
B Color Image Link Quote Code Smilies @朋友 |上传

本版积分规则

在线客服

客服电话

欢迎来电咨询

188-9985 8350

微信关注

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

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

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

返回顶部

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

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