Add --audio-encoder option

Similar to --video-encoder, but for audio.
This commit is contained in:
Romain Vimont 2023-02-19 20:20:29 +01:00
commit d31f858204
10 changed files with 63 additions and 6 deletions

View file

@ -31,6 +31,10 @@ Select an audio codec (opus or aac).
Default is opus. Default is opus.
.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, \-\-bit\-rate " value .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). 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

@ -61,6 +61,7 @@
#define OPT_NO_AUDIO 1041 #define OPT_NO_AUDIO 1041
#define OPT_AUDIO_BIT_RATE 1042 #define OPT_AUDIO_BIT_RATE 1042
#define OPT_AUDIO_CODEC 1043 #define OPT_AUDIO_CODEC 1043
#define OPT_AUDIO_ENCODER_NAME 1044
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@ -116,6 +117,13 @@ static const struct sc_option options[] = {
.text = "Select an audio codec (opus or aac).\n" .text = "Select an audio codec (opus or aac).\n"
"Default is opus.", "Default is opus.",
}, },
{
.longopt_id = OPT_AUDIO_ENCODER_NAME,
.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 = "bit-rate", .longopt = "bit-rate",
@ -1635,6 +1643,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_ENCODER_NAME: case OPT_ENCODER_NAME:
opts->encoder_name = optarg; opts->encoder_name = optarg;
break; break;
case OPT_AUDIO_ENCODER_NAME:
opts->audio_encoder_name = 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

@ -97,6 +97,7 @@ struct scrcpy_options {
const char *render_driver; const char *render_driver;
const char *codec_options; const char *codec_options;
const char *encoder_name; const char *encoder_name;
const char *audio_encoder_name;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
const char *v4l2_device; const char *v4l2_device;
#endif #endif

View file

@ -368,6 +368,7 @@ scrcpy(struct scrcpy_options *options) {
.stay_awake = options->stay_awake, .stay_awake = options->stay_awake,
.codec_options = options->codec_options, .codec_options = options->codec_options,
.encoder_name = options->encoder_name, .encoder_name = options->encoder_name,
.audio_encoder_name = options->audio_encoder_name,
.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

@ -73,6 +73,7 @@ sc_server_params_destroy(struct sc_server_params *params) {
free((char *) params->crop); free((char *) params->crop);
free((char *) params->codec_options); free((char *) params->codec_options);
free((char *) params->encoder_name); free((char *) params->encoder_name);
free((char *) params->audio_encoder_name);
free((char *) params->tcpip_dst); free((char *) params->tcpip_dst);
} }
@ -97,6 +98,7 @@ sc_server_params_copy(struct sc_server_params *dst,
COPY(crop); COPY(crop);
COPY(codec_options); COPY(codec_options);
COPY(encoder_name); COPY(encoder_name);
COPY(audio_encoder_name);
COPY(tcpip_dst); COPY(tcpip_dst);
#undef COPY #undef COPY
@ -270,6 +272,9 @@ execute_server(struct sc_server *server,
if (params->encoder_name) { if (params->encoder_name) {
ADD_PARAM("encoder_name=%s", params->encoder_name); ADD_PARAM("encoder_name=%s", params->encoder_name);
} }
if (params->audio_encoder_name) {
ADD_PARAM("audio_encoder_name=%s", params->audio_encoder_name);
}
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

@ -30,6 +30,7 @@ struct sc_server_params {
const char *crop; const char *crop;
const char *codec_options; const char *codec_options;
const char *encoder_name; const char *encoder_name;
const char *audio_encoder_name;
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

@ -45,6 +45,7 @@ public final class AudioEncoder {
private final Streamer streamer; private final Streamer streamer;
private final int bitRate; private final int bitRate;
private final String encoderName;
private AudioRecord recorder; private AudioRecord recorder;
private MediaCodec mediaCodec; private MediaCodec mediaCodec;
@ -62,9 +63,10 @@ public final class AudioEncoder {
private boolean ended; private boolean ended;
public AudioEncoder(Streamer streamer, int bitRate) { public AudioEncoder(Streamer streamer, int bitRate, String encoderName) {
this.streamer = streamer; this.streamer = streamer;
this.bitRate = bitRate; this.bitRate = bitRate;
this.encoderName = encoderName;
} }
private static AudioFormat createAudioFormat() { private static AudioFormat createAudioFormat() {
@ -208,15 +210,15 @@ public final class AudioEncoder {
try { try {
try { try {
String mimeType = streamer.getCodec().getMimeType(); Codec codec = streamer.getCodec();
mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException mediaCodec = createMediaCodec(codec, encoderName);
recorder = createAudioRecord(); recorder = createAudioRecord();
mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread = new HandlerThread("AudioEncoder");
mediaCodecThread.start(); mediaCodecThread.start();
MediaFormat format = createFormat(mimeType, bitRate); MediaFormat format = createFormat(codec.getMimeType(), 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);
@ -255,6 +257,8 @@ public final class AudioEncoder {
mediaCodec.start(); mediaCodec.start();
inputThread.start(); inputThread.start();
outputThread.start(); outputThread.start();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (Throwable e) { } catch (Throwable e) {
if (mediaCodec != null) { if (mediaCodec != null) {
mediaCodec.release(); mediaCodec.release();
@ -272,6 +276,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 void cleanUp() { private void cleanUp() {
mediaCodecThread.getLooper().quit(); mediaCodecThread.getLooper().quit();
inputThread.interrupt(); inputThread.interrupt();

View file

@ -18,8 +18,10 @@ public final class CodecUtils {
MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); MediaCodecInfo[] encoders = listEncoders(codec.getMimeType());
if (encoders != null && encoders.length > 0) { if (encoders != null && encoders.length > 0) {
msg.append("\nTry to use one of the available encoders:"); msg.append("\nTry to use one of the available encoders:");
String codecOption = codec.getType() == Codec.Type.VIDEO ? "codec" : "audio-codec";
for (MediaCodecInfo encoder : encoders) { for (MediaCodecInfo encoder : encoders) {
msg.append("\n scrcpy --codec=").append(codec.getName()).append(" --encoder='").append(encoder.getName()).append("'"); msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName());
msg.append(" --encoder='").append(encoder.getName()).append("'");
} }
} }
return msg.toString(); return msg.toString();

View file

@ -24,6 +24,7 @@ public class Options {
private boolean stayAwake; private boolean stayAwake;
private List<CodecOption> codecOptions; private List<CodecOption> codecOptions;
private String encoderName; private String encoderName;
private String audioEncoderName;
private boolean powerOffScreenOnClose; private boolean powerOffScreenOnClose;
private boolean clipboardAutosync = true; private boolean clipboardAutosync = true;
private boolean downsizeOnError = true; private boolean downsizeOnError = true;
@ -180,6 +181,14 @@ public class Options {
this.encoderName = encoderName; this.encoderName = encoderName;
} }
public String getAudioEncoderName() {
return audioEncoderName;
}
public void setAudioEncoderName(String audioEncoderName) {
this.audioEncoderName = audioEncoderName;
}
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose; this.powerOffScreenOnClose = powerOffScreenOnClose;
} }

View file

@ -113,7 +113,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()); audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioEncoderName());
audioEncoder.start(); audioEncoder.start();
} }
@ -264,6 +264,10 @@ public final class Server {
options.setEncoderName(value); options.setEncoderName(value);
} }
break; break;
case "audio_encoder_name":
if (!value.isEmpty()) {
options.setAudioEncoderName(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);