Android API 21新增了Camera2,這與以前的camera架構徹底不一樣,使用起來也比較複雜,可是功能變得很強大。數組
在講解開啓預覽以前,首先須要瞭解camera2的幾個比較重要的類:session
CameraManager: 管理手機上的全部攝像頭設備,它的做用主要是獲取攝像頭列表和打開指定的攝像頭
CameraDevice: 具體的攝像頭設備,它有一系列參數(預覽尺寸、拍照尺寸等),能夠經過CameraManager的getCameraCharacteristics()方法獲取。它的做用主要是建立CameraCaptureSession和CaptureRequest
CameraCaptureSession: 相機捕獲會話,用於處理拍照和預覽的工做(很重要)
CaptureRequest: 捕獲請求,定義輸出緩衝區以及顯示界面(TextureView或SurfaceView)等
1,定義TextureView做爲預覽界面
在佈局文件中加入TextureView控件,而後實現其監聽事件架構
textureView = (TextureView) findViewById(R.id.textureView);
1
而後咱們能夠在OnResume()方法中設置監聽SurefaceTexture的事件app
textureView.setSurfaceTextureListener(textureListener);
1
當SurefaceTexture準備好後會回調SurfaceTextureListener 的onSurfaceTextureAvailable()方法ide
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//當SurefaceTexture可用的時候,設置相機參數並打開相機
setupCamera(width, height);
openCamera();
}
};
2,設置相機參數
爲了更好地預覽,咱們根據TextureView的尺寸設置預覽尺寸,Camera2中使用CameraManager來管理攝像頭佈局
private void setupCamera(int width, int height) {
//獲取攝像頭的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//遍歷全部攝像頭
for (String cameraId: manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
//默認打開後置攝像頭
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
continue;
//獲取StreamConfigurationMap,它是管理攝像頭支持的全部輸出格式和尺寸
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//根據TextureView的尺寸設置預覽尺寸
mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
3,開啓相機
Camera2中打開相機也須要經過CameraManager類post
private void openCamera() {
//獲取攝像頭的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//檢查權限
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
//打開相機,第一個參數指示打開哪一個攝像頭,第二個參數stateCallback爲相機的狀態回調接口,第三個參數用來肯定Callback在哪一個線程執行,爲null的話就在當前線程執行
manager.openCamera(mCameraId, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
實現StateCallback 接口,當相機打開後會回調onOpened方法,在這個方法裏面開啓預覽ui
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
mCameraDevice = camera;
//開啓預覽
startPreview();
}
}
4,開啓相機預覽
咱們使用TextureView顯示相機預覽數據,Camera2的預覽和拍照數據都是使用CameraCaptureSession會話來請求的this
private void startPreview() {
SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();
//設置TextureView的緩衝區大小
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//獲取Surface顯示預覽數據
Surface mSurface = new Surface(mSurfaceTexture);
try {
//建立CaptureRequestBuilder,TEMPLATE_PREVIEW比表示預覽請求
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//設置Surface做爲預覽數據的顯示界面
mCaptureRequestBuilder.addTarget(mSurface);
//建立相機捕獲會話,第一個參數是捕獲數據的輸出Surface列表,第二個參數是CameraCaptureSession的狀態回調接口,當它建立好後會回調onConfigured方法,第三個參數用來肯定Callback在哪一個線程執行,爲null的話就在當前線程執行
mCameraDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
try {
//建立捕獲請求
mCaptureRequest = mCaptureRequestBuilder.build();
mPreviewSession = session;
//設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示
mPreviewSession.setRepeatingRequest(mCaptureRequest, mSessionCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}.net
@Override
public void onConfigureFailed(CameraCaptureSession session) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
5,實現PreviewCallback
Camera2中並無Camera1中的PreviewCallback接口,那怎麼實現獲取預覽幀數據呢?答案就是使用ImageReader間接實現
首先建立一個ImageReader,並監聽它的事件
private void setupImageReader() {
//前三個參數分別是須要的尺寸和格式,最後一個參數表明每次最多獲取幾幀數據,本例的2表明ImageReader中最多能夠獲取兩幀圖像流
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.JPEG, 2);
//監聽ImageReader的事件,當有圖像流數據可用時會回調onImageAvailable方法,它的參數就是預覽幀數據,能夠對這幀數據進行處理
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
//咱們能夠將這幀數據轉成字節數組,相似於Camera1的PreviewCallback回調的預覽幀數據
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
image.close();
}
}, null);
}
注意:必定要調用reader.acquireLatestImage()和close()方法,不然畫面就會卡住
而後咱們在開啓預覽以前,設置ImageReader爲輸出Surface
setupImageReader();
//獲取ImageReader的Surface
Surface imageReaderSurface = mImageReader.getSurface();
//CaptureRequest添加imageReaderSurface,不加的話就會致使ImageReader的onImageAvailable()方法不會回調
mCaptureRequestBuilder.addTarget(imageReaderSurface);
//建立CaptureSession時加上imageReaderSurface,以下,這樣預覽數據就會同時輸出到previewSurface和imageReaderSurface了
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, imageReaderSurface), new CameraCaptureSession.StateCallback() {
}
關閉相機時別忘了關閉ImageReader
6,拍照
Camera2拍照也是經過ImageReader來實現的
首先先作些準備工做,設置拍照參數,如方向、尺寸等
private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
設置拍照尺寸,能夠跟預覽尺寸一塊兒設置,而後ImageReader初始化使用此尺寸
mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator<Size>() {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getHeight() * rhs.getWidth());
}
});
建立保存圖片的線程
public static class imageSaver implements Runnable {
private Image mImage;
public imageSaver(Image image) {
mImage = image;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(mImageFile);
fos.write(data, 0 ,data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImageFile = null;
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
而後當ImageReader有數據時,經過此線程保存圖片
//使用前面獲取的拍照尺寸
mImageReader = ImageReader.newInstance(mCaptureSize.getWidth(), mCaptureSize.getHeight(),
ImageFormat.JPEG, 2);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//執行圖像保存子線程
mCameraHandler.post(new imageSaver(reader.acquireNextImage()));
}
}, mCameraHandler);
而後開啓預覽建立CaptureSession時把ImageReader添加進去
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
}
如今準備工做作好了,還須要響應點擊拍照事件,咱們設置點擊拍照按鈕調用capture()方法,capture()方法即實現拍照
private void capture() {
try {
//首先咱們建立請求拍照的CaptureRequest
final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
//獲取屏幕方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
//設置CaptureRequest輸出到mImageReader
mCaptureBuilder.addTarget(mImageReader.getSurface());
//設置拍照方向
mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
//這個回調接口用於拍照結束時重啓預覽,由於拍照會致使預覽中止
CameraCaptureSession.CaptureCallback mImageSavedCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
Toast.makeText(getApplicationContext(), "Image Saved!", Toast.LENGTH_SHORT).show();
//重啓預覽
restartPreview();
}
};
//中止預覽
mCameraCaptureSession.stopRepeating();
//開始拍照,而後回調上面的接口重啓預覽,由於mCaptureBuilder設置ImageReader做爲target,因此會自動回調ImageReader的onImageAvailable()方法保存圖片
mCameraCaptureSession.capture(mCaptureBuilder.build(), mImageSavedCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
重啓預覽的方法很簡單了
private void restartPreview() { try { //執行setRepeatingRequest方法就好了,注意mCaptureRequest是以前開啓預覽設置的請求 mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }