前言
最近在做相機相關的項目净响,考慮到調用系統(tǒng)相機部分功能不能滿足項目需求,于是考慮自定相機這一塊喳瓣,由于之前沒有接觸過自定義相機這一塊馋贤,于是查閱了相關資料,結合自身實踐畏陕,做一個簡單的總結配乓。
相關參考資料
感謝下面文章提供的參考
- android camera2 詳解說明(一)
- android.hardware.camera2 使用指南
- Android 用 camera2 API 自定義相機
- 本文基于谷歌官方demo修改實現(xiàn)
切入正題
1)camera2包架構示意圖:
2) camera2包中的主要API結構圖:
3)主要API詳解:
-
CameraManager
:攝像頭管理器。這是一個全新的系統(tǒng)管理器惠毁,專門用于檢測系統(tǒng)攝像頭犹芹、打開系統(tǒng)攝像頭。除此之外鞠绰,調用CameraManager
的getCameraCharacteristics(String)
方法即可獲取指定攝像頭的相關特性腰埂。 -
CameraCharacteristics
:攝像頭特性。該對象通過CameraManager
來獲取蜈膨,用于描述特定攝像頭所支持的各種特性屿笼。 -
CameraDevice
:代表系統(tǒng)攝像頭。該類的功能類似于早期的Camera
類翁巍。 -
CameraCaptureSession
:這是一個非常重要的API驴一,當程序需要預覽、拍照時曙咽,都需要先通過該類的實例創(chuàng)建Session
蛔趴。而且不管預覽還是拍照,也都是由該對象的方法進行控制的,其中控制預覽的方法為setRepeatingRequest()
孝情;控制拍照的方法為capture()
鱼蝉。
為了監(jiān)聽CameraCaptureSession
的創(chuàng)建過程,以及監(jiān)聽CameraCaptureSession
的拍照過程箫荡,Camera v2
API為CameraCaptureSession
提供了StateCallback
魁亦、CaptureCallback
等內部類。 -
CameraRequest
和CameraRequest.Builder
:當程序調用setRepeatingRequest()
方法進行預覽時羔挡,或調用capture()
方法進行拍照時洁奈,都需要傳入CameraRequest
參數(shù)。CameraRequest
代表了一次捕獲請求绞灼,用于描述捕獲圖片的各種參數(shù)設置利术,比如對焦模式、曝光模式……總之低矮,程序需要對照片所做的各種控制印叁,都通過CameraRequest
參數(shù)進行設置。CameraRequest.Builder
則負責生成CameraRequest
對象军掂。
4)自定義相機拍照大致流程:
1.
調用 CameraManager
的openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)
方法打開指定攝像頭轮蜕。該方法的第一個參數(shù)cameraId
代表要打開的攝像頭ID(攝像頭ID(通常0代表后置攝像頭,1代表前置攝像頭)蝗锥;第二個參數(shù)用于監(jiān)聽攝像頭的狀態(tài)跃洛;第三個參數(shù)代表執(zhí)行callback
的Handler
,如果程序希望直接在當前線程中執(zhí)行callback
终议,則可將handler
參數(shù)設為null
汇竭。
2. 獲取CameraDevice
對象
當攝像頭被打開之后,程序即可獲取CameraDevice
—即根據(jù)攝像頭ID獲取了指定攝像頭設備穴张,然后調用CameraDevice
的createCaptureSession(List<Surface> outputs, CameraCaptureSession. StateCallback callback韩玩,Handler handler)
方法來創(chuàng)建CameraCaptureSession
。該方法的第一個參數(shù)是一個List
集合陆馁,封裝了所有需要從該攝像頭獲取圖片的Surface
,第二個參數(shù)用于監(jiān)聽CameraCaptureSession
的創(chuàng)建過程合愈;第三個參數(shù)代表執(zhí)行callback
的Handler
叮贩,如果程序希望直接在當前線程中執(zhí)行callback
,則可將handler
參數(shù)設為null
佛析。
3. 設置設置攝像頭模式
不管預覽還是拍照益老,程序都調用CameraDevice
的createCaptureRequest(int templateType)
方法創(chuàng)建CaptureRequest.Builder
,該方法支持TEMPLATE_PREVIEW
(預覽)寸莫、TEMPLATE_RECORD
(拍攝視頻)捺萌、TEMPLATE_STILL_CAPTURE
(拍照)等參數(shù)。
通過第3步所調用方法返回的CaptureRequest.Builder
設置拍照的各種參數(shù)膘茎,比如對焦模式桃纯、曝光模式等酷誓。
調用CaptureRequest.Builder
的build()
方法即可得到CaptureRequest
對象,接下來程序可通過CameraCaptureSession
的setRepeatingRequest()
方法開始預覽态坦,或調用capture()
方法拍照盐数。
5)案例代碼
配置靜態(tài)權限
6.0之后記得在代碼中添加相機和讀寫存儲卡的動態(tài)權限
<!-- 相機 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autocues"/>
<!-- 用于讀寫存儲卡 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
相機界面簡單布局
界面很簡單,就一個TextureView和一個拍照按鈕
activity_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.ticp.uwonders.imagerecognize.activity.CameraActivity">
<TextureView
android:id="@+id/camera_texture_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/capture_ib"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="bottom|center"
android:layout_marginBottom="10dp"
android:background="@drawable/home_scan" />
</FrameLayout>
CameraActivity
代碼實現(xiàn)伞梯,項目中使用了 Butterknife 簡單注解
該文件注釋比較清楚玫氢,相信大家能夠看懂,就不分方法細講了
/**
* @author Marlon
* @desc Camera2Activity 基于Camera2 API 自定義相機
* @date 2018/6/13
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2Activity extends AppCompatActivity {
@BindView(R.id.texture)
AutoFitTextureView texture;
@BindView(R.id.takephoto)
ImageView takephoto;
@BindView(R.id.change)
Switch change;
@BindView(R.id.auto)
RadioButton auto;
@BindView(R.id.open)
RadioButton open;
@BindView(R.id.close)
RadioButton close;
@BindView(R.id.flash_rg)
RadioGroup flashRg;
/*** 相機管理類*/
CameraManager mCameraManager;
/*** 指定攝像頭ID對應的Camera實體對象*/
CameraDevice mCameraDevice;
/**
* 預覽尺寸
*/
private Size mPreviewSize;
private int mSurfaceWidth;
private int mSurfaceHeight;
/*** 打開攝像頭的ID{@link CameraDevice}.*/
private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
/*** 處理靜態(tài)圖像捕獲的ImageReader谜诫。{@link ImageReader}*/
private ImageReader mImageReader;
/*** 用于相機預覽的{@Link CameraCaptureSession}漾峡。*/
private CameraCaptureSession mCaptureSession;
/*** {@link CaptureRequest.Builder}用于相機預覽請求的構造器*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/***預覽請求, 由上面的構建器構建出來*/
private CaptureRequest mPreviewRequest;
/**
* 從屏幕旋轉圖片轉換方向。
*/
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 boolean mFlashSupported;
/*** 用于運行不應阻塞UI的任務的附加線程喻旷。*/
private HandlerThread mBackgroundThread;
/*** 用于在后臺運行任務的{@link Handler}生逸。*/
private Handler mBackgroundHandler;
/**
* 文件存儲路徑
*/
private File mFile;
/**
* 預覽請求構建器, 用來構建"預覽請求"(下面定義的)通過pipeline發(fā)送到Camera device
* 這是{@link ImageReader}的回調對象。 當靜止圖像準備保存時掰邢,將會調用“onImageAvailable”牺陶。
*/
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
}
};
/*** {@link CameraDevice.StateCallback}打開指定攝像頭回調{@link CameraDevice}*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
createCameraPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
cameraDevice = null;
}
};
/**
* TextureView 生命周期響應
*/
private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override //創(chuàng)建
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//當TextureView創(chuàng)建完成,打開指定攝像頭相機
openCamera(width, height, mCameraId);
}
@Override //尺寸改變
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override //銷毀
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override //更新
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
};
private int CONTROL_AE_MODE;
/**
* 打開指定攝像頭ID的相機
*
* @param width
* @param height
* @param cameraId
*/
private void openCamera(int width, int height, int cameraId) {
if (ActivityCompat.checkSelfPermission(Camera2Activity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
return;
}
try {
mSurfaceWidth = width;
mSurfaceHeight = height;
// getCameraId(cameraId);
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId + "");
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// 獲取設備方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
int totalRotation = sensorToDeviceRotation(characteristics, rotation);
boolean swapRotation = totalRotation == 90 || totalRotation == 270;
int rotatedWidth = mSurfaceWidth;
int rotatedHeight = mSurfaceHeight;
if (swapRotation) {
rotatedWidth = mSurfaceHeight;
rotatedHeight = mSurfaceWidth;
}
// 獲取最佳的預覽尺寸
mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
if (swapRotation) {
texture.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
texture.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
if (mImageReader == null) {
// 創(chuàng)建一個ImageReader對象辣之,用于獲取攝像頭的圖像數(shù)據(jù),maxImages是ImageReader一次可以訪問的最大圖片數(shù)量
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.JPEG, 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
}
//檢查是否支持閃光燈
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
mCameraManager.openCamera(mCameraId + "", mStateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 設置相機閃關燈模式
*
* @param AE_MODE 閃關燈的模式
* @throws CameraAccessException
*/
private void setFlashMode(int AE_MODE) {
if (mFlashSupported) {
this.CONTROL_AE_MODE = AE_MODE;
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, AE_MODE);
if (AE_MODE == CaptureRequest.CONTROL_AE_MODE_OFF) {
mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
}
}
// 構建上述的請求
mPreviewRequest = mPreviewRequestBuilder.build();
// 重復進行上面構建的請求, 用于顯示預覽
try {
mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 創(chuàng)建預覽對話
*/
private void createCameraPreview() {
try {
// 獲取texture實例
SurfaceTexture surfaceTexture = texture.getSurfaceTexture();
assert surfaceTexture != null;
//我們將默認緩沖區(qū)的大小配置為我們想要的相機預覽的大小掰伸。
surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// 用來開始預覽的輸出surface
Surface surface = new Surface(surfaceTexture);
//創(chuàng)建預覽請求構建器
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//將TextureView的Surface作為相機的預覽顯示輸出
mPreviewRequestBuilder.addTarget(surface);
//在這里,我們?yōu)橄鄼C預覽創(chuàng)建一個CameraCaptureSession怀估。
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// 相機關閉時, 直接返回
if (null == mCameraDevice) {
return;
}
//會話準備就緒后狮鸭,我們開始顯示預覽。
// 會話可行時, 將構建的會話賦給field
mCaptureSession = cameraCaptureSession;
//相機預覽應該連續(xù)自動對焦多搀。
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//設置閃關燈模式
setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("預覽失敗了");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 拍照時調用方法
*/
private void captureStillPicture() {
try {
if (mCameraDevice == null) {
return;
}
// 創(chuàng)建作為拍照的CaptureRequest.Builder
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 將imageReader的surface作為CaptureRequest.Builder的目標
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
/* // 設置自動對焦模式
mBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 設置自動曝光模式
mBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);*/
//設置為自動模式
// mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
setFlashMode(CONTROL_AE_MODE);
// 停止連續(xù)取景
mCaptureSession.stopRepeating();
// 捕獲靜態(tài)圖像
mCaptureSession.capture(mPreviewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
// 拍照完成時激發(fā)該方法
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
//重新打開預覽
createCameraPreview();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 獲取設備方向
*
* @param characteristics
* @param deviceOrientation
* @return
*/
private static int sensorToDeviceRotation(CameraCharacteristics characteristics, int deviceOrientation) {
int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
deviceOrientation = ORIENTATIONS.get(deviceOrientation);
return (sensorOrientation + deviceOrientation + 360) % 360;
}
/**
* 獲取可用設備可用攝像頭列表
*/
private void getCameraId(int ID) {
try {
for (String cameraId : mCameraManager.getCameraIdList()) {
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
if (characteristics.get(CameraCharacteristics.LENS_FACING) == ID) {
continue;
}
mCameraId = Integer.valueOf(cameraId);
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 設置最佳尺寸
*
* @param sizes
* @param width
* @param height
* @return
*/
private Size getPreferredPreviewSize(Size[] sizes, int width, int height) {
List<Size> collectorSizes = new ArrayList<>();
for (Size option : sizes) {
if (width > height) {
if (option.getWidth() > width && option.getHeight() > height) {
collectorSizes.add(option);
}
} else {
if (option.getHeight() > width && option.getWidth() > height) {
collectorSizes.add(option);
}
}
}
if (collectorSizes.size() > 0) {
return Collections.min(collectorSizes, new Comparator<Size>() {
@Override
public int compare(Size s1, Size s2) {
return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight());
}
});
}
return sizes[0];
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
ButterKnife.bind(this);
initView();
}
private void initView() {
change.setOnCheckedChangeListener((buttonView, isChecked) -> {
closeCamera();
if (!isChecked) {
//后置攝像頭
mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
if (texture.isAvailable()) {
openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
} else {
texture.setSurfaceTextureListener(textureListener);
}
} else {
//前置攝像頭
mCameraId = CameraCharacteristics.LENS_FACING_BACK;
if (texture.isAvailable()) {
openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
} else {
texture.setSurfaceTextureListener(textureListener);
}
}
});
flashRg.setOnCheckedChangeListener((group, checkedId) -> {
switch (checkedId) {
case R.id.auto:
//自動閃光燈
setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
break;
case R.id.open:
//開啟閃光燈
setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
break;
case R.id.close:
//關閉閃光燈
setFlashMode(CaptureRequest.CONTROL_AE_MODE_OFF);
break;
default:
break;
}
});
// 獲取CameraManager 相機設備管理器
mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
}
/**
* 初試化拍照線程
*/
public void startBackgroundThread() {
mBackgroundThread = new HandlerThread("Camera Background");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
public void stopBackgroundThread() {
if (mBackgroundThread != null) {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Closes the current {@link CameraDevice}.
* 關閉正在使用的相機
*/
private void closeCamera() {
// 關閉捕獲會話
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
// 關閉當前相機
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
// 關閉拍照處理器
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
}
@Override
protected void onResume() {
super.onResume();
if (texture.isAvailable()) {
openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
} else {
texture.setSurfaceTextureListener(textureListener);
}
startBackgroundThread();
}
@Override
protected void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
@OnClick(R.id.takephoto)
public void onViewClicked() {
captureStillPicture();
}
/**
* Shows a {@link Toast} on the UI thread.
* 在UI上顯示Toast的方法
*
* @param text The message to show
*/
/**
* Shows a {@link Toast} on the UI thread.
* 在UI上顯示Toast的方法
*
* @param text The message to show
*/
private void showToast(final String text) {
runOnUiThread(() -> Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show());
}
/**
* Saves a JPEG {@link Image} into the specified {@link File}.
* 保存圖片到自定目錄
* 保存jpeg到指定的文件夾下, 開啟子線程執(zhí)行保存操作
*/
private static class ImageSaver implements Runnable {
/**
* The JPEG image
* 要保存的圖片
*/
private final Image mImage;
/**
* The file we save the image into.
* 圖片存儲的路徑
*/
private final File mFile;
ImageSaver(Image image, File file) {
mImage = image;
mFile = file;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(mFile);
output.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
if (null != output) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
具體代碼請查看項目demo地址demo
總結
本文主要總結使用Android Camera2 API 自定義相機歧蕉,及實現(xiàn)閃光燈的打開,關閉康铭,自動惯退,前后攝像頭的切換等功能的集成等)。
demo中包含了使用Android Camera1/ Camera2 API 自定義相機及相關功能从藤,同時也包含了使用google框架 cameraView 自定義相機的demo催跪,歡迎大家瀏覽和Star!
筆者水平有限夷野,如有任何疑問懊蒸,請大神多多賜教!