diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt index 2ca3d1b1..06d116a4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt @@ -158,33 +158,58 @@ class RefreshRateService if (refreshRateSwitch) { displayModes .filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth } - .filter { - it.refreshRateRounded % streamRate == 0 || // Exact multiple - it.refreshRateRounded == (streamRate * 2.5).roundToInt() // eg 24 & 60fps - } + .filter { frameRateMatches(it.refreshRateRounded, streamRate) } } else { displayModes .filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth } } // Timber.v("display modes candidates: %s", candidates.joinToString("\n")) return if (!resolutionSwitch) { - candidates.maxByOrNull { it.physicalWidth * it.physicalHeight } + candidates + .filter { it.refreshRateRounded == streamRate } + .maxByOrNull { it.physicalWidth * it.physicalHeight } + ?: candidates.maxByOrNull { it.physicalWidth * it.physicalHeight } } else { + // Exact resolution & frame rate candidates.firstOrNull { it.physicalWidth == streamWidth && it.physicalHeight == streamHeight && it.refreshRateRounded == streamRate } - ?: candidates.firstOrNull { - it.physicalWidth == streamWidth && + // Next highest resolution, exact frame rate + ?: candidates.lastOrNull { + it.physicalWidth >= streamWidth && it.physicalHeight >= streamHeight && it.refreshRateRounded == streamRate } + // Exact resolution, acceptable frame rate + ?: candidates.lastOrNull { + it.physicalWidth == streamWidth && + it.physicalHeight == streamHeight && + frameRateMatches(it.refreshRateRounded, streamRate) + } + // Next highest resolution, acceptable frame rate + ?: candidates.lastOrNull { + it.physicalWidth >= streamWidth && + it.physicalHeight >= streamHeight && + frameRateMatches(it.refreshRateRounded, streamRate) + } + // Fall back to largest resolution, exact frame rate ?: candidates .filter { it.refreshRateRounded == streamRate } .maxByOrNull { it.physicalWidth * it.physicalHeight } + // Finally, just the highest resolution + ?: candidates.maxByOrNull { it.physicalWidth * it.physicalHeight } } } + + fun frameRateMatches( + refreshRateRounded: Int, + streamRate: Int, + ): Boolean { + return refreshRateRounded % streamRate == 0 || // Exact multiple + refreshRateRounded == (streamRate * 2.5).roundToInt() // eg 24 & 60fps + } } } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestDisplayModeChoice.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestDisplayModeChoice.kt index 7af1bfce..d7e4abfd 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestDisplayModeChoice.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestDisplayModeChoice.kt @@ -15,7 +15,25 @@ class TestDisplayModeChoice { val UHD_30 = DisplayMode(4, 3840, 2160, 30f) val UHD_24 = DisplayMode(5, 3840, 2160, 24f) - val ALL_MODES = listOf(UHD_24, UHD_30, UHD_60, HD_24, HD_30, HD_60) + val HD720_60 = DisplayMode(6, 1280, 720, 60f) + val HD720_30 = DisplayMode(7, 1280, 720, 30f) + val HD720_24 = DisplayMode(8, 1280, 720, 24f) + + val ALL_MODES = + listOf( + UHD_24, + UHD_30, + UHD_60, + HD_24, + HD_30, + HD_60, + HD720_60, + HD720_30, + HD720_24, + ).sortedWith( + compareByDescending({ it.physicalWidth * it.physicalHeight }) + .thenBy { it.refreshRateRounded }, + ) } @Test @@ -32,7 +50,7 @@ class TestDisplayModeChoice { refreshRateSwitch = true, resolutionSwitch = false, ) - Assert.assertEquals(3, result?.modeId) + Assert.assertEquals(UHD_60.modeId, result?.modeId) } @Test @@ -49,7 +67,7 @@ class TestDisplayModeChoice { refreshRateSwitch = true, resolutionSwitch = true, ) - Assert.assertEquals(0, result?.modeId) + Assert.assertEquals(HD_60.modeId, result?.modeId) } @Test @@ -66,7 +84,7 @@ class TestDisplayModeChoice { refreshRateSwitch = true, resolutionSwitch = false, ) - Assert.assertEquals(4, result?.modeId) + Assert.assertEquals(UHD_30.modeId, result?.modeId) } @Test @@ -83,14 +101,14 @@ class TestDisplayModeChoice { refreshRateSwitch = false, resolutionSwitch = true, ) - Assert.assertEquals(1, result?.modeId) + Assert.assertEquals(HD_30.modeId, result?.modeId) } @Test fun testFraction() { val streamWidth = 1920 val streamHeight = 1080 - val streamRealFrameRate = 30f + val streamRealFrameRate = 29.970f val displayModes = listOf( @@ -104,7 +122,7 @@ class TestDisplayModeChoice { displayModes = displayModes, streamWidth = streamWidth, streamHeight = streamHeight, - targetFrameRate = 29.970f, + targetFrameRate = streamRealFrameRate, refreshRateSwitch = true, resolutionSwitch = false, ) @@ -119,6 +137,136 @@ class TestDisplayModeChoice { refreshRateSwitch = true, resolutionSwitch = false, ) - Assert.assertEquals(1, result2?.modeId) + Assert.assertEquals(HD_30.modeId, result2?.modeId) + } + + private fun test( + expected: DisplayMode, + streamWidth: Int, + streamHeight: Int, + streamRealFrameRate: Float, + ) { + val result = + RefreshRateService.findDisplayMode( + displayModes = ALL_MODES, + streamWidth = streamWidth, + streamHeight = streamHeight, + targetFrameRate = streamRealFrameRate, + refreshRateSwitch = false, + resolutionSwitch = true, + ) + Assert.assertEquals( + "streamWidth=$streamWidth, streamHeight=$streamHeight, streamRealFrameRate=$streamRealFrameRate", + expected.modeId, + result?.modeId, + ) + } + + @Test + fun `Test choose best resolution`() { + test(HD720_30, 1280, 720, 30f) + test(HD720_30, 1280, 548, 30f) + test(HD720_30, 640, 480, 30f) + test(HD720_30, 960, 720, 30f) + test(HD720_24, 960, 720, 24f) + } + + @Test + fun `Test 60fps for 24 without resolution switch`() { + val streamWidth = 1920 + val streamHeight = 1080 + val streamRealFrameRate = 24f + + val displayModes = + listOf( + UHD_60, + HD_60, + HD_30, + ) + + val result = + RefreshRateService.findDisplayMode( + displayModes = displayModes, + streamWidth = streamWidth, + streamHeight = streamHeight, + targetFrameRate = streamRealFrameRate, + refreshRateSwitch = true, + resolutionSwitch = false, + ) + Assert.assertEquals(UHD_60.modeId, result?.modeId) + } + + @Test + fun `Test 60fps for 24 with resolution switch`() { + val streamWidth = 1920 + val streamHeight = 1080 + val streamRealFrameRate = 24f + + val displayModes = + listOf( + HD_60, + HD_30, + ) + + val result = + RefreshRateService.findDisplayMode( + displayModes = displayModes, + streamWidth = streamWidth, + streamHeight = streamHeight, + targetFrameRate = streamRealFrameRate, + refreshRateSwitch = true, + resolutionSwitch = true, + ) + Assert.assertEquals(HD_60.modeId, result?.modeId) + } + + @Test + fun `Test prefer refresh rate over resolution`() { + val streamWidth = 1280 + val streamHeight = 720 + val streamRealFrameRate = 24f + + // 720@60 is an acceptable refresh rate, but want to prioritize exact refresh rate + val displayModes = + listOf( + HD_24, + HD720_60, + ) + + val result = + RefreshRateService.findDisplayMode( + displayModes = displayModes, + streamWidth = streamWidth, + streamHeight = streamHeight, + targetFrameRate = streamRealFrameRate, + refreshRateSwitch = true, + resolutionSwitch = true, + ) + Assert.assertEquals(HD_24.modeId, result?.modeId) + } + + @Test + fun `Test prefer refresh rate over resolution2`() { + val streamWidth = 1280 + val streamHeight = 720 + val streamRealFrameRate = 30f + + // 720@60 is an acceptable refresh rate, but want to prioritize exact refresh rate + val displayModes = + listOf( + HD_30, + HD720_60, + ) + + val result = + RefreshRateService.findDisplayMode( + displayModes = displayModes, + streamWidth = streamWidth, + streamHeight = streamHeight, + targetFrameRate = streamRealFrameRate, + refreshRateSwitch = true, + resolutionSwitch = true, + ) + Assert.assertEquals(HD_30.modeId, result?.modeId) } }