main fixes
This commit is contained in:
413
ARCHITECTURE_DIAGRAM.md
Normal file
413
ARCHITECTURE_DIAGRAM.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# 🎬 Визуальное объяснение проблемы и решения
|
||||
|
||||
## ПРОБЛЕМА: Видео не отправляется на сервер
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ АРХИТЕКТУРА ДО ИСПРАВЛЕНИЯ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────┐
|
||||
│ 📷 КАМЕРА │
|
||||
└──────┬──────┘
|
||||
│ (RGBA frames)
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ CameraManager │
|
||||
│ • Preview только │ ← ПРОБЛЕМА: ImageAnalysis отсутствует!
|
||||
│ • Нет обработки │
|
||||
│ • Нет callback │
|
||||
└────────────────────┘
|
||||
|
||||
❌ РАЗОРВАНО ❌
|
||||
|
||||
┌──────────────────────┐
|
||||
│ MainActivity │
|
||||
│ • Не передает │ ← ПРОБЛЕМА: Нет callback для передачи фреймов
|
||||
│ фреймы │
|
||||
└──────────────────────┘
|
||||
|
||||
❌ РАЗОРВАНО ❌
|
||||
|
||||
┌──────────────────────┐
|
||||
│ StreamViewModel │
|
||||
│ • sendVideoFrame() │ ← НИКОГДА НЕ ВЫЗЫВАЕТСЯ!
|
||||
│ существует │
|
||||
│ • Но никто не │
|
||||
│ вызывает │
|
||||
└──────────────────────┘
|
||||
|
||||
❌ РАЗОРВАНО ❌
|
||||
|
||||
┌──────────────────────┐
|
||||
│ WebSocketManager │
|
||||
│ • sendBinary() есть │ ← НИЧЕГО НЕ ОТПРАВЛЯЕТСЯ
|
||||
│ • Но данных нет │
|
||||
└──────────────────────┘
|
||||
|
||||
❌ РАЗОРВАНО ❌
|
||||
|
||||
┌──────────────────────────┐
|
||||
│ 🖥️ СЕРВЕР │
|
||||
│ • Нет видео ❌ │
|
||||
└──────────────────────────┘
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
ПОТОК: 📷 → ✗ → ? → ✗ → ? → ✗ → 🖥️
|
||||
↑ ↑
|
||||
РАЗОРВАНО РАЗОРВАНО
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## РЕШЕНИЕ: Соединяем цепь обработки видео
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ АРХИТЕКТУРА ПОСЛЕ ИСПРАВЛЕНИЯ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────┐
|
||||
│ 📷 КАМЕРА │
|
||||
│ (30 fps, 1920x1080) │
|
||||
└──────────┬───────────┘
|
||||
│ RGBA frames
|
||||
▼
|
||||
╔══════════════════════════════════════╗
|
||||
║ ✅ CameraManager ║
|
||||
║ ├─ Preview (для экрана) ║ ← НОВОЕ: ImageAnalysis
|
||||
║ ├─ ImageCapture (снимки) ║
|
||||
║ └─ ✨ ImageAnalysis (потоковое) ║ ← НОВОЕ: processFrame()
|
||||
║ ├─ Analyzer: processFrame() ║ ← НОВОЕ: onFrameAvailable callback
|
||||
║ ├─ Convert → ByteArray ║
|
||||
║ └─ Invoke onFrameAvailable() ║
|
||||
╚════════════╤═════════════════════════╝
|
||||
│ ByteArray (фрейм)
|
||||
│
|
||||
┌────────────▼──────────────────────────┐
|
||||
│ ✅ MainActivity │
|
||||
│ ├─ startCamera(...) │
|
||||
│ └─ onFrame = { frameData → │ ← НОВОЕ: callback функция
|
||||
│ viewModel.sendVideoFrame(...) │
|
||||
│ } │
|
||||
└────────────┬──────────────────────────┘
|
||||
│ ByteArray
|
||||
│
|
||||
┌────────────▼────────────────────────────────────┐
|
||||
│ ✅ StreamViewModel │
|
||||
│ ├─ sendVideoFrame(frameData) │
|
||||
│ ├─ wsManager.sendBinary(frameData) │
|
||||
│ ├─ frameCount++ │
|
||||
│ ├─ totalBytesTransferred += size │
|
||||
│ └─ Log: "FPS: X, bytes sent: Y" ← УЛУЧШЕНО │
|
||||
└────────────┬─────────────────────────────────────┘
|
||||
│ ByteArray
|
||||
│
|
||||
┌────────────▼──────────────────────────────────┐
|
||||
│ ✅ WebSocketManager │
|
||||
│ ├─ sendBinary(data) │
|
||||
│ ├─ ByteString.toByteString() ← ИСПРАВЛЕНО │
|
||||
│ ├─ webSocket.send(byteString) │
|
||||
│ └─ Log: "Binary data sent: X bytes" │
|
||||
└────────────┬───────────────────────────────────┘
|
||||
│ WebSocket Binary Frame
|
||||
│ (через интернет)
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ 🖥️ СЕРВЕР │
|
||||
│ ✅ Видео получено! │
|
||||
│ • Фреймы приходят │
|
||||
│ • Отображаются │
|
||||
└──────────────────────────┘
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
ПОТОК: 📷 ✅ → CameraManager → MainActivity → ViewModel → WebSocket → 🖥️
|
||||
✅ ✅ ✅ ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Детальная диаграмма CameraManager
|
||||
|
||||
### БЫЛО ❌
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ startCamera() │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 1. Get ProcessCameraProvider │
|
||||
│ 2. Create Preview (for display) │
|
||||
│ 3. Create ImageCapture (for photos) │
|
||||
│ 4. Select back camera │
|
||||
│ 5. bindToLifecycle( │
|
||||
│ preview, │
|
||||
│ imageCapture │
|
||||
│ ← НЕ ХВАТАЕТ АНАЛИЗАТОРА! │
|
||||
│ ) │
|
||||
│ 6. Log "Camera started" │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
РЕЗУЛЬТАТ: 📷 → [Preview] → 🖥️
|
||||
→ (ничего не выходит)
|
||||
```
|
||||
|
||||
### СТАЛО ✅
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ startCamera() │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ 1. Get ProcessCameraProvider │
|
||||
│ 2. Create Preview (for display) │
|
||||
│ 3. Create ImageCapture (for photos) │
|
||||
│ 4. ✨ Create ImageAnalysis (for streaming) │
|
||||
│ ├─ setBackpressureStrategy(KEEP_ONLY_LATEST) │
|
||||
│ ├─ setOutputImageFormat(RGBA_8888) │
|
||||
│ └─ setAnalyzer(processFrame) │
|
||||
│ 5. Select back camera │
|
||||
│ 6. bindToLifecycle( │
|
||||
│ preview, │
|
||||
│ imageCapture, │
|
||||
│ imageAnalysis ✨ │
|
||||
│ ) │
|
||||
│ 7. Log "Camera started with video streaming" │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
|
||||
РЕЗУЛЬТАТ: 📷 → [Preview] → 🖥️
|
||||
→ [ImageAnalysis] → processFrame() → ByteArray → onFrameAvailable()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Диаграмма processFrame()
|
||||
|
||||
```
|
||||
ImageAnalysis получает фрейм каждые ~33мс (30 FPS)
|
||||
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ processFrame(imageProxy: ImageProxy) │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ frameCount++ │
|
||||
│ currentTime = System.currentTimeMillis() │
|
||||
│ │
|
||||
│ if (currentTime - lastLogTime > 5000) { │
|
||||
│ Log "Processing frameCount frames/5s" │
|
||||
│ frameCount = 0 │
|
||||
│ lastLogTime = currentTime │
|
||||
│ } │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ ОБРАБОТКА ФРЕЙМА: │ │
|
||||
│ │ 1. buffer = imageProxy.planes[0] │ │
|
||||
│ │ 2. buffer.rewind() │ │
|
||||
│ │ 3. frameData = ByteArray(size) │ │
|
||||
│ │ 4. buffer.get(frameData) │ │
|
||||
│ │ = Копируем пиксели в ByteArray │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ onFrameAvailable?.invoke(frameData) │
|
||||
│ ↓ Отправляем фрейм через callback ↓ │
|
||||
│ (попадает в MainActivity.onFrame) │
|
||||
│ │
|
||||
│ imageProxy.close() │
|
||||
│ (Освобождаем ресурсы) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
ПОТОК ДАННЫХ:
|
||||
ImageProxy → Buffer → ByteArray → Callback → ViewModel → Server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Диаграмма MainActivity callback
|
||||
|
||||
### БЫЛО ❌
|
||||
|
||||
```
|
||||
DisposableEffect(previewViewRef, isCameraRunning) {
|
||||
if (pv != null && isCameraRunning) {
|
||||
cameraManager.startCamera(
|
||||
lifecycleOwner,
|
||||
pv.surfaceProvider,
|
||||
onError = { err → ... }
|
||||
← НЕ ПЕРЕДАЕМ ФРЕЙМЫ!
|
||||
)
|
||||
}
|
||||
onDispose { cameraManager.stopCamera() }
|
||||
}
|
||||
|
||||
РЕЗУЛЬТАТ: Камера работает, но фреймы теряются!
|
||||
```
|
||||
|
||||
### СТАЛО ✅
|
||||
|
||||
```
|
||||
DisposableEffect(previewViewRef, isCameraRunning) {
|
||||
if (pv != null && isCameraRunning) {
|
||||
cameraManager.startCamera(
|
||||
lifecycleOwner,
|
||||
pv.surfaceProvider,
|
||||
onError = { err → ... },
|
||||
onFrame = { frameData → ← НОВОЕ!
|
||||
viewModel.sendVideoFrame(frameData)
|
||||
}
|
||||
)
|
||||
}
|
||||
onDispose { cameraManager.stopCamera() }
|
||||
}
|
||||
|
||||
РЕЗУЛЬТАТ: Каждый фрейм → ViewModel → Server ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Диаграмма StreamViewModel
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ sendVideoFrame(frameData: ByteArray) │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ wsManager.sendBinary(frameData) │
|
||||
│ ↓ Отправляем через WebSocket ↓ │
|
||||
│ │
|
||||
│ frameCount++ │
|
||||
│ totalBytesTransferred += frameData.size │
|
||||
│ _bytesTransferred.value = totalBytesTransferred│
|
||||
│ │
|
||||
│ if (currentTime - lastFpsTime >= 1000) { │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ КАЖДУЮ СЕКУНДУ: │ │
|
||||
│ │ Log "FPS: $frameCount, Bytes: $total" │
|
||||
│ │ _fps.value = frameCount │ │
|
||||
│ │ frameCount = 0 │ │
|
||||
│ │ lastFpsTime = currentTime │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ } │
|
||||
│ │
|
||||
└────────────────────────────────────────────────┘
|
||||
|
||||
СТАТИСТИКА КАЖДУЮ СЕКУНДУ:
|
||||
- FPS: количество фреймов в секунду
|
||||
- Total bytes: объем переданных данных
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Диаграмма WebSocketManager
|
||||
|
||||
### БЫЛО ❌ (рефлексия)
|
||||
|
||||
```
|
||||
fun sendBinary(data: ByteArray) {
|
||||
val byteStringClass = Class.forName("okhttp3.ByteString")
|
||||
val ofMethod = byteStringClass.getMethod("of", ByteArray::class.java)
|
||||
val byteString = ofMethod.invoke(null, data)
|
||||
val sendMethod = WebSocket::class.java.getMethod("send", byteStringClass)
|
||||
sendMethod.invoke(webSocket, byteString)
|
||||
}
|
||||
|
||||
ПРОБЛЕМЫ:
|
||||
- Медленно (рефлексия)
|
||||
- Хрупко (завязано на имена методов)
|
||||
- Сложно (много промежуточных объектов)
|
||||
- Может сломаться при обновлении OkHttp
|
||||
```
|
||||
|
||||
### СТАЛО ✅ (Clean API)
|
||||
|
||||
```
|
||||
import okio.ByteString.Companion.toByteString
|
||||
|
||||
fun sendBinary(data: ByteArray) {
|
||||
val byteString = data.toByteString() ← ОДИН ВЫЗОВ
|
||||
webSocket?.send(byteString) ← ПРЯМОЙ API
|
||||
}
|
||||
|
||||
ПРЕИМУЩЕСТВА:
|
||||
- Быстро (прямой вызов)
|
||||
- Надежно (стандартный API)
|
||||
- Просто (две строки)
|
||||
- Стабильно (часть OkHttp)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Таблица сравнения
|
||||
|
||||
| Аспект | БЫЛО ❌ | СТАЛО ✅ |
|
||||
|--------|---------|----------|
|
||||
| **Захват видео** | Только превью | ImageAnalysis + Preview |
|
||||
| **Обработка фреймов** | Нет | processFrame() |
|
||||
| **Передача фреймов** | Разорвано | onFrame callback |
|
||||
| **Отправка на сервер** | Никогда | Каждый фрейм |
|
||||
| **Отправка бинарных данных** | Рефлексия | okio.ByteString |
|
||||
| **Логирование** | Минимальное | Подробное (FPS, bytes) |
|
||||
| **Результат** | Видео не идет | ✅ Видео идет |
|
||||
|
||||
---
|
||||
|
||||
## Временная шкала обработки фрейма
|
||||
|
||||
```
|
||||
0ms → Камера захватывает фрейм (30fps = каждые 33мс)
|
||||
2ms → ImageAnalysis получает фрейм
|
||||
3ms → processFrame() преобразует в ByteArray
|
||||
4ms → Вызывается callback onFrameAvailable()
|
||||
4ms → MainActivity получает frameData
|
||||
5ms → viewModel.sendVideoFrame() вызывается
|
||||
6ms → StreamViewModel отправляет через WebSocket
|
||||
7ms → WebSocket отправляет на сервер
|
||||
50ms → Сервер получает фрейм
|
||||
```
|
||||
|
||||
**Итого:** ~50мс задержка (нормально для потоковой передачи видео)
|
||||
|
||||
---
|
||||
|
||||
## Поток данных в памяти
|
||||
|
||||
```
|
||||
1. ImageProxy (в памяти GPU)
|
||||
│
|
||||
├─ ~10.7 МБ (1920×1080×4 байта для RGBA)
|
||||
│
|
||||
▼
|
||||
2. ByteArray (в памяти RAM)
|
||||
│
|
||||
├─ ~10.7 МБ
|
||||
│
|
||||
▼
|
||||
3. okio.ByteString
|
||||
│
|
||||
├─ ~10.7 МБ (временно)
|
||||
│
|
||||
▼
|
||||
4. WebSocket буфер
|
||||
│
|
||||
├─ Фрагментируется по 16KB блокам
|
||||
│
|
||||
▼
|
||||
5. Socket отправляет
|
||||
│
|
||||
├─ ~10.7 МБ/сек при 30fps
|
||||
│ (100 Мбит/сек требуется!)
|
||||
│
|
||||
▼
|
||||
6. Сервер получает
|
||||
|
||||
ОПТИМИЗАЦИЯ: Использовать H.264 кодирование
|
||||
- 10.7 МБ → 0.1-0.5 МБ (100x сжатие)
|
||||
- 10 Мбит/сек (вместо 100)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Рисунок создан:** 2025-12-03
|
||||
**Для:** Объяснения архитектуры видеопотока в CamControl
|
||||
**Статус:** ✅ Все компоненты исправлены и соединены
|
||||
|
||||
Reference in New Issue
Block a user