@@ -61,13 +61,12 @@ class AvcServer(private val bitrate: Int, private val scale: Float, private val
6161 " --fps" -> {
6262 if (i + 1 < args.size) {
6363 val parsedFps = args[i + 1 ].toIntOrNull()
64- if (parsedFps == null ) {
65- throw IllegalArgumentException ( " Invalid fps value: ${args[i + 1 ]} . Must be an integer between $MIN_FPS and $MAX_FPS " )
66- }
67- if (parsedFps < MIN_FPS || parsedFps > MAX_FPS ) {
68- throw IllegalArgumentException ( " fps value out of range: $parsedFps . Must be between $MIN_FPS and $MAX_FPS " )
64+ fps = if (parsedFps != null && parsedFps in MIN_FPS .. MAX_FPS ) {
65+ parsedFps
66+ } else {
67+ Log .w( TAG , " Invalid fps value: ${args[i + 1 ]} . Using default: $DEFAULT_FPS " )
68+ DEFAULT_FPS
6969 }
70- fps = parsedFps
7170 i++
7271 }
7372 }
@@ -103,6 +102,33 @@ class AvcServer(private val bitrate: Int, private val scale: Float, private val
103102 shutdownLatch.countDown()
104103 }
105104
105+ private fun cleanupResources (
106+ stdoutChannel : java.nio.channels.FileChannel ? ,
107+ codec : MediaCodec ? ,
108+ virtualDisplay : VirtualDisplay ?
109+ ) {
110+ try {
111+ stdoutChannel?.close()
112+ } catch (e: Exception ) {
113+ Log .e(TAG , " Error closing stdout channel" , e)
114+ }
115+ try {
116+ codec?.stop()
117+ } catch (e: Exception ) {
118+ Log .e(TAG , " Error stopping codec" , e)
119+ }
120+ try {
121+ codec?.release()
122+ } catch (e: Exception ) {
123+ Log .e(TAG , " Error releasing codec" , e)
124+ }
125+ try {
126+ virtualDisplay?.release()
127+ } catch (e: Exception ) {
128+ Log .e(TAG , " Error releasing virtual display" , e)
129+ }
130+ }
131+
106132 private fun streamAvcFrames () {
107133 val displayInfo = DisplayUtils .getDisplayInfo()
108134 val scaledWidth = (displayInfo.width * scale).toInt()
@@ -163,6 +189,8 @@ class AvcServer(private val bitrate: Int, private val scale: Float, private val
163189 // Low latency settings
164190 setInteger(MediaFormat .KEY_LATENCY , 0 ) // Request lowest latency
165191 setInteger(MediaFormat .KEY_PRIORITY , 0 ) // Realtime priority
192+ // Repeat previous frame after 100ms to keep stream alive when screen is static
193+ setLong(MediaFormat .KEY_REPEAT_PREVIOUS_FRAME_AFTER , 100_000L ) // 100ms in microseconds
166194 }
167195
168196 Log .d(TAG , " MediaFormat created: $format " )
@@ -206,7 +234,7 @@ class AvcServer(private val bitrate: Int, private val scale: Float, private val
206234 Log .d(TAG , " AVC encoder started" )
207235
208236 val bufferInfo = MediaCodec .BufferInfo ()
209- val timeout = 10000L // 10ms timeout for lower latency
237+ val timeout = 100_000L // 100ms timeout for responsive shutdown (matches REPEAT_FRAME_DELAY)
210238
211239 // Get FileChannel for stdout to write directly from ByteBuffer (zero-copy)
212240 val stdoutChannel = FileOutputStream (FileDescriptor .out ).channel
@@ -243,9 +271,9 @@ class AvcServer(private val bitrate: Int, private val scale: Float, private val
243271 }
244272 } catch (e: IOException ) {
245273 // Pipe broken - client disconnected
246- Log .d(TAG , " Output pipe broken, shutting down " )
247- shutdown( )
248- break
274+ Log .d(TAG , " Output pipe broken, cleaning up and exiting " )
275+ cleanupResources(stdoutChannel, codec, virtualDisplay )
276+ exitProcess( 0 )
249277 }
250278
251279 // Log frame info
@@ -297,10 +325,7 @@ class AvcServer(private val bitrate: Int, private val scale: Float, private val
297325 }
298326 } finally {
299327 Log .d(TAG , " Stopping AVC encoder" )
300- stdoutChannel.close()
301- codec.stop()
302- codec.release()
303- virtualDisplay.release()
328+ cleanupResources(stdoutChannel, codec, virtualDisplay)
304329 }
305330 }
306331}
0 commit comments