capture
capture 在用户明确授权后创建摄像头或麦克风流。宿主代持真实设备、系统权限和采集缓冲,guest 只得到类型化句柄及有界媒体数据。
基本示例
// 必须直接发生在可信的用户操作处理器中。
async function startCamera() {
const stream = await host.capture.requestCamera({
facing: 'user',
maxWidth: 1280,
maxHeight: 720,
});
if (!stream) return; // 用户取消
const target = new Uint8Array(stream.frameBytes);
const frame = await host.capture.frame(stream.camera, target);
consumeFrame(target.subarray(0, frame.written), frame);
}
适用场景
- 扫码、头像拍摄、视频特效的单帧或连续摄像头输入。
- 录音、音量分析、语音处理的麦克风 PCM 输入。
- 需要随权限撤销立即终止的数据流。
屏幕捕获、系统音频、后台监听、设备枚举和长期设备标识不在 v0.1。
L1 边界
宿主独占的是摄像头/麦克风设备、系统权限、可信采集指示与撤销状态。guest 无法靠计算取得这些数据;媒体分析、效果、编码和录制文件结构在拿到受限帧后可由 guest 实现,不进入 L1。
能力声明
{
"capabilities": [
"host:capture.requestCamera",
"host:capture.requestMic"
],
"events": ["capture.ended"]
}
摄像头和麦克风分别声明;申请一种设备不会获得另一种。frame、readAudio 和 stop 由返回句柄中的 read/close 权限授权,不是独立 capability。
Reference
方法
- capture.requestCamera():经可信 UI 申请摄像头句柄。
- capture.requestMic():经可信 UI 申请麦克风句柄。
- capture.frame():读取最新可用视频帧。
- capture.readAudio():读取有界 PCM 采样。
- capture.stop():停止设备并使句柄失效。
事件
- capture.ended:权限撤销、设备断开、应用停止或采集错误。
约束对象
v0.1 只接受宿主白名单字段:摄像头朝向、最大宽高、最大帧率;麦克风最大声道数和偏好采样率。它们是偏好或上限,不是浏览器原始 constraints 透传。
v0.1 视频固定为紧密或带 padding 的 rgba8;音频固定为交错、little-endian f32 PCM。申请成功时宿主返回会话内不变的实际格式:
CameraConstraints {
facing?: "user" | "environment"
maxWidth?: u32
maxHeight?: u32
maxFrameRate?: u32
}
MicrophoneConstraints {
preferredSampleRate?: u32
maxChannels?: 1 | 2
}
CameraStream {
camera: Handle<camera-stream, read+close, external-resource, session>
facing: "user" | "environment"
width: u32, height: u32, stride: u32, format: "rgba8", frameBytes: u32
nominalFrameRate: u32
}
MicrophoneStream {
microphone: Handle<microphone-stream, read+close, external-resource, session>
sampleRate: u32, channels: 1 | 2, format: "f32le", maxFramesPerRead: u32
}
VideoFrameResult { written: u32, width: u32, height: u32, stride: u32, format: "rgba8", sequence: u64 }
AudioReadResult { written: u32, sampleRate: u32, channels: 1 | 2, format: "f32le",
frames: u32, sequence: u64, droppedFrames: u32 }
capture.frame(camera, sink, afterSequence?, options?) 返回 sequence 严格大于 afterSequence 的最新帧;省略时返回调用开始时已缓冲的最新帧,没有则异步等待。afterSequence 若提供,必须是此前由同一句柄成功返回过的序号,否则以 invalid-sequence 失败。它允许跳过中间视频帧,不返回相同序号。
capture.frame 的 sink 容量必须至少为 frameBytes;成功时恰好写入 frameBytes,written === frameBytes === stride × height。capture.readAudio(microphone, sink, options?) 的 sink 容量必须是 channels × 4 的正整数倍;一次成功读取 1..min(sinkCapacity / (channels × 4), maxFramesPerRead) 个完整帧并令 written === frames × channels × 4。它异步等待至少一个完整采样帧并按时间顺序消费。sequence 是返回块首个音频帧在本会话中的零基绝对序号;若有界宿主缓冲因 guest 读取过慢而丢帧,droppedFrames 精确报告本块之前新丢弃的帧数。两者都可取消,取消不关闭句柄;同一句柄同时存在第二个读取时以 busy 失败。
摄像头源的第一个捕获帧 sequence 为 0,之后每个实际捕获帧递增 1;首次读取最新帧可以观察到大于 0 的序号,间隙就是未返回的视频帧数。nominalFrameRate 是宿主选择的名义采集上限,必须为正;请求提供 maxFrameRate 时不得超过它。实际到达频率可以更低,不形成计时承诺。音频 sequence 以单个多声道采样帧为单位,从 0 开始;相邻返回块满足 next.sequence = previous.sequence + previous.frames + next.droppedFrames。任一序列即将耗尽 u64 时,宿主必须先以 error 终止会话,不得回绕。所有返回的格式、朝向、宽高、stride、采样率和声道必须与申请结果一致。
麦克风工作流示例
const stream = await host.capture.requestMic({
preferredSampleRate: 48_000,
maxChannels: 1,
});
if (!stream) return;
const bytes = new Uint8Array(stream.maxFramesPerRead * stream.channels * 4);
let sequence;
for (;;) {
const result = await host.capture.readAudio(stream.microphone, bytes);
sequence = result.sequence + BigInt(result.frames);
if (result.droppedFrames) reportGap(result.droppedFrames);
if (result.frames > 0) analyzePcm(bytes.subarray(0, result.written), result);
}
maxChannels 是硬上限;提供时返回 channels <= maxChannels,省略时宿主选择 1 或 2。preferredSampleRate 是偏好,返回值可以不同,但必须是 [8000, 192000] 内整数。maxFramesPerRead 恰好等于宿主公布的 capture.maxAudioFramesPerRead。
约束字段都可省略,但对象本身必填;空对象表示接受宿主默认。所有数值必须为正并受公布预算限制。成功的摄像头尺寸必须为正,满足 stride >= width × 4、frameBytes === stride × height <= capture.maxFrameBytes。宿主选择不超过各 max* 的实际值;指定 facing 时只能返回同一朝向,未指定时可选择任一朝向并在 CameraStream.facing 中如实返回。无法满足时以 device-unavailable 失败,不得悄悄超过上限或切换指定朝向。
每次 readAudio 都会等待数据或终止,不需要另一个“可读”事件;完成后继续调用不会形成忙轮询。
原生 WASM 示例
CameraStream stream = host_capture_request_camera(camera_constraints());
if (!stream.camera.valid) return; // 用户取消
uint8_t *buffer = frame_pool_acquire(stream.frame_bytes);
VideoFrameResult r = host_capture_frame(
stream.camera,
byte_sink(buffer, stream.frame_bytes),
NO_PREVIOUS_SEQUENCE,
NO_CANCEL
);
process_frame(buffer, r.written, r.width, r.height, r.stride);
生命周期与授权
- 句柄为
Handle<camera-stream, read+close, external-resource, session>或Handle<microphone-stream, read+close, external-resource, session>。 request*必须继承可信用户操作,并由宿主显示设备类型和应用身份。- 平台只有在可信选择器能可靠区分“关闭”和“拒绝”时才以
null表示关闭;浏览器无法区分时使用denied,调用方取消使用cancelled,系统错误返回对应HostError。 - 权限撤销、设备断开、应用隐藏策略或
stop使句柄立即失效。 stop幂等,不能在关闭后继续交付缓冲数据。
安全与预算
- 信任档位:yellow;数据来自独立治理的外部资源。
- 宿主不返回设备名、序列号或稳定 deviceId。
- v0.1 预算键为
capture.maxStreams(摄像头与麦克风合计的每应用活动流数)、capture.maxFrameBytes(每个视频帧)、capture.maxAudioFramesPerRead(每次音频读取)和capture.readsPerSecond(两种读取合计的每应用调用数)。 - 视频仅
rgba8,音频仅f32le;元数据不能夹带浏览器设备对象。 - 采集状态必须在可信壳层持续可见,并提供停止入口。
错误与测试
特有错误包括 activation-required、denied、device-unavailable、invalid-sequence、sink-too-small、stale-handle、busy 和 limit-exceeded。一致性测试至少覆盖平台可区分时的选择器关闭、权限拒绝与撤销、设备热拔插、视频跳帧、音频缓冲溢出与精确 droppedFrames、朝向/格式协商、后台策略、读取取消、并发读取、重复停止和跨类型句柄误用。