diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 450bd32d..02ade8d0 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -2,6 +2,7 @@ _scrcpy() { local cur prev words cword local opts=" --always-on-top + --audio-bit-rate= -b --video-bit-rate= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 86d9ffbf..28d017e3 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -9,6 +9,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' + '--audio-bit-rate=[Encode the audio at the given bit-rate]' {-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 34bb750e..7c11f6e5 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -19,6 +19,12 @@ provides display and control of Android devices connected on USB (or over TCP/IP .B \-\-always\-on\-top Make scrcpy window always on top (above other windows). +.TP +.BI "\-\-audio\-bit\-rate " value +Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). + +Default is 128K (128000). + .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 685c2f3d..7187b878 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -64,6 +64,7 @@ enum { OPT_CODEC, OPT_VIDEO_CODEC, OPT_NO_AUDIO, + OPT_AUDIO_BIT_RATE, }; struct sc_option { @@ -105,6 +106,14 @@ static const struct sc_option options[] = { .longopt = "always-on-top", .text = "Make scrcpy window always on top (above other windows).", }, + { + .longopt_id = OPT_AUDIO_BIT_RATE, + .longopt = "audio-bit-rate", + .argdesc = "value", + .text = "Encode the audio at the given bit-rate, expressed in bits/s. " + "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" + "Default is 128K (128000).", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1461,6 +1470,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_AUDIO_BIT_RATE: + if (!parse_bit_rate(optarg, &opts->audio_bit_rate)) { + return false; + } + break; case OPT_CROP: opts->crop = optarg; break; diff --git a/app/src/options.c b/app/src/options.c index fa025dea..70d26a6f 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -29,6 +29,7 @@ const struct scrcpy_options scrcpy_options_default = { }, .max_size = 0, .video_bit_rate = 0, + .audio_bit_rate = 0, .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .rotation = 0, diff --git a/app/src/options.h b/app/src/options.h index 3c602b7e..92a53653 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -109,6 +109,7 @@ struct scrcpy_options { struct sc_shortcut_mods shortcut_mods; uint16_t max_size; uint32_t video_bit_rate; + uint32_t audio_bit_rate; uint16_t max_fps; enum sc_lock_video_orientation lock_video_orientation; uint8_t rotation; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 776f5d13..478f0e87 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -321,6 +321,7 @@ scrcpy(struct scrcpy_options *options) { .tunnel_port = options->tunnel_port, .max_size = options->max_size, .video_bit_rate = options->video_bit_rate, + .audio_bit_rate = options->audio_bit_rate, .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, .control = options->control, diff --git a/app/src/server.c b/app/src/server.c index 583c338e..fa8d8300 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -221,6 +221,8 @@ execute_server(struct sc_server *server, } if (!params->audio) { ADD_PARAM("audio=false"); + } else if (params->audio_bit_rate) { + ADD_PARAM("audio_bit_rate=%" PRIu32, params->audio_bit_rate); } if (params->video_codec != SC_CODEC_H264) { ADD_PARAM("video_codec=%s", diff --git a/app/src/server.h b/app/src/server.h index 97c9aea2..805bdaf2 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -34,6 +34,7 @@ struct sc_server_params { uint16_t tunnel_port; uint16_t max_size; uint32_t video_bit_rate; + uint32_t audio_bit_rate; uint16_t max_fps; int8_t lock_video_orientation; bool control; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index d06898d6..5704f768 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -44,12 +44,12 @@ public final class AudioEncoder { private static final int CHANNELS = 2; private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int BYTES_PER_SAMPLE = 2; - private static final int BIT_RATE = 128000; private static final int READ_MS = 5; // milliseconds private static final int READ_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * READ_MS / 1000; private final Streamer streamer; + private final int bitRate; // 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. @@ -64,8 +64,9 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer) { + public AudioEncoder(Streamer streamer, int bitRate) { this.streamer = streamer; + this.bitRate = bitRate; } private static AudioFormat createAudioFormat() { @@ -92,10 +93,10 @@ public final class AudioEncoder { return builder.build(); } - private static MediaFormat createFormat() { + private static MediaFormat createFormat(int bitRate) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, MIMETYPE); - format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); return format; @@ -220,7 +221,7 @@ public final class AudioEncoder { mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(); + MediaFormat format = createFormat(bitRate); 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 c518bf07..44bc73ec 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -12,6 +12,7 @@ public class Options { private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; private int videoBitRate = 8000000; + private int audioBitRate = 128000; private int maxFps; private int lockVideoOrientation = -1; private boolean tunnelForward; @@ -82,6 +83,14 @@ public class Options { this.videoBitRate = videoBitRate; } + public int getAudioBitRate() { + return audioBitRate; + } + + public void setAudioBitRate(int audioBitRate) { + this.audioBitRate = audioBitRate; + } + public int getMaxFps() { return maxFps; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 800b2fb6..c10e3209 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -110,7 +110,7 @@ public final class Server { if (audio) { Streamer audioStreamer = new Streamer(connection.getAudioFd(), AudioCodec.OPUS, options.getSendCodecId(), options.getSendFrameMeta()); - audioEncoder = new AudioEncoder(audioStreamer); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate()); audioEncoder.start(); } @@ -210,6 +210,10 @@ public final class Server { int videoBitRate = Integer.parseInt(value); options.setVideoBitRate(videoBitRate); break; + case "audio_bit_rate": + int audioBitRate = Integer.parseInt(value); + options.setAudioBitRate(audioBitRate); + break; case "max_fps": int maxFps = Integer.parseInt(value); options.setMaxFps(maxFps);