音視頻系列文章:
Android 音視頻開發(fā)(一) -- 使用AudioRecord 錄制PCM(錄音);AudioTrack播放音頻
Android 音視頻開發(fā)(二) -- Camera1 實(shí)現(xiàn)預(yù)覽疯汁、拍照功能工程:VideoDemo
上一章牲尺,已經(jīng)我們已經(jīng)用 Camera1 實(shí)現(xiàn)了預(yù)覽和拍照的功能,但也說(shuō)到幌蚊,在API21的時(shí)候谤碳,Camera1已經(jīng)被標(biāo)注為棄用,因?yàn)樗腁PI功能和靈活性滿足不了現(xiàn)在日益復(fù)雜的相機(jī)開發(fā)了溢豆,所以在 API21之后蜒简,引入了 Camera2 。
一. Camera2 簡(jiǎn)介
從功能來(lái)講漩仙,Camera2 廢棄了 Camera1 的框架搓茬,它支持更多的功能,比如:
- 獲取更多的幀(預(yù)覽/拍照)信息队他,以及每一幀的參數(shù)配置
- 支持更多的圖片格式(yuv/raw)等
- 一些新特性
...
而今天要完成的效果如下:
1.1. Pipeline
Camera2 的將攝像頭包裝成管道(Pipeline)卷仑,它會(huì)捕獲單個(gè)幀的輸入請(qǐng)求,每個(gè)請(qǐng)求捕獲單個(gè)圖像麸折,然后把這些數(shù)據(jù)包裝成數(shù)據(jù)包系枪,從而把這些圖像數(shù)據(jù)輸出到圖片緩沖區(qū)中。
這些請(qǐng)求時(shí)按順序處理的磕谅,它按順序處理每一幀的請(qǐng)求私爷,并返回請(qǐng)求結(jié)果給客戶端,看下面這張圖:
假設(shè)我們要拍攝同時(shí)拍攝兩張不同尺寸的圖片膊夹,那么它的過(guò)程應(yīng)該是這樣的:
- 創(chuàng)建一個(gè)用于 Pipeline 獲取圖片信息的 CaptureRequest
- 創(chuàng)建兩個(gè)不同的 Surface 衬浑,用來(lái)接收?qǐng)D片數(shù)據(jù),并把他們加到 CaptureRequest 中
- 發(fā)送配置好的的 CaptureRequest 到Pipeline 中放刨,等待返回拍照結(jié)果
上面需要記住的是工秩,CaptureReuqest 創(chuàng)建之前,我們已經(jīng)把相機(jī)的數(shù)據(jù)都配置好进统,比如聚焦助币、閃光燈等,接著才把它輸入給 Camrea2 的底層螟碎,它會(huì)被放入到一個(gè)被叫做 In-Flight Capture Queue 的隊(duì)列中眉菱,當(dāng) In-Flight Capture Queue 隊(duì)列空閑時(shí),我們就可以從它拿到不同的 圖片數(shù)據(jù) 給到Surface 掉分,且 能拿到 CaptureResult 這個(gè)返回結(jié)果信息俭缓。
1.2. Supported Hardware Level
我們支持克伊,Camera 支持了很多新功能的特性,但這也要看你的手機(jī)廠商的支持程度华坦,所以愿吹,為了方便區(qū)分,Camera2 使用 Supported Hardware Level 來(lái)判斷你是否支持 Camera2 的特性惜姐,它分為4個(gè)登記:
- LEGACY :向后兼容模式犁跪,支持Camera1 的功能,不支持 Camera2 的新特性
- LIMITED :除了支持 Camera1 的特性歹袁,還支持部分 Camera2 的高級(jí)特性
- FULL :支持所有 Camera2 的高級(jí)特性
- LEVEL_3 :新增更多的 Camera2 特性耘拇,利于YUV 數(shù)據(jù)等
下面我們將根據(jù)這些特性,來(lái)完成 Camera2 的開發(fā)宇攻。
二. 相機(jī)預(yù)覽
要注意的是惫叛,Camera2 與 Camera1 是兩個(gè)不同的框架,不要被 Camera1 的思想禁錮逞刷,把它當(dāng)做新知識(shí)學(xué)習(xí)即可嘉涌,一個(gè)相機(jī)的流程圖如下:
- 通過(guò)context.getSystemService(Context.CAMERA_SERVICE) 獲取CameraManager.
- 通過(guò) getCameraCharacteristics() 方法,拿到相機(jī)的所有信息夸浅,比如支持的預(yù)覽大小仑最,level 等
- 通過(guò) CameraManager 的 openCamera() 方法,從回調(diào)中拿到 CameraDevice 帆喇,它表示當(dāng)前相機(jī)設(shè)備
- CameraDevice 通過(guò) createCaptureRequest 創(chuàng)建 CaptureRequest.Builder 警医,用來(lái)配置相機(jī)屬性,通過(guò) createCaptureSession 創(chuàng)建 CameraCaptureSession 坯钦,它是 Pipeline 的實(shí)例预皇,然后交給底層處理
別忘了,您要添加以下權(quán)限:
<uses-permission android:name="android.permission.CAMERA" /> <!-- 支持相機(jī)才能運(yùn)行 -->
<!--需要設(shè)備有相機(jī)-->
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
2.1. 獲取相機(jī)信息
從上面的流程圖支持婉刀,我們需要通過(guò) CameraManager 的 getCameraCharacteristics() 方法吟温,來(lái)獲取相機(jī)的信息;
CameraManager 是一個(gè)負(fù)責(zé)查詢和建立相機(jī)連接的系統(tǒng)服務(wù),它的功能不多突颊,主要如下:
- 將相機(jī)信息裝載到 CameraCharacteristics 中鲁豪。
- 根據(jù)指定的相機(jī)ID 連接相機(jī)
- 提供將閃光燈設(shè)置為手電筒的快捷方式
所以它的代碼如下:
try {
//獲取相機(jī)服務(wù) CameraManager
mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//遍歷設(shè)備支持的相機(jī) ID ,比如前置律秃,后置等
String[] cameraIdList = mCameraManager.getCameraIdList();
for (String cameraId : cameraIdList) {
// 拿到裝在所有相機(jī)信息的 CameraCharacteristics 類
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
//拿到相機(jī)的方向爬橡,前置,后置棒动,外置
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null) {
//后置攝像頭
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
mBackCameraId = cameraId;
mBackCameraCharacteristics = characteristics;
}else if (facing == CameraCharacteristics.LENS_FACING_FRONT){
//前置攝像頭
mFrontCameraId = cameraId;
mFrontCameraCharacteristics = characteristics;
}
mCameraId = cameraId;
}
//是否支持 Camera2 的高級(jí)特性
Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
/**
* 不支持 Camera2 的特性
*/
if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY){
// Toast.makeText(this, "您的手機(jī)不支持Camera2的高級(jí)特效", Toast.LENGTH_SHORT).show();
// break;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
上面的注釋都很清晰了糙申,我們是通過(guò) CameraCharacteristics 去知道當(dāng)前的相機(jī)方向;除了這些迁客,它還包含相機(jī)的其他信息郭宝,比如:
- 是否有閃光燈 FLASH_INFO_AVAILABLE
- 是否有 AE 模式 CONTROL_AE_AVAILABLE_MODES
- 光爆等,如果你對(duì) Camera1 比較熟悉掷漱,那么 CameraCharacteristics 有點(diǎn)像 Camera1 的 Camera.CameraInfo 或者 Camera.Parameters
2.2 打開攝像頭
比 Camera1 好的就是粘室,Camera2 在打開攝像頭之前,就可以進(jìn)行參數(shù)的配置卜范,比如預(yù)覽尺寸等衔统。
我們知道,攝像頭需要 Surface 來(lái)裝載數(shù)據(jù)海雪,這里使用的是 TextureView 來(lái)裝載:
mTextureView = findViewById(R.id.surface);
所以锦爵,當(dāng)它創(chuàng)建完成拿到寬高之后,我們就可以打開攝像頭了:
private void openCamera(int width, int height) {
//判斷不同攝像頭奥裸,拿到 CameraCharacteristics
CameraCharacteristics characteristics = mCameraId.equals(mBackCameraId) ? mBackCameraCharacteristics : mFrontCameraCharacteristics;
//拿到配置的map
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//獲取攝像頭傳感器的方向
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
//獲取預(yù)覽尺寸
Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);
//獲取最佳尺寸
Size bestSize = getBestSize(width, height, previewSizes);
/**
* 配置預(yù)覽屬性
* 與 Cmaera1 不同的是险掀,Camera2 是把尺寸信息給到 Surface (SurfaceView 或者 ImageReader),
* Camera2 會(huì)根據(jù) Surface 配置的大小湾宙,輸出對(duì)應(yīng)尺寸的畫面;
* 注意攝像頭的 width > height 樟氢,而我們使用豎屏,所以寬高要變化一下
*/
mTextureView.getSurfaceTexture().setDefaultBufferSize(bestSize.getHeight(),bestSize.getWidth());
/**
* 設(shè)置圖片尺寸侠鳄,這里圖片的話雕崩,選擇最大的分辨率即可
*/
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
Size largest = Collections.max(
Arrays.asList(sizes),
new CompareSizesByArea());
//設(shè)置imagereader戏售,配置大小,且最大Image為 1,因?yàn)槭?JPEG
mImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),
ImageFormat.JPEG,1);
//拍照監(jiān)聽(tīng)
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);
try {
//打開攝像頭当辐,監(jiān)聽(tīng)數(shù)據(jù)
mCameraManager.openCamera(mCameraId,new CameraDeviceCallback(),null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
在用 mCameraManager.openCamera() 打開攝像頭之前,我們通過(guò)
//拿到配置的map
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
拿到可用流的map督怜,這樣就可以通過(guò) getOutputSizes() 等拿到相機(jī)支持的所有尺寸了异希,通過(guò)上一章 Android 音視頻開發(fā)(二) -- Camera1 實(shí)現(xiàn)預(yù)覽、拍照功能 也解釋了挡育,為啥要有最佳尺寸罢绽,不然會(huì)有圖片拉伸的問(wèn)題。
而與 Camera1 不同的是静盅,Camera2 會(huì)根據(jù) Surface 配置的大小良价,輸出對(duì)應(yīng)尺寸的畫面,所以這里設(shè)置 mTextureView 的大小即可蒿叠,注意攝像頭的 width > height 明垢,而我們使用豎屏,所以寬高要變化一下市咽。
接著設(shè)置圖片的尺寸痊银,這里當(dāng)然是越輕越好了,所以選擇最大尺寸即可施绎。ImageReader 等到后面拍照時(shí)再講解溯革。
最后調(diào)用 mCameraManager.openCamera() 贞绳,它有三個(gè)參數(shù):
- cameraId :Camera 的 ID,比如前置致稀、后置和外置
- CameraDevice.StateCallback :當(dāng)連接到相機(jī)時(shí)冈闭,該回調(diào)就會(huì)被調(diào)用,生成 CameraDevice
- handler : 調(diào)用 CameraDevice.StateCallback 的 Handler抖单,傳null萎攒,則調(diào)用主線程,建議傳入 HandlerThread 的hander矛绘,畢竟這種都是耗時(shí)的耍休。
2.3. CameraDevice
CameraDevice 表示當(dāng)前的相機(jī)設(shè)備,它的主要職責(zé)有:
- 根據(jù)指定的參數(shù)創(chuàng)建 CameraCaptureSession
- 根據(jù)指定的模板創(chuàng)建 CaptureRequest
- 關(guān)閉相機(jī)設(shè)備
- 監(jiān)聽(tīng)相機(jī)狀態(tài)货矮,比如斷開羊精,開啟成功失敗的監(jiān)聽(tīng)
如下:
class CameraDeviceCallback extends CameraDevice.StateCallback{
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
//此時(shí)攝像頭已經(jīng)打開,可以預(yù)覽了
createPreviewPipeline(camera);
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
}
}
在 onOpened 創(chuàng)建我們CaptureRequest 囚玫,配置相機(jī)參數(shù)信息园匹,如下:
private void createPreviewPipeline(CameraDevice cameraDevice){
try {
//創(chuàng)建作為預(yù)覽的 CaptureRequst.builder
final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Surface surface = new Surface(mTextureView.getSurfaceTexture());
//添加 surface 容器
captureBuilder.addTarget(surface);
// 創(chuàng)建CameraCaptureSession,該對(duì)象負(fù)責(zé)管理處理預(yù)覽請(qǐng)求和拍照請(qǐng)求,這個(gè)必須在創(chuàng)建 Seesion 之前就準(zhǔn)備好劫灶,傳遞給底層用于皮遏制 pipeline
cameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
try {
//設(shè)置自動(dòng)聚焦
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//設(shè)置自動(dòng)曝光
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
//創(chuàng)建 CaptureRequest
CaptureRequest build = captureBuilder.build();
//設(shè)置預(yù)覽時(shí)連續(xù)捕獲圖片數(shù)據(jù)
session.setRepeatingRequest(build,null,null);
}catch (Exception e){
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Toast.makeText(Camera2Activity.this, "配置失敗", Toast.LENGTH_SHORT).show();
}
},null);
} catch (Exception e) {
e.printStackTrace();
}
}
在開啟預(yù)覽之前裸违,我們需要先創(chuàng)建 CaptureRequest ,上面已經(jīng)說(shuō)過(guò) CaptureRequest 是向 CameraCaptureSession 提交 Capture 請(qǐng)求的信息載體本昏,內(nèi)部包含了本次的 Capture 參數(shù)配置和接受圖像數(shù)據(jù)的 Surface供汛。
CaptureRequest 可以配置的信息非常多,比如圖像格式涌穆、圖像分辨率怔昨、聚焦、閃光燈控制等宿稀,可以說(shuō)絕大部分配置都是通過(guò) CaptureRequest 配置的趁舀。
上面通過(guò) cameraDevice.createCaptureRequest() 來(lái)創(chuàng)建一個(gè) CaptureRequest.Builder 對(duì)象,其中createCaptureRequest() 方法的參數(shù)是 templateType 用于指定哪種模板祝沸,Camera2 根據(jù)不同場(chǎng)景矮烹,為我們配置了一些常用的參數(shù)模板:
- TEMPLATE_PREVIEW:適用于配置預(yù)覽的模板
- TEMPLATE_RECORD:適用于視頻錄制的模板。
- TEMPLATE_STILL_CAPTURE:適用于拍照的模板罩锐。
- TEMPLATE_VIDEO_SNAPSHOT:適用于在錄制視頻過(guò)程中支持拍照的模板奉狈。
- TEMPLATE_MANUAL:適用于希望自己手動(dòng)配置大部分參數(shù)的模板。
這里我們需要一個(gè)預(yù)覽的 CaptureRequest 涩惑,所以選擇 TEMPLATE_PREVIEW的模板仁期。
接著,需要設(shè)置要承載圖像數(shù)據(jù)的 Surface,我們用到兩個(gè)跛蛋,一個(gè)是 TextureView 用來(lái)預(yù)覽的熬的,一個(gè)是 ImageReader 用來(lái)拍照的。
目前這個(gè) CaptureRequest 是用來(lái)預(yù)覽的赊级,所以通過(guò) addTarget 設(shè)置進(jìn)去:
//添加 surface 容器
captureBuilder.addTarget(surface);
最后通過(guò) cameraDevice.createCaptureSession() 創(chuàng)建 CameraCaptureSession 押框,然后再配置一下聚焦和曝光的配置,就可以把 CaptureRequest 通過(guò) Session 發(fā)送給底層了:
// 創(chuàng)建CameraCaptureSession此衅,該對(duì)象負(fù)責(zé)管理處理預(yù)覽請(qǐng)求和拍照請(qǐng)求,這個(gè)必須在創(chuàng)建 Seesion 之前就準(zhǔn)備好强戴,傳遞給底層用于配置 pipeline
cameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
try {
//設(shè)置自動(dòng)聚焦
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//設(shè)置自動(dòng)曝光
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
//創(chuàng)建 CaptureRequest
CaptureRequest build = captureBuilder.build();
//設(shè)置預(yù)覽時(shí)連續(xù)捕獲圖片數(shù)據(jù)
session.setRepeatingRequest(build,null,null);
}catch (Exception e){
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Toast.makeText(Camera2Activity.this, "配置失敗", Toast.LENGTH_SHORT).show();
}
},null);
2.4. 開啟和關(guān)閉預(yù)覽
在 Camera2 中亭螟,本質(zhì)上是不斷的重復(fù) Captrue 的過(guò)程挡鞍,每一次 Capture 都會(huì)把預(yù)覽的數(shù)據(jù)輸出到對(duì)應(yīng)的 Surface 中,所以预烙,為了達(dá)到預(yù)覽的效果墨微,需要使用:
session.setRepeatingRequest(build,null,null);
它的三個(gè)參數(shù)如下:
- request : 在不斷重復(fù)執(zhí)行 Capture 時(shí)使用的 CaptureRequest 對(duì)象
- callback :監(jiān)聽(tīng)每一次 Capture 狀態(tài)的 CameraCaptureSession.CaptureCallback 對(duì)象,例如 onCaptureStarted() 意味著一次 Capture 的開始扁掸,而 onCaptureCompleted() 意味著一次 Capture 的結(jié)束翘县。
- handler :用于執(zhí)行 CameraCaptureSession.CaptureCallback 的Handler 對(duì)象,傳null為主線程谴分,也可以使用其他線程的 Handler
關(guān)閉預(yù)覽
通過(guò)上面的理解锈麸,關(guān)閉預(yù)覽也很簡(jiǎn)單啦:
//停止預(yù)覽
mCameraCaptureSession.stopRepeating();
三. 拍照
上面我們學(xué)習(xí)了預(yù)覽,也提到了 ImageReader 是用來(lái)接收?qǐng)D像數(shù)據(jù)的牺蹄,那怎么拍照呢忘伞?
拍照其實(shí)也是一個(gè) Captrue,這樣的話沙兰,我們就可以再創(chuàng)建一個(gè) CaptureRequest 去執(zhí)行拍照的就可以了氓奈,代碼如下:
//創(chuàng)建一個(gè)拍照的 session
final CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
//設(shè)置裝在圖像數(shù)據(jù)的 Surface
captureRequest.addTarget(mImageReader.getSurface());
//聚焦
captureRequest.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//自動(dòng)曝光
captureRequest.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 獲取設(shè)備方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
// 根據(jù)設(shè)備方向計(jì)算設(shè)置照片的方向
captureRequest.set(CaptureRequest.JPEG_ORIENTATION
, getOrientation(rotation));
// 先停止預(yù)覽
mCameraCaptureSession.stopRepeating();
代碼比較好理解,只是通過(guò) mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) 創(chuàng)建了一個(gè) 拍照的模板
而且在執(zhí)行拍照之前鼎天,先停止預(yù)覽:
mCameraCaptureSession.stopRepeating();
接著就可以使用 mCameraCaptureSession.capture() 執(zhí)行拍照了:
mCameraCaptureSession.capture(captureRequest.build(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
try {
//拍完之后舀奶,讓它繼續(xù)可以預(yù)覽
CaptureRequest.Builder captureRequest1 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest1.addTarget(new Surface(mTextureView.getSurfaceTexture()));
mCameraCaptureSession.setRepeatingRequest(captureRequest1.build(),null,null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
},null);
可以看到,當(dāng)拍照結(jié)束的時(shí)候斋射,我們又讓它重新預(yù)覽了育勺,當(dāng)然這里也看你的需求去設(shè)置。
3.1 保存圖片
那保存圖片在哪里弄呢罗岖?還記得我們?cè)诖蜷_攝像頭的時(shí)候怀大,配置了 ImageReader 監(jiān)聽(tīng)嗎:
//拍照監(jiān)聽(tīng)
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);
在保存之前,我們先來(lái)了解一下呀闻,什么是 ImageReader
3.2 ImageReader
在 Camrea2 中化借,Imageread 是獲取圖像數(shù)據(jù)的一個(gè)重要途徑,我們可以通過(guò)它獲取各種各樣格式的圖像數(shù)據(jù)捡多,比如 JPEG蓖康、YUV和 RAW 等铐炫。通過(guò) ImageReader.newInstance() 方法創(chuàng)建 ImageReader 對(duì)象,如下:
//設(shè)置imagereader蒜焊,配置大小倒信,且最大Image為 1,因?yàn)槭?JPEG
mImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),
ImageFormat.JPEG,1);
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);
其中前面兩個(gè)號(hào)理解泳梆,第三個(gè)參數(shù)鳖悠,則是你要獲取的圖像數(shù)據(jù)的格式,這里使用 JPEG 即可优妙,而最后一個(gè)參數(shù)乘综,則表示最大 Image 的個(gè)數(shù),可以理解成 圖像池的大小套硼。
當(dāng)有圖像數(shù)據(jù)生成時(shí)卡辰,就是調(diào)用 ImageReader.OnImageAvailableListener 里面的 onImageAvailable() 方法
/**
* 拍照監(jiān)聽(tīng),當(dāng)有圖片數(shù)據(jù)時(shí),回調(diào)該接口
*/
class ImageAvailable implements ImageReader.OnImageAvailableListener{
@Override
public void onImageAvailable(ImageReader reader) {
new SavePicAsyncTask(reader).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
接著邪意,我們可以調(diào)用 ImageReader 的acquireNextImage()方法九妈,來(lái)獲取存有最新的 Image 對(duì)象,而 Image 對(duì)象里的圖像數(shù)據(jù)又根據(jù)不同格式被劃分成多個(gè)部分雾鬼,分別存儲(chǔ)在單獨(dú)的 Plane 對(duì)象里萌朱,我們可以調(diào)用 Image.getPalnes() 獲取所有有的 Palne 對(duì)象的數(shù)組,如下
//獲取捕獲的照片數(shù)據(jù)
Image image = imageReader.acquireLatestImage();
//拿到所有的 Plane 數(shù)組
Image.Plane[] planes = image.getPlanes();
最后則通過(guò) Plane.getBuffer() 獲取每一個(gè)在 Plane 里存儲(chǔ)的圖像數(shù)據(jù) ByteBuffer策菜。比如
格式 | Image個(gè)數(shù) | Plane 層級(jí) |
---|---|---|
JPEG | 1 | 壓縮過(guò)的數(shù)據(jù)晶疼,所以行數(shù)為0,解壓縮需要使用BitmapFactory#decodeByteArray |
YUV | 3 | 一個(gè)明度通道+兩個(gè)色彩CbCr通道做入,UV的寬高是Y的一半冒晰。 |
YUV 可以用下圖來(lái)表示(圖片來(lái)源):
所以,我們獲取的圖片如下:
FileOutputStream fos = null;
Image image = null;
try {
fos = new FileOutputStream(file);
//獲取捕獲的照片數(shù)據(jù)
image = imageReader.acquireLatestImage();
//拿到所有的 Plane 數(shù)組
Image.Plane[] planes = image.getPlanes();
//由于是 JPEG 竟块,只需要獲取下標(biāo)為 0 的數(shù)據(jù)即可
ByteBuffer buffer = planes[0].getBuffer();
data = new byte[buffer.remaining()];
//把 bytebuffer 的數(shù)據(jù)給 byte數(shù)組
buffer.get(data);
Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
//旋轉(zhuǎn)圖片
if (mCameraId.equals(mFrontCameraId)){
bitmap = BitmapUtils.rotate(bitmap,270);
bitmap = BitmapUtils.mirror(bitmap);
}else{
bitmap = BitmapUtils.rotate(bitmap,90);
}
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
fos.flush();
return bitmap;
}catch (Exception e){
Log.d(TAG, "zsr doInBackground: "+e.toString());
}finally {
CloseUtils.close(fos);
//記得關(guān)閉 image
if (image != null) {
image.close();
}
}
這樣壶运,我們就寫好了,下一章浪秘,我們學(xué)習(xí) CameraX蒋情。
參考:
https://developer.android.google.cn/reference/android/hardware/camera2/package-summary?hl=en
http://www.reibang.com/p/9a2e66916fcb
http://www.reibang.com/p/23e8789fbc10
http://www.reibang.com/p/067889611ae7