Add --audio-dup
Add an option to duplicate audio on the device, compatible with the new audio playback capture (--audio-source=playback). Fixes #3875 <https://github.com/Genymobile/scrcpy/issues/3875> Fixes #4380 <https://github.com/Genymobile/scrcpy/issues/4380> PR #5102 <https://github.com/Genymobile/scrcpy/pull/5102> Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
This commit is contained in:
parent
a10f8cd798
commit
31116a60d7
@ -6,6 +6,7 @@ _scrcpy() {
|
||||
--audio-buffer=
|
||||
--audio-codec=
|
||||
--audio-codec-options=
|
||||
--audio-dup
|
||||
--audio-encoder=
|
||||
--audio-source=
|
||||
--audio-output-buffer=
|
||||
|
@ -13,6 +13,7 @@ arguments=(
|
||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||
'--audio-dup=[Duplicate audio]'
|
||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
||||
|
@ -49,6 +49,12 @@ The list of possible codec options is available in the Android documentation:
|
||||
|
||||
<https://d.android.com/reference/android/media/MediaFormat>
|
||||
|
||||
.TP
|
||||
.B \-\-audio\-dup
|
||||
Duplicate audio (capture and keep playing on the device).
|
||||
|
||||
This feature is only available with --audio-source=playback.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-encoder " name
|
||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
||||
|
@ -100,6 +100,7 @@ enum {
|
||||
OPT_NO_WINDOW,
|
||||
OPT_MOUSE_BIND,
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
OPT_AUDIO_DUP,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@ -177,6 +178,13 @@ static const struct sc_option options[] = {
|
||||
"Android documentation: "
|
||||
"<https://d.android.com/reference/android/media/MediaFormat>",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_DUP,
|
||||
.longopt = "audio-dup",
|
||||
.text = "Duplicate audio (capture and keep playing on the device).\n"
|
||||
"This feature is only available with --audio-source=playback."
|
||||
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_ENCODER,
|
||||
.longopt = "audio-encoder",
|
||||
@ -2615,6 +2623,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_NO_WINDOW:
|
||||
opts->window = false;
|
||||
break;
|
||||
case OPT_AUDIO_DUP:
|
||||
opts->audio_dup = true;
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@ -2891,6 +2902,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->audio_dup) {
|
||||
if (!opts->audio) {
|
||||
LOGE("--audio-dup not supported if audio is disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->audio_source != SC_AUDIO_SOURCE_PLAYBACK) {
|
||||
LOGE("--audio-dup is specific to --audio-source=playback");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->record_format && !opts->record_filename) {
|
||||
LOGE("Record format specified without recording");
|
||||
return false;
|
||||
|
@ -101,6 +101,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.list = 0,
|
||||
.window = true,
|
||||
.mouse_hover = true,
|
||||
.audio_dup = false,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
|
@ -297,6 +297,7 @@ struct scrcpy_options {
|
||||
uint8_t list;
|
||||
bool window;
|
||||
bool mouse_hover;
|
||||
bool audio_dup;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
@ -394,6 +394,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.display_id = options->display_id,
|
||||
.video = options->video,
|
||||
.audio = options->audio,
|
||||
.audio_dup = options->audio_dup,
|
||||
.show_touches = options->show_touches,
|
||||
.stay_awake = options->stay_awake,
|
||||
.video_codec_options = options->video_codec_options,
|
||||
|
@ -292,6 +292,9 @@ execute_server(struct sc_server *server,
|
||||
ADD_PARAM("audio_source=%s",
|
||||
sc_server_get_audio_source_name(params->audio_source));
|
||||
}
|
||||
if (params->audio_dup) {
|
||||
ADD_PARAM("audio_dup=true");
|
||||
}
|
||||
if (params->max_size) {
|
||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ struct sc_server_params {
|
||||
uint32_t display_id;
|
||||
bool video;
|
||||
bool audio;
|
||||
bool audio_dup;
|
||||
bool show_touches;
|
||||
bool stay_awake;
|
||||
bool force_adb_forward;
|
||||
|
@ -26,6 +26,7 @@ public class Options {
|
||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
||||
private VideoSource videoSource = VideoSource.DISPLAY;
|
||||
private AudioSource audioSource = AudioSource.OUTPUT;
|
||||
private boolean audioDup;
|
||||
private int videoBitRate = 8000000;
|
||||
private int audioBitRate = 128000;
|
||||
private int maxFps;
|
||||
@ -100,6 +101,10 @@ public class Options {
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public boolean getAudioDup() {
|
||||
return audioDup;
|
||||
}
|
||||
|
||||
public int getVideoBitRate() {
|
||||
return videoBitRate;
|
||||
}
|
||||
@ -303,6 +308,9 @@ public class Options {
|
||||
}
|
||||
options.audioSource = audioSource;
|
||||
break;
|
||||
case "audio_dup":
|
||||
options.audioDup = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "max_size":
|
||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||
break;
|
||||
|
@ -167,7 +167,13 @@ public final class Server {
|
||||
if (audio) {
|
||||
AudioCodec audioCodec = options.getAudioCodec();
|
||||
AudioSource audioSource = options.getAudioSource();
|
||||
AudioCapture audioCapture = audioSource.isDirect() ? new AudioDirectCapture(audioSource) : new AudioPlaybackCapture();
|
||||
AudioCapture audioCapture;
|
||||
if (audioSource.isDirect()) {
|
||||
audioCapture = new AudioDirectCapture(audioSource);
|
||||
} else {
|
||||
audioCapture = new AudioPlaybackCapture(options.getAudioDup());
|
||||
}
|
||||
|
||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
||||
AsyncProcessor audioRecorder;
|
||||
if (audioCodec == AudioCodec.RAW) {
|
||||
|
@ -18,9 +18,15 @@ import java.nio.ByteBuffer;
|
||||
|
||||
public final class AudioPlaybackCapture implements AudioCapture {
|
||||
|
||||
private final boolean keepPlayingOnDevice;
|
||||
|
||||
private AudioRecord recorder;
|
||||
private AudioRecordReader reader;
|
||||
|
||||
public AudioPlaybackCapture(boolean keepPlayingOnDevice) {
|
||||
this.keepPlayingOnDevice = keepPlayingOnDevice;
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
private AudioRecord createAudioRecord() throws AudioCaptureException {
|
||||
// See <https://github.com/Genymobile/scrcpy/issues/4380>
|
||||
@ -60,7 +66,8 @@ public final class AudioPlaybackCapture implements AudioCapture {
|
||||
Method setFormat = audioMixBuilder.getClass().getMethod("setFormat", AudioFormat.class);
|
||||
setFormat.invoke(audioMixBuilder, AudioConfig.createAudioFormat());
|
||||
|
||||
int routeFlags = audioMixClass.getField("ROUTE_FLAG_LOOP_BACK").getInt(null);
|
||||
String routeFlagName = keepPlayingOnDevice ? "ROUTE_FLAG_LOOP_BACK_RENDER" : "ROUTE_FLAG_LOOP_BACK";
|
||||
int routeFlags = audioMixClass.getField(routeFlagName).getInt(null);
|
||||
|
||||
// audioMixBuilder.setRouteFlags(routeFlag);
|
||||
Method setRouteFlags = audioMixBuilder.getClass().getMethod("setRouteFlags", int.class);
|
||||
|
Loading…
x
Reference in New Issue
Block a user