Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encoding profile option #1226

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ Set the initial window height.

Default is 0 (automatic).\n

.TP
.BI "\-P, \-\-\codec\-profile " value
Request specific encoding codec profile, see AVC profiles at: https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel for valid codec profile values. Default is 0 (automatic).

.SH SHORTCUTS

.TP
Expand Down
43 changes: 42 additions & 1 deletion app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ scrcpy_print_usage(const char *arg0) {
" Set the initial window width.\n"
" Default is 0 (automatic).\n"
"\n"
" -P, --codec-profile value\n"
" Request specific encoding codec profile, see AVC profiles at:\n"
" https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel\n"
" for valid codec profile values.\n"
" Default is 0 (automatic).\n"
"\n"
"Shortcuts:\n"
"\n"
" " CTRL_OR_CMD "+f\n"
Expand Down Expand Up @@ -368,6 +374,35 @@ parse_record_format(const char *optarg, enum recorder_format *format) {
return false;
}

static bool
parse_codec_profile(const char *optarg, uint32_t *codec_profile) {
long value;
// long may be 32 bits (it is the case on mingw), so do not use more than
// 31 bits (long is signed)
bool ok = parse_integer_arg(optarg, &value, true, 0, 0x7FFFFFFF, "codec-profile");
if (!ok) {
return false;
}

switch (value) {
case 0: // Automatic
case 0x01: // AVCProfileBaseline
case 0x02: // AVCProfileMain
case 0x04: // AVCProfileExtended
case 0x08: // AVCProfileHigh
case 0x10: // AVCProfileHigh10
case 0x20: // AVCProfileHigh422
case 0x40: // AVCProfileHigh444
case 0x10000: // AVCProfileConstrainedBaseline
case 0x80000: // AVCProfileConstrainedHigh
tzah4748 marked this conversation as resolved.
Show resolved Hide resolved
*codec_profile = (uint32_t) value;
return true;
default:
LOGE("Invalid codec-profile, Use -h for help");
return false;
}
}

static enum recorder_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
Expand Down Expand Up @@ -431,6 +466,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS},
{"codec-profile", required_argument, NULL, 'P'},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an advanced option, I would prefer not to consume a "short" option (-P) for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's totally fine, at first i had a bit of a problem understanding the code so i didn't know if it will work without a short version of the option, its ok to remove it -P and stay with --codec-profile only

{NULL, 0, NULL, 0 },
};

Expand All @@ -439,7 +475,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
optind = 0; // reset to start from the first argument in tests

int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv:P:", long_options,
NULL)) != -1) {
switch (c) {
case 'b':
Expand Down Expand Up @@ -549,6 +585,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_PREFER_TEXT:
opts->prefer_text = true;
break;
case 'P':
if (!parse_codec_profile(optarg, &opts->codec_profile)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ scrcpy(const struct scrcpy_options *options) {
.max_fps = options->max_fps,
.lock_video_orientation = options->lock_video_orientation,
.control = options->control,
.codec_profile = options->codec_profile,
};
if (!server_start(&server, options->serial, &params)) {
return false;
Expand Down
2 changes: 2 additions & 0 deletions app/src/scrcpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct scrcpy_options {
bool render_expired_frames;
bool prefer_text;
bool window_borderless;
uint32_t codec_profile;
};

#define SCRCPY_OPTIONS_DEFAULT { \
Expand Down Expand Up @@ -64,6 +65,7 @@ struct scrcpy_options {
.render_expired_frames = false, \
.prefer_text = false, \
.window_borderless = false, \
.codec_profile = 0, \
}

bool
Expand Down
3 changes: 3 additions & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ execute_server(struct server *server, const struct server_params *params) {
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
sprintf(codec_profile_string, "%"PRIu32, params->codec_profile);
const char *const cmd[] = {
"shell",
"CLASSPATH=" DEVICE_SERVER_PATH,
Expand All @@ -254,6 +255,7 @@ execute_server(struct server *server, const struct server_params *params) {
bit_rate_string,
max_fps_string,
lock_video_orientation_string,
codec_profile_string,
server->tunnel_forward ? "true" : "false",
params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp)
Expand Down Expand Up @@ -327,6 +329,7 @@ bool
server_start(struct server *server, const char *serial,
const struct server_params *params) {
server->port_range = params->port_range;
server->codec_profile = params->codec_profile;

if (serial) {
server->serial = SDL_strdup(serial);
Expand Down
3 changes: 3 additions & 0 deletions app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct server {
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
uint32_t codec_profile;
};

#define SERVER_INITIALIZER { \
Expand All @@ -34,6 +35,7 @@ struct server {
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
.codec_profile = 0,
}

struct server_params {
Expand All @@ -44,6 +46,7 @@ struct server_params {
uint16_t max_fps;
int8_t lock_video_orientation;
bool control;
uint32_t codec_profile;
};

// init default values
Expand Down
9 changes: 9 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class Options {
private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly
private boolean control;
private int codecProfile;

public int getMaxSize() {
return maxSize;
Expand Down Expand Up @@ -75,4 +76,12 @@ public boolean getControl() {
public void setControl(boolean control) {
this.control = control;
}

public int getCodecProfile() {
return codecProfile;
}

public void setCodecProfile(int codecProfile) {
this.codecProfile = codecProfile;
}
}
25 changes: 22 additions & 3 deletions server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,21 @@ public class ScreenEncoder implements Device.RotationListener {
private int maxFps;
private int lockedVideoOrientation;
private int iFrameInterval;
private int codecProfile;
private boolean sendFrameMeta;
private long ptsOrigin;

public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) {
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate;
this.maxFps = maxFps;
this.lockedVideoOrientation = lockedVideoOrientation;
this.codecProfile = codecProfile;
this.iFrameInterval = iFrameInterval;
}

public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL);
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, codecProfile, DEFAULT_I_FRAME_INTERVAL);
}

@Override
Expand All @@ -64,6 +66,7 @@ public void streamScreen(Device device, FileDescriptor fd) throws IOException {
try {
do {
MediaCodec codec = createCodec();
setCodecProfile(codec, format);
IBinder display = createDisplay();
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect();
Expand Down Expand Up @@ -139,6 +142,22 @@ private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo,
IO.writeFully(fd, headerBuffer);
}

private void setCodecProfile(MediaCodec codec, MediaFormat format) throws IOException {
if(codecProfile == 0) return;
int level = 0;
for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType("video/avc").profileLevels) {
if(profileLevel.profile == codecProfile) {
level = Math.max(level, profileLevel.level);
}
}
if(level == 0) throw new IOException("Device doesn't support the requested codec profile.");
// Profile (SDK Level 21) and Level (SDK Level 23).
format.setInteger(MediaFormat.KEY_PROFILE, codecProfile);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
format.setInteger(MediaFormat.KEY_LEVEL, level);
}
}

private static MediaCodec createCodec() throws IOException {
return MediaCodec.createEncoderByType("video/avc");
}
Expand Down
19 changes: 11 additions & 8 deletions server/src/main/java/com/genymobile/scrcpy/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private static void scrcpy(Options options) throws IOException {
boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(),
options.getLockedVideoOrientation());
options.getLockedVideoOrientation(), options.getCodecProfile());

if (options.getControl()) {
Controller controller = new Controller(device, connection);
Expand Down Expand Up @@ -77,11 +77,11 @@ private static Options createOptions(String... args) {
String clientVersion = args[0];
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
throw new IllegalArgumentException(
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
}

if (args.length != 9) {
throw new IllegalArgumentException("Expecting 9 parameters");
if (args.length != 10) {
throw new IllegalArgumentException("Expecting 10 parameters");
}

Options options = new Options();
Expand All @@ -98,17 +98,20 @@ private static Options createOptions(String... args) {
int lockedVideoOrientation = Integer.parseInt(args[4]);
options.setLockedVideoOrientation(lockedVideoOrientation);

int codecProfile = Integer.parseInt(args[5]);
options.setCodecProfile(codecProfile);

// use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[5]);
boolean tunnelForward = Boolean.parseBoolean(args[6]);
options.setTunnelForward(tunnelForward);

Rect crop = parseCrop(args[6]);
Rect crop = parseCrop(args[7]);
options.setCrop(crop);

boolean sendFrameMeta = Boolean.parseBoolean(args[7]);
boolean sendFrameMeta = Boolean.parseBoolean(args[8]);
options.setSendFrameMeta(sendFrameMeta);

boolean control = Boolean.parseBoolean(args[8]);
boolean control = Boolean.parseBoolean(args[9]);
options.setControl(control);

return options;
Expand Down