upstream: scrcpy v4.1 (#59)
编译期下载 URL/版本/SHA256 更新至 v4.1 支持 VP8 与 VP9 解码 支持 ignoreVideoEncoderConstraints 参数 AnnexBDecoder.kt 重命名至 MediaCodecVideoDecoder.kt,避免在使用 vp8/vp9 时的语义问题
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
- 依赖: scrcpy 升级至 v4.1
|
||||||
|
- 新增: vp8/vp9 解码
|
||||||
|
|
||||||
## 0.4.5
|
## 0.4.5
|
||||||
|
|
||||||
- 重构: 改了好多不知道怎么写,总之修复了一些已知问题
|
- 重构: 改了好多不知道怎么写,总之修复了一些已知问题
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ android {
|
|||||||
applicationId = "io.github.miuzarte.scrcpyforandroid"
|
applicationId = "io.github.miuzarte.scrcpyforandroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 35
|
versionCode = 36
|
||||||
versionName = "0.4.5_pre1"
|
versionName = "0.5.0"
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -168,9 +168,9 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val scrcpyServerAssetDir = "${project.projectDir}/src/main/assets/bin"
|
val scrcpyServerAssetDir = "${project.projectDir}/src/main/assets/bin"
|
||||||
val scrcpyServerAssetFile = "$scrcpyServerAssetDir/scrcpy-server-v4.0"
|
val scrcpyServerAssetFile = "$scrcpyServerAssetDir/scrcpy-server-v4.1"
|
||||||
val scrcpyServerDownloadUrl = "https://github.com/Genymobile/scrcpy/releases/download/v4.0/scrcpy-server-v4.0"
|
val scrcpyServerDownloadUrl = "https://github.com/Genymobile/scrcpy/releases/download/v4.1/scrcpy-server-v4.1"
|
||||||
val scrcpyServerSha256 = "84924bd564a1eb6089c872c7521f968058977f91f5ff02514a8c74aff3210f3a"
|
val scrcpyServerSha256 = "deacb991ed2509715160ffdc7907e47b4160eb30d1566217e9047fd5b8850cae"
|
||||||
|
|
||||||
val downloadScrcpyServer by tasks.registering {
|
val downloadScrcpyServer by tasks.registering {
|
||||||
description = "Download scrcpy-server binary from GitHub releases if absent or SHA256 mismatch"
|
description = "Download scrcpy-server binary from GitHub releases if absent or SHA256 mismatch"
|
||||||
@@ -203,7 +203,7 @@ val downloadScrcpyServer by tasks.registering {
|
|||||||
val needsDownload = !file.exists() || computeSha256(file) != expectedSha
|
val needsDownload = !file.exists() || computeSha256(file) != expectedSha
|
||||||
|
|
||||||
if (needsDownload) {
|
if (needsDownload) {
|
||||||
logger.lifecycle("Downloading scrcpy-server-v4.0 from GitHub releases...")
|
logger.lifecycle("Downloading scrcpy-server-v4.1 from GitHub releases...")
|
||||||
try {
|
try {
|
||||||
URI(url).toURL().openStream().use { input ->
|
URI(url).toURL().openStream().use { input ->
|
||||||
file.outputStream().use { output ->
|
file.outputStream().use { output ->
|
||||||
@@ -212,7 +212,7 @@ val downloadScrcpyServer by tasks.registering {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw GradleException(
|
throw GradleException(
|
||||||
"Failed to download scrcpy-server-v4.0 from GitHub releases.\n" +
|
"Failed to download scrcpy-server-v4.1 from GitHub releases.\n" +
|
||||||
" URL: $url\n" +
|
" URL: $url\n" +
|
||||||
" You may download it manually and place it at: ${file.absolutePath}\n" +
|
" You may download it manually and place it at: ${file.absolutePath}\n" +
|
||||||
" If you are behind a proxy, check your Gradle proxy settings\n" +
|
" If you are behind a proxy, check your Gradle proxy settings\n" +
|
||||||
@@ -223,14 +223,14 @@ val downloadScrcpyServer by tasks.registering {
|
|||||||
|
|
||||||
val actualSha = computeSha256(file)
|
val actualSha = computeSha256(file)
|
||||||
require(actualSha == expectedSha) {
|
require(actualSha == expectedSha) {
|
||||||
"SHA256 mismatch for scrcpy-server-v4.0!\n" +
|
"SHA256 mismatch for scrcpy-server-v4.1!\n" +
|
||||||
" Expected: $expectedSha\n" +
|
" Expected: $expectedSha\n" +
|
||||||
" Got: $actualSha\n" +
|
" Got: $actualSha\n" +
|
||||||
" Delete ${file.absolutePath} to retry download."
|
" Delete ${file.absolutePath} to retry download."
|
||||||
}
|
}
|
||||||
logger.lifecycle("scrcpy-server-v4.0 downloaded and verified.")
|
logger.lifecycle("scrcpy-server-v4.1 downloaded and verified.")
|
||||||
} else {
|
} else {
|
||||||
logger.lifecycle("scrcpy-server-v4.0 exists with correct SHA256, skip download.")
|
logger.lifecycle("scrcpy-server-v4.1 exists with correct SHA256, skip download.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ object NativeCoreFacade {
|
|||||||
val info = scrcpy.currentSessionState.value ?: return@withLock
|
val info = scrcpy.currentSessionState.value ?: return@withLock
|
||||||
if (info.width <= 0 || info.height <= 0) return@withLock
|
if (info.width <= 0 || info.height <= 0) return@withLock
|
||||||
|
|
||||||
val mime = mimeForCodec(info.codec)
|
val mime = info.codec?.mime ?: "video/avc"
|
||||||
if (!DecoderCapabilities.isSizeSupported(mime, info.width, info.height)) {
|
if (!DecoderCapabilities.isSizeSupported(mime, info.width, info.height)) {
|
||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -323,12 +323,5 @@ object NativeCoreFacade {
|
|||||||
runCatching { scrcpy.start(options) }
|
runCatching { scrcpy.start(options) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mimeForCodec(codec: Codec?): String = when (codec) {
|
|
||||||
Codec.H264 -> "video/avc"
|
|
||||||
Codec.H265 -> "video/hevc"
|
|
||||||
Codec.AV1 -> "video/av01"
|
|
||||||
else -> "video/avc"
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val TAG = "NativeCoreFacade"
|
private const val TAG = "NativeCoreFacade"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,21 @@ import android.util.Log
|
|||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AnnexBDecoder
|
* MediaCodecVideoDecoder
|
||||||
*
|
*
|
||||||
* Purpose:
|
* Purpose:
|
||||||
* - Wraps Android MediaCodec for Annex-B framed codecs (H.264/H.265/AV1).
|
* - Wraps Android MediaCodec for all video codecs (H.264/H.265/AV1/VP8/VP9).
|
||||||
* - Handles critical startup packets (config/keyframes) and provides callbacks
|
* - Handles critical startup packets (config/keyframes) and provides callbacks
|
||||||
* for output size changes and FPS updates.
|
* for output size changes and FPS updates.
|
||||||
|
* - Codecs without Annex-B config packets (VP8/VP9) work natively: no CSD is
|
||||||
|
* required for MediaCodec initialization.
|
||||||
*
|
*
|
||||||
* Threading / safety:
|
* Threading / safety:
|
||||||
* - Public methods are synchronized to allow calls from multiple threads
|
* - Public methods are synchronized to allow calls from multiple threads
|
||||||
* (packet producer vs. teardown). Internally, MediaCodec callbacks and
|
* (packet producer vs. teardown). Internally, MediaCodec callbacks and
|
||||||
* buffer queues are used on the calling thread.
|
* buffer queues are used on the calling thread.
|
||||||
*/
|
*/
|
||||||
class AnnexBDecoder(
|
class MediaCodecVideoDecoder(
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
outputSurface: Surface,
|
outputSurface: Surface,
|
||||||
@@ -232,7 +234,7 @@ class AnnexBDecoder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "AnnexBDecoder"
|
private const val TAG = "MediaCodecVideoDecoder"
|
||||||
private const val MIME_AVC = "video/avc"
|
private const val MIME_AVC = "video/avc"
|
||||||
private const val INPUT_TIMEOUT_US = 10_000L
|
private const val INPUT_TIMEOUT_US = 10_000L
|
||||||
private const val CRITICAL_INPUT_TIMEOUT_US = 50_000L
|
private const val CRITICAL_INPUT_TIMEOUT_US = 50_000L
|
||||||
@@ -13,7 +13,7 @@ import java.util.ArrayDeque
|
|||||||
import java.util.concurrent.CopyOnWriteArraySet
|
import java.util.concurrent.CopyOnWriteArraySet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owns the video [AnnexBDecoder] lifecycle and all state surrounding it:
|
* Owns the video [MediaCodecVideoDecoder] lifecycle and all state surrounding it:
|
||||||
*
|
*
|
||||||
* - Creating / rebuilding / releasing the decoder bound to the persistent renderer surface.
|
* - Creating / rebuilding / releasing the decoder bound to the persistent renderer surface.
|
||||||
* - Caching and replaying bootstrap packets (config + keyframe + following frames) so a
|
* - Caching and replaying bootstrap packets (config + keyframe + following frames) so a
|
||||||
@@ -27,7 +27,7 @@ import java.util.concurrent.CopyOnWriteArraySet
|
|||||||
* - All public methods are safe to call from any thread; mutations are guarded by
|
* - All public methods are safe to call from any thread; mutations are guarded by
|
||||||
* [controllerLock] for synchronous methods and by the facade's session mutex for
|
* [controllerLock] for synchronous methods and by the facade's session mutex for
|
||||||
* suspend methods.
|
* suspend methods.
|
||||||
* - The decoder itself is single-threaded (AnnexBDecoder is @Synchronized).
|
* - The decoder itself is single-threaded (MediaCodecVideoDecoder is @Synchronized).
|
||||||
*
|
*
|
||||||
* This class does **not** decide session restart policy; it reports errors and lets
|
* This class does **not** decide session restart policy; it reports errors and lets
|
||||||
* [NativeCoreFacade] decide whether to restart the scrcpy session.
|
* [NativeCoreFacade] decide whether to restart the scrcpy session.
|
||||||
@@ -38,7 +38,7 @@ internal class VideoDecoderController(
|
|||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
private val controllerLock = Any()
|
private val controllerLock = Any()
|
||||||
private var decoder: AnnexBDecoder? = null
|
private var decoder: MediaCodecVideoDecoder? = null
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var currentSessionInfo: Scrcpy.Session.SessionInfo? = null
|
private var currentSessionInfo: Scrcpy.Session.SessionInfo? = null
|
||||||
@@ -239,7 +239,7 @@ internal class VideoDecoderController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core method: construct a new [AnnexBDecoder] and replay bootstrap packets into it.
|
* Core method: construct a new [MediaCodecVideoDecoder] and replay bootstrap packets into it.
|
||||||
*
|
*
|
||||||
* Caller must have already released the old decoder (if any) and, when needed, recreated
|
* Caller must have already released the old decoder (if any) and, when needed, recreated
|
||||||
* the persistent surface. Must be called while holding [controllerLock].
|
* the persistent surface. Must be called while holding [controllerLock].
|
||||||
@@ -255,12 +255,7 @@ internal class VideoDecoderController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val surface = renderer.getDecoderSurface()
|
val surface = renderer.getDecoderSurface()
|
||||||
val mime = when (session.codec) {
|
val mime = session.codec?.mime ?: "video/avc"
|
||||||
Codec.H264 -> "video/avc"
|
|
||||||
Codec.H265 -> "video/hevc"
|
|
||||||
Codec.AV1 -> "video/av01"
|
|
||||||
else -> "video/avc"
|
|
||||||
}
|
|
||||||
Log.i(
|
Log.i(
|
||||||
TAG,
|
TAG,
|
||||||
"createOrReplaceDecoder(): codec=${session.codec?.string ?: "null"}, " +
|
"createOrReplaceDecoder(): codec=${session.codec?.string ?: "null"}, " +
|
||||||
@@ -269,7 +264,7 @@ internal class VideoDecoderController(
|
|||||||
"persistent=true",
|
"persistent=true",
|
||||||
)
|
)
|
||||||
val newDecoder = try {
|
val newDecoder = try {
|
||||||
AnnexBDecoder(
|
MediaCodecVideoDecoder(
|
||||||
width = session.width,
|
width = session.width,
|
||||||
height = session.height,
|
height = session.height,
|
||||||
outputSurface = surface,
|
outputSurface = surface,
|
||||||
@@ -277,7 +272,7 @@ internal class VideoDecoderController(
|
|||||||
onOutputSizeChanged = { width, height ->
|
onOutputSizeChanged = { width, height ->
|
||||||
val current = currentSessionInfo
|
val current = currentSessionInfo
|
||||||
if (current == null || (current.width == width && current.height == height)) {
|
if (current == null || (current.width == width && current.height == height)) {
|
||||||
return@AnnexBDecoder
|
return@MediaCodecVideoDecoder
|
||||||
}
|
}
|
||||||
Log.i(
|
Log.i(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -340,7 +335,7 @@ internal class VideoDecoderController(
|
|||||||
|
|
||||||
// ---------- Bootstrap packet cache ----------
|
// ---------- Bootstrap packet cache ----------
|
||||||
|
|
||||||
private fun replayBootstrapPackets(decoder: AnnexBDecoder) {
|
private fun replayBootstrapPackets(decoder: MediaCodecVideoDecoder) {
|
||||||
val snapshot = synchronized(bootstrapLock) { bootstrapPackets.toList() }
|
val snapshot = synchronized(bootstrapLock) { bootstrapPackets.toList() }
|
||||||
if (snapshot.isEmpty()) {
|
if (snapshot.isEmpty()) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1779,6 +1779,16 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.scrcpyopt_ignore_video_encoder_constraints),
|
||||||
|
summary = "--ignore-video-encoder-constraints",
|
||||||
|
checked = soBundle.ignoreVideoEncoderConstraints,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
ignoreVideoEncoderConstraints = it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -212,6 +212,12 @@ data class ClientOptions(
|
|||||||
|
|
||||||
// --flex-display
|
// --flex-display
|
||||||
var flexDisplay: Boolean = false, // to server
|
var flexDisplay: Boolean = false, // to server
|
||||||
|
|
||||||
|
// --ignore-video-encoder-constraints
|
||||||
|
var ignoreVideoEncoderConstraints: Boolean = false, // to server
|
||||||
|
|
||||||
|
// --no-terminal-title
|
||||||
|
// var updateTerminalTitle: Boolean = true,
|
||||||
) {
|
) {
|
||||||
enum class KeyInjectMode(val string: String) {
|
enum class KeyInjectMode(val string: String) {
|
||||||
MIXED("mixed"),
|
MIXED("mixed"),
|
||||||
@@ -633,6 +639,7 @@ data class ClientOptions(
|
|||||||
vdSystemDecorations = vdSystemDecorations,
|
vdSystemDecorations = vdSystemDecorations,
|
||||||
keepActive = keepActive,
|
keepActive = keepActive,
|
||||||
flexDisplay = flexDisplay,
|
flexDisplay = flexDisplay,
|
||||||
|
ignoreVideoEncoderConstraints = ignoreVideoEncoderConstraints,
|
||||||
list = list,
|
list = list,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,9 +116,9 @@ class Scrcpy(
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "Scrcpy"
|
private const val TAG = "Scrcpy"
|
||||||
|
|
||||||
const val DEFAULT_SERVER_ASSET = "bin/scrcpy-server-v4.0"
|
const val DEFAULT_SERVER_ASSET = "bin/scrcpy-server-v4.1"
|
||||||
const val DEFAULT_SERVER_ASSET_NAME = "scrcpy-server-v4.0"
|
const val DEFAULT_SERVER_ASSET_NAME = "scrcpy-server-v4.1"
|
||||||
const val DEFAULT_SERVER_VERSION = "4.0"
|
const val DEFAULT_SERVER_VERSION = "4.1"
|
||||||
const val DEFAULT_REMOTE_PATH = "/data/local/tmp/scrcpy-server.jar"
|
const val DEFAULT_REMOTE_PATH = "/data/local/tmp/scrcpy-server.jar"
|
||||||
|
|
||||||
// Regex patterns for parsing server output
|
// Regex patterns for parsing server output
|
||||||
@@ -389,6 +389,10 @@ class Scrcpy(
|
|||||||
session.setClipboard(text, paste)
|
session.setClipboard(text, paste)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun scanFile(path: String) = withContext(Dispatchers.IO) {
|
||||||
|
session.scanFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun injectTouch(
|
suspend fun injectTouch(
|
||||||
action: Int,
|
action: Int,
|
||||||
pointerId: Long,
|
pointerId: Long,
|
||||||
@@ -1317,6 +1321,10 @@ class Scrcpy(
|
|||||||
withControlWriter("setClipboard") { setClipboard(text, paste) }
|
withControlWriter("setClipboard") { setClipboard(text, paste) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun scanFile(path: String) = mutex.withLock {
|
||||||
|
withControlWriter("scanFile") { scanFile(path) }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun injectTouch(
|
suspend fun injectTouch(
|
||||||
action: Int,
|
action: Int,
|
||||||
pointerId: Long,
|
pointerId: Long,
|
||||||
@@ -1775,6 +1783,16 @@ class Scrcpy(
|
|||||||
output.flush()
|
output.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun scanFile(path: String) {
|
||||||
|
val bytes = path.toByteArray(Charsets.UTF_8)
|
||||||
|
require(bytes.size <= 256) { "scan file path is too long (max 256 bytes)" }
|
||||||
|
output.writeByte(TYPE_SCAN_FILE)
|
||||||
|
output.writeInt(bytes.size)
|
||||||
|
output.write(bytes)
|
||||||
|
output.flush()
|
||||||
|
}
|
||||||
|
|
||||||
private fun writePosition(x: Int, y: Int, screenWidth: Int, screenHeight: Int) {
|
private fun writePosition(x: Int, y: Int, screenWidth: Int, screenHeight: Int) {
|
||||||
output.writeInt(x)
|
output.writeInt(x)
|
||||||
output.writeInt(y)
|
output.writeInt(y)
|
||||||
@@ -1836,6 +1854,7 @@ class Scrcpy(
|
|||||||
private const val TYPE_CAMERA_ZOOM_IN = 19
|
private const val TYPE_CAMERA_ZOOM_IN = 19
|
||||||
private const val TYPE_CAMERA_ZOOM_OUT = 20
|
private const val TYPE_CAMERA_ZOOM_OUT = 20
|
||||||
private const val TYPE_RESIZE_DISPLAY = 21
|
private const val TYPE_RESIZE_DISPLAY = 21
|
||||||
|
private const val TYPE_SCAN_FILE = 22
|
||||||
|
|
||||||
private const val SEQUENCE_INVALID = 0L
|
private const val SEQUENCE_INVALID = 0L
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ data class ServerParams(
|
|||||||
val vdSystemDecorations: Boolean,
|
val vdSystemDecorations: Boolean,
|
||||||
val keepActive: Boolean,
|
val keepActive: Boolean,
|
||||||
val flexDisplay: Boolean,
|
val flexDisplay: Boolean,
|
||||||
|
val ignoreVideoEncoderConstraints: Boolean,
|
||||||
|
|
||||||
val list: ListOptions,
|
val list: ListOptions,
|
||||||
) {
|
) {
|
||||||
@@ -268,6 +269,9 @@ data class ServerParams(
|
|||||||
if (flexDisplay) {
|
if (flexDisplay) {
|
||||||
cmd.add("flex_display=true")
|
cmd.add("flex_display=true")
|
||||||
}
|
}
|
||||||
|
if (ignoreVideoEncoderConstraints) {
|
||||||
|
cmd.add("ignore_video_encoder_constraints=true")
|
||||||
|
}
|
||||||
if (displayImePolicy != DisplayImePolicy.UNDEFINED) {
|
if (displayImePolicy != DisplayImePolicy.UNDEFINED) {
|
||||||
cmd.add("display_ime_policy=${displayImePolicy.string}")
|
cmd.add("display_ime_policy=${displayImePolicy.string}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,18 +82,21 @@ class Shared {
|
|||||||
enum class Codec(
|
enum class Codec(
|
||||||
val string: String,
|
val string: String,
|
||||||
val displayName: String,
|
val displayName: String,
|
||||||
|
val mime: String,
|
||||||
val type: Type,
|
val type: Type,
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val isLossless: Boolean,
|
val isLossless: Boolean,
|
||||||
) {
|
) {
|
||||||
H264("h264", "H.264", Type.VIDEO, 0x68323634, false), // default, ignore when passing
|
H264("h264", "H.264", "video/avc", Type.VIDEO, 0x68323634, false), // default, ignore when passing
|
||||||
H265("h265", "H.265", Type.VIDEO, 0x68323635, false),
|
H265("h265", "H.265", "video/hevc", Type.VIDEO, 0x68323635, false),
|
||||||
AV1("av1", "AV1", Type.VIDEO, 0x00617631, false),
|
AV1("av1", "AV1", "video/av01", Type.VIDEO, 0x00617631, false),
|
||||||
|
VP8("vp8", "VP8", "video/x-vnd.on2.vp8", Type.VIDEO, 0x00767038, false),
|
||||||
|
VP9("vp9", "VP9", "video/x-vnd.on2.vp9", Type.VIDEO, 0x00767039, false),
|
||||||
|
|
||||||
OPUS("opus", "Opus", Type.AUDIO, 0x6f707573, false), // default, ignore when passing
|
OPUS("opus", "Opus", "audio/opus", Type.AUDIO, 0x6f707573, false), // default, ignore when passing
|
||||||
AAC("aac", "AAC", Type.AUDIO, 0x00616163, false),
|
AAC("aac", "AAC", "audio/mp4a-latm", Type.AUDIO, 0x00616163, false),
|
||||||
FLAC("flac", "FLAC", Type.AUDIO, 0x666c6163, true),
|
FLAC("flac", "FLAC", "audio/flac", Type.AUDIO, 0x666c6163, true),
|
||||||
RAW("raw", "RAW", Type.AUDIO, 0x00726177, true); // wav raw
|
RAW("raw", "RAW", "audio/raw", Type.AUDIO, 0x00726177, true); // wav raw
|
||||||
|
|
||||||
enum class Type { VIDEO, AUDIO }
|
enum class Type { VIDEO, AUDIO }
|
||||||
|
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
|
|||||||
booleanPreferencesKey("flex_display"),
|
booleanPreferencesKey("flex_display"),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
val IGNORE_VIDEO_ENCODER_CONSTRAINTS = Pair(
|
||||||
|
booleanPreferencesKey("ignore_video_encoder_constraints"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
fun defaultBundle() = Bundle(
|
fun defaultBundle() = Bundle(
|
||||||
crop = CROP.defaultValue,
|
crop = CROP.defaultValue,
|
||||||
@@ -351,6 +355,7 @@ class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
|
|||||||
cameraTorch = CAMERA_TORCH.defaultValue,
|
cameraTorch = CAMERA_TORCH.defaultValue,
|
||||||
keepActive = KEEP_ACTIVE.defaultValue,
|
keepActive = KEEP_ACTIVE.defaultValue,
|
||||||
flexDisplay = FLEX_DISPLAY.defaultValue,
|
flexDisplay = FLEX_DISPLAY.defaultValue,
|
||||||
|
ignoreVideoEncoderConstraints = IGNORE_VIDEO_ENCODER_CONSTRAINTS.defaultValue,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,6 +427,7 @@ class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
|
|||||||
val cameraTorch: Boolean,
|
val cameraTorch: Boolean,
|
||||||
val keepActive: Boolean,
|
val keepActive: Boolean,
|
||||||
val flexDisplay: Boolean,
|
val flexDisplay: Boolean,
|
||||||
|
val ignoreVideoEncoderConstraints: Boolean,
|
||||||
): Parcelable {
|
): Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +497,7 @@ class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
|
|||||||
bundleField(CAMERA_TORCH) { it.cameraTorch },
|
bundleField(CAMERA_TORCH) { it.cameraTorch },
|
||||||
bundleField(KEEP_ACTIVE) { it.keepActive },
|
bundleField(KEEP_ACTIVE) { it.keepActive },
|
||||||
bundleField(FLEX_DISPLAY) { it.flexDisplay },
|
bundleField(FLEX_DISPLAY) { it.flexDisplay },
|
||||||
|
bundleField(IGNORE_VIDEO_ENCODER_CONSTRAINTS) { it.ignoreVideoEncoderConstraints },
|
||||||
)
|
)
|
||||||
|
|
||||||
val bundleState: StateFlow<Bundle> = createBundleState(::bundleFromPreferences)
|
val bundleState: StateFlow<Bundle> = createBundleState(::bundleFromPreferences)
|
||||||
@@ -562,6 +569,7 @@ class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
|
|||||||
cameraTorch = preferences.read(CAMERA_TORCH),
|
cameraTorch = preferences.read(CAMERA_TORCH),
|
||||||
keepActive = preferences.read(KEEP_ACTIVE),
|
keepActive = preferences.read(KEEP_ACTIVE),
|
||||||
flexDisplay = preferences.read(FLEX_DISPLAY),
|
flexDisplay = preferences.read(FLEX_DISPLAY),
|
||||||
|
ignoreVideoEncoderConstraints = preferences.read(IGNORE_VIDEO_ENCODER_CONSTRAINTS),
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun loadBundle() = loadBundle(::bundleFromPreferences)
|
suspend fun loadBundle() = loadBundle(::bundleFromPreferences)
|
||||||
@@ -645,6 +653,7 @@ class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
|
|||||||
cameraTorch = bundle.cameraTorch,
|
cameraTorch = bundle.cameraTorch,
|
||||||
keepActive = bundle.keepActive,
|
keepActive = bundle.keepActive,
|
||||||
flexDisplay = bundle.flexDisplay,
|
flexDisplay = bundle.flexDisplay,
|
||||||
|
ignoreVideoEncoderConstraints = bundle.ignoreVideoEncoderConstraints,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,6 +725,7 @@ internal fun encodeBundleToJson(bundle: ScrcpyOptions.Bundle): JSONObject =
|
|||||||
.put("cameraTorch", bundle.cameraTorch)
|
.put("cameraTorch", bundle.cameraTorch)
|
||||||
.put("keepActive", bundle.keepActive)
|
.put("keepActive", bundle.keepActive)
|
||||||
.put("flexDisplay", bundle.flexDisplay)
|
.put("flexDisplay", bundle.flexDisplay)
|
||||||
|
.put("ignoreVideoEncoderConstraints", bundle.ignoreVideoEncoderConstraints)
|
||||||
|
|
||||||
internal fun decodeBundleFromJson(bundleJson: JSONObject?): ScrcpyOptions.Bundle {
|
internal fun decodeBundleFromJson(bundleJson: JSONObject?): ScrcpyOptions.Bundle {
|
||||||
val json = bundleJson ?: return ScrcpyOptions.defaultBundle()
|
val json = bundleJson ?: return ScrcpyOptions.defaultBundle()
|
||||||
@@ -984,6 +994,10 @@ internal fun decodeBundleFromJson(bundleJson: JSONObject?): ScrcpyOptions.Bundle
|
|||||||
"flexDisplay",
|
"flexDisplay",
|
||||||
ScrcpyOptions.FLEX_DISPLAY.defaultValue,
|
ScrcpyOptions.FLEX_DISPLAY.defaultValue,
|
||||||
),
|
),
|
||||||
|
ignoreVideoEncoderConstraints = json.optBooleanOrDefault(
|
||||||
|
"ignoreVideoEncoderConstraints",
|
||||||
|
ScrcpyOptions.IGNORE_VIDEO_ENCODER_CONSTRAINTS.defaultValue,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,6 +536,7 @@
|
|||||||
<string name="scrcpyopt_no_cleanup">禁用结束后清理</string>
|
<string name="scrcpyopt_no_cleanup">禁用结束后清理</string>
|
||||||
<string name="scrcpyopt_no_cleanup_desc">默认情况下,scrcpy 会从设备中移除服务器二进制文件,并在退出时恢复设备状态(显示触摸、保持唤醒和电源模式)\n此选项将禁用此清理操作</string>
|
<string name="scrcpyopt_no_cleanup_desc">默认情况下,scrcpy 会从设备中移除服务器二进制文件,并在退出时恢复设备状态(显示触摸、保持唤醒和电源模式)\n此选项将禁用此清理操作</string>
|
||||||
<string name="scrcpyopt_flex_display">自动调整尺寸以匹配界面</string>
|
<string name="scrcpyopt_flex_display">自动调整尺寸以匹配界面</string>
|
||||||
|
<string name="scrcpyopt_ignore_video_encoder_constraints">忽略视频编码器约束</string>
|
||||||
<string name="scrcpyopt_start_app">scrcpy 启动后打开应用</string>
|
<string name="scrcpyopt_start_app">scrcpy 启动后打开应用</string>
|
||||||
<string name="scrcpyopt_native">本机</string>
|
<string name="scrcpyopt_native">本机</string>
|
||||||
<string name="scrcpyopt_log_level">日志等级</string>
|
<string name="scrcpyopt_log_level">日志等级</string>
|
||||||
|
|||||||
@@ -537,6 +537,7 @@
|
|||||||
<string name="scrcpyopt_no_cleanup">Disable cleanup on exit</string>
|
<string name="scrcpyopt_no_cleanup">Disable cleanup on exit</string>
|
||||||
<string name="scrcpyopt_no_cleanup_desc">By default, scrcpy removes the server binary from the device and restores device state on exit (show touches, stay awake, and power mode).\nThis option disables this cleanup operation</string>
|
<string name="scrcpyopt_no_cleanup_desc">By default, scrcpy removes the server binary from the device and restores device state on exit (show touches, stay awake, and power mode).\nThis option disables this cleanup operation</string>
|
||||||
<string name="scrcpyopt_flex_display">Automatically resize to match the screen</string>
|
<string name="scrcpyopt_flex_display">Automatically resize to match the screen</string>
|
||||||
|
<string name="scrcpyopt_ignore_video_encoder_constraints">Ignore video encoder constraints</string>
|
||||||
<string name="scrcpyopt_start_app">Open app after scrcpy starts</string>
|
<string name="scrcpyopt_start_app">Open app after scrcpy starts</string>
|
||||||
<string name="scrcpyopt_native">Native</string>
|
<string name="scrcpyopt_native">Native</string>
|
||||||
<string name="scrcpyopt_log_level">Log level</string>
|
<string name="scrcpyopt_log_level">Log level</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user