From 839b842aa71a848510d9407dacc7742c61a17fea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 19:05:43 +0100 Subject: [PATCH] Add --audio-codec Introduce the selection mechanism. Alternative codecs will be added later. PR #3757 --- app/data/bash-completion/scrcpy | 5 ++++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 +++++ app/src/cli.c | 23 +++++++++++++++++++ app/src/options.c | 1 + app/src/options.h | 2 ++ app/src/scrcpy.c | 1 + app/src/server.c | 6 +++++ app/src/server.h | 1 + .../com/genymobile/scrcpy/AudioEncoder.java | 10 ++++---- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++++ .../java/com/genymobile/scrcpy/Server.java | 10 +++++++- 12 files changed, 69 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 02ade8d0..5a50f6c5 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -3,6 +3,7 @@ _scrcpy() { local opts=" --always-on-top --audio-bit-rate= + --audio-codec= -b --video-bit-rate= --crop= -d --select-usb @@ -71,6 +72,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur")) return ;; + --audio-codec) + COMPREPLY=($(compgen -W 'opus' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 28d017e3..4f7ad5ef 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,6 +10,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]' + '--audio-codec=[Select the audio codec]:codec:(opus)' {-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 7c11f6e5..89533a1f 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 128K (128000). +.TP +.BI "\-\-audio\-codec " name +Select an audio codec (opus). + +Default is opus. + .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 7187b878..5f28164e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -65,6 +65,7 @@ enum { OPT_VIDEO_CODEC, OPT_NO_AUDIO, OPT_AUDIO_BIT_RATE, + OPT_AUDIO_CODEC, }; struct sc_option { @@ -114,6 +115,13 @@ static const struct sc_option options[] = { "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 128K (128000).", }, + { + .longopt_id = OPT_AUDIO_CODEC, + .longopt = "audio-codec", + .argdesc = "name", + .text = "Select an audio codec (opus).\n" + "Default is opus.", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1452,6 +1460,16 @@ parse_video_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) { @@ -1711,6 +1729,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 70d26a6f..72f34e43 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, .video_codec = SC_CODEC_H264, + .audio_codec = SC_CODEC_OPUS, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, diff --git a/app/src/options.h b/app/src/options.h index 92a53653..c698e6e3 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 video_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 478f0e87..8b96477c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -315,6 +315,7 @@ scrcpy(struct scrcpy_options *options) { .select_tcpip = options->select_tcpip, .log_level = options->log_level, .video_codec = options->video_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 fa8d8300..a797f01d 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; } @@ -228,6 +230,10 @@ execute_server(struct sc_server *server, ADD_PARAM("video_codec=%s", sc_server_get_codec_name(params->video_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 805bdaf2..55a86605 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 video_codec; + enum sc_codec audio_codec; const char *crop; const char *video_codec_options; const char *video_encoder; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 5704f768..710e5f7d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -38,7 +38,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 CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; private static final int CHANNELS = 2; @@ -93,9 +92,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); @@ -216,12 +215,13 @@ public final class AudioEncoder { boolean mediaCodecStarted = false; boolean recorderStarted = false; try { - mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException + String mimeType = streamer.getCodec().getMimeType(); + mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException 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 44bc73ec..bdeab851 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 videoCodec = VideoCodec.H264; + private AudioCodec audioCodec = AudioCodec.OPUS; private int videoBitRate = 8000000; private int audioBitRate = 128000; private int maxFps; @@ -75,6 +76,14 @@ public class Options { this.videoCodec = videoCodec; } + public AudioCodec getAudioCodec() { + return audioCodec; + } + + public void setAudioCodec(AudioCodec audioCodec) { + this.audioCodec = audioCodec; + } + public int getVideoBitRate() { return videoBitRate; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c10e3209..4c15bd39 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -109,7 +109,8 @@ public final class Server { } 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.setVideoCodec(videoCodec); 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);