Add --audio-encoder
Similar to --video-encoder, but for audio. PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
This commit is contained in:
parent
6f332a2bc7
commit
f9960e959f
12 changed files with 64 additions and 6 deletions
|
@ -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
|
||||||
|
|
|
@ -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]'
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue