From d31f8582040d08394bf6437172bcf13dea2048fa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 20:20:29 +0100 Subject: [PATCH] Add --audio-encoder option Similar to --video-encoder, but for audio. --- app/scrcpy.1 | 4 +++ app/src/cli.c | 11 ++++++++ app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 5 ++++ app/src/server.h | 1 + .../com/genymobile/scrcpy/AudioEncoder.java | 27 ++++++++++++++++--- .../com/genymobile/scrcpy/CodecUtils.java | 4 ++- .../java/com/genymobile/scrcpy/Options.java | 9 +++++++ .../java/com/genymobile/scrcpy/Server.java | 6 ++++- 10 files changed, 63 insertions(+), 6 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a845d128..d3ddc643 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -31,6 +31,10 @@ Select an audio codec (opus or aac). Default is opus. +.TP +.BI "\-\-audio\-encoder " name +Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR). + .TP .BI "\-b, \-\-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). diff --git a/app/src/cli.c b/app/src/cli.c index c69abc38..691c6cb7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -61,6 +61,7 @@ #define OPT_NO_AUDIO 1041 #define OPT_AUDIO_BIT_RATE 1042 #define OPT_AUDIO_CODEC 1043 +#define OPT_AUDIO_ENCODER_NAME 1044 struct sc_option { char shortopt; @@ -116,6 +117,13 @@ static const struct sc_option options[] = { .text = "Select an audio codec (opus or aac).\n" "Default is opus.", }, + { + .longopt_id = OPT_AUDIO_ENCODER_NAME, + .longopt = "audio-encoder", + .argdesc = "name", + .text = "Use a specific MediaCodec audio encoder (depending on the " + "codec provided by --audio-codec).", + }, { .shortopt = 'b', .longopt = "bit-rate", @@ -1635,6 +1643,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_ENCODER_NAME: opts->encoder_name = optarg; break; + case OPT_AUDIO_ENCODER_NAME: + opts->audio_encoder_name = optarg; + break; case OPT_FORCE_ADB_FORWARD: opts->force_adb_forward = true; break; diff --git a/app/src/options.h b/app/src/options.h index e96f62cb..94a208d8 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -97,6 +97,7 @@ struct scrcpy_options { const char *render_driver; const char *codec_options; const char *encoder_name; + const char *audio_encoder_name; #ifdef HAVE_V4L2 const char *v4l2_device; #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index fc088e11..c04db664 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -368,6 +368,7 @@ scrcpy(struct scrcpy_options *options) { .stay_awake = options->stay_awake, .codec_options = options->codec_options, .encoder_name = options->encoder_name, + .audio_encoder_name = options->audio_encoder_name, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/server.c b/app/src/server.c index a7b23591..b66a411c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -73,6 +73,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->crop); free((char *) params->codec_options); free((char *) params->encoder_name); + free((char *) params->audio_encoder_name); free((char *) params->tcpip_dst); } @@ -97,6 +98,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(crop); COPY(codec_options); COPY(encoder_name); + COPY(audio_encoder_name); COPY(tcpip_dst); #undef COPY @@ -270,6 +272,9 @@ execute_server(struct sc_server *server, if (params->encoder_name) { ADD_PARAM("encoder_name=%s", params->encoder_name); } + if (params->audio_encoder_name) { + ADD_PARAM("audio_encoder_name=%s", params->audio_encoder_name); + } if (params->power_off_on_close) { ADD_PARAM("power_off_on_close=true"); } diff --git a/app/src/server.h b/app/src/server.h index a63426ef..3b2657d6 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -30,6 +30,7 @@ struct sc_server_params { const char *crop; const char *codec_options; const char *encoder_name; + const char *audio_encoder_name; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index fbf5925b..5f63f5c3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -45,6 +45,7 @@ public final class AudioEncoder { private final Streamer streamer; private final int bitRate; + private final String encoderName; private AudioRecord recorder; private MediaCodec mediaCodec; @@ -62,9 +63,10 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer, int bitRate) { + public AudioEncoder(Streamer streamer, int bitRate, String encoderName) { this.streamer = streamer; this.bitRate = bitRate; + this.encoderName = encoderName; } private static AudioFormat createAudioFormat() { @@ -208,15 +210,15 @@ public final class AudioEncoder { try { try { - String mimeType = streamer.getCodec().getMimeType(); + Codec codec = streamer.getCodec(); - mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException + mediaCodec = createMediaCodec(codec, encoderName); recorder = createAudioRecord(); mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(mimeType, bitRate); + MediaFormat format = createFormat(codec.getMimeType(), bitRate); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); @@ -255,6 +257,8 @@ public final class AudioEncoder { mediaCodec.start(); inputThread.start(); outputThread.start(); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged } catch (Throwable e) { if (mediaCodec != null) { mediaCodec.release(); @@ -272,6 +276,21 @@ public final class AudioEncoder { } } + private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { + if (encoderName != null) { + Ln.d("Creating audio encoder by name: '" + encoderName + "'"); + try { + return MediaCodec.createByCodecName(encoderName); + } catch (IllegalArgumentException e) { + Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); + throw new ConfigurationException("Unknown encoder: " + encoderName); + } + } + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); + Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; + } + private void cleanUp() { mediaCodecThread.getLooper().quit(); inputThread.interrupt(); diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java index 92cb9fcc..b916ce0d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java @@ -18,8 +18,10 @@ public final class CodecUtils { 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 ? "codec" : "audio-codec"; for (MediaCodecInfo encoder : encoders) { - msg.append("\n scrcpy --codec=").append(codec.getName()).append(" --encoder='").append(encoder.getName()).append("'"); + msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName()); + msg.append(" --encoder='").append(encoder.getName()).append("'"); } } return msg.toString(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index cc976460..87ab1142 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -24,6 +24,7 @@ public class Options { private boolean stayAwake; private List codecOptions; private String encoderName; + private String audioEncoderName; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; private boolean downsizeOnError = true; @@ -180,6 +181,14 @@ public class Options { this.encoderName = encoderName; } + public String getAudioEncoderName() { + return audioEncoderName; + } + + public void setAudioEncoderName(String audioEncoderName) { + this.audioEncoderName = audioEncoderName; + } + public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { this.powerOffScreenOnClose = powerOffScreenOnClose; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ee0a8a59..f778e6b1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -113,7 +113,7 @@ public final class Server { if (audio) { Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), options.getSendFrameMeta()); - audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate()); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioEncoderName()); audioEncoder.start(); } @@ -264,6 +264,10 @@ public final class Server { options.setEncoderName(value); } break; + case "audio_encoder_name": + if (!value.isEmpty()) { + options.setAudioEncoderName(value); + } case "power_off_on_close": boolean powerOffScreenOnClose = Boolean.parseBoolean(value); options.setPowerOffScreenOnClose(powerOffScreenOnClose);