Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
github_28344065
scrcpy
提交
ad3632f1
S
scrcpy
项目概览
github_28344065
/
scrcpy
与 Fork 源项目一致
从无法访问的项目Fork
通知
8
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
scrcpy
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
ad3632f1
编写于
4月 26, 2020
作者:
T
Tzah Mazuz
提交者:
Romain Vimont
5月 04, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add --codec-options
Co-authored-by:
N
Romain Vimont
<
rom@rom1v.com
>
上级
c7702431
变更
11
隐藏空白更改
内联
并排
Showing
11 changed file
with
300 addition
and
5 deletion
+300
-5
app/scrcpy.1
app/scrcpy.1
+8
-0
app/src/cli.c
app/src/cli.c
+14
-0
app/src/scrcpy.c
app/src/scrcpy.c
+1
-0
app/src/scrcpy.h
app/src/scrcpy.h
+2
-0
app/src/server.c
app/src/server.c
+1
-0
app/src/server.h
app/src/server.h
+1
-0
server/src/main/java/com/genymobile/scrcpy/CodecOption.java
server/src/main/java/com/genymobile/scrcpy/CodecOption.java
+112
-0
server/src/main/java/com/genymobile/scrcpy/Options.java
server/src/main/java/com/genymobile/scrcpy/Options.java
+9
-0
server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
...er/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
+30
-3
server/src/main/java/com/genymobile/scrcpy/Server.java
server/src/main/java/com/genymobile/scrcpy/Server.java
+8
-2
server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
...src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
+114
-0
未找到文件。
app/scrcpy.1
浏览文件 @
ad3632f1
...
...
@@ -25,6 +25,14 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are
Default is 8000000.
.TP
.BI "\-\-\codec\-options " key[:type]=value[,...]
Set a list of comma-separated key:type=value options for the device encoder.
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
The list of possible codec options is available in the Android documentation:
.UR https://d.android.com/reference/android/media/MediaFormat
.UE .
.TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server.
...
...
app/src/cli.c
浏览文件 @
ad3632f1
...
...
@@ -30,6 +30,15 @@ scrcpy_print_usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).
\n
"
" Default is %d.
\n
"
"
\n
"
" --codec-options key[:type]=value[,...]
\n
"
" Set a list of comma-separated key:type=value options for the
\n
"
" device encoder.
\n
"
" The possible values for 'type' are 'int' (default), 'long',
\n
"
" 'float' and 'string'.
\n
"
" The list of possible codec options is available in the
\n
"
" Android documentation:
\n
"
" <https://d.android.com/reference/android/media/MediaFormat>
\n
"
"
\n
"
" --crop width:height:x:y
\n
"
" Crop the device screen on the server.
\n
"
" The values are expressed in the device natural orientation
\n
"
...
...
@@ -472,12 +481,14 @@ guess_record_format(const char *filename) {
#define OPT_ROTATION 1015
#define OPT_RENDER_DRIVER 1016
#define OPT_NO_MIPMAPS 1017
#define OPT_CODEC_OPTIONS 1018
bool
scrcpy_parse_args
(
struct
scrcpy_cli_args
*
args
,
int
argc
,
char
*
argv
[])
{
static
const
struct
option
long_options
[]
=
{
{
"always-on-top"
,
no_argument
,
NULL
,
OPT_ALWAYS_ON_TOP
},
{
"bit-rate"
,
required_argument
,
NULL
,
'b'
},
{
"codec-options"
,
required_argument
,
NULL
,
OPT_CODEC_OPTIONS
},
{
"crop"
,
required_argument
,
NULL
,
OPT_CROP
},
{
"display"
,
required_argument
,
NULL
,
OPT_DISPLAY_ID
},
{
"fullscreen"
,
no_argument
,
NULL
,
'f'
},
...
...
@@ -647,6 +658,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case
OPT_NO_MIPMAPS
:
opts
->
mipmaps
=
false
;
break
;
case
OPT_CODEC_OPTIONS
:
opts
->
codec_options
=
optarg
;
break
;
default:
// getopt prints the error message on stderr
return
false
;
...
...
app/src/scrcpy.c
浏览文件 @
ad3632f1
...
...
@@ -279,6 +279,7 @@ scrcpy(const struct scrcpy_options *options) {
.
display_id
=
options
->
display_id
,
.
show_touches
=
options
->
show_touches
,
.
stay_awake
=
options
->
stay_awake
,
.
codec_options
=
options
->
codec_options
,
};
if
(
!
server_start
(
&
server
,
options
->
serial
,
&
params
))
{
return
false
;
...
...
app/src/scrcpy.h
浏览文件 @
ad3632f1
...
...
@@ -16,6 +16,7 @@ struct scrcpy_options {
const
char
*
window_title
;
const
char
*
push_target
;
const
char
*
render_driver
;
const
char
*
codec_options
;
enum
recorder_format
record_format
;
struct
port_range
port_range
;
uint16_t
max_size
;
...
...
@@ -48,6 +49,7 @@ struct scrcpy_options {
.window_title = NULL, \
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.record_format = RECORDER_FORMAT_AUTO, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
...
...
app/src/server.c
浏览文件 @
ad3632f1
...
...
@@ -270,6 +270,7 @@ execute_server(struct server *server, const struct server_params *params) {
display_id_string
,
params
->
show_touches
?
"true"
:
"false"
,
params
->
stay_awake
?
"true"
:
"false"
,
params
->
codec_options
?
params
->
codec_options
:
"-"
,
};
#ifdef SERVER_DEBUGGER
LOGI
(
"Server debugger waiting for a client on device port "
...
...
app/src/server.h
浏览文件 @
ad3632f1
...
...
@@ -44,6 +44,7 @@ struct server {
struct
server_params
{
const
char
*
crop
;
const
char
*
codec_options
;
struct
port_range
port_range
;
uint16_t
max_size
;
uint32_t
bit_rate
;
...
...
server/src/main/java/com/genymobile/scrcpy/CodecOption.java
0 → 100644
浏览文件 @
ad3632f1
package
com.genymobile.scrcpy
;
import
java.util.ArrayList
;
import
java.util.List
;
public
class
CodecOption
{
private
String
key
;
private
Object
value
;
public
CodecOption
(
String
key
,
Object
value
)
{
this
.
key
=
key
;
this
.
value
=
value
;
}
public
String
getKey
()
{
return
key
;
}
public
Object
getValue
()
{
return
value
;
}
public
static
List
<
CodecOption
>
parse
(
String
codecOptions
)
{
if
(
"-"
.
equals
(
codecOptions
))
{
return
null
;
}
List
<
CodecOption
>
result
=
new
ArrayList
<>();
boolean
escape
=
false
;
StringBuilder
buf
=
new
StringBuilder
();
for
(
char
c
:
codecOptions
.
toCharArray
())
{
switch
(
c
)
{
case
'\\'
:
if
(
escape
)
{
buf
.
append
(
'\\'
);
escape
=
false
;
}
else
{
escape
=
true
;
}
break
;
case
','
:
if
(
escape
)
{
buf
.
append
(
','
);
escape
=
false
;
}
else
{
// This comma is a separator between codec options
String
codecOption
=
buf
.
toString
();
result
.
add
(
parseOption
(
codecOption
));
// Clear buf
buf
.
setLength
(
0
);
}
break
;
default
:
buf
.
append
(
c
);
break
;
}
}
if
(
buf
.
length
()
>
0
)
{
String
codecOption
=
buf
.
toString
();
result
.
add
(
parseOption
(
codecOption
));
}
return
result
;
}
private
static
CodecOption
parseOption
(
String
option
)
{
int
equalSignIndex
=
option
.
indexOf
(
'='
);
if
(
equalSignIndex
==
-
1
)
{
throw
new
IllegalArgumentException
(
"'=' expected"
);
}
String
keyAndType
=
option
.
substring
(
0
,
equalSignIndex
);
if
(
keyAndType
.
length
()
==
0
)
{
throw
new
IllegalArgumentException
(
"Key may not be null"
);
}
String
key
;
String
type
;
int
colonIndex
=
keyAndType
.
indexOf
(
':'
);
if
(
colonIndex
!=
-
1
)
{
key
=
keyAndType
.
substring
(
0
,
colonIndex
);
type
=
keyAndType
.
substring
(
colonIndex
+
1
);
}
else
{
key
=
keyAndType
;
type
=
"int"
;
// assume int by default
}
Object
value
;
String
valueString
=
option
.
substring
(
equalSignIndex
+
1
);
switch
(
type
)
{
case
"int"
:
value
=
Integer
.
parseInt
(
valueString
);
break
;
case
"long"
:
value
=
Long
.
parseLong
(
valueString
);
break
;
case
"float"
:
value
=
Float
.
parseFloat
(
valueString
);
break
;
case
"string"
:
value
=
valueString
;
break
;
default
:
throw
new
IllegalArgumentException
(
"Invalid codec option type (int, long, float, str): "
+
type
);
}
return
new
CodecOption
(
key
,
value
);
}
}
server/src/main/java/com/genymobile/scrcpy/Options.java
浏览文件 @
ad3632f1
...
...
@@ -14,6 +14,7 @@ public class Options {
private
int
displayId
;
private
boolean
showTouches
;
private
boolean
stayAwake
;
private
String
codecOptions
;
public
int
getMaxSize
()
{
return
maxSize
;
...
...
@@ -102,4 +103,12 @@ public class Options {
public
void
setStayAwake
(
boolean
stayAwake
)
{
this
.
stayAwake
=
stayAwake
;
}
public
String
getCodecOptions
()
{
return
codecOptions
;
}
public
void
setCodecOptions
(
String
codecOptions
)
{
this
.
codecOptions
=
codecOptions
;
}
}
server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
浏览文件 @
ad3632f1
...
...
@@ -12,6 +12,7 @@ import android.view.Surface;
import
java.io.FileDescriptor
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.util.List
;
import
java.util.concurrent.atomic.AtomicBoolean
;
public
class
ScreenEncoder
implements
Device
.
RotationListener
{
...
...
@@ -25,15 +26,17 @@ public class ScreenEncoder implements Device.RotationListener {
private
final
AtomicBoolean
rotationChanged
=
new
AtomicBoolean
();
private
final
ByteBuffer
headerBuffer
=
ByteBuffer
.
allocate
(
12
);
private
List
<
CodecOption
>
codecOptions
;
private
int
bitRate
;
private
int
maxFps
;
private
boolean
sendFrameMeta
;
private
long
ptsOrigin
;
public
ScreenEncoder
(
boolean
sendFrameMeta
,
int
bitRate
,
int
maxFps
)
{
public
ScreenEncoder
(
boolean
sendFrameMeta
,
int
bitRate
,
int
maxFps
,
List
<
CodecOption
>
codecOptions
)
{
this
.
sendFrameMeta
=
sendFrameMeta
;
this
.
bitRate
=
bitRate
;
this
.
maxFps
=
maxFps
;
this
.
codecOptions
=
codecOptions
;
}
@Override
...
...
@@ -61,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener {
}
private
void
internalStreamScreen
(
Device
device
,
FileDescriptor
fd
)
throws
IOException
{
MediaFormat
format
=
createFormat
(
bitRate
,
maxFps
,
DEFAULT_I_FRAME_INTERVAL
);
MediaFormat
format
=
createFormat
(
bitRate
,
maxFps
,
DEFAULT_I_FRAME_INTERVAL
,
codecOptions
);
device
.
setRotationListener
(
this
);
boolean
alive
;
try
{
...
...
@@ -151,7 +154,24 @@ public class ScreenEncoder implements Device.RotationListener {
return
MediaCodec
.
createEncoderByType
(
MediaFormat
.
MIMETYPE_VIDEO_AVC
);
}
private
static
MediaFormat
createFormat
(
int
bitRate
,
int
maxFps
,
int
iFrameInterval
)
{
private
static
void
setCodecOption
(
MediaFormat
format
,
CodecOption
codecOption
)
{
String
key
=
codecOption
.
getKey
();
Object
value
=
codecOption
.
getValue
();
if
(
value
instanceof
Integer
)
{
format
.
setInteger
(
key
,
(
Integer
)
value
);
}
else
if
(
value
instanceof
Long
)
{
format
.
setLong
(
key
,
(
Long
)
value
);
}
else
if
(
value
instanceof
Float
)
{
format
.
setFloat
(
key
,
(
Float
)
value
);
}
else
if
(
value
instanceof
String
)
{
format
.
setString
(
key
,
(
String
)
value
);
}
Ln
.
d
(
"Codec option set: "
+
key
+
" ("
+
value
.
getClass
().
getSimpleName
()
+
") = "
+
value
);
}
private
static
MediaFormat
createFormat
(
int
bitRate
,
int
maxFps
,
int
iFrameInterval
,
List
<
CodecOption
>
codecOptions
)
{
MediaFormat
format
=
new
MediaFormat
();
format
.
setString
(
MediaFormat
.
KEY_MIME
,
MediaFormat
.
MIMETYPE_VIDEO_AVC
);
format
.
setInteger
(
MediaFormat
.
KEY_BIT_RATE
,
bitRate
);
...
...
@@ -167,6 +187,13 @@ public class ScreenEncoder implements Device.RotationListener {
// <https://github.com/Genymobile/scrcpy/issues/488#issuecomment-567321437>
format
.
setFloat
(
KEY_MAX_FPS_TO_ENCODER
,
maxFps
);
}
if
(
codecOptions
!=
null
)
{
for
(
CodecOption
option
:
codecOptions
)
{
setCodecOption
(
format
,
option
);
}
}
return
format
;
}
...
...
server/src/main/java/com/genymobile/scrcpy/Server.java
浏览文件 @
ad3632f1
...
...
@@ -8,6 +8,7 @@ import android.os.BatteryManager;
import
android.os.Build
;
import
java.io.IOException
;
import
java.util.List
;
public
final
class
Server
{
...
...
@@ -19,6 +20,7 @@ public final class Server {
private
static
void
scrcpy
(
Options
options
)
throws
IOException
{
Ln
.
i
(
"Device: "
+
Build
.
MANUFACTURER
+
" "
+
Build
.
MODEL
+
" (Android "
+
Build
.
VERSION
.
RELEASE
+
")"
);
final
Device
device
=
new
Device
(
options
);
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
options
.
getCodecOptions
());
boolean
mustDisableShowTouchesOnCleanUp
=
false
;
int
restoreStayOn
=
-
1
;
...
...
@@ -49,8 +51,9 @@ public final class Server {
CleanUp
.
configure
(
mustDisableShowTouchesOnCleanUp
,
restoreStayOn
);
boolean
tunnelForward
=
options
.
isTunnelForward
();
try
(
DesktopConnection
connection
=
DesktopConnection
.
open
(
device
,
tunnelForward
))
{
ScreenEncoder
screenEncoder
=
new
ScreenEncoder
(
options
.
getSendFrameMeta
(),
options
.
getBitRate
(),
options
.
getMaxFps
());
ScreenEncoder
screenEncoder
=
new
ScreenEncoder
(
options
.
getSendFrameMeta
(),
options
.
getBitRate
(),
options
.
getMaxFps
()
,
codecOptions
);
if
(
options
.
getControl
())
{
Controller
controller
=
new
Controller
(
device
,
connection
);
...
...
@@ -109,7 +112,7 @@ public final class Server {
"The server version ("
+
BuildConfig
.
VERSION_NAME
+
") does not match the client "
+
"("
+
clientVersion
+
")"
);
}
final
int
expectedParameters
=
1
2
;
final
int
expectedParameters
=
1
3
;
if
(
args
.
length
!=
expectedParameters
)
{
throw
new
IllegalArgumentException
(
"Expecting "
+
expectedParameters
+
" parameters"
);
}
...
...
@@ -150,6 +153,9 @@ public final class Server {
boolean
stayAwake
=
Boolean
.
parseBoolean
(
args
[
11
]);
options
.
setStayAwake
(
stayAwake
);
String
codecOptions
=
args
[
12
];
options
.
setCodecOptions
(
codecOptions
);
return
options
;
}
...
...
server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
0 → 100644
浏览文件 @
ad3632f1
package
com.genymobile.scrcpy
;
import
java.util.List
;
import
org.junit.Assert
;
import
org.junit.Test
;
public
class
CodecOptionsTest
{
@Test
public
void
testIntegerImplicit
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"some_key=5"
);
Assert
.
assertEquals
(
1
,
codecOptions
.
size
());
CodecOption
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"some_key"
,
option
.
getKey
());
Assert
.
assertEquals
(
5
,
option
.
getValue
());
}
@Test
public
void
testInteger
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"some_key:int=5"
);
Assert
.
assertEquals
(
1
,
codecOptions
.
size
());
CodecOption
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"some_key"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Integer
);
Assert
.
assertEquals
(
5
,
option
.
getValue
());
}
@Test
public
void
testLong
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"some_key:long=5"
);
Assert
.
assertEquals
(
1
,
codecOptions
.
size
());
CodecOption
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"some_key"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Long
);
Assert
.
assertEquals
(
5L
,
option
.
getValue
());
}
@Test
public
void
testFloat
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"some_key:float=4.5"
);
Assert
.
assertEquals
(
1
,
codecOptions
.
size
());
CodecOption
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"some_key"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Float
);
Assert
.
assertEquals
(
4.5f
,
option
.
getValue
());
}
@Test
public
void
testString
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"some_key:string=some_value"
);
Assert
.
assertEquals
(
1
,
codecOptions
.
size
());
CodecOption
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"some_key"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
String
);
Assert
.
assertEquals
(
"some_value"
,
option
.
getValue
());
}
@Test
public
void
testStringEscaped
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"some_key:string=warning\\,this_is_not=a_new_key"
);
Assert
.
assertEquals
(
1
,
codecOptions
.
size
());
CodecOption
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"some_key"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
String
);
Assert
.
assertEquals
(
"warning,this_is_not=a_new_key"
,
option
.
getValue
());
}
@Test
public
void
testList
()
{
List
<
CodecOption
>
codecOptions
=
CodecOption
.
parse
(
"a=1,b:int=2,c:long=3,d:float=4.5,e:string=a\\,b=c"
);
Assert
.
assertEquals
(
5
,
codecOptions
.
size
());
CodecOption
option
;
option
=
codecOptions
.
get
(
0
);
Assert
.
assertEquals
(
"a"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Integer
);
Assert
.
assertEquals
(
1
,
option
.
getValue
());
option
=
codecOptions
.
get
(
1
);
Assert
.
assertEquals
(
"b"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Integer
);
Assert
.
assertEquals
(
2
,
option
.
getValue
());
option
=
codecOptions
.
get
(
2
);
Assert
.
assertEquals
(
"c"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Long
);
Assert
.
assertEquals
(
3L
,
option
.
getValue
());
option
=
codecOptions
.
get
(
3
);
Assert
.
assertEquals
(
"d"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
Float
);
Assert
.
assertEquals
(
4.5f
,
option
.
getValue
());
option
=
codecOptions
.
get
(
4
);
Assert
.
assertEquals
(
"e"
,
option
.
getKey
());
Assert
.
assertTrue
(
option
.
getValue
()
instanceof
String
);
Assert
.
assertEquals
(
"a,b=c"
,
option
.
getValue
());
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录