diff --git a/README.md b/README.md index 8eabafa9..5575fc4d 100644 --- a/README.md +++ b/README.md @@ -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 ``` -To list the available encoders, you can pass an invalid encoder name; the -error will give the available encoders: +To list the available encoders: ```bash -scrcpy --video-encoder=_ # for the default codec -scrcpy --video-codec=h265 --video-encoder=_ # for a specific codec +scrcpy --list-encoders ``` ### Capture diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index c860707f..70695019 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -19,6 +19,7 @@ _scrcpy() { -K --hid-keyboard -h --help --legacy-paste + --list-encoders --lock-video-orientation --lock-video-orientation= --max-fps= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index b122587f..268aa626 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -26,6 +26,7 @@ arguments=( {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-h,--help}'[Print the help]' '--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)' '--max-fps=[Limit the frame rate of screen capture]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ef17465a..add263c2 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -45,6 +45,8 @@ The list of possible codec options is available in the Android documentation .BI "\-\-audio\-encoder " name 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 .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). @@ -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. +.TP +.B \-\-list\-encoders +List video and audio encoders available on the device. + .TP \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. @@ -355,6 +361,8 @@ The list of possible codec options is available in the Android documentation .BI "\-\-video\-encoder " name 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 .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index 68629fd2..edb694a6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -68,6 +68,7 @@ enum { OPT_AUDIO_CODEC, OPT_AUDIO_CODEC_OPTIONS, OPT_AUDIO_ENCODER, + OPT_LIST_ENCODERS, }; struct sc_option { @@ -141,7 +142,8 @@ static const struct sc_option options[] = { .longopt = "audio-encoder", .argdesc = "name", .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', @@ -270,6 +272,11 @@ static const struct sc_option options[] = { "This is a workaround for some devices not behaving as " "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 = "lock-video-orientation", @@ -586,7 +593,8 @@ static const struct sc_option options[] = { .longopt = "video-encoder", .argdesc = "name", .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', @@ -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."); return false; #endif + case OPT_LIST_ENCODERS: + opts->list_encoders = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 40f84fdd..1839df6e 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -72,4 +72,5 @@ const struct scrcpy_options scrcpy_options_default = { .start_fps_counter = false, .power_on = true, .audio = true, + .list_encoders = false, }; diff --git a/app/src/options.h b/app/src/options.h index 804fba93..568b8155 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -154,6 +154,7 @@ struct scrcpy_options { bool start_fps_counter; bool power_on; bool audio; + bool list_encoders; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 81affe47..6d0fac9e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -183,12 +183,16 @@ await_for_server(bool *connected) { while (SDL_WaitEvent(&event)) { switch (event.type) { case SDL_QUIT: - *connected = false; + if (connected) { + *connected = false; + } return true; case SC_EVENT_SERVER_CONNECTION_FAILED: return false; case SC_EVENT_SERVER_CONNECTED: - *connected = true; + if (connected) { + *connected = true; + } return true; default: break; @@ -339,6 +343,7 @@ scrcpy(struct scrcpy_options *options) { .tcpip_dst = options->tcpip_dst, .cleanup = options->cleanup, .power_on = options->power_on, + .list_encoders = options->list_encoders, }; static const struct sc_server_callbacks cbs = { @@ -356,6 +361,12 @@ scrcpy(struct scrcpy_options *options) { 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) { sdl_set_hints(options->render_driver); } diff --git a/app/src/server.c b/app/src/server.c index b50003c9..077614a8 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -300,6 +300,9 @@ execute_server(struct sc_server *server, // By default, power_on is true ADD_PARAM("power_on=false"); } + if (params->list_encoders) { + ADD_PARAM("list_encoders=true"); + } #undef ADD_PARAM @@ -848,6 +851,25 @@ run_server(void *data) { assert(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", params->scid); if (r == -1) { @@ -857,11 +879,6 @@ run_server(void *data) { assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8); 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, server->device_socket_name, params->port_range, params->force_adb_forward); diff --git a/app/src/server.h b/app/src/server.h index c20508e0..ada04baa 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -55,6 +55,7 @@ struct sc_server_params { bool select_tcpip; bool cleanup; bool power_on; + bool list_encoders; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index a70a475b..540d8306 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -334,7 +334,7 @@ public final class AudioEncoder { try { return MediaCodec.createByCodecName(encoderName); } 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); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 831dc994..0bcd1a54 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -139,7 +139,7 @@ public final class CleanUp { builder.start(); } - private static void unlinkSelf() { + public static void unlinkSelf() { try { new File(SERVER_PATH).delete(); } catch (Exception e) { diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java index 96887c14..aca54d20 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java @@ -10,6 +10,24 @@ import java.util.List; 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() { // not instantiable } @@ -26,28 +44,63 @@ public final class CodecUtils { } } - public static String buildUnknownEncoderMessage(Codec codec, String encoderName) { - StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found"); - MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); - if (encoders != null && encoders.length > 0) { - msg.append("\nTry to use one of the available encoders:"); - String codecOption = codec.getType() == Codec.Type.VIDEO ? "video-codec" : "audio-codec"; - for (MediaCodecInfo encoder : encoders) { - msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName()); - msg.append(" --encoder='").append(encoder.getName()).append("'"); + public static String buildVideoEncoderListMessage() { + StringBuilder builder = new StringBuilder("List of video encoders:"); + List videoEncoders = CodecUtils.listVideoEncoders(); + if (videoEncoders.isEmpty()) { + builder.append("\n (none)"); + } else { + for (CodecUtils.DeviceEncoder encoder : videoEncoders) { + builder.append("\n --video-codec=").append(encoder.getCodec().getName()); + 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 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 result = new ArrayList<>(); - MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - for (MediaCodecInfo codecInfo : list.getCodecInfos()) { + for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) { if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { result.add(codecInfo); } } return result.toArray(new MediaCodecInfo[result.size()]); } + + public static List listVideoEncoders() { + List 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 listAudioEncoders() { + List 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; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 86838022..8cac5e2c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -33,6 +33,8 @@ public class Options { private boolean cleanup = true; private boolean powerOn = true; + private boolean listEncoders; + // 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 sendFrameMeta = true; // send PTS so that the client may record properly @@ -239,6 +241,14 @@ public class Options { this.powerOn = powerOn; } + public boolean getListEncoders() { + return listEncoders; + } + + public void setListEncoders(boolean listEncoders) { + this.listEncoders = listEncoders; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 77cd1de4..668a4ed0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -202,7 +202,7 @@ public class ScreenEncoder implements Device.RotationListener { try { return MediaCodec.createByCodecName(encoderName); } 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); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f30d65f6..adfbef2a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -291,6 +291,10 @@ public final class Server { boolean powerOn = Boolean.parseBoolean(value); options.setPowerOn(powerOn); break; + case "list_encoders": + boolean listEncoders = Boolean.parseBoolean(value); + options.setListEncoders(listEncoders); + break; case "send_device_meta": boolean sendDeviceMeta = Boolean.parseBoolean(value); options.setSendDeviceMeta(sendDeviceMeta); @@ -350,6 +354,17 @@ public final class Server { 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 { scrcpy(options); } catch (ConfigurationException e) {