专属HarmonyOS APP相机开发配置
一、小知识
你有没有遇到过这种情况——预览画面卡得像幻灯片,但拍出来的照片却是清晰的?或者拍出来的照片总是偏黄/偏蓝,怎么调都不对?又或者想拍微距,但怎么点屏幕都对不上焦?
这些问题,都指向同一个核心——相机配置。
相机配置就像做菜的火候。火太大,菜糊了(过曝);火太小,菜不熟(欠曝)。调料放多了,味道怪(白平衡偏色);调料放少了,没味道(色彩平淡)。对焦就像选食材,对准了拍出来清晰,对偏了就是一坨模糊。
在 HarmonyOS 的相机开发中,这些"火候"和"调料"都通过 Session 的配置方法来控制。预览分辨率决定了你看到的画面精细度,帧率决定了画面的流畅度,对焦模式决定了画面的清晰区域,曝光补偿决定了画面的明暗,白平衡决定了画面的色温。
这篇文章,我们就把这些配置参数一个一个讲清楚。
二、核心原理
2.1 相机配置体系总览
相机配置不是一盘散沙,它有清晰的层次结构:
flowchart TB
classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold
A[CameraOutputCapability<br/>输出能力查询]:::primary --> B[预览分辨率配置]:::info
A --> C[帧率配置]:::info
A --> D[拍照分辨率配置]:::warning
A --> E[录像分辨率配置]:::warning
F[Session 配置方法]:::error --> G[对焦模式]:::purple
F --> H[曝光补偿]:::purple
F --> I[白平衡]:::purple
F --> J[闪光灯]:::purple
F --> K[防抖]:::purple
F --> L[变焦]:::purple
style A stroke-width:3px
style F stroke-width:3px
两大配置维度:
- 输出能力配置(创建时确定):预览分辨率、拍照分辨率、录像分辨率、帧率范围
- 会话参数配置(运行时调整):对焦、曝光、白平衡、闪光灯、防抖、变焦
2.2 预览分辨率与帧率的关系
预览分辨率和帧率是一对矛盾体——分辨率越高,每帧数据量越大,能支持的帧率就越低:
flowchart LR
classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold
A[高分辨率<br/>1920×1080]:::primary -->|数据量大| B[低帧率<br/>15-30fps]:::warning
C[低分辨率<br/>640×480]:::info -->|数据量小| D[高帧率<br/>30-60fps]:::purple
style A stroke-width:2px
style C stroke-width:2px
2.3 对焦模式详解
| 模式 | 说明 | 适用场景 |
|---|---|---|
| FOCUS_MODE_MANUAL | 手动对焦,需指定对焦距离 | 微距拍摄、创意摄影 |
| FOCUS_MODE_CONTINUOUS_AUTO | 连续自动对焦,持续调整 | 日常拍摄(推荐默认值) |
| FOCUS_MODE_AUTO | 单次自动对焦,对准后锁定 | 静物拍摄 |
| FOCUS_MODE_LOCKED | 锁定当前对焦距离 | 拍摄固定距离的物体 |
2.4 曝光补偿原理
曝光补偿(EV)是在自动曝光的基础上,手动微调画面亮度:
- EV > 0:增加曝光,画面变亮
- EV = 0:标准曝光
- EV < 0:减少曝光,画面变暗
HarmonyOS 中曝光补偿的范围和步长由设备决定,通常范围为 -4EV 到 +4EV,步长为 1/3EV 或 1/2EV。
2.5 白平衡模式
| 模式 | 色温 | 适用场景 |
|---|---|---|
| AUTO | 自动 | 日常拍摄 |
| CLOUDY | ~6000K | 阴天 |
| DAYLIGHT | ~5500K | 晴天户外 |
| FLUORESCENT | ~4200K | 日光灯下 |
| INCANDESCENT | ~3200K | 白炽灯下 |
| MANUAL | 自定义色温 | 专业拍摄 |
三、代码实战
3.1 预览分辨率与帧率配置
这个示例展示了如何查询设备支持的预览配置,并根据场景选择最合适的分辨率和帧率。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 预览配置管理器
* 查询设备能力,选择最优预览分辨率和帧率
*/
export class PreviewConfigManager {
private cameraManager: camera.CameraManager | null = null;
/**
* 初始化
*/
init(context: Context): void {
this.cameraManager = camera.getCameraManager(context);
}
/**
* 打印所有支持的预览配置
* 这是开发调试的第一步——先了解你的设备能做什么
*/
printAllPreviewProfiles(device: camera.CameraDevice): void {
if (!this.cameraManager) return;
const capability = this.cameraManager.getSupportedOutputCapability(device);
const profiles = capability.previewProfiles;
console.info('===== 预览配置列表 =====');
profiles.forEach((profile, index) => {
console.info(
`[${index}] ${profile.size.width}×${profile.size.height} ` +
`帧率: ${profile.frameRateRange.min}-${profile.frameRateRange.max}fps ` +
`格式: ${profile.format}`
);
});
console.info(`共 ${profiles.length} 种配置`);
}
/**
* 选择最佳预览配置
* 根据场景需求选择分辨率和帧率
* @param device 相机设备
* @param scenario 使用场景
*/
selectBestPreviewProfile(
device: camera.CameraDevice,
scenario: PreviewScenario = PreviewScenario.BALANCED
): camera.Profile {
if (!this.cameraManager) {
throw new Error('CameraManager 未初始化');
}
const capability = this.cameraManager.getSupportedOutputCapability(device);
const profiles = capability.previewProfiles;
switch (scenario) {
case PreviewScenario.HIGH_QUALITY:
// 高质量:选择最高分辨率
return this.selectByResolution(profiles, 'highest');
case PreviewScenario.HIGH_FPS:
// 高帧率:选择最高帧率
return this.selectByFrameRate(profiles, 'highest');
case PreviewScenario.BALANCED:
// 平衡:1080p 30fps
return this.selectBalanced(profiles);
case PreviewScenario.LOW_POWER:
// 低功耗:720p 30fps
return this.selectLowPower(profiles);
default:
return profiles[0];
}
}
/**
* 按分辨率选择
*/
private selectByResolution(
profiles: camera.Profile[],
order: 'highest' | 'lowest'
): camera.Profile {
const sorted = [...profiles].sort((a, b) => {
const areaA = a.size.width * a.size.height;
const areaB = b.size.width * b.size.height;
return order === 'highest' ? areaB - areaA : areaA - areaB;
});
return sorted[0];
}
/**
* 按帧率选择
*/
private selectByFrameRate(
profiles: camera.Profile[],
order: 'highest' | 'lowest'
): camera.Profile {
const sorted = [...profiles].sort((a, b) => {
const fpsA = a.frameRateRange.max;
const fpsB = b.frameRateRange.max;
return order === 'highest' ? fpsB - fpsA : fpsA - fpsB;
});
return sorted[0];
}
/**
* 平衡选择:1080p 30fps
*/
private selectBalanced(profiles: camera.Profile[]): camera.Profile {
// 优先选择 1080p
const target1080 = profiles.find(p =>
p.size.width === 1920 && p.size.height === 1080
);
if (target1080) return target1080;
// 降级选择 720p
const target720 = profiles.find(p =>
p.size.width === 1280 && p.size.height === 720
);
if (target720) return target720;
// 兜底
return profiles[0];
}
/**
* 低功耗选择:720p 30fps
*/
private selectLowPower(profiles: camera.Profile[]): camera.Profile {
const target720 = profiles.find(p =>
p.size.width === 1280 && p.size.height === 720 &&
p.frameRateRange.max >= 30
);
if (target720) return target720;
// 选择最低分辨率
return this.selectByResolution(profiles, 'lowest');
}
/**
* 设置帧率
* 注意:帧率设置必须在会话 start 之后
*/
setFrameRate(
session: camera.PhotoSession | camera.VideoSession,
fps: number
): boolean {
try {
// 检查是否支持固定帧率模式
if (!session.isFrameRateSupported()) {
console.warn('[帧率] 不支持固定帧率设置');
return false;
}
session.setFrameRate(fps);
console.info(`[帧率] 已设置为 ${fps}fps`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[帧率设置失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 获取当前帧率
*/
getFrameRate(session: camera.PhotoSession | camera.VideoSession): number {
try {
return session.getActiveFrameRate();
} catch {
return 0;
}
}
}
/**
* 预览场景枚举
*/
enum PreviewScenario {
HIGH_QUALITY = 'high_quality', // 高质量(最高分辨率)
HIGH_FPS = 'high_fps', // 高帧率(最流畅)
BALANCED = 'balanced', // 平衡(1080p 30fps)
LOW_POWER = 'low_power', // 低功耗(720p 30fps)
}
3.2 对焦模式控制
对焦是影响画面清晰度的最关键参数。这个示例实现了自动对焦、手动对焦、对焦区域选择等完整功能。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 对焦控制器
* 支持自动对焦、手动对焦、触摸对焦
*/
export class FocusController {
private session: camera.PhotoSession | camera.VideoSession | null = null;
// 对焦状态
private currentFocusMode: camera.FocusMode = camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO;
private focusState: camera.FocusState = camera.FocusState.FOCUS_STATE_IDLE;
// 回调
private onFocusStateChange?: (state: camera.FocusState) => void;
/**
* 绑定会话
*/
bindSession(session: camera.PhotoSession | camera.VideoSession): void {
this.session = session;
this.setupFocusCallback();
this.printFocusInfo();
}
/**
* 打印对焦能力信息
*/
private printFocusInfo(): void {
if (!this.session) return;
const supportedModes = this.session.getSupportedFocusModes();
console.info(`[对焦] 支持的模式: ${supportedModes.map(m => this.getFocusModeText(m)).join(', ')}`);
const focalLength = this.session.getFocalLength();
console.info(`[对焦] 焦距: ${focalLength}`);
const focusDistanceRange = this.session.getFocusDistanceRange();
if (focusDistanceRange) {
console.info(`[对焦] 对焦距离范围: ${focusDistanceRange.min} - ${focusDistanceRange.max}`);
}
}
/**
* 注册对焦状态回调
*/
private setupFocusCallback(): void {
if (!this.session) return;
this.session.on('focusStateChange', (state: camera.FocusState) => {
this.focusState = state;
const stateText = this.getFocusStateText(state);
console.info(`[对焦] 状态变更: ${stateText}`);
this.onFocusStateChange?.(state);
});
}
/**
* 设置对焦模式
*/
setFocusMode(mode: camera.FocusMode): boolean {
if (!this.session) return false;
try {
const supportedModes = this.session.getSupportedFocusModes();
if (!supportedModes.includes(mode)) {
console.warn(`[对焦] 不支持模式: ${this.getFocusModeText(mode)}`);
return false;
}
this.session.setFocusMode(mode);
this.currentFocusMode = mode;
console.info(`[对焦] 已切换到: ${this.getFocusModeText(mode)}`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[对焦模式设置失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 设置手动对焦距离
* 仅在 FOCUS_MODE_MANUAL 模式下有效
* @param distance 对焦距离(单位:米),0 表示无限远
*/
setManualFocusDistance(distance: number): boolean {
if (!this.session) return false;
if (this.currentFocusMode !== camera.FocusMode.FOCUS_MODE_MANUAL) {
console.warn('[对焦] 手动对焦距离仅在手动对焦模式下有效');
return false;
}
try {
// 检查距离是否在有效范围内
const range = this.session.getFocusDistanceRange();
if (range && (distance < range.min || distance > range.max)) {
console.warn(`[对焦] 距离 ${distance} 超出范围 ${range.min}-${range.max}`);
return false;
}
this.session.setFocusDistance(distance);
console.info(`[对焦] 手动对焦距离: ${distance}m`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[手动对焦设置失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 触摸对焦(点击屏幕对焦)
* 这是最常用的对焦方式
* @param x 触摸点 X 坐标(0-1 归一化)
* @param y 触摸点 Y 坐标(0-1 归一化)
*/
touchFocus(x: number, y: number): boolean {
if (!this.session) return false;
try {
// 创建对焦区域
const focusPoint: camera.Point = {
x: Math.max(0, Math.min(1, x)),
y: Math.max(0, Math.min(1, y))
};
// 设置对焦区域(以触摸点为中心的小矩形)
const size = 0.1; // 对焦区域大小(归一化)
const focusRect: camera.Rect = {
topLeftX: Math.max(0, focusPoint.x - size / 2),
topLeftY: Math.max(0, focusPoint.y - size / 2),
width: size,
height: size
};
// 先切换到自动对焦模式
this.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
// 设置对焦区域
this.session.setFocusPoint(focusPoint);
console.info(`[触摸对焦] 位置: (${focusPoint.x.toFixed(2)}, ${focusPoint.y.toFixed(2)})`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[触摸对焦失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 锁定当前对焦
* 在自动对焦完成后锁定,防止后续画面变化导致重新对焦
*/
lockFocus(): boolean {
if (!this.session) return false;
return this.setFocusMode(camera.FocusMode.FOCUS_MODE_LOCKED);
}
/**
* 解锁对焦,恢复连续自动对焦
*/
unlockFocus(): boolean {
if (!this.session) return false;
return this.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
}
/**
* 获取当前对焦状态
*/
getFocusState(): camera.FocusState {
return this.focusState;
}
/**
* 获取对焦模式文本
*/
getFocusModeText(mode: camera.FocusMode): string {
const map: Record<number, string> = {
[camera.FocusMode.FOCUS_MODE_MANUAL]: '手动',
[camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO]: '连续自动',
[camera.FocusMode.FOCUS_MODE_AUTO]: '自动',
[camera.FocusMode.FOCUS_MODE_LOCKED]: '锁定',
};
return map[mode] || '未知';
}
/**
* 获取对焦状态文本
*/
private getFocusStateText(state: camera.FocusState): string {
const map: Record<number, string> = {
[camera.FocusState.FOCUS_STATE_IDLE]: '空闲',
[camera.FocusState.FOCUS_STATE_SCANNING]: '扫描中',
[camera.FocusState.FOCUS_STATE_FOCUSED]: '已对焦',
};
return map[state] || '未知';
}
/**
* 注销回调
*/
release(): void {
if (!this.session) return;
this.session.off('focusStateChange');
}
}
3.3 曝光补偿与白平衡控制
这个示例实现了曝光补偿的精细调节和白平衡模式的切换,是专业相机 App 的核心功能。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 曝光与白平衡控制器
* 精细调控画面明暗和色温
*/
export class ExposureWhiteBalanceController {
private session: camera.PhotoSession | camera.VideoSession | null = null;
// 曝光参数
private exposureCompensation: number = 0; // 当前曝光补偿值
private exposureCompensationRange: camera.ExposureCompensation = {
min: -4, max: 4, step: 1
};
// 白平衡参数
private currentWhiteBalance: camera.WhiteBalanceMode =
camera.WhiteBalanceMode.WHITE_BALANCE_MODE_AUTO;
/**
* 绑定会话
*/
bindSession(session: camera.PhotoSession | camera.VideoSession): void {
this.session = session;
this.queryExposureInfo();
this.queryWhiteBalanceInfo();
}
// ==================== 曝光控制 ====================
/**
* 查询曝光能力信息
*/
private queryExposureInfo(): void {
if (!this.session) return;
// 获取曝光补偿范围
this.exposureCompensationRange = this.session.getExposureCompensationRange();
console.info(`[曝光] 补偿范围: ${this.exposureCompensationRange.min} ~ ` +
`${this.exposureCompensationRange.max}, 步长: ${this.exposureCompensationRange.step}`);
// 获取当前曝光模式
const currentMode = this.session.getExposureMode();
console.info(`[曝光] 当前模式: ${this.getExposureModeText(currentMode)}`);
// 获取当前曝光补偿
this.exposureCompensation = this.session.getExposureCompensation();
console.info(`[曝光] 当前补偿: ${this.exposureCompensation}`);
}
/**
* 设置曝光模式
*/
setExposureMode(mode: camera.ExposureMode): boolean {
if (!this.session) return false;
try {
const supportedModes = this.session.getSupportedExposureModes();
if (!supportedModes.includes(mode)) {
console.warn(`[曝光] 不支持模式: ${this.getExposureModeText(mode)}`);
return false;
}
this.session.setExposureMode(mode);
console.info(`[曝光] 已切换到: ${this.getExposureModeText(mode)}`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[曝光模式设置失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 设置曝光补偿
* @param compensation 补偿值(单位:步长)
* 正值增加曝光(变亮),负值减少曝光(变暗)
*/
setExposureCompensation(compensation: number): boolean {
if (!this.session) return false;
try {
const range = this.exposureCompensationRange;
// 边界检查
const clampedValue = Math.max(range.min, Math.min(range.max, compensation));
if (clampedValue !== compensation) {
console.warn(`[曝光] 补偿值 ${compensation} 超出范围,已修正为 ${clampedValue}`);
}
this.session.setExposureCompensation(clampedValue);
this.exposureCompensation = clampedValue;
// 计算实际 EV 值
const evValue = clampedValue * range.step;
console.info(`[曝光] 补偿值: ${clampedValue}, 实际EV: ${evValue >= 0 ? '+' : ''}${evValue}`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[曝光补偿设置失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 增加曝光补偿(变亮)
*/
increaseExposure(): boolean {
return this.setExposureCompensation(this.exposureCompensation + 1);
}
/**
* 减少曝光补偿(变暗)
*/
decreaseExposure(): boolean {
return this.setExposureCompensation(this.exposureCompensation - 1);
}
/**
* 重置曝光补偿
*/
resetExposure(): boolean {
return this.setExposureCompensation(0);
}
/**
* 设置曝光区域(点击测光)
* @param x 归一化 X 坐标 (0-1)
* @param y 归一化 Y 坐标 (0-1)
*/
setExposurePoint(x: number, y: number): boolean {
if (!this.session) return false;
try {
const point: camera.Point = {
x: Math.max(0, Math.min(1, x)),
y: Math.max(0, Math.min(1, y))
};
this.session.setExposurePoint(point);
console.info(`[曝光] 测光点: (${point.x.toFixed(2)}, ${point.y.toFixed(2)})`);
return true;
} catch (error) {
console.error(`[测光点设置失败] ${error}`);
return false;
}
}
// ==================== 白平衡控制 ====================
/**
* 查询白平衡能力信息
*/
private queryWhiteBalanceInfo(): void {
if (!this.session) return;
const supportedModes = this.session.getSupportedWhiteBalanceModes();
console.info(`[白平衡] 支持的模式: ${supportedModes.map(m => this.getWhiteBalanceText(m)).join(', ')}`);
this.currentWhiteBalance = this.session.getActiveWhiteBalanceMode();
console.info(`[白平衡] 当前模式: ${this.getWhiteBalanceText(this.currentWhiteBalance)}`);
// 查询是否支持色温设置
const colorTemperatureRange = this.session.getColorTemperatureRange();
if (colorTemperatureRange) {
console.info(`[白平衡] 色温范围: ${colorTemperatureRange.min}K - ${colorTemperatureRange.max}K`);
}
}
/**
* 设置白平衡模式
*/
setWhiteBalanceMode(mode: camera.WhiteBalanceMode): boolean {
if (!this.session) return false;
try {
const supportedModes = this.session.getSupportedWhiteBalanceModes();
if (!supportedModes.includes(mode)) {
console.warn(`[白平衡] 不支持模式: ${this.getWhiteBalanceText(mode)}`);
return false;
}
this.session.setWhiteBalanceMode(mode);
this.currentWhiteBalance = mode;
console.info(`[白平衡] 已切换到: ${this.getWhiteBalanceText(mode)}`);
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[白平衡设置失败] code: ${err.code}, msg: ${err.message}`);
return false;
}
}
/**
* 设置手动色温
* 仅在 WHITE_BALANCE_MODE_MANUAL 模式下有效
* @param temperature 色温值(单位:K)
*/
setColorTemperature(temperature: number): boolean {
if (!this.session) return false;
if (this.currentWhiteBalance !== camera.WhiteBalanceMode.WHITE_BALANCE_MODE_MANUAL) {
console.warn('[白平衡] 手动色温仅在手动白平衡模式下有效');
return false;
}
try {
const range = this.session.getColorTemperatureRange();
if (range) {
const clampedTemp = Math.max(range.min, Math.min(range.max, temperature));
this.session.setColorTemperature(clampedTemp);
console.info(`[白平衡] 色温: ${clampedTemp}K`);
return true;
}
return false;
} catch (error) {
console.error(`[色温设置失败] ${error}`);
return false;
}
}
/**
* 循环切换白平衡模式
*/
cycleWhiteBalanceMode(): camera.WhiteBalanceMode {
if (!this.session) return this.currentWhiteBalance;
const supportedModes = this.session.getSupportedWhiteBalanceModes();
const currentIndex = supportedModes.indexOf(this.currentWhiteBalance);
const nextIndex = (currentIndex + 1) % supportedModes.length;
const nextMode = supportedModes[nextIndex];
this.setWhiteBalanceMode(nextMode);
return nextMode;
}
// ==================== 工具方法 ====================
getExposureModeText(mode: camera.ExposureMode): string {
const map: Record<number, string> = {
[camera.ExposureMode.EXPOSURE_MODE_MANUAL]: '手动',
[camera.ExposureMode.EXPOSURE_MODE_CONTINUOUS_AUTO]: '连续自动',
[camera.ExposureMode.EXPOSURE_MODE_AUTO]: '自动',
[camera.ExposureMode.EXPOSURE_MODE_LOCKED]: '锁定',
};
return map[mode] || '未知';
}
getWhiteBalanceText(mode: camera.WhiteBalanceMode): string {
const map: Record<number, string> = {
[camera.WhiteBalanceMode.WHITE_BALANCE_MODE_AUTO]: '自动',
[camera.WhiteBalanceMode.WHITE_BALANCE_MODE_MANUAL]: '手动',
[camera.WhiteBalanceMode.WHITE_BALANCE_MODE_CLOUDY]: '阴天',
[camera.WhiteBalanceMode.WHITE_BALANCE_MODE_DAYLIGHT]: '日光',
[camera.WhiteBalanceMode.WHITE_BALANCE_MODE_FLUORESCENT]: '荧光灯',
[camera.WhiteBalanceMode.WHITE_BALANCE_MODE_INCANDESCENT]: '白炽灯',
};
return map[mode] || '未知';
}
getExposureCompensationValue(): number {
return this.exposureCompensation;
}
getExposureCompensationRange(): camera.ExposureCompensation {
return this.exposureCompensationRange;
}
}
/**
* UI 组件 - 相机配置页面
* 集成预览、对焦、曝光、白平衡控制
*/
@Entry
@Component
struct CameraConfigPage {
private focusController: FocusController = new FocusController();
private exposureController: ExposureWhiteBalanceController = new ExposureWhiteBalanceController();
@State focusModeText: string = '连续自动';
@State exposureValue: number = 0;
@State whiteBalanceText: string = '自动';
@State exposureSliderMin: number = -4;
@State exposureSliderMax: number = 4;
build() {
Column() {
// 预览区域
Text('相机预览区域')
.width('100%')
.height(400)
.textAlign(TextAlign.Center)
.backgroundColor('#1a1a1a')
.fontColor('#666')
.fontSize(16)
Blank()
// 对焦模式切换
Row() {
Text('对焦:')
.fontSize(14)
.fontWeight(FontWeight.Medium)
Button(this.focusModeText)
.fontSize(13)
.height(32)
.onClick(() => {
// 循环切换对焦模式
const modes: camera.FocusMode[] = [
camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO,
camera.FocusMode.FOCUS_MODE_AUTO,
camera.FocusMode.FOCUS_MODE_MANUAL,
camera.FocusMode.FOCUS_MODE_LOCKED,
];
const currentIndex = modes.findIndex(
m => this.focusController.getFocusModeText(m) === this.focusModeText
);
const nextMode = modes[(currentIndex + 1) % modes.length];
this.focusController.setFocusMode(nextMode);
this.focusModeText = this.focusController.getFocusModeText(nextMode);
})
}
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 15 })
// 曝光补偿滑块
Row() {
Text('曝光:')
.fontSize(14)
.fontWeight(FontWeight.Medium)
Text(`${this.exposureValue > 0 ? '+' : ''}${this.exposureValue}`)
.fontSize(16)
.fontColor('#E8A838')
.width(40)
.textAlign(TextAlign.Center)
Slider({
value: this.exposureValue,
min: this.exposureSliderMin,
max: this.exposureSliderMax,
step: 1,
})
.width('55%')
.onChange((value: number) => {
this.exposureValue = value;
this.exposureController.setExposureCompensation(value);
})
}
.width('90%')
.margin({ bottom: 15 })
// 白平衡模式切换
Row() {
Text('白平衡:')
.fontSize(14)
.fontWeight(FontWeight.Medium)
Button(this.whiteBalanceText)
.fontSize(13)
.height(32)
.onClick(() => {
const newMode = this.exposureController.cycleWhiteBalanceMode();
this.whiteBalanceText = this.exposureController.getWhiteBalanceText(newMode);
})
}
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 15 })
// 快捷操作
Row() {
Button('重置曝光')
.fontSize(13)
.onClick(() => {
this.exposureController.resetExposure();
this.exposureValue = 0;
})
Button('日光白平衡')
.fontSize(13)
.onClick(() => {
this.exposureController.setWhiteBalanceMode(
camera.WhiteBalanceMode.WHITE_BALANCE_MODE_DAYLIGHT
);
this.whiteBalanceText = '日光';
})
Button('自动白平衡')
.fontSize(13)
.onClick(() => {
this.exposureController.setWhiteBalanceMode(
camera.WhiteBalanceMode.WHITE_BALANCE_MODE_AUTO
);
this.whiteBalanceText = '自动';
})
}
.width('90%')
.justifyContent(FlexAlign.SpaceAround)
.margin({ bottom: 20 })
}
.width('100%')
.height('100%')
}
}
四、踩坑与注意事项
4.1 配置必须在会话 start 之后
坑:在 session.start() 之前设置对焦、曝光等参数,调用成功但不生效。
解:所有 Session 的参数配置方法(对焦、曝光、白平衡、闪光灯等)都必须在 session.start() 之后调用。
// ❌ 错误:start 前设置参数
session.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
await session.start(); // 参数可能被重置
// ✅ 正确:start 后设置参数
await session.start();
session.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
4.2 曝光补偿步长不是 1
坑:以为曝光补偿步长是 1,直接传了 1.5,结果报错。
解:不同设备的步长不同,必须先查询 getExposureCompensationRange() 获取步长信息,然后按步长的整数倍设置。
const range = session.getExposureCompensationRange();
// 如果 step = 3(表示 1/3 EV),那么补偿值 3 = +1EV,6 = +2EV
// 如果 step = 2(表示 1/2 EV),那么补偿值 2 = +1EV,4 = +2EV
4.3 手动对焦距离的单位
坑:以为对焦距离是归一化的 0-1 值,传了 0.5,结果对焦到了很远的地方。
解:对焦距离的单位是米(m),0 表示无限远。设置前要查询 getFocusDistanceRange() 获取有效范围。
4.4 白平衡手动模式需要先切换
坑:直接调 setColorTemperature(5500),但当前是自动白平衡模式,设置不生效。
解:必须先切换到手动白平衡模式,再设置色温。
// ❌ 错误:自动模式下设置色温
session.setColorTemperature(5500); // 不生效
// ✅ 正确:先切手动再设色温
session.setWhiteBalanceMode(camera.WhiteBalanceMode.WHITE_BALANCE_MODE_MANUAL);
session.setColorTemperature(5500);
4.5 触摸对焦坐标归一化
坑:把屏幕像素坐标直接传给了 setFocusPoint(),结果对焦到了错误的位置。
解:setFocusPoint() 接收的是归一化坐标(0-1),需要把像素坐标转换:
// 像素坐标转归一化坐标
const normalizedX = pixelX / screenWidth;
const normalizedY = pixelY / screenHeight;
session.setFocusPoint({ x: normalizedX, y: normalizedY });
4.6 帧率设置可能不支持
坑:调了 setFrameRate(60) 但预览帧率还是 30fps。
解:不是所有设备都支持固定帧率设置。调用前先检查 isFrameRateSupported(),并且设置的帧率必须在 frameRateRange 范围内。
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5.0 | HarmonyOS 6.0 |
|---|---|---|
| 曝光模式 | 三种模式 | 新增 EXPOSURE_MODE_HDR HDR 曝光 |
| 白平衡 | 六种模式 | 新增 WHITE_BALANCE_MODE_SHADOW 阴影模式 |
| 对焦 | 四种模式 | 新增 FOCUS_MODE_TRACKING 追踪对焦 |
| 色温范围 | getColorTemperatureRange() |
保持不变 |
| 曝光锁定 | EXPOSURE_MODE_LOCKED |
新增 lockExposure() 便捷方法 |
5.2 迁移要点
- HDR 曝光:6.0 新增了 HDR 曝光模式,可以同时捕获多帧不同曝光的图像并合成:
// HarmonyOS 6.0 HDR 曝光
session.setExposureMode(camera.ExposureMode.EXPOSURE_MODE_HDR);
- 追踪对焦:6.0 新增了追踪对焦模式,可以自动追踪画面中的移动物体:
// HarmonyOS 6.0 追踪对焦
session.setFocusMode(camera.FocusMode.FOCUS_MODE_TRACKING);
// 设置追踪目标
session.setFocusPoint({ x: 0.5, y: 0.5 }); // 初始追踪点
- 曝光锁定便捷方法:6.0 中可以直接锁定当前曝光值,无需切换模式:
// HarmonyOS 6.0 曝光锁定
session.lockExposure(); // 锁定当前曝光
session.unlockExposure(); // 解锁
六、总结
mindmap
root((相机配置))
预览分辨率
查询 CameraOutputCapability
按场景选择 高质量/高帧率/平衡/低功耗
分辨率与帧率成反比
帧率设置需检查 isFrameRateSupported
对焦模式
连续自动 日常默认
单次自动 静物拍摄
手动对焦 微距拍摄
锁定对焦 固定距离
触摸对焦 归一化坐标
曝光补偿
范围和步长因设备而异
正值变亮 负值变暗
先查询 range 再设置
测光点设置 setExposurePoint
白平衡
六种预设模式
手动模式可设色温
先切手动再设色温
AUTO 模式适合日常
踩坑要点
参数配置在 start 之后
曝光步长不是固定1
对焦距离单位是米
坐标需要归一化
核心记忆口诀:
配置在 start 后,步长先查询;对焦米为单位,坐标要归一;白平衡先手动,色温才生效。
下篇文章我们将深入「相机元数据」,讲解人脸检测、物体检测、场景检测、元数据回调与 AI 辅助拍摄。
- 点赞
- 收藏
- 关注作者
评论(0)