diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 914aafd5..9c059af7 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,6 +25,12 @@ Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 196K (196000). +.TP +.BI "\-\-audio\-codec " name +Select an audio codec (opus). + +Default is opus. + .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 586673ac..5a05c369 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -60,6 +60,7 @@ #define OPT_CODEC 1040 #define OPT_NO_AUDIO 1041 #define OPT_AUDIO_BIT_RATE 1042 +#define OPT_AUDIO_CODEC 1043 struct sc_option { char shortopt; @@ -108,6 +109,13 @@ static const struct sc_option options[] = { "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 196K (196000).", }, + { + .longopt_id = OPT_AUDIO_CODEC, + .longopt = "audio-codec", + .argdesc = "name", + .text = "Select an audio codec (opus).\n" + "Default is opus.", + }, { .shortopt = 'b', .longopt = "bit-rate", @@ -1418,6 +1426,16 @@ parse_codec(const char *optarg, enum sc_codec *codec) { return false; } +static bool +parse_audio_codec(const char *optarg, enum sc_codec *codec) { + if (!strcmp(optarg, "opus")) { + *codec = SC_CODEC_OPUS; + return true; + } + LOGE("Unsupported audio codec: %s (expected opus)", optarg); + return false; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -1664,6 +1682,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_AUDIO_CODEC: + if (!parse_audio_codec(optarg, &opts->audio_codec)) { + return false; + } + break; case OPT_OTG: #ifdef HAVE_USB opts->otg = true; diff --git a/app/src/options.c b/app/src/options.c index 9465d154..0751b721 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -14,6 +14,7 @@ const struct scrcpy_options scrcpy_options_default = { #endif .log_level = SC_LOG_LEVEL_INFO, .codec = SC_CODEC_H264, + .audio_codec = SC_CODEC_OPUS, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .port_range = { diff --git a/app/src/options.h b/app/src/options.h index 965c46ca..f2ce1d89 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -27,6 +27,7 @@ enum sc_codec { SC_CODEC_H264, SC_CODEC_H265, SC_CODEC_AV1, + SC_CODEC_OPUS, }; enum sc_lock_video_orientation { @@ -100,6 +101,7 @@ struct scrcpy_options { #endif enum sc_log_level log_level; enum sc_codec codec; + enum sc_codec audio_codec; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 70aa6ce3..3c44d730 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -351,6 +351,7 @@ scrcpy(struct scrcpy_options *options) { .select_tcpip = options->select_tcpip, .log_level = options->log_level, .codec = options->codec, + .audio_codec = options->audio_codec, .crop = options->crop, .port_range = options->port_range, .tunnel_host = options->tunnel_host, diff --git a/app/src/server.c b/app/src/server.c index f77d3312..53573954 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -165,6 +165,8 @@ sc_server_get_codec_name(enum sc_codec codec) { return "h265"; case SC_CODEC_AV1: return "av1"; + case SC_CODEC_OPUS: + return "opus"; default: return NULL; } @@ -227,6 +229,10 @@ execute_server(struct sc_server *server, if (params->codec != SC_CODEC_H264) { ADD_PARAM("codec=%s", sc_server_get_codec_name(params->codec)); } + if (params->audio_codec != SC_CODEC_OPUS) { + ADD_PARAM("audio_codec=%s", + sc_server_get_codec_name(params->audio_codec)); + } if (params->max_size) { ADD_PARAM("max_size=%" PRIu16, params->max_size); } diff --git a/app/src/server.h b/app/src/server.h index 9d8c2ec2..a63426ef 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -26,6 +26,7 @@ struct sc_server_params { const char *req_serial; enum sc_log_level log_level; enum sc_codec codec; + enum sc_codec audio_codec; const char *crop; const char *codec_options; const char *encoder_name; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 44e69d32..656875c2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -37,7 +37,6 @@ public final class AudioEncoder { } } - private static final String MIMETYPE = MediaFormat.MIMETYPE_AUDIO_OPUS; private static final int SAMPLE_RATE = 48000; private static final int CHANNELS = 2; @@ -90,9 +89,9 @@ public final class AudioEncoder { return builder.build(); } - private static MediaFormat createFormat(int bitRate) { + private static MediaFormat createFormat(String mimeType, int bitRate) { MediaFormat format = new MediaFormat(); - format.setString(MediaFormat.KEY_MIME, MIMETYPE); + 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); @@ -203,13 +202,15 @@ public final class AudioEncoder { public void encode() throws IOException { try { try { - mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException + String mimeType = streamer.getCodec().getMimeType(); + + mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException recorder = createAudioRecord(); mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(bitRate); + MediaFormat format = createFormat(mimeType, 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 a7f2e9c7..cc976460 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -11,6 +11,7 @@ public class Options { private boolean audio = true; private int maxSize; private VideoCodec codec = VideoCodec.H264; + private AudioCodec audioCodec = AudioCodec.OPUS; private int bitRate = 8000000; private int audioBitRate = 196000; private int maxFps; @@ -75,6 +76,14 @@ public class Options { this.codec = codec; } + public AudioCodec getAudioCodec() { + return audioCodec; + } + + public void setAudioCodec(AudioCodec audioCodec) { + this.audioCodec = audioCodec; + } + public int getBitRate() { return bitRate; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 69f4a43b..2741774d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -111,7 +111,8 @@ public final class Server { AudioEncoder audioEncoder = null; if (audio) { - Streamer audioStreamer = new Streamer(connection.getAudioFd(), AudioCodec.OPUS, options.getSendCodecId(), options.getSendFrameMeta()); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), + options.getSendFrameMeta()); audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate()); audioEncoder.start(); } @@ -202,6 +203,13 @@ public final class Server { } options.setCodec(codec); break; + case "audio_codec": + AudioCodec audioCodec = AudioCodec.findByName(value); + if (audioCodec == null) { + throw new IllegalArgumentException("Audio codec " + value + " not supported"); + } + options.setAudioCodec(audioCodec); + break; case "max_size": int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 options.setMaxSize(maxSize);