学习资料:
2017 月 12 月 16 号
补充一个学习资料
20170513 16:25
注意权限,注意权限,注意权限
5.0在配置文件申明一下就好,6.0以上的系统需要考虑动态权限问题
评论里2个同学说因为权限问导致了黑屏,排查代码却没有发现问题,对刚刚接触安卓不久的同学造成困扰,还耽搁了不少时间,抱歉了
具体的相机设置、预览控件大小以及样张的参数之类的,需要根据自己的手机、系统做些相应调整。我测试手机是5.1系统,使用博客中的代码只是简单地拍了个照片测试了下,除了照片样张质量一般外,暂时没有发现其他问题。我并没有在项目中实际开发过拍照相关的功能,这里我也是简单地学习下。若哪位同学发现代码里的问题,请留言指出,免得造成误导浪费时间
Android 5.0(21)之后,android.hardware.Camera
被废弃(下面称为Camera1
),还有一个android.graphics.Camera
,这个android.graphics.Camera
不是用来照相的,是用来处理图像的,可以做出3D
的图像效果之类的,之前的Camera1
则由android.hardware.Camera2
来代替
Camera2
支持RAW
输出,可以调节曝光,对焦模式,快门等,功能比原先Camera
强大
1. Camera1使用
使用步骤:
- 调用
Camera.open()
,打开相机,默认为后置,可以根据摄像头ID
来指定打开前置还是后置 - 调用
Camera.getParameters()
得到一个Camera.Parameters
对象 - 使用
步骤2
得到的Camera.Parameters
对象,对拍照参数进行设置 - 调用
Camera.setPreviewDispaly(SurfaceHolder holder)
,指定使用哪个SurfaceView
来显示预览图片 - 调用
Camera.startPreview()
方法开始预览取景 - 调用
Camera.takePicture()
方法进行拍照 - 拍照结束后,调用
Camera.stopPreview()
结束取景预览,之后再replease()
方法释放资源
这几个步骤从疯狂Android讲义
中学到
1.1 简单使用
使用SurfaceView
进行取景的预览,点击屏幕进行拍照,用ImageView
来展示拍的照片
想买关于操作系统和C的书看,知乎很多人推荐这两本,就买了。然而,深入理解操作系统买早了,啃不动,看不懂
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surface_view_camera2_activity"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_show_camera2_activity"
android:layout_width="180dp"
android:layout_height="320dp"
android:visibility="gone"
android:layout_centerInParent="true"
android:scaleType="centerCrop" />
</RelativeLayout>
Activity
代码:
public class CameraActivity extends AppCompatActivity implements View.OnClickListener {
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private ImageView iv_show;
private int viewWidth, viewHeight;//mSurfaceView的宽和高
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
initView();
}
/**
* 初始化控件
*/
private void initView() {
iv_show = (ImageView) findViewById(R.id.iv_show_camera2_activity);
//mSurfaceView
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view_camera2_activity);
mSurfaceHolder = mSurfaceView.getHolder();
// mSurfaceView 不需要自己的缓冲区
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// mSurfaceView添加回调
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) { //SurfaceView创建
// 初始化Camera
initCamera();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView销毁
// 释放Camera资源
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
}
}
});
//设置点击监听
mSurfaceView.setOnClickListener(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (mSurfaceView != null) {
viewWidth = mSurfaceView.getWidth();
viewHeight = mSurfaceView.getHeight();
}
}
/**
* SurfaceHolder 回调接口方法
*/
private void initCamera() {
mCamera = Camera.open();//默认开启后置
mCamera.setDisplayOrientation(90);//摄像头进行旋转90°
if (mCamera != null) {
try {
Camera.Parameters parameters = mCamera.getParameters();
//设置预览照片的大小
parameters.setPreviewFpsRange(viewWidth, viewHeight);
//设置相机预览照片帧数
parameters.setPreviewFpsRange(4, 10);
//设置图片格式
parameters.setPictureFormat(ImageFormat.JPEG);
//设置图片的质量
parameters.set("jpeg-quality", 90);
//设置照片的大小
parameters.setPictureSize(viewWidth, viewHeight);
//通过SurfaceView显示预览
mCamera.setPreviewDisplay(mSurfaceHolder);
//开始预览
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 点击回调方法
*/
@Override
public void onClick(View v) {
if (mCamera == null) return;
//自动对焦后拍照
mCamera.autoFocus(autoFocusCallback);
}
/**
* 自动对焦 对焦成功后 就进行拍照
*/
Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {//对焦成功
camera.takePicture(new Camera.ShutterCallback() {//按下快门
@Override
public void onShutter() {
//按下快门瞬间的操作
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {//是否保存原始图片的信息
}
}, pictureCallback);
}
}
};
/**
* 获取图片
*/
Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
final Bitmap resource = BitmapFactory.decodeByteArray(data, 0, data.length);
if (resource == null) {
Toast.makeText(CameraActivity.this, "拍照失败", Toast.LENGTH_SHORT).show();
}
final Matrix matrix = new Matrix();
matrix.setRotate(90);
final Bitmap bitmap = Bitmap.createBitmap(resource, 0, 0, resource.getWidth(), resource.getHeight(), matrix, true);
if (bitmap != null && iv_show != null && iv_show.getVisibility() == View.GONE) {
mCamera.stopPreview();
iv_show.setVisibility(View.VISIBLE);
mSurfaceView.setVisibility(View.GONE);
Toast.makeText(CameraActivity.this, "拍照", Toast.LENGTH_SHORT).show();
iv_show.setImageBitmap(bitmap);
}
}
};
}
权限:
<uses-permission android:name="android.permission.CAMERA" />
在获得图片后,想要显示的效果是照片是竖直显示,resource
显示的却是逆时针旋转了90°
,照片是横着的,就使用matrix.setRotate(90)
进行旋转
2. Camera2
Camera2拍照示意图camera2中主要的类这里引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切建立在一个叫作 CameraCaptureSession 的会话中。
- CameraManaer 摄像头管理器,用于检测摄像头,打开系统摄像头,调用
CameraManager.getCameraCharacteristics(String)
可以获取指定摄像头的相关特性 - CameraCharacteristics 摄像头的特性
- CameraDevice 摄像头,类似
android.hardware.Camera
也就是Camera1
的Camera
- CameraCaptureSession 这个对象控制摄像头的预览或者拍照,
setRepeatingRequest()
开启预览,capture()
拍照,CameraCaptureSession
提供了StateCallback、CaptureCallback两个接口来监听CameraCaptureSession
的创建和拍照过程。 - CameraRequest和CameraRequest.Builder,预览或者拍照时,都需要一个
CameraRequest
对象。CameraRequest表示一次捕获请求,用来对z照片的各种参数设置,比如对焦模式、曝光模式等。CameraRequest.Builder用来生成CameraRequest对象。
2.1 简单使用
使用的依然是SurfaceView
来进行展示预览
主要思路:
- 获得摄像头管理器
CameraManager mCameraManager
,mCameraManager.openCamera()
来打开摄像头 - 指定要打开的摄像头,并创建
openCamera()
所需要的CameraDevice.StateCallback stateCallback
- 在
CameraDevice.StateCallback stateCallback
中调用takePreview()
,这个方法中,使用CaptureRequest.Builder
创建预览需要的CameraRequest
,并初始化了CameraCaptureSession
,最后调用了setRepeatingRequest(previewRequest, null, childHandler)
进行了预览 - 点击屏幕,调用
takePicture()
,这个方法内,最终调用了capture(mCaptureRequest, null, childHandler)
- 在
new ImageReader.OnImageAvailableListener(){}
回调方法中,将拍照拿到的图片进行展示
代码:
public class Camera2Activity extends AppCompatActivity implements View.OnClickListener {
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
///为了使照片竖直显示
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private ImageView iv_show;
private CameraManager mCameraManager;//摄像头管理器
private Handler childHandler, mainHandler;
private String mCameraID;//摄像头Id 0 为后 1 为前
private ImageReader mImageReader;
private CameraCaptureSession mCameraCaptureSession;
private CameraDevice mCameraDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
initVIew();
}
/**
* 初始化
*/
private void initVIew() {
iv_show = (ImageView) findViewById(R.id.iv_show_camera2_activity);
//mSurfaceView
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view_camera2_activity);
mSurfaceView.setOnClickListener(this);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.setKeepScreenOn(true);
// mSurfaceView添加回调
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) { //SurfaceView创建
// 初始化Camera
initCamera2();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView销毁
// 释放Camera资源
if (null != mCameraDevice) {
mCameraDevice.close();
Camera2Activity.this.mCameraDevice = null;
}
}
});
}
/**
* 初始化Camera2
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void initCamera2() {
HandlerThread handlerThread = new HandlerThread("Camera2");
handlerThread.start();
childHandler = new Handler(handlerThread.getLooper());
mainHandler = new Handler(getMainLooper());
mCameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;//后摄像头
mImageReader = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG,1);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在这里处理拍照得到的临时照片 例如,写入本地
@Override
public void onImageAvailable(ImageReader reader) {
mCameraDevice.close();
mSurfaceView.setVisibility(View.GONE);
iv_show.setVisibility(View.VISIBLE);
// 拿到拍照照片数据
Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);//由缓冲区存入字节数组
final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (bitmap != null) {
iv_show.setImageBitmap(bitmap);
}
}
}, mainHandler);
//获取摄像头管理
mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
//打开摄像头
mCameraManager.openCamera(mCameraID, stateCallback, mainHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 摄像头创建监听
*/
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {//打开摄像头
mCameraDevice = camera;
//开启预览
takePreview();
}
@Override
public void onDisconnected(CameraDevice camera) {//关闭摄像头
if (null != mCameraDevice) {
mCameraDevice.close();
Camera2Activity.this.mCameraDevice = null;
}
}
@Override
public void onError(CameraDevice camera, int error) {//发生错误
Toast.makeText(Camera2Activity.this, "摄像头开启失败", Toast.LENGTH_SHORT).show();
}
};
/**
* 开始预览
*/
private void takePreview() {
try {
// 创建预览需要的CaptureRequest.Builder
final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 将SurfaceView的surface作为CaptureRequest.Builder的目标
previewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
// 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求
mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
{
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
if (null == mCameraDevice) return;
// 当摄像头已经准备好时,开始显示预览
mCameraCaptureSession = cameraCaptureSession;
try {
// 自动对焦
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 打开闪光灯
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 显示预览
CaptureRequest previewRequest = previewRequestBuilder.build();
mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Toast.makeText(Camera2Activity.this, "配置失败", Toast.LENGTH_SHORT).show();
}
}, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 点击事件
*/
@Override
public void onClick(View v) {
takePicture();
}
/**
* 拍照
*/
private void takePicture() {
if (mCameraDevice == null) return;
// 创建拍照需要的CaptureRequest.Builder
final CaptureRequest.Builder captureRequestBuilder;
try {
captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 将imageReader的surface作为CaptureRequest.Builder的目标
captureRequestBuilder.addTarget(mImageReader.getSurface());
// 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 自动曝光
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 获取手机方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
// 根据设备方向计算设置照片的方向
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
//拍照
CaptureRequest mCaptureRequest = captureRequestBuilder.build();
mCameraCaptureSession.capture(mCaptureRequest, null, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
布局代码以及权限与Camera1
中一样,效果一样
预览时,是将mSurfaceHolder.getSurface()
作为目标
显示拍照结果时,是将mImageReader.getSurface()
作为目标
3. 最后
Camera2
的功能很强大,暂时也只是学习了最基本的思路
住的地方,没有桌子,于是坐地上,趴在床上敲代码,腰疼。逛淘宝买桌子去
感谢极客学院和肾虚将军的学习资料
共勉 : )
网友评论
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA},1);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
//满足这个条件才是前置的
}
如果更严格些应该进行判断。
在camera中,拍完照存入mediaStore需要发一个ACTION_NEW_PICTURE的广播。在camera2中还需要手动发送吗?