@@ -21,22 +21,54 @@ import java.util.concurrent.CountDownLatch
2121import java.util.concurrent.TimeUnit
2222import kotlin.system.exitProcess
2323
24- class MjpegServer {
24+ class MjpegServer (quality : Int , scale : Float ) {
25+ private val quality: Int = quality
26+ private val scale: Float = scale
27+
2528 companion object {
2629 private const val TAG = " MjpegServer"
2730 private const val BOUNDARY = " BoundaryString"
31+ private const val DEFAULT_QUALITY = 80
32+ private const val DEFAULT_SCALE = 1.0f
2833
2934 @JvmStatic
3035 fun main (args : Array <String >) {
3136 try {
32- val server = MjpegServer ()
37+ val (quality, scale) = parseArguments(args)
38+ val server = MjpegServer (quality, scale)
3339 server.startMjpegStream()
3440 } catch (e: Exception ) {
3541 Log .e(TAG , " Failed to start MJPEG stream" , e)
3642 System .err.println (" Error: ${e.message} " )
3743 exitProcess(1 )
3844 }
3945 }
46+
47+ private fun parseArguments (args : Array <String >): Pair <Int , Float > {
48+ var quality = DEFAULT_QUALITY
49+ var scale = DEFAULT_SCALE
50+
51+ var i = 0
52+ while (i < args.size) {
53+ when (args[i]) {
54+ " --quality" -> {
55+ if (i + 1 < args.size) {
56+ quality = args[i + 1 ].toIntOrNull()?.coerceIn(1 , 100 ) ? : DEFAULT_QUALITY
57+ i++
58+ }
59+ }
60+ " --scale" -> {
61+ if (i + 1 < args.size) {
62+ scale = args[i + 1 ].toFloatOrNull()?.coerceIn(0.1f , 2.0f ) ? : DEFAULT_SCALE
63+ i++
64+ }
65+ }
66+ }
67+ i++
68+ }
69+
70+ return Pair (quality, scale)
71+ }
4072 }
4173
4274 private val shutdownLatch = CountDownLatch (1 )
@@ -74,7 +106,9 @@ Connection: close
74106
75107 private fun streamFrames () {
76108 val displayInfo = getDisplayInfo()
77- Log .d(TAG , " Display info: ${displayInfo.width} x${displayInfo.height} " )
109+ val scaledWidth = (displayInfo.width * scale).toInt()
110+ val scaledHeight = (displayInfo.height * scale).toInt()
111+ Log .d(TAG , " Display info: ${displayInfo.width} x${displayInfo.height} , scaled: ${scaledWidth} x${scaledHeight} , quality: $quality " )
78112
79113 // Create a background thread with looper for image callbacks
80114 val handlerThread = HandlerThread (" ScreenCapture" )
@@ -95,7 +129,7 @@ Connection: close
95129 try {
96130 image = reader.acquireLatestImage()
97131 if (image != null ) {
98- val jpegData = convertImageToJpeg (image)
132+ val jpegData = ImageUtils .convertToJpeg (image, quality, scale )
99133 outputMjpegFrame(jpegData)
100134 Log .d(TAG , " Frame output: ${jpegData.size} bytes" )
101135 }
@@ -144,42 +178,6 @@ Connection: close
144178 System .out .flush()
145179 }
146180
147- private fun convertImageToJpeg (image : Image ): ByteArray {
148- val planes = image.planes
149- val buffer = planes[0 ].buffer
150- val pixelStride = planes[0 ].pixelStride
151- val rowStride = planes[0 ].rowStride
152- val rowPadding = rowStride - pixelStride * image.width
153-
154- // Create bitmap from image data
155- val bitmap = Bitmap .createBitmap(
156- image.width + rowPadding / pixelStride,
157- image.height,
158- Bitmap .Config .ARGB_8888
159- )
160- bitmap.copyPixelsFromBuffer(buffer)
161-
162- // Crop bitmap if there's padding
163- val finalBitmap = if (rowPadding == 0 ) {
164- bitmap
165- } else {
166- Bitmap .createBitmap(bitmap, 0 , 0 , image.width, image.height)
167- }
168-
169- // Convert to JPEG
170- val outputStream = ByteArrayOutputStream ()
171- finalBitmap.compress(Bitmap .CompressFormat .JPEG , 90 , outputStream)
172- val jpegData = outputStream.toByteArray()
173-
174- // Cleanup
175- if (finalBitmap != bitmap) {
176- bitmap.recycle()
177- }
178- finalBitmap.recycle()
179- outputStream.close()
180-
181- return jpegData
182- }
183181
184182 private fun createVirtualDisplay (
185183 width : Int ,
@@ -218,6 +216,7 @@ Connection: close
218216 0 ,
219217 surface
220218 ) as VirtualDisplay
219+
221220 } catch (e: Exception ) {
222221 Log .e(TAG , " Failed to create virtual display" , e)
223222 null
0 commit comments