Add --list-encoders

Add an option to list the device encoders properly.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
This commit is contained in:
Romain Vimont 2023-02-22 23:15:15 +01:00
parent b7e5284adf
commit 9196dc1563
16 changed files with 157 additions and 29 deletions

View file

@ -273,12 +273,10 @@ may cause issues or crash. It is possible to select a different encoder:
scrcpy --video-encoder=OMX.qcom.video.encoder.avc scrcpy --video-encoder=OMX.qcom.video.encoder.avc
``` ```
To list the available encoders, you can pass an invalid encoder name; the To list the available encoders:
error will give the available encoders:
```bash ```bash
scrcpy --video-encoder=_ # for the default codec scrcpy --list-encoders
scrcpy --video-codec=h265 --video-encoder=_ # for a specific codec
``` ```
### Capture ### Capture

View file

@ -19,6 +19,7 @@ _scrcpy() {
-K --hid-keyboard -K --hid-keyboard
-h --help -h --help
--legacy-paste --legacy-paste
--list-encoders
--lock-video-orientation --lock-video-orientation
--lock-video-orientation= --lock-video-orientation=
--max-fps= --max-fps=

View file

@ -26,6 +26,7 @@ arguments=(
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
{-h,--help}'[Print the help]' {-h,--help}'[Print the help]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-encoders[List video and audio encoders available on the device]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
'--max-fps=[Limit the frame rate of screen capture]' '--max-fps=[Limit the frame rate of screen capture]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'

View file

@ -45,6 +45,8 @@ The list of possible codec options is available in the Android documentation
.BI "\-\-audio\-encoder " name .BI "\-\-audio\-encoder " name
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR). Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
The available encoders can be listed by \-\-list\-encoders.
.TP .TP
.BI "\-b, \-\-video\-bit\-rate " value .BI "\-b, \-\-video\-bit\-rate " value
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
@ -128,6 +130,10 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP
.B \-\-list\-encoders
List video and audio encoders available on the device.
.TP .TP
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR] \fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise. Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
@ -355,6 +361,8 @@ The list of possible codec options is available in the Android documentation
.BI "\-\-video\-encoder " name .BI "\-\-video\-encoder " name
Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR). Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR).
The available encoders can be listed by \-\-list\-encoders.
.TP .TP
.B \-w, \-\-stay-awake .B \-w, \-\-stay-awake
Keep the device on while scrcpy is running, when the device is plugged in. Keep the device on while scrcpy is running, when the device is plugged in.

View file

@ -68,6 +68,7 @@ enum {
OPT_AUDIO_CODEC, OPT_AUDIO_CODEC,
OPT_AUDIO_CODEC_OPTIONS, OPT_AUDIO_CODEC_OPTIONS,
OPT_AUDIO_ENCODER, OPT_AUDIO_ENCODER,
OPT_LIST_ENCODERS,
}; };
struct sc_option { struct sc_option {
@ -141,7 +142,8 @@ static const struct sc_option options[] = {
.longopt = "audio-encoder", .longopt = "audio-encoder",
.argdesc = "name", .argdesc = "name",
.text = "Use a specific MediaCodec audio encoder (depending on the " .text = "Use a specific MediaCodec audio encoder (depending on the "
"codec provided by --audio-codec).", "codec provided by --audio-codec).\n"
"The available encoders can be listed by --list-encoders.",
}, },
{ {
.shortopt = 'b', .shortopt = 'b',
@ -270,6 +272,11 @@ static const struct sc_option options[] = {
"This is a workaround for some devices not behaving as " "This is a workaround for some devices not behaving as "
"expected when setting the device clipboard programmatically.", "expected when setting the device clipboard programmatically.",
}, },
{
.longopt_id = OPT_LIST_ENCODERS,
.longopt = "list-encoders",
.text = "List video and audio encoders available on the device.",
},
{ {
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION, .longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
.longopt = "lock-video-orientation", .longopt = "lock-video-orientation",
@ -586,7 +593,8 @@ static const struct sc_option options[] = {
.longopt = "video-encoder", .longopt = "video-encoder",
.argdesc = "name", .argdesc = "name",
.text = "Use a specific MediaCodec video encoder (depending on the " .text = "Use a specific MediaCodec video encoder (depending on the "
"codec provided by --video-codec).", "codec provided by --video-codec).\n"
"The available encoders can be listed by --list-encoders.",
}, },
{ {
.shortopt = 'w', .shortopt = 'w',
@ -1792,6 +1800,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("V4L2 (--v4l2-buffer) is only available on Linux."); LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
return false; return false;
#endif #endif
case OPT_LIST_ENCODERS:
opts->list_encoders = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View file

@ -72,4 +72,5 @@ const struct scrcpy_options scrcpy_options_default = {
.start_fps_counter = false, .start_fps_counter = false,
.power_on = true, .power_on = true,
.audio = true, .audio = true,
.list_encoders = false,
}; };

View file

@ -154,6 +154,7 @@ struct scrcpy_options {
bool start_fps_counter; bool start_fps_counter;
bool power_on; bool power_on;
bool audio; bool audio;
bool list_encoders;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View file

@ -183,12 +183,16 @@ await_for_server(bool *connected) {
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
switch (event.type) { switch (event.type) {
case SDL_QUIT: case SDL_QUIT:
if (connected) {
*connected = false; *connected = false;
}
return true; return true;
case SC_EVENT_SERVER_CONNECTION_FAILED: case SC_EVENT_SERVER_CONNECTION_FAILED:
return false; return false;
case SC_EVENT_SERVER_CONNECTED: case SC_EVENT_SERVER_CONNECTED:
if (connected) {
*connected = true; *connected = true;
}
return true; return true;
default: default:
break; break;
@ -339,6 +343,7 @@ scrcpy(struct scrcpy_options *options) {
.tcpip_dst = options->tcpip_dst, .tcpip_dst = options->tcpip_dst,
.cleanup = options->cleanup, .cleanup = options->cleanup,
.power_on = options->power_on, .power_on = options->power_on,
.list_encoders = options->list_encoders,
}; };
static const struct sc_server_callbacks cbs = { static const struct sc_server_callbacks cbs = {
@ -356,6 +361,12 @@ scrcpy(struct scrcpy_options *options) {
server_started = true; server_started = true;
if (options->list_encoders) {
bool ok = await_for_server(NULL);
ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE;
goto end;
}
if (options->display) { if (options->display) {
sdl_set_hints(options->render_driver); sdl_set_hints(options->render_driver);
} }

View file

@ -300,6 +300,9 @@ execute_server(struct sc_server *server,
// By default, power_on is true // By default, power_on is true
ADD_PARAM("power_on=false"); ADD_PARAM("power_on=false");
} }
if (params->list_encoders) {
ADD_PARAM("list_encoders=true");
}
#undef ADD_PARAM #undef ADD_PARAM
@ -848,6 +851,25 @@ run_server(void *data) {
assert(serial); assert(serial);
LOGD("Device serial: %s", serial); LOGD("Device serial: %s", serial);
ok = push_server(&server->intr, serial);
if (!ok) {
goto error_connection_failed;
}
// If --list-encoders is passed, then the server just prints the encoders
// then exits.
if (params->list_encoders) {
sc_pid pid = execute_server(server, params);
if (pid == SC_PROCESS_NONE) {
goto error_connection_failed;
}
sc_process_wait(pid, NULL); // ignore exit code
sc_process_close(pid);
// Wake up await_for_server()
server->cbs->on_connected(server, server->cbs_userdata);
return 0;
}
int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x", int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x",
params->scid); params->scid);
if (r == -1) { if (r == -1) {
@ -857,11 +879,6 @@ run_server(void *data) {
assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8); assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8);
assert(server->device_socket_name); assert(server->device_socket_name);
ok = push_server(&server->intr, serial);
if (!ok) {
goto error_connection_failed;
}
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial, ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
server->device_socket_name, params->port_range, server->device_socket_name, params->port_range,
params->force_adb_forward); params->force_adb_forward);

View file

@ -55,6 +55,7 @@ struct sc_server_params {
bool select_tcpip; bool select_tcpip;
bool cleanup; bool cleanup;
bool power_on; bool power_on;
bool list_encoders;
}; };
struct sc_server { struct sc_server {

View file

@ -334,7 +334,7 @@ public final class AudioEncoder {
try { try {
return MediaCodec.createByCodecName(encoderName); return MediaCodec.createByCodecName(encoderName);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + CodecUtils.buildAudioEncoderListMessage());
throw new ConfigurationException("Unknown encoder: " + encoderName); throw new ConfigurationException("Unknown encoder: " + encoderName);
} }
} }

View file

@ -139,7 +139,7 @@ public final class CleanUp {
builder.start(); builder.start();
} }
private static void unlinkSelf() { public static void unlinkSelf() {
try { try {
new File(SERVER_PATH).delete(); new File(SERVER_PATH).delete();
} catch (Exception e) { } catch (Exception e) {

View file

@ -10,6 +10,24 @@ import java.util.List;
public final class CodecUtils { public final class CodecUtils {
public static final class DeviceEncoder {
private final Codec codec;
private final MediaCodecInfo info;
DeviceEncoder(Codec codec, MediaCodecInfo info) {
this.codec = codec;
this.info = info;
}
public Codec getCodec() {
return codec;
}
public MediaCodecInfo getInfo() {
return info;
}
}
private CodecUtils() { private CodecUtils() {
// not instantiable // not instantiable
} }
@ -26,28 +44,63 @@ public final class CodecUtils {
} }
} }
public static String buildUnknownEncoderMessage(Codec codec, String encoderName) { public static String buildVideoEncoderListMessage() {
StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found"); StringBuilder builder = new StringBuilder("List of video encoders:");
MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); List<CodecUtils.DeviceEncoder> videoEncoders = CodecUtils.listVideoEncoders();
if (encoders != null && encoders.length > 0) { if (videoEncoders.isEmpty()) {
msg.append("\nTry to use one of the available encoders:"); builder.append("\n (none)");
String codecOption = codec.getType() == Codec.Type.VIDEO ? "video-codec" : "audio-codec"; } else {
for (MediaCodecInfo encoder : encoders) { for (CodecUtils.DeviceEncoder encoder : videoEncoders) {
msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName()); builder.append("\n --video-codec=").append(encoder.getCodec().getName());
msg.append(" --encoder='").append(encoder.getName()).append("'"); builder.append(" --video-encoder='").append(encoder.getInfo().getName()).append("'");
} }
} }
return msg.toString(); return builder.toString();
} }
private static MediaCodecInfo[] listEncoders(String mimeType) { public static String buildAudioEncoderListMessage() {
StringBuilder builder = new StringBuilder("List of audio encoders:");
List<CodecUtils.DeviceEncoder> audioEncoders = CodecUtils.listAudioEncoders();
if (audioEncoders.isEmpty()) {
builder.append("\n (none)");
} else {
for (CodecUtils.DeviceEncoder encoder : audioEncoders) {
builder.append("\n --audio-codec=").append(encoder.getCodec().getName());
builder.append(" --audio-encoder='").append(encoder.getInfo().getName()).append("'");
}
}
return builder.toString();
}
private static MediaCodecInfo[] getEncoders(MediaCodecList codecs, String mimeType) {
List<MediaCodecInfo> result = new ArrayList<>(); List<MediaCodecInfo> result = new ArrayList<>();
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) {
for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) {
result.add(codecInfo); result.add(codecInfo);
} }
} }
return result.toArray(new MediaCodecInfo[result.size()]); return result.toArray(new MediaCodecInfo[result.size()]);
} }
public static List<DeviceEncoder> listVideoEncoders() {
List<DeviceEncoder> encoders = new ArrayList<>();
MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (VideoCodec codec : VideoCodec.values()) {
for (MediaCodecInfo info : getEncoders(codecs, codec.getMimeType())) {
encoders.add(new DeviceEncoder(codec, info));
}
}
return encoders;
}
public static List<DeviceEncoder> listAudioEncoders() {
List<DeviceEncoder> encoders = new ArrayList<>();
MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (AudioCodec codec : AudioCodec.values()) {
for (MediaCodecInfo info : getEncoders(codecs, codec.getMimeType())) {
encoders.add(new DeviceEncoder(codec, info));
}
}
return encoders;
}
} }

View file

@ -33,6 +33,8 @@ public class Options {
private boolean cleanup = true; private boolean cleanup = true;
private boolean powerOn = true; private boolean powerOn = true;
private boolean listEncoders;
// Options not used by the scrcpy client, but useful to use scrcpy-server directly // Options not used by the scrcpy client, but useful to use scrcpy-server directly
private boolean sendDeviceMeta = true; // send device name and size private boolean sendDeviceMeta = true; // send device name and size
private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean sendFrameMeta = true; // send PTS so that the client may record properly
@ -239,6 +241,14 @@ public class Options {
this.powerOn = powerOn; this.powerOn = powerOn;
} }
public boolean getListEncoders() {
return listEncoders;
}
public void setListEncoders(boolean listEncoders) {
this.listEncoders = listEncoders;
}
public boolean getSendDeviceMeta() { public boolean getSendDeviceMeta() {
return sendDeviceMeta; return sendDeviceMeta;
} }

View file

@ -202,7 +202,7 @@ public class ScreenEncoder implements Device.RotationListener {
try { try {
return MediaCodec.createByCodecName(encoderName); return MediaCodec.createByCodecName(encoderName);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + CodecUtils.buildVideoEncoderListMessage());
throw new ConfigurationException("Unknown encoder: " + encoderName); throw new ConfigurationException("Unknown encoder: " + encoderName);
} }
} }

View file

@ -291,6 +291,10 @@ public final class Server {
boolean powerOn = Boolean.parseBoolean(value); boolean powerOn = Boolean.parseBoolean(value);
options.setPowerOn(powerOn); options.setPowerOn(powerOn);
break; break;
case "list_encoders":
boolean listEncoders = Boolean.parseBoolean(value);
options.setListEncoders(listEncoders);
break;
case "send_device_meta": case "send_device_meta":
boolean sendDeviceMeta = Boolean.parseBoolean(value); boolean sendDeviceMeta = Boolean.parseBoolean(value);
options.setSendDeviceMeta(sendDeviceMeta); options.setSendDeviceMeta(sendDeviceMeta);
@ -350,6 +354,17 @@ public final class Server {
Ln.initLogLevel(options.getLogLevel()); Ln.initLogLevel(options.getLogLevel());
if (options.getListEncoders()) {
if (options.getCleanup()) {
CleanUp.unlinkSelf();
}
Ln.i(CodecUtils.buildVideoEncoderListMessage());
Ln.i(CodecUtils.buildAudioEncoderListMessage());
// Just print the available encoders, do not mirror
return;
}
try { try {
scrcpy(options); scrcpy(options);
} catch (ConfigurationException e) { } catch (ConfigurationException e) {