# Отладка: Приложение отправляет видео на сервер ## Проблема **Симптомы:** - ✓ Приложение подключается к серверу по WebSocket - ✓ На экране видна картинка с камеры (превью работает) - ❌ На сервере не получается видео - ❌ В logcat нет сообщений об отправке видеофреймов **Из logcat (2025-12-03 20:27:19):** ``` WebSocket: Connected! Response code=101 WebSocket: Header: Upgrade=websocket WebSocket: Message received: {"error": "Invalid room or password"} StreamViewModel: Status: Подключено к серверу ✓ StreamViewModel: Status: Получено: {"error": "Invalid room or password"} ``` **Но видеофреймы вообще не отправлялись!** --- ## Анализ ### Кто виноват? Проблема была **в архитектуре приложения**, а не в WebSocket подключении. #### 1. **CameraManager** - не захватывал фреймы ```kotlin // ❌ ДО: только превью, никакой обработки фреймов val preview = Preview.Builder().build() imageCapture = ImageCapture.Builder().build() // ✅ ПОСЛЕ: добавлен ImageAnalysis для захвата фреймов val imageAnalysis = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) .build() .apply { setAnalyzer(analysisExecutor) { imageProxy -> processFrame(imageProxy) // Обработка каждого фрейма } } ``` #### 2. **MainActivity** - не передавал фреймы в ViewModel ```kotlin // ❌ ДО: просто запускает камеру cameraManager.startCamera(lifecycleOwner, pv.surfaceProvider, onError) // ✅ ПОСЛЕ: передаёт фреймы в ViewModel cameraManager.startCamera( lifecycleOwner, pv.surfaceProvider, onError = { err -> Log.e("CameraManager", "Camera error: $err") }, onFrame = { frameData -> viewModel.sendVideoFrame(frameData) // ← КЛЮЧЕВАЯ СТРОКА! } ) ``` #### 3. **WebSocketManager** - использовал сложную рефлексию для бинарных данных ```kotlin // ❌ ДО: сложная и хрупкая рефлексия 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) // ✅ ПОСЛЕ: простой и надёжный API import okio.ByteString.Companion.toByteString val byteString = data.toByteString() webSocket?.send(byteString) ``` --- ## Решение ### Файл 1: CameraManager.kt **Что изменилось:** 1. Добавлены импорты для `ImageAnalysis` и `ImageProxy` 2. Добавлены новые поля: - `analysisExecutor` - выполняет анализ фреймов в отдельном потоке - `onFrameAvailable` - callback для отправки фреймов 3. Изменена сигнатура `startCamera()`: ```kotlin fun startCamera( lifecycleOwner: LifecycleOwner, previewSurfaceProvider: Preview.SurfaceProvider, onError: (String) -> Unit, onFrame: ((ByteArray) -> Unit)? = null // ← НОВЫЙ ПАРАМЕТР ) ``` 4. Добавлен `ImageAnalysis` при привязке к камере 5. Новый метод `processFrame()`: - Извлекает пиксельные данные из каждого фрейма - Отправляет через callback `onFrameAvailable` - Логирует количество фреймов в секунду ### Файл 2: MainActivity.kt **Что изменилось:** ```kotlin // Добавлена передача callback функции cameraManager.startCamera( lifecycleOwner, pv.surfaceProvider, onError = { err -> Log.e("CameraManager", "Camera error: $err") }, onFrame = { frameData -> // ← НОВЫЙ CALLBACK viewModel.sendVideoFrame(frameData) } ) ``` **Как это работает:** 1. Камера захватывает фрейм 2. `CameraManager` преобразует его в ByteArray 3. Вызывает callback `onFrame` с данными фрейма 4. ViewModel получает фрейм и отправляет на сервер через WebSocket ### Файл 3: WebSocketManager.kt **Что изменилось:** **Импорты:** ```kotlin // ❌ ДО import okhttp3.ByteString // ✅ ПОСЛЕ import okio.ByteString.Companion.toByteString ``` **Метод sendBinary():** ```kotlin // ✅ НОВАЯ РЕАЛИЗАЦИЯ fun sendBinary(data: ByteArray) { try { val byteString = data.toByteString() webSocket?.send(byteString) Log.d("WebSocket", "Binary data sent: ${data.size} bytes") } catch (e: Exception) { Log.e("WebSocket", "Binary send error: ${e.message}") onError(e.message ?: "Failed to send binary data") } } ``` ### Файл 4: StreamViewModel.kt **Улучшено логирование:** ```kotlin fun sendVideoFrame(frameData: ByteArray) { try { wsManager?.sendBinary(frameData) // ... обновление статистики ... if (currentTime - lastFpsTime >= 1000) { Log.d("StreamViewModel", "FPS: $frameCount, Total bytes sent: $totalBytesTransferred") _fps.value = frameCount frameCount = 0 lastFpsTime = currentTime } } catch (e: Exception) { Log.e("StreamViewModel", "Failed to send frame: ${e.message}", e) } } ``` ### Файл 5: AndroidManifest.xml **Удалены ненужные разрешения:** ```xml ``` --- ## Проверка в logcat **После исправлений ожидаются следующие логи:** ### При подключении: ``` WebSocket: Connecting to: ws://192.168.0.112:8000/ws/client/HhfoHArOGcT/1 WebSocket: Connected! Response code=101 CameraManager: Camera started successfully with video streaming StreamViewModel: Connected to server ``` ### При потоке видео: ``` CameraManager: Processing 25 frames/5s, sending to server WebSocket: Binary data sent: 12345 bytes StreamViewModel: FPS: 25, Total bytes sent: 308640 WebSocket: Binary data sent: 12340 bytes StreamViewModel: FPS: 25, Total bytes sent: 617280 ``` --- ## Результат ✅ **Проект успешно скомпилирован:** ``` BUILD SUCCESSFUL in 6s 97 actionable tasks: 15 executed, 82 up-to-date ``` ✅ **Теперь приложение:** 1. Подключается к серверу 2. Захватывает видеофреймы с камеры 3. Отправляет их на сервер через WebSocket 4. Логирует скорость передачи (FPS и байты) --- ## Технические детали ### ImageAnalysis в CameraX ``` ┌─────────────────────────────────────────┐ │ Camera Hardware │ └────────────┬────────────────────────────┘ │ ┌────────┴──────────┬─────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌──────────┐ ┌──────────┐ │ Preview │ │ImageCapture ImageAnalysis│ │(Display)│ │(Photos) │ │(Frames) │ └─────────┘ └──────────┘ └──────────┘ │ │ │ └──────────────────┬┴─────────────┘ │ ┌──────▼──────┐ │ GPU/Display │ └──────────────┘ CameraManager привязывает ВСЕ три use case одновременно: bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis) ``` ### Поток видео ``` Camera → ImageAnalysis → processFrame() ↓ ByteArray → onFrameAvailable callback ↓ MainActivity → viewModel.sendVideoFrame() ↓ StreamViewModel → wsManager.sendBinary() ↓ WebSocketManager → okhttp3 WebSocket ↓ Server ``` --- ## Файлы изменены | Файл | Изменения | |------|-----------| | `CameraManager.kt` | +ImageAnalysis, +processFrame(), обновлена startCamera() | | `MainActivity.kt` | +onFrame callback при запуске камеры | | `WebSocketManager.kt` | Исправлен sendBinary(), заменена рефлексия на okio API | | `StreamViewModel.kt` | Улучшено логирование FPS и размера данных | | `AndroidManifest.xml` | Удалены ненужные разрешения | --- ## Как тестировать 1. **Запустите приложение** на устройстве/эмуляторе 2. **Введите параметры подключения:** - IP сервера: `192.168.0.112` - Порт: `8000` - Room ID: `HhfoHArOGcT` - Password: `1` 3. **Нажмите "Подключиться"** 4. **Откройте logcat** и отфильтруйте по: - `CameraManager` - статус камеры - `WebSocket` - отправка видео - `StreamViewModel` - статистика FPS 5. **На сервере** должна появиться видео-трансляция --- ## Потенциальные улучшения 1. **Кодирование видео** - сейчас отправляются raw RGBA фреймы (очень большой размер) - Используйте H.264 или VP9 кодирование - Это уменьшит пропускную способность в 10-100 раз 2. **Качество и масштабирование** - Добавить регулировку разрешения камеры - Масштабировать фреймы перед отправкой 3. **Обработка ошибок** - Переподключение при разрыве соединения - Буферизация фреймов при медленной сети 4. **Производительность** - Использовать `STRATEGY_BLOCK_CAPTURE_SESSION` если нужна синхронизация - Оптимизировать работу потоков