From f9960e959fa7b46cc1ed6b15115fe2958724766d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 20:20:29 +0100 Subject: [PATCH] Add --audio-encoder Similar to --video-encoder, but for audio. PR #3757 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 +++ app/src/cli.c | 11 +++++++ app/src/options.c | 1 + 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 | 29 +++++++++++++++---- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++ .../java/com/genymobile/scrcpy/Server.java | 6 +++- 12 files changed, 64 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index da245acc..c860707f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -5,6 +5,7 @@ _scrcpy() { --audio-bit-rate= --audio-codec= --audio-codec-options= + --audio-encoder= -b --video-bit-rate= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index aa7928c6..b122587f 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -12,6 +12,7 @@ arguments=( '--audio-bit-rate=[Encode the audio at the given bit-rate]' '--audio-codec=[Select the audio codec]:codec:(opus aac)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' + '--audio-encoder=[Use a specific MediaCodec audio encoder]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index fd7746c4..ef17465a 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -41,6 +41,10 @@ The list of possible codec options is available in the Android documentation .UR https://d.android.com/reference/android/media/MediaFormat .UE . +.TP +.BI "\-\-audio\-encoder " name +Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR). + .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). diff --git a/app/src/cli.c b/app/src/cli.c index 9f61e6cb..68629fd2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -67,6 +67,7 @@ enum { OPT_AUDIO_BIT_RATE, OPT_AUDIO_CODEC, OPT_AUDIO_CODEC_OPTIONS, + OPT_AUDIO_ENCODER, }; struct sc_option { @@ -135,6 +136,13 @@ static const struct sc_option options[] = { "Android documentation: " "", }, + { + .longopt_id = OPT_AUDIO_ENCODER, + .longopt = "audio-encoder", + .argdesc = "name", + .text = "Use a specific MediaCodec audio encoder (depending on the " + "codec provided by --audio-codec).", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1694,6 +1702,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_VIDEO_ENCODER: opts->video_encoder = optarg; break; + case OPT_AUDIO_ENCODER: + opts->audio_encoder = optarg; + break; case OPT_FORCE_ADB_FORWARD: opts->force_adb_forward = true; break; diff --git a/app/src/options.c b/app/src/options.c index a9be5dfa..40f84fdd 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -10,6 +10,7 @@ const struct scrcpy_options scrcpy_options_default = { .video_codec_options = NULL, .audio_codec_options = NULL, .video_encoder = NULL, + .audio_encoder = NULL, #ifdef HAVE_V4L2 .v4l2_device = NULL, #endif diff --git a/app/src/options.h b/app/src/options.h index bbb52eb7..804fba93 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -98,6 +98,7 @@ struct scrcpy_options { const char *video_codec_options; const char *audio_codec_options; const char *video_encoder; + const char *audio_encoder; #ifdef HAVE_V4L2 const char *v4l2_device; #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a43c2687..6bfed295 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -333,6 +333,7 @@ scrcpy(struct scrcpy_options *options) { .video_codec_options = options->video_codec_options, .audio_codec_options = options->audio_codec_options, .video_encoder = options->video_encoder, + .audio_encoder = options->audio_encoder, .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 95e4670d..b50003c9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -74,6 +74,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->video_codec_options); free((char *) params->audio_codec_options); free((char *) params->video_encoder); + free((char *) params->audio_encoder); free((char *) params->tcpip_dst); } @@ -99,6 +100,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(video_codec_options); COPY(audio_codec_options); COPY(video_encoder); + COPY(audio_encoder); COPY(tcpip_dst); #undef COPY @@ -276,6 +278,9 @@ execute_server(struct sc_server *server, if (params->video_encoder) { ADD_PARAM("video_encoder=%s", params->video_encoder); } + if (params->audio_encoder) { + ADD_PARAM("audio_encoder=%s", params->audio_encoder); + } 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 d96f997e..c20508e0 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -31,6 +31,7 @@ struct sc_server_params { const char *video_codec_options; const char *audio_codec_options; const char *video_encoder; + const char *audio_encoder; 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 56ff207f..a70a475b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -51,6 +51,7 @@ public final class AudioEncoder { private final Streamer streamer; private final int bitRate; private final List codecOptions; + private final String encoderName; // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). // So many pending tasks would lead to an unacceptable delay anyway. @@ -65,10 +66,11 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer, int bitRate, List codecOptions) { + public AudioEncoder(Streamer streamer, int bitRate, List codecOptions, String encoderName) { this.streamer = streamer; this.bitRate = bitRate; this.codecOptions = codecOptions; + this.encoderName = encoderName; } private static AudioFormat createAudioFormat() { @@ -177,6 +179,8 @@ public final class AudioEncoder { thread = new Thread(() -> { try { encode(); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged } catch (IOException e) { Ln.e("Audio encoding error", e); } finally { @@ -215,7 +219,7 @@ public final class AudioEncoder { } @TargetApi(Build.VERSION_CODES.M) - public void encode() throws IOException { + public void encode() throws IOException, ConfigurationException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { Ln.w("Audio disabled: it is not supported before Android 11"); streamer.writeDisableStream(); @@ -228,13 +232,13 @@ public final class AudioEncoder { boolean mediaCodecStarted = false; boolean recorderStarted = false; try { - String mimeType = streamer.getCodec().getMimeType(); - mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException + Codec codec = streamer.getCodec(); + mediaCodec = createMediaCodec(codec, encoderName); mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(mimeType, bitRate, codecOptions); + MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); @@ -324,6 +328,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 class EncoderCallback extends MediaCodec.Callback { @TargetApi(Build.VERSION_CODES.N) @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 4cb21e28..86838022 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -26,6 +26,7 @@ public class Options { private List audioCodecOptions; private String videoEncoder; + private String audioEncoder; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; private boolean downsizeOnError = true; @@ -190,6 +191,14 @@ public class Options { this.videoEncoder = videoEncoder; } + public String getAudioEncoder() { + return audioEncoder; + } + + public void setAudioEncoder(String audioEncoder) { + this.audioEncoder = audioEncoder; + } + 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 f4e36bff..f30d65f6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -111,7 +111,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(), options.getAudioCodecOptions()); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); audioEncoder.start(); } @@ -267,6 +267,10 @@ public final class Server { options.setVideoEncoder(value); } break; + case "audio_encoder": + if (!value.isEmpty()) { + options.setAudioEncoder(value); + } case "power_off_on_close": boolean powerOffScreenOnClose = Boolean.parseBoolean(value); options.setPowerOffScreenOnClose(powerOffScreenOnClose);