跳到主要内容

使用相机预配置(ArkTS)

相机预配置(Preconfig),对常用的场景和分辨率进行了预配置集成,可简化开发相机应用流程,提高应用的开发效率。

开发者在开发相机应用时,在获取到CameraDevice之后,如果遵循通用流程开发,步骤较为繁琐。需要先调用CameraManagergetSupportedOutputCapability来查询当前相机在指定模式下所支持的各类输出的配置信息,拿到CameraOutputCapability之后,应用开发者还需要对里面的各类数据进行解析,筛选,找到自己需要的配置数据Profile以及VideoProfile。最后使用对应的Profile以及VideoProfile创建对应的PreviewOutputPhotoOutput以及VideoOutput

为了解决上述问题,优化应用开发流程,系统针对拍照(PhotoSession)、录像(VideoSession)两类场景,提供了preconfig接口帮助开发者快速完成相机参数配置。推荐仅需要自定义拍照界面的无需开发专业相机应用的开发者,使用相机预配置功能快速开发应用。

在开发相机应用时,需要先申请相关权限。以拍照(PhotoSession)为例,相机预配置(Preconfig)开发流程与通用流程开发,存在以下差异:

其他相关能力:

规格说明

系统提供了4种预配置类型(PreconfigType),分别为PRECONFIG_720P、PRECONFIG_1080P、PRECONFIG_4K、PRECONFIG_HIGH_QUALITY。以及3种画幅比例规格(PreconfigRatio),1:1画幅(PRECONFIG_RATIO_1_1)、4:3画幅(PRECONFIG_RATIO_4_3)、16:9画幅(PRECONFIG_RATIO_16_9)。

由于不同的设备所支持的能力不同。使用相机预配置(Preconfig)功能时,需要先调用canPreconfig检查对应的PreconfigType和PreconfigRatio的组合在当前设备上是否支持。

在不同的画幅比例下,其分辨率规格不同,详见下表。

  • 普通拍照模式下的预览输出

    预配置类型PreconfigTypePRECONFIG_RATIO_1_1PRECONFIG_RATIO_4_3PRECONFIG_RATIO_16_9
    PRECONFIG_720P720x720960x7201280x720
    PRECONFIG_1080P1080x10801440x10801920x1080
    PRECONFIG_4K1080x10801440x10801920x1080
    PRECONFIG_HIGH_QUALITY1440x14401920x14402560x1440
  • 普通拍照模式下的拍照输出

    预配置类型PreconfigTypePRECONFIG_RATIO_1_1PRECONFIG_RATIO_4_3PRECONFIG_RATIO_16_9
    PRECONFIG_720P720x720960x7201280x720
    PRECONFIG_1080P1080x10801440x10801920x1080
    PRECONFIG_4K2160x21602880x21603840x2160
    PRECONFIG_HIGH_QUALITY跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力
  • 普通录像模式下的预览输出

    预配置类型PreconfigTypePRECONFIG_RATIO_1_1PRECONFIG_RATIO_4_3PRECONFIG_RATIO_16_9
    PRECONFIG_720P720x720960x7201280x720
    PRECONFIG_1080P1080x10801440x10801920x1080
    PRECONFIG_4K1080x10801440x10801920x1080
    PRECONFIG_HIGH_QUALITY1080x10801440x10801920x1080
  • 普通录像模式下的录像输出

    预配置类型PreconfigTypePRECONFIG_RATIO_1_1PRECONFIG_RATIO_4_3PRECONFIG_RATIO_16_9
    PRECONFIG_720P720x720960x7201280x720
    PRECONFIG_1080P1080x10801440x10801920x1080
    PRECONFIG_4K2160x21602880x21603840x2160
    PRECONFIG_HIGH_QUALITY2160x21602880x21603840x2160
  • 普通录像模式下的拍照输出

    预配置类型PreconfigTypePRECONFIG_RATIO_1_1PRECONFIG_RATIO_4_3PRECONFIG_RATIO_16_9
    PRECONFIG_720P跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力
    PRECONFIG_1080P跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力
    PRECONFIG_4K跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力
    PRECONFIG_HIGH_QUALITY跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力跟随Sensor(镜头)最大能力

开发步骤

详细的API说明请参考Camera API参考

  1. 导入相关接口。

    import { camera } from '@kit.CameraKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { common } from '@kit.AbilityKit';
  2. 创建输出流Output。

    此处以创建预览流和拍照流为例。

    创建预览输出流时,涉及参数surfaceId。XComponent组件为预览流提供Surface(获取surfaceId请参考getXcomponentSurfaceId方法),而XComponent的能力由UI提供,相关介绍可参考XComponent组件参考

    创建cameraManager对象的方法可参考getCameraManager

    Context获取方式请参考:获取UIAbility的上下文信息

    function createCameraOutput(context: common.BaseContext, surfaceId: string) : void {
    // 创建预览输出流
    let previewOutput: camera.PreviewOutput | undefined = undefined;
    let cameraManager = camera.getCameraManager(context);
    try {
    previewOutput = cameraManager.createPreviewOutput(surfaceId);
    } catch (error) {
    let err = error as BusinessError;
    console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`);
    }
    if (previewOutput === undefined) {
    return;
    }

    // 创建拍照输出流
    let photoOutput: camera.PhotoOutput | undefined = undefined;
    try {
    photoOutput = cameraManager.createPhotoOutput();
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to createPhotoOutput errorCode = ' + err.code);
    }
    if (photoOutput === undefined) {
    return;
    }
    }
  3. 调用CameraManager中的createCameraInput方法,创建输入流Input。

    async function createAndOpenCameraInput(cameraManager: camera.CameraManager) : Promise<void> {
    // 获取相机列表
    let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
    if (cameraArray.length <= 0) {
    console.error("cameraManager.getSupportedCameras error");
    return;
    }
    let cameraInput: camera.CameraInput | undefined = undefined;
    try {
    cameraInput = cameraManager.createCameraInput(cameraArray[0]);
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to createCameraInput errorCode = ' + err.code);
    }
    if (cameraInput === undefined) {
    return;
    }
    // 打开相机
    await cameraInput.open();
    }
  4. 调用createSession创建会话(Session)。

    SceneMode需要指定为NORMAL_PHOTO或NORMAL_VIDEO,对应拍照场景PhotoSession和录像场景VideoSession

    function createCameraSession(cameraManager: camera.CameraManager) : void {
    // 创建会话
    let photoSession: camera.PhotoSession | undefined = undefined;
    try {
    photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to create the session instance. errorCode = ' + err.code);
    }
    if (photoSession === undefined) {
    return;
    }
    }
  5. 调用canPreconfig检查对应的PreconfigType和PreconfigRatio的组合在当前设备上是否支持。确认支持后,调用preconfig启用Preconfig配置。

    function GetPreconfig(photoSession: camera.PhotoSession) : void {
    // 查询Preconfig能力
    try {
    let isPreconfigSupport = photoSession.canPreconfig(camera.PreconfigType.PRECONFIG_1080P);
    if (!isPreconfigSupport) {
    console.error('PhotoSession canPreconfig check fail.');
    return;
    }
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to call canPreconfig. errorCode = ' + err.code);
    return;
    }

    // 配置Preconfig
    try {
    photoSession.preconfig(camera.PreconfigType.PRECONFIG_1080P);
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to call preconfig. errorCode = ' + err.code);
    return;
    }
    }

    若当前模式为普通录像模式,且PreconfigType使用PRECONFIG_HIGH_QUALITY类型时,为避免录制失败,请务必将AVRecorderProfile配置文件中videoCodec参数必须配置为VIDEO_HEVC,isHdr参数配置为true。

  6. Session添加Input和Output。

    Session调用preconfig接口成功之后,Session内部会将预置数据准备好,如果向Session中进行添加未配置Profile的Output,Session则会对相应的Output进行配置对应Profile。如果向Session中添加已配置Profile的Output,则Session的预配置数据不生效。

    async function initCamera(photoSession: camera.PhotoSession, cameraInput: camera.CameraInput,
    previewOutput: camera.PreviewOutput, photoOutput: camera.PhotoOutput) : Promise<void> {
    // 开始配置会话
    try {
    photoSession.beginConfig();
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to beginConfig. errorCode = ' + err.code);
    }

    // 向会话中添加相机输入流
    try {
    photoSession.addInput(cameraInput);
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to addInput. errorCode = ' + err.code);
    }

    // 向会话中添加预览输出流
    try {
    photoSession.addOutput(previewOutput);
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code);
    }

    // 向会话中添加拍照输出流
    try {
    photoSession.addOutput(photoOutput);
    } catch (error) {
    let err = error as BusinessError;
    console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code);
    }
    // 提交会话配置
    await photoSession.commitConfig();
    }
  7. 启动Session。

    async function startCamera(photoSession: camera.PhotoSession) : Promise<void> {
    // 启动会话
    await photoSession.start().then(() => {
    console.info('Promise returned to indicate the session start success.');
    });
    }

完整示例

Context获取方式请参考:获取UIAbility的上下文信息

import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';

async function savePicture(buffer: ArrayBuffer, img: image.Image, context: Context): Promise<void> {
let accessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
let options: photoAccessHelper.CreateOptions = {
title: Date.now().toString()
};
let photoUri: string = await accessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg', options);
let file: fileIo.File = fileIo.openSync(photoUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
fileIo.writeSync(file.fd, buffer);
fileIo.closeSync(file);
await img.release();
}

function setPhotoOutputCb(photoOutput: camera.PhotoOutput, context: Context): void {
// 设置回调之后,调用photoOutput的capture方法,就会将拍照的buffer回传到回调中
photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
console.info('getPhoto start');
console.info(`err: ${JSON.stringify(errCode)}`);
if (errCode || photo === undefined) {
console.error('getPhoto failed');
return;
}
let imageObj = photo.main;
imageObj.getComponent(image.ComponentType.JPEG, (errCode: BusinessError, component: image.Component): void => {
console.info('getComponent start');
if (errCode || component === undefined) {
console.error('getComponent failed');
return;
}
let buffer: ArrayBuffer;
if (component.byteBuffer) {
buffer = component.byteBuffer;
} else {
console.error('byteBuffer is null');
return;
}
savePicture(buffer, imageObj, context);
});
});
}

async function cameraShootingCase(context: Context, surfaceId: string): Promise<void> {
// 创建CameraManager对象
let cameraManager: camera.CameraManager = camera.getCameraManager(context);
if (!cameraManager) {
console.error("camera.getCameraManager error");
return;
}
// 监听相机状态变化
cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {
if (err !== undefined && err.code !== 0) {
console.error('cameraStatus with errorCode = ' + err.code);
return;
}
console.info(`camera : ${cameraStatusInfo.camera.cameraId}`);
console.info(`status: ${cameraStatusInfo.status}`);
});

// 获取相机列表
let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
if (cameraArray.length <= 0) {
console.error("cameraManager.getSupportedCameras error");
return;
}

for (let index = 0; index < cameraArray.length; index++) {
console.info('cameraId : ' + cameraArray[index].cameraId); // 获取相机ID
console.info('cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置
console.info('cameraType : ' + cameraArray[index].cameraType); // 获取相机类型
console.info('connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型
}

// 创建相机输入流
let cameraInput: camera.CameraInput | undefined = undefined;
try {
cameraInput = cameraManager.createCameraInput(cameraArray[0]);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to createCameraInput errorCode = ' + err.code);
}
if (cameraInput === undefined) {
return;
}

// 监听cameraInput错误信息
let cameraDevice: camera.CameraDevice = cameraArray[0];
cameraInput.on('error', cameraDevice, (error: BusinessError) => {
console.error(`Camera input error code: ${error.code}`);
})

// 打开相机
await cameraInput.open();

// 获取支持的模式类型
let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);
let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
if (!isSupportPhotoMode) {
console.error('photo mode not support');
return;
}

// 创建会话
let photoSession: camera.PhotoSession | undefined = undefined;
try {
photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
} catch (error) {
let err = error as BusinessError;
console.error('Failed to create the session instance. errorCode = ' + err.code);
}
if (photoSession === undefined) {
return;
}

// 监听session错误信息
photoSession.on('error', (error: BusinessError) => {
console.error(`Capture session error code: ${error.code}`);
});

// 查询Preconfig能力
try {
let isPreconfigSupport = photoSession.canPreconfig(camera.PreconfigType.PRECONFIG_1080P);
if (!isPreconfigSupport) {
console.error('PhotoSession canPreconfig check fail.');
return;
}
} catch (error) {
let err = error as BusinessError;
console.error('Failed to call canPreconfig. errorCode = ' + err.code);
return;
}

// 配置Preconfig
try {
photoSession.preconfig(camera.PreconfigType.PRECONFIG_1080P);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to call preconfig. errorCode = ' + err.code);
return;
}

// 创建预览输出流,其中参数 surfaceId 参考上文 XComponent 组件,预览流为XComponent组件提供的surface
let previewOutput: camera.PreviewOutput | undefined = undefined;
try {
previewOutput = cameraManager.createPreviewOutput(surfaceId);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`);
}
if (previewOutput === undefined) {
return;
}
// 监听预览输出错误信息
previewOutput.on('error', (error: BusinessError) => {
console.error(`Preview output error code: ${error.code}`);
});

// 创建拍照输出流
let photoOutput: camera.PhotoOutput | undefined = undefined;
try {
photoOutput = cameraManager.createPhotoOutput();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to createPhotoOutput errorCode = ' + err.code);
}
if (photoOutput === undefined) {
return;
}

// 调用上面的回调函数来保存图片
setPhotoOutputCb(photoOutput, context);

// 开始配置会话
try {
photoSession.beginConfig();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to beginConfig. errorCode = ' + err.code);
}

// 向会话中添加相机输入流
try {
photoSession.addInput(cameraInput);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to addInput. errorCode = ' + err.code);
}

// 向会话中添加预览输出流
try {
photoSession.addOutput(previewOutput);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code);
}

// 向会话中添加拍照输出流
try {
photoSession.addOutput(photoOutput);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code);
}

// 提交会话配置
await photoSession.commitConfig();

// 启动会话
await photoSession.start().then(() => {
console.info('Promise returned to indicate the session start success.');
});
// 判断设备是否支持闪光灯
let flashStatus: boolean = false;
try {
flashStatus = photoSession.hasFlash();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to hasFlash. errorCode = ' + err.code);
}
console.info('Returned with the flash light support status:' + flashStatus);

if (flashStatus) {
// 判断是否支持自动闪光灯模式
let flashModeStatus: boolean = false;
try {
let status: boolean = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO);
flashModeStatus = status;
} catch (error) {
let err = error as BusinessError;
console.error('Failed to check whether the flash mode is supported. errorCode = ' + err.code);
}
if(flashModeStatus) {
// 设置自动闪光灯模式
try {
photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to set the flash mode. errorCode = ' + err.code);
}
}
}

// 判断是否支持连续自动变焦模式
let focusModeStatus: boolean = false;
try {
let status: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
focusModeStatus = status;
} catch (error) {
let err = error as BusinessError;
console.error('Failed to check whether the focus mode is supported. errorCode = ' + err.code);
}

if (focusModeStatus) {
// 设置连续自动变焦模式
try {
photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to set the focus mode. errorCode = ' + err.code);
}
}

// 获取相机支持的可变焦距比范围
let zoomRatioRange: Array<number> = [];
try {
zoomRatioRange = photoSession.getZoomRatioRange();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to get the zoom ratio range. errorCode = ' + err.code);
}
if (zoomRatioRange.length <= 0) {
return;
}
// 设置可变焦距比
try {
photoSession.setZoomRatio(zoomRatioRange[0]);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to set the zoom ratio value. errorCode = ' + err.code);
}
let photoCaptureSetting: camera.PhotoCaptureSetting = {
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高
rotation: camera.ImageRotation.ROTATION_0 // 设置图片旋转角度0
}
// 使用当前拍照设置进行拍照,需要在拍照时主动调用该接口拍摄图片。
photoOutput.capture(photoCaptureSetting, (err: BusinessError) => {
if (err) {
console.error(`Failed to capture the photo ${err.message}`);
return;
}
console.info('Callback invoked to indicate the photo capture request success.');
});

// 需要在拍照结束之后调用以下关闭摄像头和释放会话流程,避免拍照未结束就将会话释放。
// 停止当前会话
await photoSession.stop();

// 释放相机输入流
await cameraInput.close();

// 释放预览输出流
await previewOutput.release();

// 释放拍照输出流
await photoOutput.release();

// 释放会话
await photoSession.release();

// 会话置空
photoSession = undefined;
}