本文主要分为两大部分,首先介绍了Camera API和SurfaceView,然后介绍了在Android中使用照相功能和录像功能的方法。
Camera API 和 SurfaceView介绍
Camera API 是Android提供的用于拍照的类,Camera实例提供了对设备相机硬件级别的调用。相机是一种独占性资源,一次只能有一个Activity调用相机。
也就是说,在使用Camera时,需要时使用,用完需立即释放,若忘记释放,除非重启设备,否则其他应用将无法使用相机。
管理Camera实例有如下几种方法:
//从API 9开始引入,cameraId = 0时默认打开后置摄像头,cameraId = 1时默认打开前置摄像头
public static Camera open(int cameraId)
//API 8及以下版本初始化Camera实例,默认打开后置摄像头
public static Camera open()
//Activity/Fragment被销毁时,应及时调用下面方法释放相机资源
public final void release()
SurfaceView实例是相机的取景器,SurfaceView是一种特殊的视图,可直接将要显示的内容渲染输出到设备的屏幕上。
简单地说,SurfaceView可以完成单位时间内大量界面变化的需求,比如视频播放器,游戏画面,照相机取景等。
SurfaceView的内部利用了双缓冲机制实现了画面的快速刷新,所谓的双缓冲机制,就是利用SurfaceView内部包含的两个子线程(假设为Thread A和Thread B)交替工作,其工作示意图如下:
Thread A : 解码图像—>前台显示—>解码图像—>前台显示 。。。
Thread B : —(空)—- 解码图像—>前台显示—>解码图像 。。。
也就是说,Thread A 和Thread B交替工作,当A线程解码图像的时候,B线程在同一时刻把刚刚解码的图像切换至UI线程(主线程)并进行显示,以保证在任意时刻总有解码完毕的图像显示在界面上,这样,就达到了流畅的视频播播放效果。
在SurfaceView中了实现SurfaceHolder.Callback接口,SurfaceHolder是用户与Surface对象联系的纽带,而Surface对象代表原始像素数据的缓冲区。
当SurfaceView出现在屏幕上时,会创建Surface;当SurfaceView从屏幕上消失时候,Surface随即被销毁。Surface不存在时候,必须保证没有任何内容要在它上面绘制。
不同于其他视图对象,SurfaceView及其协同工作对象都不会自我绘制内容,对于任何想将内容绘制到Surface缓冲区的对象,我们将其称之为Surface客户端,Camera对象就是一个Surface客户端。
也就是说,只有当Surface对象创建完成后,Surface的客户端(如Camera)才能在Surface的缓冲区绘制,而当Surface不存在时,Surface的缓冲区不能有任何绘制的内容。SurfaceHolder.Callback接口就是用于监听Surface的生命周期事件,以便控制Surface与其客户端协同工作,其三个回调方法如下(当Surface和其客户端关联/不再关联时回调):
//包含SurfaceView的视图层级结构被放到屏幕上时调用该方法,也是Surface与其客户端(如Camera)关联的地方
public abstract void surfaceCreated(SurfaceHolder holder)
//Surface首次出现在屏幕上时调用,该方法通知Surface客户端(如Camera),有多大的绘制区域可以使用
public abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
//SurfaceView从屏幕移除时,Surface也被随即销毁,通过该方法通知Surface的客户端(如Camera)停止使用Surface
public abstract void surfaceDestoryed(SurfaceHolder holder)
下面三个Camera中的方法用于响应相应的Surface生命周期事件:
//用于连接Surface客户端(如Camera)和Surface,在surfaceCreated()中调用
public final void setPreviewDisplay(SurfaceHolder holder)
//用于在Surface上绘制,在surfaceChanged()中调用
public final void startPreview()
//用于停止在Surface上绘制,在surfaceDestroyed()中调用
public final void stopPreview()
使用Intent调用系统相机拍照
实现一个相机拍照功能,最简单的方式是使用隐式Intent调用系统自带的照相机拍照功能,要实现此功能,需要为Intent设置一个action和一个extra,代码片段如下:
//在Intent中设置action为MediaStore.ACTION_IMAGE_CAPTURE,表示启动一个包含照相功能的组件
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//为Intent添加一个extra,键为MediaStore.EXTRA_OUTPUT,值为SD卡根目录。表示将相机捕捉的图像保存在该位置
file = new File(Environment.getExternalStorageDirectory(),
System.currentTimeMillis() + ".jpg");
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
在回调方法onActivityResult方法中,将目标intent携带的返回信息(即拍摄的照片)显示在imageView中:
mImageView.setImageURI(Uri.fromFile(file));
为捕捉的图像添加预览功能:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "image/*");
startActivity(intent);
使用Camera和SurfaceView自定义照相机
根据第一部分的介绍,下面将简介一个自定义的照相机应用。
实现步骤:
- 检测设备上是否有摄像头;
- 检测设备版本,根据版本型号调用相应的open方法,以创建Camera对象;
- 为Camera对象设置Parameter参数;
- 定制实现了SurfaceHolder.Callback接口的SurfaceView类,并在回调方法中连接/断开Surface客户端(本例中为Camera);
- 将定制的SurfaceView添加至承载布局容器中;
- 调用Camera的takePicture方法进行拍照。
下面将对如上所述的各个步骤以代码的方式结合介绍:
//第一步
//检测设备是否有摄像头
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA)) {
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
//第二步,判断设备版本,创建Camera实例
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
{
//若设备的版本API>=9(Android 2.3 代号姜饼),则调用open(int)方法实例化Camera对象
mCamera = Camera.open(0);
}
else
{
//否则调用open()方法实例化Camera对象
mCamera = Camera.open();
}
//第三步,为Camera设置参数
Parameters parameters = camera.getParameters();
// 设置闪光灯强制打开
parameters.setFlashMode(parameters.FLASH_MODE_AUTO);
// 设置白平衡,WHITE_BALANCE parameters.setWhiteBalance(Parameters.WHITE_BALANCE_AUTO);
// 设置照片颜色特效,EFFECT parameters.setColorEffect(parameters.EFFECT_SEPIA);
// 设置拍摄照片的尺寸
parameters.setPictureSize(1280, 720);
// 设置照片的预览尺寸
parameters.setPreviewSize(1280, 720);
// 设置照片的质量
parameters.setJpegQuality(100);
// Android 2.2及以后(>=API 8) 0 水平 90垂直方向
camera.setDisplayOrientation(90);
//第四步
//定制SurfaceView类
//得到SurfaceHolder对象
SurfaceHolder holder = mSurfaceView.getHolder();
//兼容Android3.0以下版本设备的相机预览
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
//添加SurfaceHolder.Callback接口,用于监听Surface的生命周期,并在特定的时机创建、改变、销毁Surface的客户端(Camera实例)
holder.addCallback(new SurfaceHolder.Callback()
{
//通知Camera可以使用Surface作为其预览区域时回调
@override
public void surfaceCreated(SurfaceHolder holder)
{
try
{
if(mCamera != null)
{
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
}
}
catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
//通知Camera需释放其资源时回调
@override
public void surfaceDestroyed(SurfaceHolder holder) {
if(mCamera != null)
{
mCamera.stopPreview();
}
}
//当预览界面发生改变时(如屏幕旋转、全屏切换等),通知Camera重绘
@override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (holder.getSurface() == null) {
// 若surface已被释放,直接返回
return;
}
// 在重绘之前,先调用stopPreview停止预览
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// 重绘预览
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
});
//第五步:将定制的SurfaceView添加至承载布局容器中
CustomSurfaceView preview = new CustomSurfaceView(this, mCamera);
mContainLayout.addView(preview);
第六步:调用下面方法实现拍摄一张照片
public final void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Callback.PictureCallback jpeg)
- 参数1:Camera.ShutterCallback
在接口Camera.ShutterCallback中的回调方法
public abstract void onShutter();
会在相机捕获图像时调用,但此时,图像还未处理完成,所以,在该回调方法中,一般会显示一个进度条,告知用户保存图片的处理进度。
- 参数2和参数3:Camera.PictureCallback
在接口Camera.PictureCallback中的回调方法
public abstract void onPictureToken(byte[] data, Camera camera)
参数2和参数3接口都会回调上述方法,但参数2一般是在加工处理原始图像数据且没有存储之前;而参数3回调是在JPEG版本的图像可用时。
//调用Camera.takePicture()
mCamera.takePicture(new ShutterCallback() {
@Override
public void onShutter() {
Toast.makeText(getApplicationContext(),"点击快门", 0).show();
}
}, null,
new PictureCallback() {
@Override
public void onPictureTaken(byte[] data,Camera camera) {
try {
//将图片保存在SD根目录下,文件名设置为开机时的时间
File file = new File(Environment .getExternalStorageDirectory(), SystemClock.uptimeMillis()+ ".jpg");
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.close();
Toast.makeText(getApplicationContext(),"拍照成功", 0).show();
mCamera.startPreview();
}
catch (Exception e) {
e.printStackTrace();
}
}
});
注意事项
- 使用相机拍摄照片与访问用户的外置存储卡都涉及侵犯用户隐私,故需要在AndroidManifest中向系统声明权限:
<!-- 申请访问设备摄像头 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 申请访问设备的外置存储卡 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 使用uses-feature标签告知应用商店(如Google Play)本应用将使用设备的摄像头,若设备上没有摄像头,设备将无法在应用商店(如Google Play)中搜索到该应用:
<uses-feature android:name="android.hardware.camera" />
使用Intent调用系统相机录像
与调用系统相机拍照相仿,使用隐式Intent调用系统相机录像只是Intent的action不同:
//设置action为MediaStore.ACTION_VIDEO_CAPTURE,表示启动一个可以进行录像组件
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
file = new File(Environment.getExternalStorageDirectory(), SystemClock.uptimeMillis() + ".mp4");
// 创建一个文件存储file,并设置文件名,设置uri为存储录像的路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file))
startActivityForResult(intent, 200);
在onActivityResult方法中接收启动的目标activity的返回信息:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 200:
Toast.makeText(MainActivity.this, "录像成功", 0).show();
break;
default:
break;
}
}
启动系统播放器,播放拍摄的视频
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "video/*");
startActivity(intent);
使用Camera、MediaRecorder和SurfaceView自定义摄像机
自定义摄像机功能如下:
- 检测设备上是否有摄像头;(此步在下面的示例代码中省略)
- 检测设备版本,根据版本型号调用相应的open方法,以创建Camera对象;(此步在下面的示例代码中省略)
- 定制实现了SurfaceHolder.Callback接口的SurfaceView类,并在回调方法中连接/断开Surface客户端(本例中为Camera);
- 解锁摄像头(Camera.unlock());
- 创建MediaRecorder对象,并为其设置参数;
- 依次调用MediaRecorder.prepare()和MediaRecorder.start()方法开始录像;
- 当触发终止录像操作时,分别调用MediaRecorder.stop()、MediaRecorder.release()释放MediaRecorder对象,并将对象置为空(null),最后调用Camera.lock()锁定Camera。
public class MainActivity extends Activity {
private Button bt_take_video;
private SurfaceView sView;
private Camera camera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_take_video = (Button) findViewById(R.id.bt_take_video);
sView = (SurfaceView) findViewById(R.id.id_sv);
//为兼容Android3.0以下版本,需设置下面的语句 sView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
sView.getHolder().addCallback(new MycallBack());
bt_take_video.setOnClickListener(new OnClickListener() {
private MediaRecorder recorder;
@Override
public void onClick(View v) {
Button button = (Button) v;
if ("开始录像".equals(button.getText())) {
button.setText("暂停录像");
// 解锁摄像头
camera.unlock();
// 创建一个多媒体记录器对象
recorder = new MediaRecorder();
// 给记录器设置摄像头
recorder.setCamera(camera);
// 设置音频源
recorder.setAudioSource(AudioSource.MIC);
//设置视频源
recorder.setVideoSource(VideoSource.CAMERA);
// 设置输出格式和编码,Android2.2版本及以上。
recorder.setProfile(CamcorderProfile
.get(CamcorderProfile.QUALITY_HIGH));
// 设置文件输出路径
recorder.setOutputFile("/mnt/sdcard/"
+ System.currentTimeMillis() + ".mp4");
// 设置预览
recorder.setPreviewDisplay(sView.getHolder().getSurface());
try {
// 准备
recorder.prepare();
} catch (Exception e) {
e.printStackTrace();
}
// 开始录像
recorder.start();
} else {
button.setText("开始录像");
if (recorder != null) {
// 停止多媒体记录器
recorder.stop();
// 释放
recorder.release();
// 将MediaRecorder引用置空,Garbage Collection将回收对象占用的内存
recorder = null;
// 锁定摄像头
camera.lock();
}
}
}
});
}
// 定制SurfaceView的回调接口SurfaceHolder.Callback
private class MycallBack implements SurfaceHolder.Callback {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera = Camera.open();
camera.setPreviewDisplay(sView.getHolder());
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (camera != null) {
camera.stopPreview();
camera.release();
camera = null;
}
}
}
}
注意事项
在AndroidManifest中设置应用需要访问的系统权限:
<!-- 向系统申请应用程序访问摄像头的权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 向系统申请应用程序访问麦克风的权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 向系统申请应用程序访问外置存储卡的权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />