Demo下载地址:
概述
Android手机关于Camera的使用,一是拍照,二是摄像,由于Android提供了强大的组件功能,为此对于在Android手机系统上进行Camera的开发,我们可以使用两类方法:一是借助Intent和MediaStore调用系统Camera App程序来实现拍照和摄像功能,二是根据Camera API自写Camera程序。
基础知识
Android系统提供API来支持自定义相机拍照和系统拍照,以下是有关的类:
- Camera
该类提供基础API来使用设备上的相机,且该类可以为你的应用提供拍照和录像相关的API。 - SurfaceView
该类用于显示相机的预览数据。如果你对SurfaceView还不熟悉,请参考Android SurfaceView的使用这篇文章。 - MediaRecorder
该类提供相机录像相关的API。
注意事项
在你的应用程序能够在Android设备上使用相机之前,你应该考虑几个问题,那就是你的App打算如何使用相机拍照或者录像?
- Camera需求的声明:
使用相机功能对于你的应用程序来说是否很重要并且你不希望你的应用程序被安装在没有相机的机器上?如果是这样,那么你需要把相机需求声明在配置文件里。 - 调用系统拍照还是自定义相机:
你的应用程序该如何使用相机?你是否仅仅需要拍摄一张照片或者一个视频,或者你的应用程序希望提供一种使用相机的新的方式? - 存储:
是否你的应用生成的图片和视频仅对你的应用可见,还是其他应用程序例如相册或者其他的多媒体和社交App也可以使用它们?你是否希望你的应用程序被卸载后,这些照片和视频仍然可用,还是一起被删除?
权限申明
- Camera Permission - 你的应用必须申请相机权限才可以使用设备相机。
<uses-permission android:name="android.permission.CAMERA" />
- 1
注意:如果你使用Intent调用系统相机,你的应用无需申请该权限。
- Storage Permission - 如果你的应用需要保存照片或者视频到设备存储中,你必须在Manifest指定文件的写权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 1
- Audio Recording Permission - 你必须申请录音权限才能使用相机来录像.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- 1
- Location Permission - 当然如果你需要拍摄的照片记录地理位置,你同样需要申请如下权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
- 1
调用系统相机
你的应用可以通过发送一个Intent 到系统相机应用来实现抓取一张照片或者一段视频剪辑,然后将它们返回给你的应用。
使用camera intent调用系统相机流程如下:
(1)Compose a Camera Intent - 创建一个Intent请求用来拍照或者录像,有关的Intent类型如下:
- MediaStore.ACTION_IMAGE_CAPTURE - 该Intent action 类型用于请求系统相机拍照。
- MediaStore.ACTION_VIDEO_CAPTURE - 该Intent action 类型用于请求系统相机录像。
(2)Start the Camera Intent - 调用activity的startActivityForResult()方法来发送camera intent请求拍照或者录像,当发送camera intent 以后,当前应用会跳转到系统相机应用app界面,让用户可以拍照或者录像。
(3)Receive the Intent Result - 在你的应用中实现onActivityResult()回调方法去接收来自系统相机的拍摄结果。该方法在用户完成拍照或者录像以后由系统调用。
系统拍照
代码如下,按上面的三步走:
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
});
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
/**
* 通过data取得数据
*/
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
Bundle extras = data.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");
image.setImageBitmap(bitmap);
}
}
它只是一张缩略图。所以,我们需要获取到拍摄的原图,就不能使用这种方法。但是我们可以这样做,我们可以指定MediaStore类的一个EXTRA_OUTPUT来指定拍摄图像保存的位置,相当于建立一个临时文件。在onActivityResult中,我们不使用data来获取图像,而是直接去读这个临时文件即可。如果自己代码指定了保存图片的uri,data里面就不会保存数据。
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
});
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/**
* 通过存储Uri取得数据
*/
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
image.setImageURI(getOutputMediaFileUri(MEDIA_TYPE_IMAGE));
}
}
}
这样我们就可以获取到完整的拍摄图片了。后面你可以让图像显示出来。
下面来看看保存多媒体文件:
拍照或者录像生成的多媒体文件需要保存到手机存储目录中(SD Card),所以在应用中必须有往手机中写文件的权限。一般可以有多种本地路径来保存多媒体文件,但是主要有如下两种常用的路径:
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
该方法返回一个标准的外部存储路径去保存照片和视频。这个路径是公共的,所以其他应用也可以访问,修改,删除该路径下的照片和视频,如果你的应用被卸载了,媒体文件依然存在本地储存中。为了避免和其他多媒体文件混淆,你应该在公共目录下创建一个子目录来保存你自己应用中的多媒体数据。 - Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
该方法返回一个标准的,唯独当前应用自己可见的路径去保存照片和视频。如果该应用被卸载,在该目录下的所有多媒体数据将会被移除。但是有一个好处就是其他应用无法去访问,修改,删除该路径下的文件。
如下示例代码演示如何创建一个路径用来保存照片和视频:
public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;
/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
return Uri.fromFile(getOutputMediaFile(type));
}
/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "WatsonCamera");
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
return null;
}
}
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_watson.jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_watson.mp4");
} else {
return null;
}
return mediaFile;
}
系统录像
发送Intent录像携带的外部数据extra的信息如下:
- MediaStore.EXTRA_OUTPUT
该关键字和拍照使用的关键字一样,意思就是制定一个路径和文件名来构建一个Uri对象来保存录像结果。 - MediaStore.EXTRA_VIDEO_QUALITY
该关键字用于指定拍摄的录像质量,参数0表示低质量,参数1表示高质量。 - MediaStore.EXTRA_DURATION_LIMIT
该关键之用于指定拍摄的录像的时间限制,单位是秒。 - MediaStore.EXTRA_SIZE_LIMIT
该关键字用于指定拍摄的录像文件大小限制,单位值byte。
代码如下,按上面的三步走:
button2.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
Uri fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
}
});
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
image.setVisibility(View.VISIBLE);
video.setVisibility(View.GONE);
image.setImageURI(getOutputMediaFileUri(MEDIA_TYPE_IMAGE));
}
} else if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
video.setVisibility(View.VISIBLE);
image.setVisibility(View.GONE);
video.setVideoURI(getOutputMediaFileUri(MEDIA_TYPE_VIDEO));
video.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
video.start();
}
});
video.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
if (null != video) {
video.stopPlayback();
}
}
});
}
}
}
自定义相机
创建一个自定义的相机app基本遵循如下步骤:
- 检测和访问相机:
首先代码检测该设备相机是否存在,如果存在才能请求访问设备相机。 - 创建一个预览来显示相机图像:
在你的布局中使用SurfaceView控件,然后在代码中继承SurfaceHolder.Callback接口并且实现接口中的方法来显示来自相机的图像信息。 - 设置相机基本参数:
根据需求设置相机预览尺寸,图片大小,预览方向,图片方向等。 - 设置拍照录像监听:
当用户按下按钮时调用Camera.takePicture()或者MediaRecorder.start()来进行拍照或录像。 - 文件保存:
当拍照结束或者录像视频结束时,需要开启一个后台线程去保存图片或者视频文件。 - 释放相机资源:
Camera硬件是一个共享资源,所以你必须小心的编写你的应用代码来管理相机资源。一般在Activity的生命周期的onResume中开启相机,在onPause中释放相机。
注意: 当你不在使用相机资源时,记得调用Camera.release()方法来释放相机资源,否则其他应用甚至你自己的应用再次请求访问设备相机时会失败,并且crash。
检测相机硬件是否存在
一般情况,我们会在运行代码时检测该设备是否有相机硬件,如果有相机硬件,才进一步去访问相机,如下是检测相机硬件是否存在是代码示例:
/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
return true;
} else {
return false;
}
}
Android 设备可以有多个相机硬件,现在一般手机都是前后两个camera,因此我们在Android2.3以后也可以使用Camera.getNumberOfCameras()方法来获得当前设备camera个数来判断相机硬件是否存在。
创建Camera预览
Camera预览布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/record_navigation_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="#F8F8F8" >
<ImageView
android:id="@+id/record_act_back"
android:layout_width="25dp"
android:layout_height="31dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:src="@drawable/icon_ll_back" />
</RelativeLayout>
<SurfaceView
android:id="@+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_above="@+id/record_bottom_bar"
android:layout_below="@+id/record_navigation_bar" />
<RelativeLayout
android:id="@+id/record_bottom_bar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="@drawable/recording_bottom_bar_bg_interview" >
<Button
android:id="@+id/btn_start_recording"
android:layout_width="58dp"
android:layout_height="58dp"
android:layout_centerInParent="true"
android:background="@drawable/recording_act_vedio_start" />
<Button
android:id="@+id/btn_change_module"
android:layout_width="45dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_marginLeft="30dp"
android:background="@drawable/change_module_photo" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_below="@+id/record_navigation_bar"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical" >
<View
android:id="@+id/record_video_tip"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginRight="10dp"
android:background="@drawable/record_video_tip" />
<TextView
android:id="@+id/record_video_time"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_marginRight="10dp"
android:gravity="center_vertical"
android:text="00:00"
android:textColor="@android:color/white"
android:textSize="17sp" />
</LinearLayout>
</RelativeLayout>
然后,我们创建一个Activity,用来展示Camera的预览,那么在这个Activity里面,我们需要做什么呢?两件事情:
- 初始化相机
- 将内容显示到SurfaceView
Android的Camera是独享的,如果多处调用,就会抛出异常,所以,我们需要将Camera的生命周期与SurfaceView的生命周期绑定:
- surfaceCreated方法中初始化相机
- surfaceDestroyed方法中释放相机
初始化相机非常简单:
private Camera getCamera() {
Camera camera;
try {
camera = Camera.open();
} catch (Exception e) {
camera = null;
}
return camera;
}
注意: 在调用Camera.open()方法时总是要去捕获一个异常,以免打开相机设备失败导致整个应用crash。在Android2.3以及更高api上,你可以使用Camera.open(int)来打开指定的相机。以上代码示例总是默认打开后置camera,一般情况参数为0表示打开后置camera,参数为1表示打开前置camera。
释放相机也非常简单:
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (camera != null) {
try {
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}
那么下面我们再来看如何把相机图像设置到SurfaceView中进行预览:
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera = getCamera();
camera.setPreviewDisplay(holder); //camera关联到SurfaceView
camera.setDisplayOrientation(90); //旋转90度
camera.startPreview(); //开始预览
} catch (Exception e) {
finish();
}
}
是不是也非常简单,camera的一个方法已经帮我们自动关联了SurfaceView。
这里需要注意下这个方法camera.setDisplayOrientation(90),通过这个方法,我们可以调整摄像头的角度,不然默认是横屏,图像会显示的比较奇怪。当然,即使你设置了90,图像也有可能比较奇怪,这是因为你没有对图像进行正确的缩放,比例不对。
通过上面的设置,我们已经可以正常预览摄像头的图像内容了。
拍照
一旦你创建了camera preview并且加载到布局中可以实时显示预览画面了,此时就可以进行拍照了。为了配合拍照,我们需要做一些设置,设置拍照参数,当然你也可以不设置而使用默认参数,默认参数基本上就能满足我们的要求。
Camera.Parameters params = mCamera.getParameters();
params.setPictureFormat(ImageFormat.JPEG);
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(params);
//自动聚焦
camera.autoFocus(new AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
if (success)
System.out.println("聚焦成功 !");
else
System.out.println("聚焦失败 !");
}
});
在代码中你应该实现一个监听回调来捕获用户拍照的行为。可以调用camera.takePciture()方法来进行拍照。
public final void takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg);
- 1
该方法接受三个参数,第一个参数ShutterCallback响应快门的接口,第二个参数PictureCallback接收raw格式的图片数据,第三个参数PictureCallback接收jpeg格式的图片数据。为了保存图片数据,你可以根据需要实现以上三个接口。此处我们暂且实现第三个PictureCallback接口回调。示例代码如下:
//拍照
if (camera != null) {
camera.takePicture(null, null, mPictureCallback);
}
//第三个PictureCallback接口回调,通过data[]保持图片数据信息
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = MainActivity.getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (pictureFile == null){
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(RecordVedioAct.this, "图像已保存", Toast.LENGTH_SHORT).show();
camera.startPreview(); //拍完继续预览
}
};
录像
Camera视频录像不仅涉及到Camera类还用到了MediaRecorder类。当你使用Camera录像时,你应该调用Camera.lock()和Camera.unlock()来管理camera硬件,允许MediaRecorder访问camera硬件。你应该在camera和MediaRecorder关联之前调用Camera.unlock()来解锁camera,允许MediaRecorder访问Camera,在释放MediaRecorder资源以后调用Camera.lock()来锁定camera以保证camera硬件资源的共享性。
注:在Android4.0以后,系统会自动管理camera.unlock()以及camera.lock(),无需用户自己管理。
启动录像流程需要一个指定调用顺序,如下是详细的步骤流程:
(1)Open Camera – 使用Camera.open()静态方法来获得camera对象实例。
(2)Connect Preview – 使用camera.setPreviewDiaplay(holder)方法将相机的预览画面显示在SurfaceView控件上。
(3)Start Preview – 使用camera.startPreview()方法开始启动预览画面。
(4)Start Recording Video – 必须完成以下步骤才能正常开始正常录音:
- Unlock the Camera - 调用camera.unlock()方法解锁camera,使得MediaRecorder进程能访问Camera硬件。
- Configure MediaRecorder - 在这一步,分别调用MediaRecorder类中如下方法来配置MediaRecorder:
/**配置MediaRecorder*/
recorder.setCamera(camera); //设置camera用于录像
recorder.setOutputFile(filePath); //设置输出文件路径
recorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置录像音频来源
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //设置录像视频来源
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //设置视频的输出格式
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); //设置视频的编码格式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //设置音频的编码格式
/**输出格式和编码格式,对于Android2.2或者更高版本使用MediaRecorder.setProfile方法即可,使用方法CamcorderProfile.get()来获得一个配置信息*/
recorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
setPreviewDisplay(holder.getSurface()) //为MediaRecorder指定预览显示
全部方法来配置MediaRecorder,否则你的应用将无法正常录像并且报错。
由于录像默认设置了很多参数,无需用户太关心更细节的参数设置,但是如果需要在你的应用中修改这些默认参数设置,你可以使用如下方法来修改默认参数:
recorder.setAudioEncodingBitRate(); //设置音频编码的字节率
recorder.setVideoEncodingBitRate(); //设置视频编码的字节率
recorder.setOrientationHint(tureAngle); //设置MediaRecorder旋转角度
recorder.setAudioSamplingRate(); //设置音频采样率
recorder.setMaxDuration(5 * 60 * 1000); //设置最大录制时间
recorder.setVideoSize(640, 480); //设置视频尺寸大小,在setVideoSource()和setOutFormat()之后
recorder.setVideoFrameRate() //设置视频帧率,在setVideoSource()和setOutFormat()之后
recorder.setAudioChannels(); //设置音频的频道数目,参数一般1/2
- Prepare MediaRecorder - 在配置完MediaRecorder参数之后调用mediaRecorder.prepare()方法来准备MediaRecorder。
- Start MediaRecorder - 调用mediaRecorder.start()方法启动录像。
(5)Stop Recording Video – 当你结束录像时调用如下方法:
- Stop MediaRecorder - 首先调用 mediaRecorder.stop()方法停止多媒体录像。
- Reset MediaRecorder - 调用mediaRecorder.reset()方法重置多媒体状态,调用该方法之后之前的所有MediaRecorder configuration将被移除,你如果还想再次录像,需要再次配置多媒体参数。
- Release MediaRecorder - 调用 mediaRecorder.release()方法释放多媒体资源。
- Lock the Camera - 调用camera.lock()方法来给Camera硬件加锁。在Android4.0及以后无需调用该方法,除非在调用mediaRecorder.prepare()失败时,才需要再次调用该方法。
(6)Stop the Preview - 当你的Activity已经不再使用camera时,调用camera.stopPreview()方法来停止预览。
(7)Release Camera - 当不再使用Camera时,调用camera.release()方法来释放camera,以便其他应用可以使用camera资源。
注意: 当完成一段视频录像时,不要马上去释放camera资源或者停止当前预览,因为有可能用户会再次启动录像操作。本文中将camera释放操作放在surfaceDestroyed里面。
如下代码演示在button的点击事件中去启动和停止视频录像操作:
/**录像*/
if (isRecording) {
stopRecord();
} else {
startRecord();
}
// 开始录像
private void startRecord() {
if (prepareVideoRecorder()) {
mediaRecorder.start();
//修改状态
isRecording = true;
Toast.makeText(RecordVedioAct.this, "开始录像", Toast.LENGTH_SHORT).show();
btn_start_recording.setBackgroundResource(R.drawable.recording_act_vedio_stop);
start_time = 0;
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
start_time++;
handler.sendEmptyMessage(0);
}
}, 0, 1000);
} else {
mediaRecorder.release();
camera.lock();
}
}
// 停止录像
private void stopRecord() {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
camera.lock();
//修改状态
isRecording = false;
btn_start_recording.setBackgroundResource(R.drawable.recording_act_vedio_start);
timer.cancel();
record_video_tip.setVisibility(View.VISIBLE);
Toast.makeText(RecordVedioAct.this, "录像已保存", Toast.LENGTH_SHORT).show();
}
private boolean prepareVideoRecorder(){
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mediaRecorder.setOutputFile(MainActivity.getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
mediaRecorder.setPreviewDisplay(myHolder.getSurface());
try {
mediaRecorder.prepare();
} catch (Exception e) {
mediaRecorder.release();
camera.lock();
return false;
}
return true;
}
public static Camera.CameraInfo getCameraInfo(int cameraId) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, cameraInfo);
return cameraInfo;
}
CameraInfo类:
public static class CameraInfo {
public static final int CAMERA_FACING_BACK = 0; //后置camera
public static final int CAMERA_FACING_FRONT = 1; //前置camera
public boolean canDisableShutterSound; //是否能禁止拍照快门声音
public int facing;
public int orientation; //camera数据流的方向
}
}
相机的特性功能
Android支持一组相机功能来控制你的相机应用,比如:生成的照片格式,闪光灯模式,对焦设置等等。这里罗列出通用的相机功能并且显示怎么使用它们来辅助我们拍照。有关相机更多的参数设置,可以直接阅读Camera.Parameters类。下表罗列出通用的相机参数:
注意:以上列表中的功能并是不在所有的Android设备上都支持,因此你在使用以上参数时需要去检测当前Android设备是否支持该参数,然后再去使用它们来辅助相机拍照。
检测相机特性是否可用
首先你要知道,并不是所有android设备都支持全部的camera特性功能,因此在应用总使用camera特性功能需要先检测是否支持,然后在去使用。否则你使用了不支持的camera特性功能将会报错。
在应用中可以通过得到camera 的参数parameters类,然后通过该类中的一些方法来检测当前设备是否支持camea特性功能。如下代码示例演示了如何获得一个Camera.Parameters对象且检测camera是否支持自动对焦特性:
Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
/** Autofocus mode is supported */
}
使用相机的特性功能
大部分Android 相机特性功能都可以通过 Camera.Parameters类来控制。首先你可以获得一个Camera实例,然后调用camera.getParameters()方法的返回值来得到Camera.Parameters实例,之后就可以通过Parameters.setxxx()系列方法来设置一些参数使用相机的一些特性功能。以下是实例代码:
Camera.Parameters params = mCamera.getParameters();
/** set the focus mode*/
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
camera.setParameters(params);
相机的所有参数都可以通过类似以上方法来设置,一般在打开相机成功以后就可以设置相机的基本参数。
注意:相机的一些特性不能在任意时刻改变,比如改变预览的尺寸和方向时,首先需要停止preview,修改预览尺寸之后再次重启preview。自从Android4.0以后修改预览方向以后无需 再次重启preview。
下面介绍三个相机的其他功能的使用:
- Metering and focus areas
- Face detection
- Time lapse video
“测光和对焦区域”特性
在某些摄像情景中,自动调焦和测光可能不能达到设计结果。从Android4.0(API Level 14)开始,你的Camera应用程序能够提供另外的控制允许应用程序或用户指定图像中特定区域用于进行调焦或光线级别的设置,并且把这些值传递给Camera硬件用于采集图片或视频。
测光和调焦区域的工作与其他Camera功能非常类似,你可以通过Camera.Parameters对象中的方法来控制它们。下列代码演示如何给Camera示例设置两个测光区域:
private void setMeteringFocusAreas(Camera camera) {
Camera.Parameters params = camera.getParameters();
if (params.getMaxNumMeteringAreas() > 0){ //检测是否支持测光和对焦区域功能
List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
Rect areaRect1 = new Rect(-100, -100, 100, 100); //定义图像中间的一个区域
meteringAreas.add(new Camera.Area(areaRect1, 600)); //权重60%
Rect areaRect2 = new Rect(800, -1000, 1000, -800); //定义图像右上侧的一个区域
meteringAreas.add(new Camera.Area(areaRect2, 400)); //权重40%
params.setMeteringAreas(meteringAreas);
}
camera.setParameters(params);
}
Camera.Area对象包含了两个数据参数:Rect对象,它用于指定Camera预览窗口一块矩形区域;一个权重值:它告诉Camera这块指定区域应该给予的测光或调焦计算的重要性等级。
Camera预览窗口被映射成2000x2000单元格的矩形。坐标(-1000,-1000)代表Camera图像的左上角,(1000,1000)代表Camera图像的右下角,如下图所示:
图中的红线说明了Camera预览窗口中给Camera.Area指定的坐标系统。用Rect的值是(-100, -100, 100, 100)和(800, -1000, 1000, -800)蓝色框代表了需要设置的测光或调焦区域。
“人脸识别”特性
对于包含人的图片,通常人脸是图片的最重要的部分,并且在采集图像时,应该使用调焦和白平衡来进行检测。Android4.0(API Level 14)框架提供了用于识别人脸和使用人脸识别技术来计算图片设置的API。
注意:当人脸识别在运行时,setWhiteBalance(String), setFocusAreas(List) 和setMeteringAreas(List)方法都无效。
在你的应用中使用人脸识别技术一般需要如下几步:
- 检测当前设备是否支持人脸识别。
- 创建一个人脸识别监听回调。
- 将监听回调关联到camera 对象中去。
- 在启动preview预览之后去启动人脸识别。(每次重启preview之后都有去启动一个人脸识别)
人脸识别功能并不是在所有设备上都支持。你应当调用getMaxNumDetectedFaces()方法来检测当前设备是否支持人脸识别技术,只有当以上方法返回值大于0时,你才可以去调用startFaceDetection()方法去启动人脸识别。
为了通知和响应人脸识别,你的相机应用必须设置一个人脸检测监听事件,为了到达这个目的,你必须要创建一个实现Camera.FaceDetectionListener接口的监听器类,如下所示:
class MyFaceDetectionListener implements Camera.FaceDetectionListener {
@Override
public void onFaceDetection(Face[] faces, Camera camera) {
if (faces.length > 0){
showlog("face detected: "+ faces.length + " Face 1 Location X: " + faces[0].rect.centerX() + "Y: " + faces[0].rect.centerY() );
}
}
}
创建这个类之后,把它设置给你的应用程序的Camera对象:
camera.setFaceDetectionListener(new MyFaceDetectionListener());
- 1
你的应用必须在每次重启相机preview时候去启动一个人脸检测。创建一个启动人脸识别的方法,在你需要的时候调用它。示例代码如下:
public void startFaceDetection(){
Camera.Parameters params = camera.getParameters();
if (params.getMaxNumDetectedFaces() > 0){
camera.startFaceDetection();
}
}
你必须在每次启动Camera预览窗口时都要启动面部识别。
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera = getCamera();
camera.setFaceDetectionListener(new MyFaceDetectionListener()); //添加监听回调
camera.setPreviewDisplay(holder);
camera.setDisplayOrientation(90);
camera.startPreview();
startFaceDetection(); //开始检测
} catch (Exception e) {
finish();
}
}
“延时摄影”特性
延时摄影允许用户把几张图片合成为一个几秒或几分钟的视频剪辑。这个功能要使用MediaRecorder对象来记录图像的延时序列。
要用MediaRecorder对象来记录延时视频,和录制普通视频一样,必须要配置MediaRecorder对象,并把每秒采集的帧数设置到较小的数字,并且要使用一个延时品质设置,如下代码所示:
...
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH)); /**QUALITY_TIME_LAPSE_HIGH*/
mediaRecorder.setCaptureRate(0.1); /**每10秒抓取一帧*/
这些设置是要对MediaRecorder对象所要做的必要设置的一大部分。对于完全的配置代码示例,请看上篇博客内容。一旦配置完成,你就可以把它当做普通的视频剪辑来录制视频了。
解决照片旋转问题
使用上篇文章的demo,拍摄照片,如果你找到拍摄后的照片,会发现照片是横向的,也就是有90度的旋转,那么如何解决这个问题呢?还有,如果我们想不管相机在拍照时的旋转角度如何,都希望拍出来的照片是正向的,又该怎么办呢?
我的解决思路是在Camera.PictureCallback中将图片的byte[]数据转化为Bitmap,然后根据拍摄角度将Bitmap进行相应的矩阵旋转操作,最后将旋转后的Bitmap再次转化为byte[]数组。这样不管你相机的拍摄角度如何,拍出来的照片都是正向的。
private OrientationEventListener mOrientationListener;
private int orientations;
...
@Override
protected void onResume() {
super.onResume();
mOrientationListener = new OrientationEventListener(this){
@Override
public void onOrientationChanged(int orientation) {
orientations = orientation;
}
};
if(mOrientationListener != null){
mOrientationListener.enable();
}
@Override
protected void onStop() {
super.onStop();
if(mOrientationListener != null){
mOrientationListener.disable();
}
}
//拍摄照片后的回调
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = MainActivity.getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (pictureFile == null){
return;
}
/***********************解决照片旋转问题start************************/
if (null != data && data.length > 0) {
// data[]转为bitmap
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
int h1 = bitmap.getHeight();
int w1 = bitmap.getWidth();
Matrix matrix = new Matrix();
int initialAngle = 0;
if (h1 < w1) {
initialAngle = 90;
}
showlog("orientations : " + orientations);
if (orientations > 325 || orientations <= 45) {
matrix.setRotate(0 + initialAngle);
} else if (orientations > 45 && orientations <= 135) {
matrix.setRotate(90 + initialAngle);
} else if (orientations > 135 && orientations < 225) {
matrix.setRotate(180 + initialAngle);
} else {
matrix.setRotate(270 + initialAngle);
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
data = Bitmap2Bytes(bitmap);
}
/***********************解决照片旋转问题end************************/
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(RecordVedioAct.this, "图像已保存", Toast.LENGTH_SHORT).show();
camera.startPreview(); //拍完继续预览
}
};
public byte[] Bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
解决视频旋转问题
同样,如果我们想不管相机在拍摄时的旋转角度如何,都希望拍出来的视频是正向的,怎么办呢?可以参考上面拍照时的思路。
拍摄时先获取相机角度turnAngle,初始化MediaRecorder时,将MediaRecorder通过函数mediaRecorder.setOrientationHint(turnAngle);旋转角度turnAngle,再进行拍摄。
// 开始录像
private void startRecord() {
/***********************解决视频旋转问题start************************/
int turnAngle = 0;
showlog("orientations : " + orientations);
if (orientations > 325 || orientations <= 45) {
turnAngle = 90;
} else if (orientations > 45 && orientations <= 135) {
turnAngle = 180;
} else if (orientations > 135 && orientations < 225) {
turnAngle = 270;
} else {
turnAngle = 0;
}
/***********************解决视频旋转问题end************************/
if (prepareVideoRecorder(turnAngle)) {
mediaRecorder.start();
isRecording = true;
Toast.makeText(RecordVedioAct.this, "开始录像", Toast.LENGTH_SHORT).show();
btn_start_recording.setBackgroundResource(R.drawable.recording_act_vedio_stop);
start_time = 0;
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
start_time++;
handler.sendEmptyMessage(0);
}
}, 0, 1000);
} else {
mediaRecorder.release();
camera.lock();
}
}
//初始化MediaRecorder
private boolean prepareVideoRecorder(int turnAngle){
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mediaRecorder.setOutputFile(MainActivity.getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
mediaRecorder.setPreviewDisplay(myHolder.getSurface());
mediaRecorder.setOrientationHint(turnAngle); //mediaRecorder旋转角度turnAngle
try {
mediaRecorder.prepare();
} catch (Exception e) {
mediaRecorder.release();
camera.lock();
return false;
}
return true;
}