Add --audio-encoder

Similar to --video-encoder, but for audio.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
This commit is contained in:
Romain Vimont 2023-02-19 20:20:29 +01:00
parent 6f332a2bc7
commit f9960e959f
12 changed files with 64 additions and 6 deletions

View file

@ -5,6 +5,7 @@ _scrcpy() {
--audio-bit-rate= --audio-bit-rate=
--audio-codec= --audio-codec=
--audio-codec-options= --audio-codec-options=
--audio-encoder=
-b --video-bit-rate= -b --video-bit-rate=
--crop= --crop=
-d --select-usb -d --select-usb

View file

@ -12,6 +12,7 @@ arguments=(
'--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 aac)' '--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]' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
{-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

@ -41,6 +41,10 @@ The list of possible codec options is available in the Android documentation
.UR https://d.android.com/reference/android/media/MediaFormat .UR https://d.android.com/reference/android/media/MediaFormat
.UE . .UE .
.TP
.BI "\-\-audio\-encoder " name
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
.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

@ -67,6 +67,7 @@ enum {
OPT_AUDIO_BIT_RATE, OPT_AUDIO_BIT_RATE,
OPT_AUDIO_CODEC, OPT_AUDIO_CODEC,
OPT_AUDIO_CODEC_OPTIONS, OPT_AUDIO_CODEC_OPTIONS,
OPT_AUDIO_ENCODER,
}; };
struct sc_option { struct sc_option {
@ -135,6 +136,13 @@ static const struct sc_option options[] = {
"Android documentation: " "Android documentation: "
"<https://d.android.com/reference/android/media/MediaFormat>", "<https://d.android.com/reference/android/media/MediaFormat>",
}, },
{
.longopt_id = OPT_AUDIO_ENCODER,
.longopt = "audio-encoder",
.argdesc = "name",
.text = "Use a specific MediaCodec audio encoder (depending on the "
"codec provided by --audio-codec).",
},
{ {
.shortopt = 'b', .shortopt = 'b',
.longopt = "video-bit-rate", .longopt = "video-bit-rate",
@ -1694,6 +1702,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_VIDEO_ENCODER: case OPT_VIDEO_ENCODER:
opts->video_encoder = optarg; opts->video_encoder = optarg;
break; break;
case OPT_AUDIO_ENCODER:
opts->audio_encoder = optarg;
break;
case OPT_FORCE_ADB_FORWARD: case OPT_FORCE_ADB_FORWARD:
opts->force_adb_forward = true; opts->force_adb_forward = true;
break; break;

View file

@ -10,6 +10,7 @@ const struct scrcpy_options scrcpy_options_default = {
.video_codec_options = NULL, .video_codec_options = NULL,
.audio_codec_options = NULL, .audio_codec_options = NULL,
.video_encoder = NULL, .video_encoder = NULL,
.audio_encoder = NULL,
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
.v4l2_device = NULL, .v4l2_device = NULL,
#endif #endif

View file

@ -98,6 +98,7 @@ struct scrcpy_options {
const char *video_codec_options; const char *video_codec_options;
const char *audio_codec_options; const char *audio_codec_options;
const char *video_encoder; const char *video_encoder;
const char *audio_encoder;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
const char *v4l2_device; const char *v4l2_device;
#endif #endif

View file

@ -333,6 +333,7 @@ scrcpy(struct scrcpy_options *options) {
.video_codec_options = options->video_codec_options, .video_codec_options = options->video_codec_options,
.audio_codec_options = options->audio_codec_options, .audio_codec_options = options->audio_codec_options,
.video_encoder = options->video_encoder, .video_encoder = options->video_encoder,
.audio_encoder = options->audio_encoder,
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close, .power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync, .clipboard_autosync = options->clipboard_autosync,

View file

@ -74,6 +74,7 @@ sc_server_params_destroy(struct sc_server_params *params) {
free((char *) params->video_codec_options); free((char *) params->video_codec_options);
free((char *) params->audio_codec_options); free((char *) params->audio_codec_options);
free((char *) params->video_encoder); free((char *) params->video_encoder);
free((char *) params->audio_encoder);
free((char *) params->tcpip_dst); free((char *) params->tcpip_dst);
} }
@ -99,6 +100,7 @@ sc_server_params_copy(struct sc_server_params *dst,
COPY(video_codec_options); COPY(video_codec_options);
COPY(audio_codec_options); COPY(audio_codec_options);
COPY(video_encoder); COPY(video_encoder);
COPY(audio_encoder);
COPY(tcpip_dst); COPY(tcpip_dst);
#undef COPY #undef COPY
@ -276,6 +278,9 @@ execute_server(struct sc_server *server,
if (params->video_encoder) { if (params->video_encoder) {
ADD_PARAM("video_encoder=%s", params->video_encoder); ADD_PARAM("video_encoder=%s", params->video_encoder);
} }
if (params->audio_encoder) {
ADD_PARAM("audio_encoder=%s", params->audio_encoder);
}
if (params->power_off_on_close) { if (params->power_off_on_close) {
ADD_PARAM("power_off_on_close=true"); ADD_PARAM("power_off_on_close=true");
} }

View file

@ -31,6 +31,7 @@ struct sc_server_params {
const char *video_codec_options; const char *video_codec_options;
const char *audio_codec_options; const char *audio_codec_options;
const char *video_encoder; const char *video_encoder;
const char *audio_encoder;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host; uint32_t tunnel_host;
uint16_t tunnel_port; uint16_t tunnel_port;

View file

@ -51,6 +51,7 @@ public final class AudioEncoder {
private final Streamer streamer; private final Streamer streamer;
private final int bitRate; private final int bitRate;
private final List<CodecOption> codecOptions; private final List<CodecOption> codecOptions;
private final String encoderName;
// Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). // 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. // So many pending tasks would lead to an unacceptable delay anyway.
@ -65,10 +66,11 @@ public final class AudioEncoder {
private boolean ended; private boolean ended;
public AudioEncoder(Streamer streamer, int bitRate, List<CodecOption> codecOptions) { public AudioEncoder(Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
this.streamer = streamer; this.streamer = streamer;
this.bitRate = bitRate; this.bitRate = bitRate;
this.codecOptions = codecOptions; this.codecOptions = codecOptions;
this.encoderName = encoderName;
} }
private static AudioFormat createAudioFormat() { private static AudioFormat createAudioFormat() {
@ -177,6 +179,8 @@ public final class AudioEncoder {
thread = new Thread(() -> { thread = new Thread(() -> {
try { try {
encode(); encode();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) { } catch (IOException e) {
Ln.e("Audio encoding error", e); Ln.e("Audio encoding error", e);
} finally { } finally {
@ -215,7 +219,7 @@ public final class AudioEncoder {
} }
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
public void encode() throws IOException { public void encode() throws IOException, ConfigurationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Ln.w("Audio disabled: it is not supported before Android 11"); Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(); streamer.writeDisableStream();
@ -228,13 +232,13 @@ public final class AudioEncoder {
boolean mediaCodecStarted = false; boolean mediaCodecStarted = false;
boolean recorderStarted = false; boolean recorderStarted = false;
try { try {
String mimeType = streamer.getCodec().getMimeType(); Codec codec = streamer.getCodec();
mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException mediaCodec = createMediaCodec(codec, encoderName);
mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread = new HandlerThread("AudioEncoder");
mediaCodecThread.start(); mediaCodecThread.start();
MediaFormat format = createFormat(mimeType, bitRate, codecOptions); MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions);
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);
@ -324,6 +328,21 @@ public final class AudioEncoder {
} }
} }
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
if (encoderName != null) {
Ln.d("Creating audio encoder by name: '" + encoderName + "'");
try {
return MediaCodec.createByCodecName(encoderName);
} catch (IllegalArgumentException e) {
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName));
throw new ConfigurationException("Unknown encoder: " + encoderName);
}
}
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'");
return mediaCodec;
}
private class EncoderCallback extends MediaCodec.Callback { private class EncoderCallback extends MediaCodec.Callback {
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
@Override @Override

View file

@ -26,6 +26,7 @@ public class Options {
private List<CodecOption> audioCodecOptions; private List<CodecOption> audioCodecOptions;
private String videoEncoder; private String videoEncoder;
private String audioEncoder;
private boolean powerOffScreenOnClose; private boolean powerOffScreenOnClose;
private boolean clipboardAutosync = true; private boolean clipboardAutosync = true;
private boolean downsizeOnError = true; private boolean downsizeOnError = true;
@ -190,6 +191,14 @@ public class Options {
this.videoEncoder = videoEncoder; this.videoEncoder = videoEncoder;
} }
public String getAudioEncoder() {
return audioEncoder;
}
public void setAudioEncoder(String audioEncoder) {
this.audioEncoder = audioEncoder;
}
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose; this.powerOffScreenOnClose = powerOffScreenOnClose;
} }

View file

@ -111,7 +111,7 @@ public final class Server {
if (audio) { if (audio) {
Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(),
options.getSendFrameMeta()); options.getSendFrameMeta());
audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions()); audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder());
audioEncoder.start(); audioEncoder.start();
} }
@ -267,6 +267,10 @@ public final class Server {
options.setVideoEncoder(value); options.setVideoEncoder(value);
} }
break; break;
case "audio_encoder":
if (!value.isEmpty()) {
options.setAudioEncoder(value);
}
case "power_off_on_close": case "power_off_on_close":
boolean powerOffScreenOnClose = Boolean.parseBoolean(value); boolean powerOffScreenOnClose = Boolean.parseBoolean(value);
options.setPowerOffScreenOnClose(powerOffScreenOnClose); options.setPowerOffScreenOnClose(powerOffScreenOnClose);