Files
cam_control_android/CHANGES.md
2025-12-09 21:21:26 +09:00

11 KiB
Raw Permalink Blame History

и# 📝 Полный список изменений

Дата: 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

📦 Артефакты

Созданные файлы документации

  1. VIDEO_STREAMING_FIX.md - Детальное объяснение
  2. DEBUGGING_SUMMARY.md - Полный анализ
  3. TESTING_GUIDE.md - Инструкции тестирования
  4. QUICK_SUMMARY.md - Краткое резюме
  5. LOGS_COMPARISON.md - Сравнение логов
  6. CHANGES.md - Этот файл

Скомпилированные APK файлы

  • /app/build/outputs/apk/debug/app-debug.apk
  • /app/build/outputs/apk/release/app-release.apk

🎯 Следующие шаги

Немедленно

  1. Скомпилировать: ./gradlew build (ГОТОВО)
  2. Установить: ./gradlew installDebug
  3. Запустить на устройстве
  4. Проверить логи в logcat

Для оптимизации

  1. Добавить H.264 видеокодирование
  2. Масштабировать фреймы перед отправкой
  3. Реализовать переподключение при разрыве

🔗 Связанные файлы

  • app/src/main/java/com/example/camcontrol/CameraManager.kt
  • app/src/main/java/com/example/camcontrol/MainActivity.kt
  • app/src/main/java/com/example/camcontrol/WebSocketManager.kt
  • app/src/main/java/com/example/camcontrol/StreamViewModel.kt
  • app/src/main/AndroidManifest.xml

Создано: 2025-12-03
Проверено: Успешно компилируется
Статус: 🚀 Готово к тестированию