11 KiB
11 KiB
и# 📝 Полный список изменений
Дата: 2025-12-03
Статус: ✅ Завершено и скомпилировано
1️⃣ CameraManager.kt
Добавлены импорты
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
Добавлены новые поля
private val analysisExecutor = Executors.newSingleThreadExecutor()
private var onFrameAvailable: ((ByteArray) -> Unit)? = null
private var frameCount = 0
private var lastLogTime = System.currentTimeMillis()
Изменена функция startCamera()
Было:
fun startCamera(
lifecycleOwner: LifecycleOwner,
previewSurfaceProvider: Preview.SurfaceProvider,
onError: (String) -> Unit
)
Стало:
fun startCamera(
lifecycleOwner: LifecycleOwner,
previewSurfaceProvider: Preview.SurfaceProvider,
onError: (String) -> Unit,
onFrame: ((ByteArray) -> Unit)? = null // ← НОВЫЙ ПАРАМЕТР
)
Добавлен ImageAnalysis при привязке к камере
// Create image analysis for frame processing
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build()
.apply {
setAnalyzer(analysisExecutor) { imageProxy ->
processFrame(imageProxy)
}
}
// Bind with imageAnalysis
cameraProvider?.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageCapture,
imageAnalysis // ← ДОБАВЛЕН
)
Добавлена новая функция processFrame()
private fun processFrame(imageProxy: ImageProxy) {
try {
frameCount++
val currentTime = System.currentTimeMillis()
// Log every 5 seconds
if (currentTime - lastLogTime > 5000) {
Log.d("CameraManager", "Processing $frameCount frames/5s, sending to server")
frameCount = 0
lastLogTime = currentTime
}
// Convert image to byte array
val buffer = imageProxy.planes[0].buffer
buffer.rewind()
val frameData = ByteArray(buffer.remaining())
buffer.get(frameData)
// Send frame to callback
onFrameAvailable?.invoke(frameData)
imageProxy.close()
} catch (e: Exception) {
Log.e("CameraManager", "Error processing frame: ${e.message}")
imageProxy.close()
}
}
Обновлена функция stopCamera()
fun stopCamera() {
try {
cameraProvider?.unbindAll()
analysisExecutor.shutdown() // ← ДОБАВЛЕНА
cameraExecutor.shutdown()
onFrameAvailable = null // ← ДОБАВЛЕНА
Log.d("CameraManager", "Camera stopped")
} catch (exc: Exception) {
Log.e("CameraManager", "Error stopping camera", exc)
}
}
2️⃣ MainActivity.kt
Изменен вызов startCamera()
Было:
cameraManager.startCamera(
lifecycleOwner,
pv.surfaceProvider,
onError = { err -> Log.e("CameraManager", "Camera error: $err") }
)
Стало:
cameraManager.startCamera(
lifecycleOwner,
pv.surfaceProvider,
onError = { err -> Log.e("CameraManager", "Camera error: $err") },
onFrame = { frameData -> // ← НОВЫЙ CALLBACK
// Send video frame to ViewModel for transmission to server
viewModel.sendVideoFrame(frameData)
}
)
3️⃣ WebSocketManager.kt
Изменены импорты
Было:
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okhttp3.Response
Стало:
import android.util.Log
import okio.ByteString.Companion.toByteString // ← ИЗМЕНЕНО
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okhttp3.Response
import java.util.concurrent.TimeUnit
Переписана функция sendBinary()
Было:
fun sendBinary(data: ByteArray) {
try {
// Create ByteString from ByteArray using reflection to avoid import issues
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)
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")
}
}
Стало:
fun sendBinary(data: ByteArray) {
try {
val byteString = data.toByteString() // ← CLEAN API
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
Улучшена функция sendVideoFrame()
Было:
fun sendVideoFrame(frameData: ByteArray) {
try {
wsManager?.sendBinary(frameData)
// Update statistics
frameCount++
totalBytesTransferred += frameData.size
_bytesTransferred.value = totalBytesTransferred
// Update FPS every second
val currentTime = System.currentTimeMillis()
if (currentTime - lastFpsTime >= 1000) {
_fps.value = frameCount
frameCount = 0
lastFpsTime = currentTime
}
} catch (e: Exception) {
Log.e("StreamViewModel", "Failed to send frame: ${e.message}")
}
}
Стало:
fun sendVideoFrame(frameData: ByteArray) {
try {
wsManager?.sendBinary(frameData)
// Update statistics
frameCount++
totalBytesTransferred += frameData.size
_bytesTransferred.value = totalBytesTransferred
// Update FPS every second
val currentTime = System.currentTimeMillis()
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
Удалены ненужные разрешения
Было:
<!-- Required permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<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" />
Стало:
<!-- Required permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
📊 Статистика изменений
| Файл | Строк добавлено | Строк удалено | Изменено |
|---|---|---|---|
| CameraManager.kt | 45 | 5 | Функция startCamera(), метод processFrame() |
| MainActivity.kt | 5 | 1 | Вызов startCamera() |
| WebSocketManager.kt | 8 | 15 | Метод sendBinary() |
| StreamViewModel.kt | 2 | 1 | Функция sendVideoFrame() |
| AndroidManifest.xml | 0 | 4 | Разрешения |
| ВСЕГО | 60 | 26 | 5 файлов |
🔍 Детализация по функциям
CameraManager - Добавлены строки (примерно)
Новые поля:
line 18: private val analysisExecutor = Executors.newSingleThreadExecutor()
line 19: private var onFrameAvailable: ((ByteArray) -> Unit)? = null
line 20: private var frameCount = 0
line 21: private var lastLogTime = System.currentTimeMillis()
Параметр startCamera:
line 24: onFrame: ((ByteArray) -> Unit)? = null
Новый ImageAnalysis:
line 49-58: val imageAnalysis = ImageAnalysis.Builder()
Новая функция processFrame:
line 89-110: private fun processFrame(imageProxy: ImageProxy) { ... }
В bindToLifecycle:
line 76: imageAnalysis
В stopCamera:
line 124: analysisExecutor.shutdown()
line 125: onFrameAvailable = null
MainActivity - Добавлены строки
Новый callback в startCamera:
line X: onFrame = { frameData ->
line X+1: viewModel.sendVideoFrame(frameData)
line X+2: }
✅ Компиляция
BUILD SUCCESSFUL in 6s
97 actionable tasks: 15 executed, 82 up-to-date
Никаких ошибок:
- ✅ No compilation errors
- ✅ All imports resolved
- ✅ Type safety verified
📦 Артефакты
Созданные файлы документации
VIDEO_STREAMING_FIX.md- Детальное объяснениеDEBUGGING_SUMMARY.md- Полный анализTESTING_GUIDE.md- Инструкции тестированияQUICK_SUMMARY.md- Краткое резюмеLOGS_COMPARISON.md- Сравнение логовCHANGES.md- Этот файл
Скомпилированные APK файлы
/app/build/outputs/apk/debug/app-debug.apk/app/build/outputs/apk/release/app-release.apk
🎯 Следующие шаги
Немедленно
- ✅ Скомпилировать:
./gradlew build(ГОТОВО) - Установить:
./gradlew installDebug - Запустить на устройстве
- Проверить логи в logcat
Для оптимизации
- Добавить H.264 видеокодирование
- Масштабировать фреймы перед отправкой
- Реализовать переподключение при разрыве
🔗 Связанные файлы
app/src/main/java/com/example/camcontrol/CameraManager.ktapp/src/main/java/com/example/camcontrol/MainActivity.ktapp/src/main/java/com/example/camcontrol/WebSocketManager.ktapp/src/main/java/com/example/camcontrol/StreamViewModel.ktapp/src/main/AndroidManifest.xml
Создано: 2025-12-03
Проверено: ✅ Успешно компилируется
Статус: 🚀 Готово к тестированию