Skip to content

Commit e216862

Browse files
committed
feat(摄像头): 优化帧率与图片压缩,移除无用覆盖层
- 帧间隔从 10s 调整为 5s - 拍照后使用 ImageManipulator resize 到 512px + compress 0.5 将每帧大小从 ~4MB 压缩至 ~50-100KB,避免 WebSocket 断连 - 删除已废弃的 CameraStreamOverlay 组件
1 parent d131918 commit e216862

3 files changed

Lines changed: 39 additions & 226 deletions

File tree

components/CameraStreamOverlay.tsx

Lines changed: 0 additions & 204 deletions
This file was deleted.

hooks/useCameraStream.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export interface UseCameraStreamReturn {
2424
hasPermission: boolean | null;
2525
isCameraReady: boolean;
2626
checkAndRequestPermission: () => Promise<boolean>;
27+
/** CameraView 是否应该挂载(用于条件渲染,避免占用摄像头资源) */
28+
shouldMount: boolean;
2729
}
2830

2931
/**
@@ -41,6 +43,8 @@ export function useCameraStream(
4143
const [error, setError] = useState<string | null>(null);
4244
const [facing, setFacing] = useState<CameraType>('front');
4345
const [isCameraReady, setIsCameraReady] = useState(false);
46+
// 控制 CameraView 挂载(只在需要时挂载,避免白占摄像头资源)
47+
const [shouldMount, setShouldMount] = useState(false);
4448

4549
// 使用 ref 存储配置,避免闭包过期
4650
const sendMessageRef = useRef(config.sendMessage);
@@ -67,7 +71,7 @@ export function useCameraStream(
6771
setError(err.message);
6872
setStatus('error');
6973
},
70-
frameInterval: 10000, // 10s,大幅降低频率避免阻塞主线程
74+
frameInterval: 5000, // 5s
7175
});
7276

7377
serviceRef.current = service;
@@ -78,33 +82,31 @@ export function useCameraStream(
7882
};
7983
}, []);
8084

81-
// CameraView 就绪回调
85+
// CameraView 就绪回调 —— 此时 CameraView 已挂载,可以启动服务
8286
const onCameraReady = useCallback(() => {
8387
console.log('📹 CameraView 已就绪');
8488
setIsCameraReady(true);
89+
if (serviceRef.current && cameraRef.current) {
90+
serviceRef.current.setCameraRef(cameraRef.current);
91+
serviceRef.current.start();
92+
}
8593
}, []);
8694

87-
// 开始流式传输
95+
// 开始流式传输:设置方向 + 触发 CameraView 挂载,实际启动由 onCameraReady 完成
8896
const startStreaming = useCallback((selectedFacing?: CameraType) => {
89-
console.log('📹 启动摄像头流,方向:', selectedFacing || facing);
90-
97+
console.log('📹 请求启动摄像头流,方向:', selectedFacing || facing);
98+
setIsCameraReady(false);
9199
if (selectedFacing) {
92100
setFacing(selectedFacing);
93101
}
94-
95-
// 延迟启动,确保 CameraView 已就绪(如果刚改变 facing)
96-
setTimeout(() => {
97-
if (serviceRef.current && cameraRef.current) {
98-
serviceRef.current.setCameraRef(cameraRef.current);
99-
serviceRef.current.start();
100-
}
101-
}, 100);
102+
setShouldMount(true);
102103
}, [facing]);
103104

104105
// 停止流式传输
105106
const stopStreaming = useCallback(() => {
106107
console.log('📹 停止摄像头流');
107108
serviceRef.current?.stop();
109+
setShouldMount(false);
108110
setError(null);
109111
}, []);
110112

@@ -172,5 +174,6 @@ export function useCameraStream(
172174
hasPermission: permission?.granted ?? null,
173175
isCameraReady,
174176
checkAndRequestPermission,
177+
shouldMount,
175178
};
176179
}

services/CameraStreamService.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CameraView } from 'expo-camera';
2+
import * as ImageManipulator from 'expo-image-manipulator';
23

34
export type CameraStreamStatus = 'idle' | 'streaming' | 'paused' | 'error';
45

@@ -31,7 +32,7 @@ export class CameraStreamService {
3132

3233
constructor(config: CameraStreamConfig) {
3334
this.config = config;
34-
this.frameInterval = config.frameInterval ?? 15000; // 默认 15s
35+
this.frameInterval = config.frameInterval ?? 5000; // 默认 5s
3536
}
3637

3738
private setStatus(status: CameraStreamStatus) {
@@ -132,25 +133,38 @@ export class CameraStreamService {
132133

133134
console.log('📷 开始捕获...');
134135

135-
// Step 2: 拍照(最低质量
136+
// Step 2: 拍照(跳过处理,拿原始帧
136137
const photo = await this.cameraRef.takePictureAsync({
137-
base64: true,
138-
quality: 0.1, // 极低质量,减少处理时间
138+
base64: false,
139+
quality: 1,
139140
shutterSound: false,
140141
skipProcessing: true,
141142
});
142143

143-
// Step 3: 立即让出主线程
144+
// Step 3: 让出主线程
144145
await yieldToMain(100);
145146

146-
if (!photo.base64) {
147+
if (!photo.uri) {
147148
console.warn('⚠️ 拍照失败');
148149
return;
149150
}
150151

151-
// Step 4: 构造消息(同步操作,但很快)
152-
const base64WithPrefix = `data:image/jpeg;base64,${photo.base64}`;
153-
const sizeKB = Math.round(photo.base64.length * 0.75 / 1024);
152+
// Step 4: resize 到 512px 以内,压缩到 ~50-100KB
153+
const resized = await ImageManipulator.manipulateAsync(
154+
photo.uri,
155+
[{ resize: { width: 512 } }],
156+
{ compress: 0.5, format: ImageManipulator.SaveFormat.JPEG, base64: true }
157+
);
158+
159+
await yieldToMain(50);
160+
161+
if (!resized.base64) {
162+
console.warn('⚠️ 压缩失败');
163+
return;
164+
}
165+
166+
const base64WithPrefix = `data:image/jpeg;base64,${resized.base64}`;
167+
const sizeKB = Math.round(resized.base64.length * 0.75 / 1024);
154168

155169
console.log('📷 帧完成:', `${sizeKB}KB`);
156170

0 commit comments

Comments
 (0)