查看: 9895|回复: 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做个简单的使用小结。
0 ^0 q1 @" d8 S7 @# y7 ]& l8 J  d
" |/ g7 H0 {" I' B( J+ X) E调用系统Camera App实现拍照和摄像功能
9 w) d7 _# ^. m! U% x8 k
    不是专门的Camera应用,一般用到Camera的需求就是获取照片或者视频,比如微博分享、随手记等,对于在Symbian系统上通过简单地调用系统自带的Camera APP来实现该功能是做不到的,但是Android系统强大的组件特性,使得应用开发者只需通过Intent就可以方便的打开系统自带的Camera APP,并通过MediaStroe方便地获取照片和视频的文件路径。具体我们还是用代码来说话吧:
0 d9 J1 F& \$ ^, ~例1、 实现拍照
5 b0 I+ }, |/ [2 v在菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,具体如下:4 }; D' z$ s7 Z5 [2 N% b, l6 z+ l
imgPath = "/sdcard/test/img.jpg"; & F7 F: e2 s1 _$ Y& M4 P# T! m
//必须确保文件夹路径存在,否则拍照后无法完成回调
5 J9 d3 O" t; C: D3 k) `File vFile = new File(imgPath); ( z+ q  @8 J% V' A( e7 N
if(!vFile.exists()) 0 f( f5 `% {$ w; q
{
/ O' N/ R0 ^% ^. [' Q0 |File vDirPath = vFile.getParentFile(); //new File(vFile.getParent()); : h/ h4 l% Z5 @& g- t8 }4 @6 f
vDirPath.mkdirs();
0 c, |% c7 f, f8 P0 \" k}
6 k% Z) y7 _2 U0 z. K3 ?' o* R# H: LUri uri = Uri.fromFile(vFile); 1 [' v8 Y: ~8 Y8 w& v
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
: n3 s% d( [4 v0 ~9 t% ?' u$ ^5 Q" }intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);//
: k( C3 I' e: Q) v5 jstartActivityForResult(intent, SystemCapture);
8 O1 j' w6 L7 d% q上面我们使用的是startActivityForResult,所以最好需要重载void onActivityResult(int requestCode, int resultCode, Intent data)函数,不过因为当传入文件路径的的情况下,data返回参数是null值,只要resultCode为RESULT_OK,则上述代码中/sdcard/test/img.jpg的图片文件就是最新的照片文件。所以我们在这里只需给出如下简单的代码,将其显示到ImageView中 5 O. W( x+ c5 s. c0 d0 H. N) s' g
if (resultCode == RESULT_OK)  : o. F5 n6 Q' M7 g
{ # b: ]( G& ~9 q5 q
iViewPic.setImageURI(Uri.fromFile(new File(imgPath)));
2 }0 F5 Y' C! ^}
3 ^7 `* A2 `7 r2 _2 i- t% _假设不传参数MediaStore.EXTRA_OUTPUT的情况下,onActivityResult函数在resultCode为RESULT_OK的情况下,data返回的参数是经过实际拍摄照片经过缩放的图像数据,可以通过类似如下方法来打印缩放图像的尺寸 * g; M" H# a; A
if (resultCode == RESULT_OK)  
/ k/ x0 M; }/ i! s{ + X" W) `2 W$ |% z+ `& z$ t1 n
Bitmap bmp = (Bitmap)data.getExtras().get("data"); + \, G1 o; t3 Y5 m5 s
Log.d("Test", "bmp width:" + bmp.getWidth() + ", height:" + bmp.getHeight());
% I$ @1 [3 {1 I% r} ) z6 n% E. V% a
另外假如仅仅是调用系统照相机拍照,不关心拍照结果,则可以简单使用如下代码 0 \& H% u2 L5 L  ]
Intent intent = new Intent(); //调用照相机
1 n/ N- j- U- f5 x5 d4 y7 [# \intent.setAction("android.media.action.STILL_IMAGE_CAMERA"); # g% W1 r( v2 N/ p
startActivity(intent); 1 n4 W! {0 J/ ~- Q& d9 Q/ J- F
备注:上面设置MediaStore.EXTRA_OUTPUT的方法,经过手机实测除了我们设定的路径下有照片外,在手机存储卡上也会保存一份照片,默认目录为sdcard/dcim/camera下面,我曾经尝试着想如果每次返回可以取得sdcard/dcim/camera下面的路径就好了,但是目前看来没办法直接获得,可以借助MediaStroe每次去查询最后一条照片记录,应该也是可行的。
. y# `: F; h' c& A" p例2、 实现摄像
- _' ?- X4 r7 P  W在摄像功能时,尝试着设置MediaStore.EXTRA_OUTPUT以传入类似拍照时的文件路径,结果在我的测试真机上,那个视频文件居然是一个0k的空文件,最后通过类似如下代码实现
' E" L2 C+ x, Y1 L) a* Q3 L9 ^Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
- o* r: c: ]" J0 {& }intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//参数设置可以省略
4 {, m# B2 @8 E/ [& o4 G' FstartActivityForResult(intent, SystemVideoRecord);   X6 t* R/ \' t
在onActivityResult函数中进行如下代码调用
$ i' a' P8 z% k! O& _; MUri videoUri = data.getData(); * j- p" H) |7 Z  c6 h
//String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.SIZE };
5 ?; c. @- c1 t! M2 B+ mCursor cursor = managedQuery(videoUri, null, null, null, null); 2 _! N$ t- g0 M! c1 p
cursor.moveToFirst();//这个必须加,否则下面读取会报错 * P; b+ V7 q& {1 U) q1 m
int num = cursor.getCount();
3 O. S, ~- R) NString recordedVideoFilePath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); # p$ ]8 E' c+ V
int recordedVideoFileSize = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.SIZE));
6 Q7 v  S  `; ~+ O  b3 s8 aiResultText.setText(recordedVideoFilePath);
$ U4 f: F( p. B1 _  P! v* FLog.i("videoFilePath", recordedVideoFilePath); $ N" w& M7 @7 V( C' B) r+ {
Log.i("videoSize", ""+recordedVideoFileSize); * {0 r/ `" m- q, T; [$ {! p
上面的返回参数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,但是该文件居然是空白内容(不知道是不是跟手机有关,也没有在其它手机上验证过)。
: d) I' Z) M# i  L$ L根据Camera API实现自己的拍照和摄像程序 通过上面对调用系统Camera App实现拍照和摄像功能的例子,我们发现虽然能够满足我们的需求,但是毕竟自由度降低了,而且拍照的界面就是系统的样子,现在很多拍照程序,比如火爆的Camera 360软件等,就需要根据SDK提供的Camera API来编写自己的程序。 3 {* M6 c; x1 e
准备工作 上面调用系统Camera App,我们压根不需要任何权限,但是这里用Camera API,就必须在manifest内声明使用权限,通常由以下三项 : b8 l' i- s2 z* f8 x7 D
<uses-permission android:name = "android.permission.CAMERA" /> . i3 a. Y/ F2 |* ~3 m+ b
<uses-feature android:name = "android.hardware.camera" />
, c$ q  b" x7 a2 j4 R* G8 v& ~<uses-feature android:name = "android.hardware.camera.autofocus" /> - H) c# Q( p# O# r+ I. {
一般拍照和摄像的时候需要写到sd卡上,所以还有一向权限声明如下 0 o  S+ X1 F) Q0 R* l: t4 v
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> $ ^2 m. V5 X) |5 Q! ]3 G
真做摄像功能时,需要音频录制和视频录制功能,所以又需要下面两项权限声明
4 D4 x6 P  h8 C5 {, }) x; V% U<uses-permission android:name="android.permission.RECORD_VIDEO"/>
# p2 E; |: p# k* ~% v, y6 ~<uses-permission android:name="android.permission.RECORD_AUDIO"/> % q- N0 Q: Y* z
另外使用Camera API拍照或摄像,都需要用到预览,预览就要用到SurfaceView,为此Activity的布局中必须有SurfaceView。
9 b, C, `- @% Y
& K  z& G% y- d( d( _3 H7 x
1 ]! A# w( E% Q( e
拍照流程 上面简单介绍了下准备工作,下面结合拍照过程中的需要用到的API对拍照流程做下简单描述
# e& B1 Y" H9 @( {7 @$ Z3 g1、在Activity的OnCreate函数中设置好SurfaceView,包括设置SurfaceHolder.Callback对象和SurfaceHolder对象的类型,具体如下 ) p- c  z9 S# _
SurfaceView mpreview = (SurfaceView) this.findViewById(R.id.camera_preview);
% ^9 B$ h6 Y0 K  w7 L+ TSurfaceHolder mSurfaceHolder = mpreview.getHolder();
; k/ t2 a; {  {4 [& x" pmSurfaceHolder.addCallback(this);
$ f7 ^& r6 N/ U2 N5 h2 O2 UmSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 8 o, U0 \: P! K' j6 ]
2、在SurfaceHolder.Callback的surfaceCreated函数中,使用Camera的Open函数开机摄像头硬件,这个API在SDK 2.3之前,是没有参数的,2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,再通过getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,假如摄像头开启成功则返回一个Camera对象,否则就抛出异常;
8 z- S1 L5 y# U; t& v5 F3、开启成功的情况下,在SurfaceHolder.Callback的surfaceChanged函数中调用getParameters函数得到已打开的摄像头的配置参数Parameters对象,如果有需要就修改对象的参数,然后调用setParameters函数设置进去(SDK2.2以后,还可以通过Camera::setDisplayOrientation设置方向);
- f( Q4 F! B* Q" {2 |8 O5 h4、同样在surfaceChanged函数中,通过Camera::setPreviewDisplay为摄像头设置SurfaceHolder对象,设置成功后调用Camera::startPreview函数开启预览功能,上面3,4两步的代码可以如下所示
4 n2 I" `' U, G6 Wpublic void surfaceChanged(SurfaceHolder holder, int format, int w, int h) . N- Q. n9 z. q  G
{ % `/ `; E* f* K
//已经获得Surface的width和height,设置Camera的参数
5 x# W" G3 M) _9 n( r4 C& dCamera.Parameters parameters = camera.getParameters();
# P- S$ W# z2 f7 _) N: B  tparameters.setPreviewSize(w, h); $ u, g9 _2 o: }5 ~; Q. M+ z
List<Size> vSizeList = parameters.getSupportedPictureSizes();
+ f4 D, u: n* T/ g! C& Q1 w9 {1 M+ Qfor(int num = 0; num < vSizeList.size(); num++) 6 U9 ?3 y# b$ f, F
{
3 x" M# M- n8 p6 U4 FSize vSize = vSizeList.get(num);
1 U# l5 l: B( _3 _# v, S}
; D) m3 T/ ^- k4 h& Lif(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) ' F$ N% H8 y% u' L% W
{ # ?: ^. s: P- T% s6 T0 y8 y
//如果是竖屏
. |1 C6 x% V$ N5 ?5 pparameters.set("orientation", "portrait"); ) L7 t$ [2 ]4 B7 `7 d
//在2.2以上可以使用   o4 i9 D3 r/ }* g, S
//camera.setDisplayOrientation(90); ) ?- Y; g3 |& V
} # R; Y9 f3 \. k) H' Q
else ; ]. i& b# P+ b; S3 e5 ]7 S
{
$ q! a1 d& F) }/ q4 kparameters.set("orientation", "landscape");
) v" e1 k+ i- I. ~+ A4 _//在2.2以上可以使用 1 Z  Q( ~6 W* h7 p8 g. J
//camera.setDisplayOrientation(0);
4 v' X, Q+ l* X7 D" V! W} * @. f- m. O) f8 z2 L. v# t4 Q
camera.setParameters(parameters); ! Y  K; N/ r8 b1 l% y! o
try { 6 t' H" N2 T( ], b1 z/ k& p
//设置显示
5 i, }5 x8 a/ d2 ]camera.setPreviewDisplay(holder); 1 L8 Z% e" J7 t9 I: D' u; b
} catch (IOException exception) {
3 g3 A: N6 o; k# D6 n/ U! M# S0 mcamera.release(); " |0 @* p& r$ h5 Q
camera = null;
( P, b, f7 w' H+ Q& f, m& R4 T5 Z$ d: H}
, e3 G8 b' ^* y2 _! b' X" K& C//开始预览 . v/ l1 k+ V4 w3 y8 D0 Y; \# @3 b$ U) J
camera.startPreview();
# |3 C" J! g: h) z  z$ C, h' G} 7 A; v$ [0 U) Y
5、假设要支持自动对焦功能,则在需要的情况下,或者在上述surfaceChanged调用完startPreview函数后,可以调用Camera::autoFocus函数来设置自动对焦回调函数,该步是可选操作,有些设备可能不支持,可以通过Camera::getFocusMode函数查询。代码可以参考如下: * {" Z# K% P; c
// 自动对焦
6 R# X2 w7 D! @! m- J" [9 V3 Dcamera.autoFocus(new AutoFocusCallback() 3 i$ T: I) I5 R: l1 }9 @1 A7 F
{ 9 a4 j" @# Z% s9 d6 a
@Override
0 G+ N0 d' A) }8 xpublic void onAutoFocus(boolean success, Camera camera)
  M6 n* R" |/ y5 a# |{
( _  |' l+ N. t$ O" b" Vif (success) 7 I  [6 \; c" z4 q5 W
{ % ~3 w7 a/ g9 L0 p- k8 g% q5 L
// success为true表示对焦成功,改变对焦状态图像
' E# c$ `7 |% X/ JivFocus.setImageResource(R.drawable.focus2);
0 D- l6 m& L6 ?4 m7 C8 y} , g6 }/ n% D- |
}
+ H% ~, ^% Z2 V/ g) Y});   R2 Q' p9 r- V) @; e
6、在需要拍照的时候,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)函数来完成拍照,这个函数中可以四个回调接口,ShutterCallback是快门按下的回调,在这里我们可以设置播放“咔嚓”声之类的操作,后面有三个PictureCallback接口,分别对应三份图像数据,分别是原始图像、缩放和压缩图像和JPG图像,图像数据可以在PictureCallback接口的void onPictureTaken(byte[] data, Camera camera)中获得,三份数据相应的三个回调正好按照参数顺序调用,通常我们只关心JPG图像数据,此时前面两个PictureCallback接口参数可以直接传null; 4 m) r& `  i  ^2 Z+ \8 z9 O/ K8 {' q
7、每次调用takePicture获取图像后,摄像头会停止预览,假如需要继续拍照,则我们需要在上面的PictureCallback的onPictureTaken函数末尾,再次掉哟更Camera::startPreview函数;
; g& ^6 @3 k% P* @, _& G1 R) C5 Z8、在不需要拍照的时候,我们需要主动调用Camera::stopPreview函数停止预览功能,并且调用Camera::release函数释放Camera,以便其他应用程序调用。SDK中建议放在Activity的Pause函数中,但是我觉得放在surfaceDestroyed函数中更好,示例代码如下 7 l1 L( _) F, ]1 q1 @! M
// 停止拍照时调用该方法
3 W8 [' O7 X/ M% Q: W. Q. [5 Gpublic void surfaceDestroyed(SurfaceHolder holder) 4 d: r0 S( K0 I5 J% |- S
{ 0 S4 B0 `) k, T' |" [
// 释放手机摄像头 3 g* ?* P) q  u  S& k; y4 v8 [
camera.release(); 2 R7 ?) k& K+ c% V+ G
}
0 T; [' j8 m: |" M0 z; [) b& [

7 V; w3 }5 z! u5 L7 d" G4 G2 l) b6 T4 r" y# \# [

( h( T' {- i) G. {" \以上就是自己实现拍照程序的的流程,一般还可以还可以获取预览帧的图像数据,可以分别通过Camera::setPreviewCallback和Camera::setOneShotPreviewCallback来设置每帧或下一帧图像数据的回调,这里就不做展开了。
. t, D! O# [0 A" `- x2 U& L
5 M& t! N' c  r摄像流程 摄像流程也是需要预览的,而且流程上与拍照流程在起始的1~4步流程和结束的8流程是一样的,唯一不同的是6和7两个步骤,至于5自动对焦本身就是可选的,在摄像流程也没必要。

/ @! V6 V9 x$ s% r: C  w+ x
5 _) i2 L7 `9 ^6、开启视频录制,需要创建一个MediaRecorder对象,并调用Camera::unLock操作解锁摄像头,因为默认Camera都是锁定的,只有解锁后MediaRecorder等多媒体进程调用,并设置一些参数,然后调用MediaRecorder:: start开启录制具体可以参阅如下代码: . V* f- L$ W. \; n9 E; N0 o
MediaRecorder mMediaRecorder = new MediaRecorder(); + g3 y) r4 J  V/ S" h8 v
// Unlock the camera object before passing it to media recorder.
! Y2 e; K8 A# wcamera.unlock();
8 `8 i+ T4 ^; [3 T: HmMediaRecorder.setCamera(camera); $ L* U1 ^2 h, y% I3 @7 ~
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
/ C1 A( H7 F$ u7 M! W2 f; P4 @mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
" M9 y% J1 G3 B" v' zmMediaRecorder.setProfile(mProfile);
: I+ p4 ?' n8 vmMediaRecorder.setMaxDuration(100000);//ms为单位 ; a( X" j# S9 j9 k6 e
long dateTaken = System.currentTimeMillis();
( X  V/ d, x) ?$ eDate date = new Date(dateTaken);
; ?$ \( j( d# y( uSimpleDateFormat dateFormat = new SimpleDateFormat(getString(R.string.video_file_name_format));
" z9 }! y- Q1 O4 iString title = dateFormat.format(date); 7 C. s3 U6 L6 a3 X8 L+ F' _/ l
String filename = title + ".3gp"; // Used when emailing.
1 W# V2 \# Z  ]) k# `String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
( P5 W7 `$ e" a; m5 u& KString filePath = cameraDirPath + "/" + filename; 5 o* s7 u6 K* ?) P3 }5 q1 X2 y
File cameraDir = new File(cameraDirPath); . i  G+ {, {, R3 g2 i
cameraDir.mkdirs(); + t5 a" M. Q4 L9 f6 W
mMediaRecorder.setOutputFile(filePath);
; K! a- S" t7 C& c, O, ~try {
9 L6 O' A$ U( ]. u0 S/ VmMediaRecorder.prepare(); ; E9 a) v& {! t; c
mMediaRecorder.start(); // Recording is now started
* T2 J9 n% b2 c$ ?# }; a' N0 h8 B- f} catch (RuntimeException e) { ' N, L5 e5 l4 P9 ^! k! i- C' ]; p7 D
Log.e(TAG, "Could not start media recorder. ", e);
# i( D3 ^3 J0 k' }7 l# Dreturn; 7 _, `, m" d9 w
} : t3 \5 w8 G" m$ t; z
7、上面设置了最大间隔为100s,当100是视频录制结束,录制就会被停止,如果没有设时长和文件大小限制,那么通常需要调用MediaRecorder:: stop函数主动停止视频的录制,并将Camera对象通过lock函数继续加锁,示例代码如下
6 _8 N- h: {  U3 {mMediaRecorder.stop();
+ Y) g3 U4 m) \) S* L0 E" qmMediaRecorder.reset();
& M9 D) P# }; [! ~9 Y5 ~1 E( v3 {  RmMediaRecorder.release(); & D$ Z- _6 J" ^4 m$ L# l" [
mMediaRecorder = null; 2 p, d0 E# U* ~. J* a- U) n! d
if(camera != null)  - W+ V  j- \( y& j  j4 z
camera.lock();
$ d! M6 h% [2 f' n4 A" `3 [" J
! V, D( k' d7 l0 x0 \4 Z4 i; W
之后的操作根据交互要么重新录制要么就释放Camera对象回到拍照流程的8步骤了。在这里就不做赘述了。
% O, Q- ^! P! e; ?$ v
: z& l" e0 B  d% h0 D6 x

" B3 G: @2 H  A  u2 t0 I6 b' X+ ^; H% P& n) u% j& }
摄像头模组论坛网,行业交流,分享,学习...
0 P, ^1 \. N# o! \, c9 ~1 }& e: swww.ccm99.com5 q- Y; ?- @& j9 K, N
高级模式
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.