Add --audio-codec

Introduce the selection mechanism. Alternative codecs will be added
later.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
This commit is contained in:
Romain Vimont 2023-02-18 19:05:43 +01:00
parent 0870b8c8be
commit 839b842aa7
12 changed files with 69 additions and 6 deletions

View file

@ -3,6 +3,7 @@ _scrcpy() {
local opts=" local opts="
--always-on-top --always-on-top
--audio-bit-rate= --audio-bit-rate=
--audio-codec=
-b --video-bit-rate= -b --video-bit-rate=
--crop= --crop=
-d --select-usb -d --select-usb
@ -71,6 +72,10 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur")) COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur"))
return return
;; ;;
--audio-codec)
COMPREPLY=($(compgen -W 'opus' -- "$cur"))
return
;;
--lock-video-orientation) --lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
return return

View file

@ -10,6 +10,7 @@ local arguments
arguments=( arguments=(
'--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--always-on-top[Make scrcpy window always on top \(above other windows\)]'
'--audio-bit-rate=[Encode the audio at the given bit-rate]' '--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]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]' {-d,--select-usb}'[Use USB device]'

View file

@ -25,6 +25,12 @@ Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are
Default is 128K (128000). Default is 128K (128000).
.TP
.BI "\-\-audio\-codec " name
Select an audio codec (opus).
Default is opus.
.TP .TP
.BI "\-b, \-\-video\-bit\-rate " value .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). Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).

View file

@ -65,6 +65,7 @@ enum {
OPT_VIDEO_CODEC, OPT_VIDEO_CODEC,
OPT_NO_AUDIO, OPT_NO_AUDIO,
OPT_AUDIO_BIT_RATE, OPT_AUDIO_BIT_RATE,
OPT_AUDIO_CODEC,
}; };
struct sc_option { struct sc_option {
@ -114,6 +115,13 @@ static const struct sc_option options[] = {
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
"Default is 128K (128000).", "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', .shortopt = 'b',
.longopt = "video-bit-rate", .longopt = "video-bit-rate",
@ -1452,6 +1460,16 @@ parse_video_codec(const char *optarg, enum sc_codec *codec) {
return false; 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 static bool
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
const char *optstring, const struct option *longopts) { 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; return false;
} }
break; break;
case OPT_AUDIO_CODEC:
if (!parse_audio_codec(optarg, &opts->audio_codec)) {
return false;
}
break;
case OPT_OTG: case OPT_OTG:
#ifdef HAVE_USB #ifdef HAVE_USB
opts->otg = true; opts->otg = true;

View file

@ -14,6 +14,7 @@ const struct scrcpy_options scrcpy_options_default = {
#endif #endif
.log_level = SC_LOG_LEVEL_INFO, .log_level = SC_LOG_LEVEL_INFO,
.video_codec = SC_CODEC_H264, .video_codec = SC_CODEC_H264,
.audio_codec = SC_CODEC_OPUS,
.record_format = SC_RECORD_FORMAT_AUTO, .record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,

View file

@ -27,6 +27,7 @@ enum sc_codec {
SC_CODEC_H264, SC_CODEC_H264,
SC_CODEC_H265, SC_CODEC_H265,
SC_CODEC_AV1, SC_CODEC_AV1,
SC_CODEC_OPUS,
}; };
enum sc_lock_video_orientation { enum sc_lock_video_orientation {
@ -100,6 +101,7 @@ struct scrcpy_options {
#endif #endif
enum sc_log_level log_level; enum sc_log_level log_level;
enum sc_codec video_codec; enum sc_codec video_codec;
enum sc_codec audio_codec;
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode; enum sc_mouse_input_mode mouse_input_mode;

View file

@ -315,6 +315,7 @@ scrcpy(struct scrcpy_options *options) {
.select_tcpip = options->select_tcpip, .select_tcpip = options->select_tcpip,
.log_level = options->log_level, .log_level = options->log_level,
.video_codec = options->video_codec, .video_codec = options->video_codec,
.audio_codec = options->audio_codec,
.crop = options->crop, .crop = options->crop,
.port_range = options->port_range, .port_range = options->port_range,
.tunnel_host = options->tunnel_host, .tunnel_host = options->tunnel_host,

View file

@ -165,6 +165,8 @@ sc_server_get_codec_name(enum sc_codec codec) {
return "h265"; return "h265";
case SC_CODEC_AV1: case SC_CODEC_AV1:
return "av1"; return "av1";
case SC_CODEC_OPUS:
return "opus";
default: default:
return NULL; return NULL;
} }
@ -228,6 +230,10 @@ execute_server(struct sc_server *server,
ADD_PARAM("video_codec=%s", ADD_PARAM("video_codec=%s",
sc_server_get_codec_name(params->video_codec)); 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) { if (params->max_size) {
ADD_PARAM("max_size=%" PRIu16, params->max_size); ADD_PARAM("max_size=%" PRIu16, params->max_size);
} }

View file

@ -26,6 +26,7 @@ struct sc_server_params {
const char *req_serial; const char *req_serial;
enum sc_log_level log_level; enum sc_log_level log_level;
enum sc_codec video_codec; enum sc_codec video_codec;
enum sc_codec audio_codec;
const char *crop; const char *crop;
const char *video_codec_options; const char *video_codec_options;
const char *video_encoder; const char *video_encoder;

View file

@ -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 SAMPLE_RATE = 48000;
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
private static final int CHANNELS = 2; private static final int CHANNELS = 2;
@ -93,9 +92,9 @@ public final class AudioEncoder {
return builder.build(); return builder.build();
} }
private static MediaFormat createFormat(int bitRate) { private static MediaFormat createFormat(String mimeType, int bitRate) {
MediaFormat format = new MediaFormat(); 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_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
@ -216,12 +215,13 @@ public final class AudioEncoder {
boolean mediaCodecStarted = false; boolean mediaCodecStarted = false;
boolean recorderStarted = false; boolean recorderStarted = false;
try { 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 = new HandlerThread("AudioEncoder");
mediaCodecThread.start(); mediaCodecThread.start();
MediaFormat format = createFormat(bitRate); MediaFormat format = createFormat(mimeType, bitRate);
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

View file

@ -11,6 +11,7 @@ public class Options {
private boolean audio = true; private boolean audio = true;
private int maxSize; private int maxSize;
private VideoCodec videoCodec = VideoCodec.H264; private VideoCodec videoCodec = VideoCodec.H264;
private AudioCodec audioCodec = AudioCodec.OPUS;
private int videoBitRate = 8000000; private int videoBitRate = 8000000;
private int audioBitRate = 128000; private int audioBitRate = 128000;
private int maxFps; private int maxFps;
@ -75,6 +76,14 @@ public class Options {
this.videoCodec = videoCodec; this.videoCodec = videoCodec;
} }
public AudioCodec getAudioCodec() {
return audioCodec;
}
public void setAudioCodec(AudioCodec audioCodec) {
this.audioCodec = audioCodec;
}
public int getVideoBitRate() { public int getVideoBitRate() {
return videoBitRate; return videoBitRate;
} }

View file

@ -109,7 +109,8 @@ public final class Server {
} }
if (audio) { 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 = new AudioEncoder(audioStreamer, options.getAudioBitRate());
audioEncoder.start(); audioEncoder.start();
} }
@ -202,6 +203,13 @@ public final class Server {
} }
options.setVideoCodec(videoCodec); options.setVideoCodec(videoCodec);
break; 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": case "max_size":
int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 int maxSize = Integer.parseInt(value) & ~7; // multiple of 8
options.setMaxSize(maxSize); options.setMaxSize(maxSize);