325 lines
14 KiB
Markdown
325 lines
14 KiB
Markdown
# 🎥 CamControl - Исправления Видеопотока
|
||
|
||
## Статус: ✅ ГОТОВО К ТЕСТИРОВАНИЮ
|
||
|
||
Проект успешно скомпилирован и содержит все исправления для отправки видео на сервер.
|
||
|
||
---
|
||
|
||
## 📋 Что было исправлено
|
||
|
||
### Проблема
|
||
Приложение показывало превью камеры локально, но **видео вообще не отправлялось на сервер**.
|
||
|
||
### Причины
|
||
1. **CameraManager** захватывал только превью, но не обрабатывал видеофреймы
|
||
2. **MainActivity** запускал камеру, но не передавал фреймы в ViewModel
|
||
3. **WebSocketManager** использовал хрупкую рефлексию для бинарных данных
|
||
4. Цепь обработки: Камера → Фреймы → Сервер была **разорвана**
|
||
|
||
### Решение
|
||
- ✅ Добавлен `ImageAnalysis` в CameraManager для захвата фреймов
|
||
- ✅ Добавлен callback `onFrame` в MainActivity для передачи фреймов в ViewModel
|
||
- ✅ Исправлена отправка бинарных данных в WebSocketManager (окончательная рефлексия → okio.ByteString)
|
||
- ✅ Улучшено логирование для отслеживания процесса
|
||
|
||
---
|
||
|
||
## 📁 Измененные файлы
|
||
|
||
### 1️⃣ **CameraManager.kt** (Основное изменение)
|
||
**Добавлено:**
|
||
- ImageAnalysis для захвата видеофреймов
|
||
- Метод processFrame() для обработки каждого фрейма
|
||
- Callback onFrame для отправки фреймов наружу
|
||
- analysisExecutor для асинхронной обработки
|
||
|
||
**Ключевые строки:**
|
||
```kotlin
|
||
// Захват фреймов
|
||
val imageAnalysis = ImageAnalysis.Builder()
|
||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
|
||
.build()
|
||
.apply {
|
||
setAnalyzer(analysisExecutor) { imageProxy ->
|
||
processFrame(imageProxy)
|
||
}
|
||
}
|
||
|
||
// Обработка каждого фрейма
|
||
private fun processFrame(imageProxy: ImageProxy) {
|
||
val frameData = ByteArray(buffer.remaining())
|
||
buffer.get(frameData)
|
||
onFrameAvailable?.invoke(frameData)
|
||
}
|
||
```
|
||
|
||
### 2️⃣ **MainActivity.kt** (Критическое изменение)
|
||
**Добавлено:**
|
||
- Callback onFrame при запуске камеры
|
||
- Передача фреймов в ViewModel через sendVideoFrame()
|
||
|
||
**Ключевые строки:**
|
||
```kotlin
|
||
cameraManager.startCamera(
|
||
lifecycleOwner,
|
||
pv.surfaceProvider,
|
||
onError = { err -> Log.e("CameraManager", "Camera error: $err") },
|
||
onFrame = { frameData ->
|
||
viewModel.sendVideoFrame(frameData) // ← ГЛАВНОЕ!
|
||
}
|
||
)
|
||
```
|
||
|
||
### 3️⃣ **WebSocketManager.kt** (Техническое улучшение)
|
||
**Изменено:**
|
||
- Заменена рефлексия на стандартный API okio
|
||
- Улучшена надежность и производительность
|
||
|
||
**Ключевые строки:**
|
||
```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)
|
||
|
||
// Стало (clean API):
|
||
import okio.ByteString.Companion.toByteString
|
||
val byteString = data.toByteString()
|
||
webSocket?.send(byteString)
|
||
```
|
||
|
||
### 4️⃣ **StreamViewModel.kt** (Логирование)
|
||
**Улучшено:**
|
||
- Логирование FPS (фреймов в секунду)
|
||
- Логирование объема переданных данных
|
||
- Трассировка исключений при ошибках
|
||
|
||
### 5️⃣ **AndroidManifest.xml** (Очистка)
|
||
**Удалены ненужные разрешения:**
|
||
- SEND_SMS
|
||
- RECORD_AUDIO
|
||
- ACCESS_COARSE_LOCATION
|
||
- ACCESS_FINE_LOCATION
|
||
|
||
---
|
||
|
||
## 🚀 Как тестировать
|
||
|
||
### Шаг 1: Убедитесь, что сервер работает
|
||
```bash
|
||
python manage.py runserver 192.168.0.112:8000
|
||
```
|
||
|
||
### Шаг 2: Запустите приложение
|
||
```bash
|
||
./gradlew installDebug
|
||
# Или используйте Android Studio: Run > Run 'app'
|
||
```
|
||
|
||
### Шаг 3: Введите параметры подключения
|
||
- **Server IP:** 192.168.0.112
|
||
- **Server Port:** 8000
|
||
- **Room ID:** HhfoHArOGcT
|
||
- **Password:** 1
|
||
|
||
### Шаг 4: Проверьте логи
|
||
```bash
|
||
adb logcat | grep -E "CameraManager|WebSocket|StreamViewModel"
|
||
```
|
||
|
||
**Ожидаемые логи:**
|
||
```
|
||
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
|
||
```
|
||
|
||
### Шаг 5: Проверьте, что видео получено на сервере
|
||
|
||
---
|
||
|
||
## 🔍 Диагностика
|
||
|
||
### Если видео не отправляется
|
||
|
||
**Проверить логи:**
|
||
```bash
|
||
# Все логи приложения
|
||
adb logcat | grep com.example.camcontrol
|
||
|
||
# Только ошибки
|
||
adb logcat | grep ERROR
|
||
|
||
# Только WebSocket
|
||
adb logcat | grep WebSocket
|
||
|
||
# Только CameraManager
|
||
adb logcat | grep CameraManager
|
||
```
|
||
|
||
**Что проверить:**
|
||
1. ✓ WebSocket подключен: `Connected! Response code=101`
|
||
2. ✓ Камера запущена: `Camera started successfully`
|
||
3. ✓ Фреймы отправляются: `Binary data sent: X bytes`
|
||
4. ✓ Нет исключений: ошибок в logcat
|
||
|
||
### Если подключение не работает
|
||
|
||
**Проверьте:**
|
||
1. IP адрес сервера доступен: `ping 192.168.0.112`
|
||
2. Сервер слушает на правильном порту: `netstat -ln | grep 8000`
|
||
3. Сетевая конфигурация Android: `app/src/main/res/xml/network_security_config.xml`
|
||
|
||
---
|
||
|
||
## 📊 Структура потока видео
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ Android Device │
|
||
├──────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────┐ │
|
||
│ │ Camera │ (задняя камера) │
|
||
│ └──────┬──────┘ │
|
||
│ │ │
|
||
│ │ RGBA_8888 frames │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ CameraManager.processFrame() │ │
|
||
│ │ - Extract pixel data │ │
|
||
│ │ - Convert to ByteArray │ │
|
||
│ │ - Invoke onFrameAvailable │ │
|
||
│ └──────┬──────────────────────────┘ │
|
||
│ │ │
|
||
│ │ ByteArray (frame data) │
|
||
│ ▼ │
|
||
│ ┌──────────────────────────────────────────────┐ │
|
||
│ │ MainActivity │ │
|
||
│ │ - Receive frame in onFrame callback │ │
|
||
│ │ - Call viewModel.sendVideoFrame(frameData) │ │
|
||
│ └──────┬───────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ │ ByteArray (frame data) │
|
||
│ ▼ │
|
||
│ ┌────────────────────────────────────────────────┐ │
|
||
│ │ StreamViewModel │ │
|
||
│ │ - sendVideoFrame(frameData) │ │
|
||
│ │ - wsManager.sendBinary(frameData) │ │
|
||
│ │ - Update statistics (FPS, bytes sent) │ │
|
||
│ └────────┬─────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ │ okio.ByteString │
|
||
│ ▼ │
|
||
│ ┌────────────────────────────────────────────────┐ │
|
||
│ │ WebSocketManager │ │
|
||
│ │ - Convert to okio.ByteString │ │
|
||
│ │ - webSocket.send(byteString) │ │
|
||
│ └────────┬─────────────────────────────────────┘ │
|
||
│ │ │
|
||
└───────────┼─────────────────────────────────────────┘
|
||
│
|
||
│ WebSocket Binary Frame
|
||
▼
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ Server (192.168.0.112:8000) │
|
||
│ - Receive frame │
|
||
│ - Decode frame │
|
||
│ - Display video │
|
||
└──────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 Оптимизация (для будущего)
|
||
|
||
### 1. Кодирование видео
|
||
Сейчас отправляются raw RGBA фреймы (~3 МБ/сек при 30fps, 1920x1080)
|
||
|
||
**Что сделать:**
|
||
- Использовать H.264 кодирование (видео кодек)
|
||
- Или VP9/AV1 для лучшего качества
|
||
- Это уменьшит пропускную способность в 10-100 раз
|
||
|
||
**Код:**
|
||
```kotlin
|
||
import androidx.camera.video.VideoCapture
|
||
import androidx.camera.video.Recording
|
||
|
||
// Использовать VideoCapture вместо ImageAnalysis
|
||
val videoCapture = VideoCapture.withOutput(...)
|
||
```
|
||
|
||
### 2. Масштабирование
|
||
- Уменьшить разрешение перед отправкой
|
||
- Добавить регулировку качества
|
||
|
||
### 3. Буферизация
|
||
- Добавить очередь фреймов при медленной сети
|
||
- Пропускать фреймы при отставании
|
||
|
||
---
|
||
|
||
## 📝 История изменений
|
||
|
||
| Дата | Изменение |
|
||
|------|-----------|
|
||
| 2025-12-03 | Найдена проблема: видео не отправляется на сервер |
|
||
| 2025-12-03 | Добавлен ImageAnalysis в CameraManager |
|
||
| 2025-12-03 | Добавлен callback onFrame в MainActivity |
|
||
| 2025-12-03 | Исправлена отправка бинарных данных в WebSocketManager |
|
||
| 2025-12-03 | Проект успешно скомпилирован |
|
||
|
||
---
|
||
|
||
## ❓ FAQ
|
||
|
||
**Q: Почему видео не показывается на сервере?**
|
||
A: Проверьте, что:
|
||
1. WebSocket подключен (logcat: "Connected! Response code=101")
|
||
2. Приложение запустило камеру (logcat: "Camera started successfully")
|
||
3. Логи показывают "Binary data sent" (фреймы отправляются)
|
||
|
||
**Q: Почему столько данных отправляется?**
|
||
A: Отправляются raw RGBA фреймы без кодирования. Это 3-4 МБ/сек.
|
||
Используйте H.264 видеокодирование для сжатия (см. раздел "Оптимизация").
|
||
|
||
**Q: Можно ли изменить разрешение камеры?**
|
||
A: Да, добавьте в CameraManager:
|
||
```kotlin
|
||
val imageAnalysis = ImageAnalysis.Builder()
|
||
.setTargetResolution(android.util.Size(640, 480)) // Меньшее разрешение
|
||
.build()
|
||
```
|
||
|
||
**Q: Как отладить проблемы с WebSocket?**
|
||
A: Используйте фильтр в logcat:
|
||
```bash
|
||
adb logcat | grep "WebSocket"
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 Следующие шаги
|
||
|
||
1. ✅ Скомпилировать проект (`./gradlew build`)
|
||
2. ✅ Запустить на устройстве
|
||
3. ✅ Проверить логи в logcat
|
||
4. ✅ Убедиться, что видео отправляется на сервер
|
||
5. 📋 Оптимизировать качество видео и битрейт
|
||
6. 📋 Добавить кодирование видео (H.264)
|
||
7. 📋 Реализовать переподключение при разрыве
|
||
|
||
---
|
||
|
||
**Создано:** 2025-12-03
|
||
**Статус:** Готово к тестированию
|
||
**Контакт:** Trevor (тестирующий)
|
||
|