HarmonyOS之深入解析相機的功能和使用
HarmonyOS 相機模塊支持相機業務的開發,開發者可以通過已開放的接口實現相機硬件的訪問、操作和新功能開發,最常見的操作如:預覽、拍照、連拍和錄像等。
相機靜態能力:用于描述相機的固有能力的一系列參數,比如朝向、支持的分辨率等信息。
物理相機:物理相機就是獨立的實體攝像頭設備。物理相機ID是用于標志每個物理攝像頭的唯一字串。
邏輯相機:邏輯相機是多個物理相機組合出來的抽象設備,邏輯相機通過同時控制多個物理相機設備來完成相機某些功能,如大光圈、變焦等功能。邏輯攝像機ID是一個唯一的字符串,標識多個物理攝像機的抽象能力。
幀捕獲:相機啟動后對幀的捕獲動作統稱為幀捕獲。主要包含單幀捕獲、多幀捕獲、循環幀捕獲。
單幀捕獲:指的是相機啟動后,在幀數據流中捕獲一幀數據,常用于普通拍照。
多幀捕獲:指的是相機啟動后,在幀數據流中連續捕獲多幀數據,常用于連拍。
循環幀捕獲:指的是相機啟動后,在幀數據流中一直捕獲幀數據,常用于預覽和錄像。
在同一時刻只能有一個相機應用在運行中。
相機模塊內部有狀態控制,開發者必須按照指導文檔中的流程進行接口的順序調用,否則可能會出現調用失敗等問題。
為了開發的相機應用擁有更好的兼容性,在創建相機對象或者參數相關設置前請務必進行能力查詢。
相機模塊主要工作是給相機應用開發者提供基本的相機 API 接口,用于使用相機系統的功能,進行相機硬件的訪問、操作和新功能開發。
相機的開發流程如圖所示:
相機模塊為相機應用開發者提供了3個包的內容,包括方法、枚舉、以及常量/變量,方便開發者更容易地實現相機功能。
相機 API 如下所示:
HarmonyOS 根據接口所涉數據的敏感程度或所涉能力的安全威脅影響,定義了不同開放范圍與授權方式的權限來保護數據。
當前權限的開放范圍分為:
all:所有應用可用
signature:平臺簽名應用可用
privileged:預制特權應用可用
restricted:證書可控應用可用
應用在使用對應服務的能力或數據時,需要申請對應權限:
已在 config.json 文件中聲明的非敏感權限,會在應用安裝時自動授予,該類權限的授權方式為系統授權(system_grant)。
敏感權限需要應用動態申請,通過運行時發送彈窗的方式請求用戶授權,該類權限的授權方式為用戶授權(user_grant)。
當應用調用服務時,服務會對應用進行權限檢查,如果沒有對應權限則無法使用該服務。
敏感權限的申請需要按照動態申請流程向用戶申請授權,敏感權限說明如下:
非敏感權限不涉及用戶的敏感數據或危險操作,僅需在 config.json 中聲明,應用安裝后即被授權。
受限開放的權限通常是不允許三方應用申請的。如果有特殊場景需要使用,請提供相關申請材料到應用市場申請相應權限證書。如果應用未申請相應的權限證書,卻試圖在 config.json 文件中聲明此類權限,將會導致應用安裝失敗。另外,由于此類權限涉及到用戶敏感數據或危險操作,當應用申請到權限證書后,還需按照動態申請權限的流程向用戶申請授權。受限開放權限說明如下:
在使用相機之前,需要申請相機的相關權限,保證應用擁有相機硬件及其他功能權限。相機權限列表:
CameraKit 類是相機的入口 API 類,用于獲取相機設備特性、打開相機,其接口如下表:
在實現一個相機應用之前必須先創建一個獨立的相機設備,然后才能繼續相機的其他操作。相機設備創建的建議步驟如下:
通過 CameraKit.getInstance(Context context) 方法獲取唯一的 CameraKit 對象(如果此步驟操作失敗,相機可能被占用或無法使用,如果被占用,必須等到相機釋放后才能重新獲取 CameraKit 對象):
private void openCamera() { // 獲取CameraKit對象 CameraKit cameraKit = CameraKit.getInstance(context); if (cameraKit == null) { // 處理cameraKit獲取失敗的情況 } }
1
2
3
4
5
6
7
通過 getCameraIds() 方法,獲取當前使用的設備支持的邏輯相機列表。邏輯相機列表中存儲了當前設備擁有的所有邏輯相機 ID,如果列表不為空,則列表中的每個 ID 都支持獨立創建相機對象;否則,說明正在使用的設備無可用的相機,不能繼續后續的操作:
try { // 獲取當前設備的邏輯相機列表 String[] cameraIds = cameraKit.getCameraIds(); if (cameraIds.length <= 0) { HiLog.error(LABEL, "cameraIds size is 0"); } } catch (IllegalStateException e) { // 處理異常 }
1
2
3
4
5
6
7
8
9
還可以繼續查詢指定相機 ID 的靜態信息:
調用 getDeviceLinkType (String physicalId) 方法獲取物理相機連接方式;
調用 getCameraInfo(String cameraId) 方法查詢相機硬件朝向等信息;
調用 getCameraAbility(String cameraId) 方法查詢相機能力信息(比如支持的分辨率列表等)。
CameraInfo 的主要接口:
CameraAbility 的主要接口:
通過 createCamera(String cameraId, CameraStateCallback callback, EventHandler handler) 方法,創建相機對象,此步驟執行成功意味著相機系統的硬件已經完成了上電:
第一個參數 cameraId 可以是上一步獲取的邏輯相機列表中的任何一個相機 ID。
第二和第三個參數負責相機創建和相機運行時的數據和狀態檢測,請務必保證在整個相機運行周期內有效。
// 創建相機設備 cameraKit.createCamera(cameraIds[0], cameraStateCallback, eventHandler);
1
2
private final class CameraStateCallbackImpl extends CameraStateCallback { @Override public void onCreated(Camera camera) { // 創建相機設備 } @Override public void onConfigured(Camera camera) { // 配置相機設備 } @Override public void onPartialConfigured(Camera camera) { // 當使用了addDeferredSurfaceSize配置了相機,會接到此回調 } @Override public void onReleased(Camera camera) { // 釋放相機設備 } } // 相機創建和相機運行時的回調 CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import ohos.eventhandler.EventHandler; import ohos.eventhandler.EventRunner; // 執行回調的EventHandler EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb"));
1
2
3
4
5
至此,相機設備的創建已經完成。相機設備創建成功會在 CameraStateCallback 中觸發 onCreated(Camera camera) 回調。在進入相機設備配置前,請確保相機設備已經創建成功。否則會觸發相機設備創建失敗的回調,并返回錯誤碼,需要進行錯誤處理后,重新執行相機設備的創建。
創建相機設備成功后,在 CameraStateCallback 中會觸發 onCreated(Camera camera) 回調,并且帶回 Camera 對象,用于執行相機設備的操作。
當一個新的相機設備成功創建后,首先需要對相機進行配置,調用 configure(CameraConfig) 方法實現配置。相機配置主要是設置預覽、拍照、錄像用到的 Surface(詳見 ohos.agp.graphics.Surface),沒有配置過 Surface,相應的功能不能使用。
為了進行相機幀捕獲結果的數據和狀態檢測,還需要在相機配置時調用 setFrameStateCallback(FrameStateCallback, EventHandler) 方法設置幀回調。
private void initSurface() { surfaceProvider = new SurfaceProvider(this); DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig( ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT); surfaceProvider.setLayoutConfig(params); surfaceProvider.pinToZTop(false); surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack()); ((ComponentContainer) findComponentById(ResourceTable.Id_surface_container)).addComponent(surfaceProvider); } private FrameStateCallback frameStateCallbackImpl = new FrameStateCallback(){ @Override public void onFrameStarted(Camera camera, FrameConfig frameConfig, long frameNumber, long timestamp) { ... } @Override public void onFrameProgressed(Camera camera, FrameConfig frameConfig, FrameResult frameResult) { ... } @Override public void onFrameFinished(Camera camera, FrameConfig frameConfig, FrameResult frameResult) { ... } @Override public void onFrameError(Camera camera, FrameConfig frameConfig, int errorCode, FrameResult frameResult) { ... } @Override public void onCaptureTriggerStarted(Camera camera, int captureTriggerId, long firstFrameNumber) { ... } @Override public void onCaptureTriggerFinished(Camera camera, int captureTriggerId, long lastFrameNumber) { ... } @Override public void onCaptureTriggerInterrupted(Camera camera, int captureTriggerId) { ... } }; private final class CameraStateCallbackImpl extends CameraStateCallback { @Override public void onCreated(Camera camera) { cameraDevice = camera; previewSurface = surfaceProvider.getSurfaceOps().get().getSurface(); cameraConfigBuilder = camera.getCameraConfigBuilder(); if (cameraConfigBuilder == null) { HiLog.error(LABEL, "onCreated cameraConfigBuilder is null"); return; } // 配置預覽的Surface cameraConfigBuilder.addSurface(previewSurface); // 配置拍照的Surface cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface()); // 配置幀結果的回調 cameraConfigBuilder.setFrameStateCallback(frameStateCallbackImpl, handler); try { // 相機設備配置 camera.configure(cameraConfigBuilder.build()); } catch (IllegalArgumentException e) { HiLog.error(LABEL, "Argument Exception"); } catch (IllegalStateException e) { HiLog.error(LABEL, "State Exception"); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
相機配置成功后,在 CameraStateCallback 中會觸發 onConfigured(Camera camera) 回調,然后才可以執行相機幀捕獲相關的操作。
CameraConfig.Builder 的主要接口:
Camera 操作類,包括相機預覽、錄像、拍照等功能接口。Camera 的主要接口如下:
用戶一般都是先看見預覽畫面才執行拍照或者其他功能,所以對于一個普通的相機應用,預覽是必不可少的。
啟動預覽的建議步驟如下:
通過 getFrameConfigBuilder(FRAME_CONFIG_PREVIEW) 方法獲取預覽配置模板,常用幀配置項見下表:
通過 triggerLoopingCapture(FrameConfig) 方法實現循環幀捕獲(如預覽/錄像):
private final class CameraStateCallbackImpl extends CameraStateCallback { @Override public void onConfigured(Camera camera) { // 獲取預覽配置模板 frameConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW); // 配置預覽Surface frameConfigBuilder.addSurface(previewSurface); previewFrameConfig = frameConfigBuilder.build(); try { // 啟動循環幀捕獲 int triggerId = camera.triggerLoopingCapture(previewFrameConfig); } catch (IllegalArgumentException e) { HiLog.error(LABEL, "Argument Exception"); } catch (IllegalStateException e) { HiLog.error(LABEL, "State Exception"); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
經過以上的操作,相機應用已經可以正常進行實時預覽了。在預覽狀態下,開發者還可以執行其他操作,比如:當預覽幀配置更改時,可以通過 triggerLoopingCapture(FrameConfig) 方法實現預覽幀配置的更新;
// 預覽幀變焦值變更 frameConfigBuilder.setZoom(1.2f); // 調用triggerLoopingCapture方法實現預覽幀配置更新 triggerLoopingCapture(frameConfigBuilder.build());
1
2
3
4
通過 stopLoopingCapture() 方法停止循環幀捕獲(停止預覽):
// 停止預覽幀捕獲 camera.stopLoopingCapture()
1
2
拍照功能屬于相機應用的最重要功能之一,而且照片質量對用戶至關重要。相機模塊基于相機復雜的邏輯,從應用接口層到器件驅動層都已經默認的做好了最適合用戶的配置,這些默認配置盡可能地保證用戶拍出的每張照片的質量。
發起拍照的建議步驟如下:
通過 getFrameConfigBuilder(FRAME_CONFIG_PICTURE) 方法獲取拍照配置模板,并且設置拍照幀配置,如下表:
拍照前準備圖像幀數據的接收實現:
// 圖像幀數據接收處理對象 private ImageReceiver imageReceiver; // 執行回調的EventHandler private EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb")); // 拍照支持分辨率 private Size pictureSize; // 單幀捕獲生成圖像回調Listener private final ImageReceiver.IImageArrivalListener imageArrivalListener = new ImageReceiver.IImageArrivalListener() { @Override public void onImageArrival(ImageReceiver imageReceiver) { StringBuffer fileName = new StringBuffer("picture_"); fileName.append(UUID.randomUUID()).append(".jpg"); // 定義生成圖片文件名 File myFile = new File(dirFile, fileName.toString()); // 創建圖片文件 imageSaver = new ImageSaver(imageReceiver.readNextImage(), myFile); // 創建一個讀寫線程任務用于保存圖片 eventHandler.postTask(imageSaver); // 執行讀寫線程任務生成圖片 } }; // 保存圖片, 圖片數據讀寫,及圖像生成見run方法 class ImageSaver implements Runnable { private final Image myImage; private final File myFile; ImageSaver(Image image, File file) { myImage = image; myFile = file; } @Override public void run() { Image.Component component = myImage.getComponent(ImageFormat.ComponentType.JPEG); byte[] bytes = new byte[component.remaining()]; component.read(bytes); FileOutputStream output = null; try { output = new FileOutputStream(myFile); output.write(bytes); // 寫圖像數據 } catch (IOException e) { HiLog.error(LABEL, "save picture occur exception!"); } finally { if (output != null) { try { output.close(); // 關閉流 } catch (IOException e) { HiLog.error(LABEL, "image release occur exception!"); } } myImage.release(); } } } private void takePictureInit() { List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
通過 triggerSingleCapture(FrameConfig) 方法實現單幀捕獲(如拍照):
private void capture() { // 獲取拍照配置模板 framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE); // 配置拍照Surface framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface()); // 配置拍照其他參數 framePictureConfigBuilder.setImageRotation(90); try { // 啟動單幀捕獲(拍照) cameraDevice.triggerSingleCapture(framePictureConfigBuilder.build()); } catch (IllegalArgumentException e) { HiLog.error(LABEL, "Argument Exception"); } catch (IllegalStateException e) { HiLog.error(LABEL, "State Exception"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
為了捕獲到質量更高和效果更好的圖片,還可以在幀結果中實時監測自動對焦和自動曝光的狀態,一般而言,在自動對焦完成,自動曝光收斂后的瞬間是發起單幀捕獲的最佳時機。
連拍功能方便用戶一次拍照獲取多張照片,用于捕捉精彩瞬間。
同普通拍照的實現流程一致,但連拍需要使用 triggerMultiCapture(List frameConfigs) 方法。
啟動錄像和啟動預覽類似,但需要另外配置錄像 Surface 才能使用。
錄像前需要進行音視頻模塊的配置:
private Source source; // 音視頻源 private AudioProperty.Builder audioPropertyBuilder; // 音頻屬性構造器 private VideoProperty.Builder videoPropertyBuilder; // 視頻屬性構造器 private StorageProperty.Builder storagePropertyBuilder; // 音視頻存儲屬性構造器 private Recorder mediaRecorder; // 錄像操作對象 private String recordName; // 音視頻文件名 private void initMediaRecorder() { videoPropertyBuilder.setRecorderBitRate(10000000); // 設置錄制比特率 int rotation = DisplayManager.getInstance().getDefaultDisplay(this).get().getRotation(); videoPropertyBuilder.setRecorderDegrees(getOrientation(rotation)); // 設置錄像方向 videoPropertyBuilder.setRecorderFps(30); // 設置錄制采樣率 videoPropertyBuilder.setRecorderHeight(Math.min(recordSize.height, recordSize.width)); // 設置錄像支持的分辨率,需保證width > height videoPropertyBuilder.setRecorderWidth(Math.max(recordSize.height, recordSize.width)); videoPropertyBuilder.setRecorderVideoEncoder(Recorder.VideoEncoder.H264); // 設置視頻編碼方式 videoPropertyBuilder.setRecorderRate(30); // 設置錄制幀率 source.setRecorderAudioSource(Recorder.AudioSource.MIC); // 設置錄制音頻源 source.setRecorderVideoSource(Recorder.VideoSource.SURFACE); // 設置視頻窗口 mediaRecorder.setSource(source); // 設置音視頻源 mediaRecorder.setOutputFormat(Recorder.OutputFormat.MPEG_4); // 設置音視頻輸出格式 StringBuffer fileName = new StringBuffer("record_"); // 生成隨機文件名 fileName.append(UUID.randomUUID()).append(".mp4"); recordName = fileName.toString(); File file = new File(dirFile, recordName); // 創建錄像文件對象 storagePropertyBuilder.setRecorderFile(file); // 設置存儲音視頻文件名 mediaRecorder.setStorageProperty(storagePropertyBuilder.build()); audioPropertyBuilder.setRecorderAudioEncoder(Recorder.AudioEncoder.AAC); // 設置音頻編碼格式 mediaRecorder.setAudioProperty(audioPropertyBuilder.build()); // 設置音頻屬性 mediaRecorder.setVideoProperty(videoPropertyBuilder.build()); // 設置視頻屬性 mediaRecorder.prepare(); // 準備錄制 HiLog.info(LABEL, "initMediaRecorder end"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
配置錄像幀,啟動錄像:
private final class CameraStateCallbackImpl extends CameraStateCallback { @Override public void onConfigured(Camera camera) { // 獲取錄像配置模板 frameConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_RECORD); // 配置預覽Surface frameConfigBuilder.addSurface(previewSurface); // 配置錄像的Surface mRecorderSurface = mediaRecorder.getVideoSurface(); frameConfigBuilder.addSurface(mRecorderSurface); previewFrameConfig = frameConfigBuilder.build(); try { // 啟動循環幀捕獲 int triggerId = camera.triggerLoopingCapture(previewFrameConfig); } catch (IllegalArgumentException e) { HiLog.error(LABEL, "Argument Exception"); } catch (IllegalStateException e) { HiLog.error(LABEL, "State Exception"); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
通過 camera.stopLoopingCapture() 方法停止循環幀捕獲(錄像)。
使用完相機后,必須通過 release() 來關閉相機和釋放資源,否則可能導致其他相機應用無法啟動。一旦相機被釋放,它所提供的操作就不能再被調用,否則會導致不可預期的結果,或是會引發狀態異常。
相機設備釋放的示例代碼如下:
private void releaseCamera() { if (camera != null) { // 關閉相機和釋放資源 camera.release(); camera = null; } // 拍照配置模板置空 framePictureConfigBuilder = null; // 預覽配置模板置空 previewFrameConfig = null; }
1
2
3
4
5
6
7
8
9
10
11
HarmonyOS之演示如何通過相機模塊相關接口實現拍照、錄像等功能。
API 開發者
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。