小白也能開發相機?Sample來教你
上期我們給大家介紹了HarmonyOS Sample,收到了不少小伙伴的反饋,想學習一下HarmonyOS相機開發,現在,他來了!
相機開發概覽
相機是智能設備最重要的功能之一,它能捕捉美好的瞬間、記錄關鍵的時刻,被廣泛應用于日常生活中。本文將詳細地講解一個帶有拍攝照片和錄制視頻功能的相機開發過程。

目前,HarmonyOS相機模塊支持相機業務的開發,開發者可以通過已開放的接口實現相機硬件的訪問、操作和新功能開發。如下圖所示,是HarmonyOS為相機應用開發者提供的3個包的內容,包括方法、枚舉、以及常量/變量,方便開發者更容易地實現相機功能:

相機的開發流程如圖所示:

相機權限申請
在使用相機之前,需要申請相機的相關權限,比如獲取設備的相機權限、麥克風權限、存儲權限等。保證應用擁有相機硬件及其他功能權限。
開發者需在config.json中配置相關的權限,如下所示:
- "reqPermissions": [
- {
- "name": "ohos.permission.CAMERA"
- },
- {
- "name": "ohos.permission.WRITE_USER_STORAGE"
- },
- {
- "name": "ohos.permission.READ_USER_STORAGE"
- },
- {
- "name": "ohos.permission.MICROPHONE"
- },
- {
- "name": "ohos.permission.LOCATION"
- }
- ]
獲取相關權限的具體代碼如下所示:
- private void requestPermissions() {
- String[] permissions = {
- //允許應用程序創建或刪除文件,或將數據寫入設備存儲中的文件。
- SystemPermission.WRITE_USER_STORAGE,
- //允許應用程序從設備存儲中讀取文件。
- SystemPermission.READ_USER_STORAGE,
- //允許應用程序使用相機。
- SystemPermission.CAMERA,
- //允許應用程序訪問麥克風。
- SystemPermission.MICROPHONE,
- //允許應用程序獲取設備位置。
- SystemPermission.LOCATION
- };
- List<String> permissionFiltered = Arrays.stream(permissions)
- .filter(permission -> verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED)
- .collect(Collectors.toList());
- requestPermissionsFromUser(permissionFiltered.toArray(new String[permissionFiltered.size()]), 0);
初始化相機界面
獲取到設備權限后,需要初始化相機界面。通過getWindow()獲取當前Ability對應的窗口,再通過addCallback()添加界面操作的回調。代碼如下所示:
- private void initSurface() {
- //獲取當前Ability對應的窗口,設置是否啟用透明度。isEnable -指定是否啟用透明度。True表示啟用透明度,False表示禁用。
- getWindow().setTransparent(true);
- DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig(
- ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);
- surfaceProvider = new SurfaceProvider(this);
- surfaceProvider.setLayoutConfig(params);
- /**
- * 設置是否將此SurfaceProvider的Surface放置在AGP容器組件的頂層。
- * 參數說明:
- * toTop -指定是否將相機界面固定在頂部。值true表示將Surface放在AGP容器組件的頂層,而值false表示相反。*/
- surfaceProvider.pinToZTop(false);
- //通過addCallback()添加界面操作的回調
- surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
- surfaceContainer.addComponent(surfaceProvider);
- }
通過postTask()來執行相機任務:
- private class SurfaceCallBack implements SurfaceOps.Callback {
- @Override
- public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
- if (callbackSurfaceOps != null) {
- //setFixedSize:設置界面的固定大小。參數說明:寬度-指示界面的寬度。高度-指示界面的高度。
- callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT,SCREEN_WIDTH);
- }
- //設置延時200ms的postTask,用于相機界面的顯示
- eventHandler.postTask(new Runnable() {
- @Override
- public void run() {
- openCamera();
- }
- },200);
- }
相機設備創建
1.創建相機對象
相機界面準備好后,我們需要創建相機設備。在實現一個相機應用之前必須先創建一個獨立的相機設備,然后才能繼續相機的其他操作。CameraKit類是相機的入口API類,如下所示:

相機設備創建的代碼如下:
- private void openCamera() {
- //ImageReceiver:連接到圖像輸出設備的圖像接收器,并提供一個緩沖隊列來接收圖像數據
- //create:根據指定的圖像寬度、高度、格式和緩沖隊列容量創建ImageReceiver實例
- imageReceiver = ImageReceiver.create(SCREEN_WIDTH, SCREEN_HEIGHT, ImageFormat.JPEG, IMAGE_RCV_CAPACITY);
- //設置接收到新圖像數據時將調用的圖像偵聽器。
- imageReceiver.setImageArrivalListener(this::saveImage);
- //獲取CameraKit實例
- CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
- //獲取當前使用的設備支持的邏輯相機列表
- String[] cameraList = cameraKit.getCameraIds();
- String cameraId = "";
- //獲取當前邏輯相機的信息
- for (String logicalCameraId : cameraList) {
- int faceType = cameraKit.getCameraInfo(logicalCameraId).getFacingType();
- switch (faceType){
- case CameraInfo.FacingType.CAMERA_FACING_FRONT:
- if (isFrontCamera) {
- cameraId = logicalCameraId;
- }
- break;
- case CameraInfo.FacingType.CAMERA_FACING_BACK:
- if (!isFrontCamera) {
- cameraId = logicalCameraId;
- }
- break;
- case CameraInfo.FacingType.CAMERA_FACING_OTHERS:
- default:
- break;
- }
- }
- if (cameraId != null && !cameraId.isEmpty()) {
- CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
- /**createCamera()用于創建相機對象
- * 該方法的第一個參數代表要打開的攝像頭ID;
- * 第二個參數用于監聽攝像頭的狀態;
- * 第三個參數代表執行callback的Handler,如果程序希望直接在當前線程中執行callback,則可將handler參數設為null。*/
- cameraKit.createCamera(cameraId, cameraStateCallback, eventHandler);
- }
- }
創建相機設備成功后,在CameraStateCallback中會觸發onCreated(Camera camera)回調,并且帶回Camera對象,用于執行相機設備的操作。
- @Override
- public void onCreated(Camera camera) {
- previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
- if (previewSurface == null) {
- HiLog.error(LABEL_LOG, "%{public}s", "Create camera filed, preview surface is null");
- return;
- }
- //獲取相機Config.Builder實例。
- CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
- //addSurface:添加界面作為相機流的輸出。
- cameraConfigBuilder.addSurface(previewSurface);
- cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
- camera.configure(cameraConfigBuilder.build());
- cameraDevice = camera;
- updateComponentVisible(true);
- }
2.配置預覽
在使用相機的過程中,用戶一般都是先看見預覽畫面才執行拍照或者其他功能,所以對于一個普通的相機應用,預覽是必不可少的。在上述CameraStateCallback中,會調用configure()方法實現預覽配置,通過triggerLoopingCapture()方法實現循環幀捕獲,從而達到預覽的目的。具體代碼如下:
- @Override
- public void onConfigured(Camera camera) {
- FrameConfig.Builder framePreviewConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
- framePreviewConfigBuilder.addSurface(previewSurface);
- //triggerLoopingCapture:開始循環幀捕獲。循環幀捕獲通常用于預覽或錄制
- camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
- }
相機功能實現
相機的基本功能主要分為拍攝照片和錄制視頻,目前HarmonyOS為開發者提供了如下相機拍照功能實現的Camera操作類,開發者可以通過這些方法,實現各種相機應用的開發:

如下圖所示是相機的使用過程,接下來的相機功能實現,也會根據此流程圖來實現。

(1)選擇功能
通過初始化相機界面組件,設置點擊事件偵聽器來實現相機功能選擇。代碼如下所示:
- private void initComponents() {
- Component takePhoto = findComponentById(ResourceTable.Id_take_photo);
- Component videoRecord = findComponentById(ResourceTable.Id_video_record);
- //設置點擊事件偵聽器,用于拍攝照片
- takePhoto.setClickedListener((component) -> startAbility(TakePhotoAbility.class.getName()));
- //設置點擊事件偵聽器,用于錄制視頻
- videoRecord.setClickedListener((component) -> startAbility(VideoRecordAbility.class.getName()));
- }
(2)切換攝像頭
開始拍攝照片或錄制視頻時,由于相機默認打開后置攝像頭,需根據場景切換前置攝像頭或后置攝像頭。如果檢測到相機正在工作中,將執行release()方法釋放當前相機設備。代碼如下:
- Image takePhotoImage = (Image) findComponentById(ResourceTable.Id_tack_picture_btn);
- //setClickedListener:為組件中的單擊事件注冊偵聽器。
- takePhotoImage.setClickedListener(this::takeSingleCapture);
- private void takeSingleCapture(Component component) {
- if (cameraDevice == null || imageReceiver == null) {
- return;
- }
- //用于配置幀捕獲、圖像處理和圖像輸出的接口
- FrameConfig.Builder framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
- framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
- FrameConfig pictureFrameConfig = framePictureConfigBuilder.build();
- //triggerSingleCapture():開始單幀捕獲。這種方法通常用于拍照。
- cameraDevice.triggerSingleCapture(pictureFrameConfig);
- }
(3)拍攝照片
拍照功能屬于相機應用的最重要功能之一,而且照片質量對用戶至關重要。相機模塊基于相機復雜的邏輯,從應用接口層到器件驅動層都已經默認的做好了最適合用戶的配置,這些默認配置盡可能地保證用戶拍出的每張照片的質量。
實現單幀拍照
單幀拍照,其實就是單幀捕獲的過程。通過設置點擊事件偵聽器setClickedListener(),來觸發takeSingleCapture()方法,實現單幀捕獲。具體代碼如下:
- Image takePhotoImage = (Image) findComponentById(ResourceTable.Id_tack_picture_btn);
- //setClickedListener:為組件中的單擊事件注冊偵聽器。
- takePhotoImage.setClickedListener(this::takeSingleCapture);
- private void takeSingleCapture(Component component) {
- if (cameraDevice == null || imageReceiver == null) {
- return;
- }
- //用于配置幀捕獲、圖像處理和圖像輸出的接口
- FrameConfig.Builder framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
- framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
- FrameConfig pictureFrameConfig = framePictureConfigBuilder.build();
- //triggerSingleCapture():開始單幀捕獲。這種方法通常用于拍照。
- cameraDevice.triggerSingleCapture(pictureFrameConfig);
- }
實現連拍
連拍功能方便用戶一次拍照獲取多張照片,用于捕捉精彩瞬間。
同單幀拍照的實現流程一致,但連拍需要使用triggerMultiCapture(frameConfigs)方法用于多幀捕獲。
- private void takeMultiCapture(Component component) {
- FrameConfig.Builder framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
- framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
- List<FrameConfig> frameConfigs = new ArrayList<>();
- FrameConfig firstFrameConfig = framePictureConfigBuilder.build();
- frameConfigs.add(firstFrameConfig);
- FrameConfig secondFrameConfig = framePictureConfigBuilder.build();
- frameConfigs.add(secondFrameConfig);
- /**triggerMultiCapture(frameConfigs):啟動多幀捕獲。
- cameraDevice.triggerMultiCapture(frameConfigs);
- }
存儲照片
拍攝后的照片通過saveImage()實現照片存儲。具體代碼如下:
- /**
- * 存儲照片*/
- private void saveImage(ImageReceiver receiver) {
- //getFilesDir():獲取應用程序在設備內部存儲上的文件存儲目錄。
- File saveFile = new File(getFilesDir(), "IMG_" + System.currentTimeMillis() + ".jpg");
- ohos.media.image.Image image = receiver.readNextImage();
- //定義圖像格式,提供獲取圖像格式信息的接口。
- ohos.media.image.Image.Component component = image.getComponent(ImageFormat.ComponentType.JPEG);
- byte[] bytes = new byte[component.remaining()];
- component.read(bytes);
- try (FileOutputStream output = new FileOutputStream(saveFile)) {
- output.write(bytes);
- output.flush();
- showTips(this, "Take photo succeed");
- } catch (IOException e) {
- HiLog.error(LABEL_LOG, "%{public}s", "saveImage IOException");
- }
- }
(4)錄制視頻
配置音視頻模塊
錄制視頻除了要進行預覽配置,還需要進行音視頻模塊的配置。
比如視頻編碼格式配置:
- setRecorderVideoEncoder(Recorder.VideoEncoder.H264)
音頻編碼格式配置:
- setRecorderAudioEncoder(Recorder.AudioEncoder.AAC)
以及視頻的編碼碼率、幀捕獲率、幀率配置等。代碼如下所示:
- private void initMediaRecorder() {
- mediaRecorder = new Recorder();
- VideoProperty.Builder videoPropertyBuilder = new VideoProperty.Builder();
- videoPropertyBuilder.setRecorderBitRate(10000000);//setRecorderBitRate:設置視頻編碼碼率。
- videoPropertyBuilder.setRecorderDegrees(90); //setRecorderDegrees:設置視頻旋轉角度。
- videoPropertyBuilder.setRecorderFps(30);//setRecorderFps:設置視頻幀捕獲速率。
- videoPropertyBuilder.setRecorderHeight(Math.min(1440, 720));//設置視頻高度。
- videoPropertyBuilder.setRecorderWidth(Math.max(1440, 720));//設置視頻寬度。
- videoPropertyBuilder.setRecorderVideoEncoder(Recorder.VideoEncoder.H264);//setRecorderVideoEncoder:設置視頻編碼格式。
- videoPropertyBuilder.setRecorderRate(30);//setRecorderRate:設置視頻幀率。
- Source source = new Source();
- //表示使用麥克風作為音頻源。
- source.setRecorderAudioSource(Recorder.AudioSource.MIC);
- //表示使用相機界面作為視頻源。
- source.setRecorderVideoSource(Recorder.VideoSource.SURFACE);
- mediaRecorder.setSource(source);
- //setOutputFormat:設置輸出文件格式。
- mediaRecorder.setOutputFormat(Recorder.OutputFormat.MPEG_4);
- //getFilesDir():獲取文件存儲目錄。
- File file = new File(getFilesDir(), "VID_" + System.currentTimeMillis() + ".mp4");
- StorageProperty.Builder storagePropertyBuilder = new StorageProperty.Builder();
- storagePropertyBuilder.setRecorderFile(file);
- mediaRecorder.setStorageProperty(storagePropertyBuilder.build());
- AudioProperty.Builder audioPropertyBuilder = new AudioProperty.Builder();
- //setRecorderAudioEncoder:設置音頻編碼格式。
- audioPropertyBuilder.setRecorderAudioEncoder(Recorder.AudioEncoder.AAC);
- mediaRecorder.setAudioProperty(audioPropertyBuilder.build());
- mediaRecorder.setVideoProperty(videoPropertyBuilder.build());
- mediaRecorder.prepare();
- }
開始錄制
通過設置長按點擊事件偵聽器setLongClickedListener()來觸發startRecord(),實現開始錄制。代碼如下:
- //為組件中的長單擊事件注冊偵聽器(單擊并按住組件)。所有注冊的觀察員都將收到調度到組件的長單擊事件的通知。
- videoRecord.setLongClickedListener(component -> {
- startRecord();
- isRecording = true;
- videoRecord.setPixelMap(ResourceTable.Media_ic_camera_video_press);
- });
- private void startRecord() {
- if (cameraDevice == null) {
- HiLog.error(LABEL_LOG, "%{public}s", "startRecord failed, parameters is illegal");
- return;
- }
- synchronized (lock) {
- initMediaRecorder();
- recorderSurface = mediaRecorder.getVideoSurface();
- cameraConfigBuilder = cameraDevice.getCameraConfigBuilder();
- try {
- cameraConfigBuilder.addSurface(previewSurface);
- if (recorderSurface != null) {
- //添加界面作為相機流的輸出。
- cameraConfigBuilder.addSurface(recorderSurface);
- }
- cameraDevice.configure(cameraConfigBuilder.build());
- } catch (IllegalStateException | IllegalArgumentException e) {
- HiLog.error(LABEL_LOG, "%{public}s", "startRecord IllegalStateException ");
- }
- }
- new ToastDialog(this).setText("Recording").show();
- }
停止錄制
通過設置觸摸事件偵聽器setTouchEventListener()來觸發stopRecord(),實現停止錄制。
- //在組件中注冊觸摸事件的偵聽器。所有注冊的觀察員都將收到調度到組件的觸摸事件的通知。
- videoRecord.setTouchEventListener((component, touchEvent) -> {
- if (touchEvent != null && touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP && isRecording) {
- stopRecord();
- isRecording = false;
- videoRecord.setPixelMap(ResourceTable.Media_ic_camera_video_ready);
- }
- return true;
- });
- private void stopRecord() {
- synchronized (lock) {
- try {
- eventHandler.postTask(() -> mediaRecorder.stop());//stop():停止錄制。
- if (cameraDevice == null || cameraDevice.getCameraConfigBuilder() == null) {
- HiLog.error(LABEL_LOG, "%{public}s", "StopRecord cameraDevice or getCameraConfigBuilder is null");
- return;
- }
- cameraConfigBuilder = cameraDevice.getCameraConfigBuilder();
- cameraConfigBuilder.addSurface(previewSurface);
- cameraConfigBuilder.removeSurface(recorderSurface);
- cameraDevice.configure(cameraConfigBuilder.build());
- } catch (IllegalStateException | IllegalArgumentException exception) {
- HiLog.error(LABEL_LOG, "%{public}s", "stopRecord occur exception");
- }
- }
- new ToastDialog(this).setText("video saved").show();
- }
視頻存儲和照片存儲類似,本文就不做贅述,更多相機的開發請參照HarmonyOS相機開發指導:
相機設備釋放
使用完相機后,必須通過release()來關閉相機和釋放資源,否則可能導致其他相機應用無法啟動。一旦相機被釋放,它所提供的操作就不能再被調用,否則會導致不可預期的結果,或是會引發狀態異常。相機設備釋放的示例代碼如下:
- private void releaseCamera() {
- if (cameraDevice != null) {
- //release():釋放攝像機。
- //釋放攝像機后,攝像機的所有操作都不可用。任何操作都會導致意外結果或異常。
- cameraDevice.release();
- }
- if (imageReceiver != null) {
- imageReceiver.release();
- }
- }
至此,就完成了一個具有拍攝照片和錄制視頻功能的相機開發,開發者也可以通過合適的接口或者接口組合實現閃光燈控制、曝光時間控制、手動對焦和自動對焦控制、變焦控制、人臉識別以及更多的功能。
文章相關附件可以點擊下面的原文鏈接前往下載