12 KiB
12 KiB
Отладка: Приложение отправляет видео на сервер
Проблема
Симптомы:
- ✓ Приложение подключается к серверу по 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 - не захватывал фреймы
// ❌ ДО: только превью, никакой обработки фреймов
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
// ❌ ДО: просто запускает камеру
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 - использовал сложную рефлексию для бинарных данных
// ❌ ДО: сложная и хрупкая рефлексия
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
Что изменилось:
- Добавлены импорты для
ImageAnalysisиImageProxy - Добавлены новые поля:
analysisExecutor- выполняет анализ фреймов в отдельном потокеonFrameAvailable- callback для отправки фреймов
- Изменена сигнатура
startCamera():fun startCamera( lifecycleOwner: LifecycleOwner, previewSurfaceProvider: Preview.SurfaceProvider, onError: (String) -> Unit, onFrame: ((ByteArray) -> Unit)? = null // ← НОВЫЙ ПАРАМЕТР ) - Добавлен
ImageAnalysisпри привязке к камере - Новый метод
processFrame():- Извлекает пиксельные данные из каждого фрейма
- Отправляет через callback
onFrameAvailable - Логирует количество фреймов в секунду
Файл 2: MainActivity.kt
Что изменилось:
// Добавлена передача callback функции
cameraManager.startCamera(
lifecycleOwner,
pv.surfaceProvider,
onError = { err -> Log.e("CameraManager", "Camera error: $err") },
onFrame = { frameData -> // ← НОВЫЙ CALLBACK
viewModel.sendVideoFrame(frameData)
}
)
Как это работает:
- Камера захватывает фрейм
CameraManagerпреобразует его в ByteArray- Вызывает callback
onFrameс данными фрейма - ViewModel получает фрейм и отправляет на сервер через WebSocket
Файл 3: WebSocketManager.kt
Что изменилось:
Импорты:
// ❌ ДО
import okhttp3.ByteString
// ✅ ПОСЛЕ
import okio.ByteString.Companion.toByteString
Метод sendBinary():
// ✅ НОВАЯ РЕАЛИЗАЦИЯ
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
Улучшено логирование:
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
Удалены ненужные разрешения:
<!-- ❌ УДАЛЕНЫ -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- ✅ ОСТАЛИСЬ -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Проверка в 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
✅ Теперь приложение:
- Подключается к серверу
- Захватывает видеофреймы с камеры
- Отправляет их на сервер через WebSocket
- Логирует скорость передачи (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 |
Удалены ненужные разрешения |
Как тестировать
- Запустите приложение на устройстве/эмуляторе
- Введите параметры подключения:
- IP сервера:
192.168.0.112 - Порт:
8000 - Room ID:
HhfoHArOGcT - Password:
1
- IP сервера:
- Нажмите "Подключиться"
- Откройте logcat и отфильтруйте по:
CameraManager- статус камерыWebSocket- отправка видеоStreamViewModel- статистика FPS
- На сервере должна появиться видео-трансляция
Потенциальные улучшения
-
Кодирование видео - сейчас отправляются raw RGBA фреймы (очень большой размер)
- Используйте H.264 или VP9 кодирование
- Это уменьшит пропускную способность в 10-100 раз
-
Качество и масштабирование
- Добавить регулировку разрешения камеры
- Масштабировать фреймы перед отправкой
-
Обработка ошибок
- Переподключение при разрыве соединения
- Буферизация фреймов при медленной сети
-
Производительность
- Использовать
STRATEGY_BLOCK_CAPTURE_SESSIONесли нужна синхронизация - Оптимизировать работу потоков
- Использовать