diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index f303ff66..da245acc 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -4,6 +4,7 @@ _scrcpy() { --always-on-top --audio-bit-rate= --audio-codec= + --audio-codec-options= -b --video-bit-rate= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index a0d83a05..aa7928c6 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -11,6 +11,7 @@ arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--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]' {-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 3ccbb111..fd7746c4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -31,6 +31,16 @@ Select an audio codec (opus or aac). Default is opus. +.TP +.BI "\-\-audio\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] +Set a list of comma-separated key:type=value options for the device audio encoder. + +The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. + +The list of possible codec options is available in the Android documentation +.UR https://d.android.com/reference/android/media/MediaFormat +.UE . + .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 afd060b8..9f61e6cb 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -66,6 +66,7 @@ enum { OPT_NO_AUDIO, OPT_AUDIO_BIT_RATE, OPT_AUDIO_CODEC, + OPT_AUDIO_CODEC_OPTIONS, }; struct sc_option { @@ -122,6 +123,18 @@ static const struct sc_option options[] = { .text = "Select an audio codec (opus or aac).\n" "Default is opus.", }, + { + .longopt_id = OPT_AUDIO_CODEC_OPTIONS, + .longopt = "audio-codec-options", + .argdesc = "key[:type]=value[,...]", + .text = "Set a list of comma-separated key:type=value options for the " + "device audio encoder.\n" + "The possible values for 'type' are 'int' (default), 'long', " + "'float' and 'string'.\n" + "The list of possible codec options is available in the " + "Android documentation: " + "", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1672,6 +1685,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_VIDEO_CODEC_OPTIONS: opts->video_codec_options = optarg; break; + case OPT_AUDIO_CODEC_OPTIONS: + opts->audio_codec_options = optarg; + break; case OPT_ENCODER: LOGW("--encoder is deprecated, use --video-encoder instead."); // fall through diff --git a/app/src/options.c b/app/src/options.c index 72f34e43..a9be5dfa 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -8,6 +8,7 @@ const struct scrcpy_options scrcpy_options_default = { .push_target = NULL, .render_driver = NULL, .video_codec_options = NULL, + .audio_codec_options = NULL, .video_encoder = NULL, #ifdef HAVE_V4L2 .v4l2_device = NULL, diff --git a/app/src/options.h b/app/src/options.h index 3efa2dd6..bbb52eb7 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -96,6 +96,7 @@ struct scrcpy_options { const char *push_target; const char *render_driver; const char *video_codec_options; + const char *audio_codec_options; const char *video_encoder; #ifdef HAVE_V4L2 const char *v4l2_device; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8b96477c..a43c2687 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -331,6 +331,7 @@ scrcpy(struct scrcpy_options *options) { .show_touches = options->show_touches, .stay_awake = options->stay_awake, .video_codec_options = options->video_codec_options, + .audio_codec_options = options->audio_codec_options, .video_encoder = options->video_encoder, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, diff --git a/app/src/server.c b/app/src/server.c index 36146d86..95e4670d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -72,6 +72,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->req_serial); free((char *) params->crop); free((char *) params->video_codec_options); + free((char *) params->audio_codec_options); free((char *) params->video_encoder); free((char *) params->tcpip_dst); } @@ -96,6 +97,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(req_serial); COPY(crop); COPY(video_codec_options); + COPY(audio_codec_options); COPY(video_encoder); COPY(tcpip_dst); #undef COPY @@ -268,6 +270,9 @@ execute_server(struct sc_server *server, if (params->video_codec_options) { ADD_PARAM("video_codec_options=%s", params->video_codec_options); } + if (params->audio_codec_options) { + ADD_PARAM("audio_codec_options=%s", params->audio_codec_options); + } if (params->video_encoder) { ADD_PARAM("video_encoder=%s", params->video_encoder); } diff --git a/app/src/server.h b/app/src/server.h index 55a86605..d96f997e 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -29,6 +29,7 @@ struct sc_server_params { enum sc_codec audio_codec; const char *crop; const char *video_codec_options; + const char *audio_codec_options; const char *video_encoder; struct sc_port_range port_range; uint32_t tunnel_host; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 710e5f7d..56ff207f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -15,6 +15,7 @@ import android.os.Looper; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -49,6 +50,7 @@ public final class AudioEncoder { private final Streamer streamer; private final int bitRate; + private final List codecOptions; // 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. @@ -63,9 +65,10 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer, int bitRate) { + public AudioEncoder(Streamer streamer, int bitRate, List codecOptions) { this.streamer = streamer; this.bitRate = bitRate; + this.codecOptions = codecOptions; } private static AudioFormat createAudioFormat() { @@ -92,12 +95,22 @@ public final class AudioEncoder { return builder.build(); } - private static MediaFormat createFormat(String mimeType, int bitRate) { + private static MediaFormat createFormat(String mimeType, int bitRate, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, mimeType); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); + + if (codecOptions != null) { + for (CodecOption option : codecOptions) { + String key = option.getKey(); + Object value = option.getValue(); + CodecUtils.setCodecOption(format, key, value); + Ln.d("Audio codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); + } + } + return format; } @@ -221,7 +234,7 @@ public final class AudioEncoder { mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(mimeType, bitRate); + MediaFormat format = createFormat(mimeType, bitRate, codecOptions); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index bdeab851..4cb21e28 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -23,6 +23,8 @@ public class Options { private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; + private List audioCodecOptions; + private String videoEncoder; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; @@ -172,6 +174,14 @@ public class Options { this.videoCodecOptions = videoCodecOptions; } + public List getAudioCodecOptions() { + return audioCodecOptions; + } + + public void setAudioCodecOptions(List audioCodecOptions) { + this.audioCodecOptions = audioCodecOptions; + } + public String getVideoEncoder() { return videoEncoder; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4c15bd39..f4e36bff 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()); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions()); audioEncoder.start(); } @@ -258,6 +258,10 @@ public final class Server { List videoCodecOptions = CodecOption.parse(value); options.setVideoCodecOptions(videoCodecOptions); break; + case "audio_codec_options": + List audioCodecOptions = CodecOption.parse(value); + options.setAudioCodecOptions(audioCodecOptions); + break; case "video_encoder": if (!value.isEmpty()) { options.setVideoEncoder(value);