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-buffer=
|
||||||
--audio-codec=
|
--audio-codec=
|
||||||
--audio-codec-options=
|
--audio-codec-options=
|
||||||
|
--audio-dup
|
||||||
--audio-encoder=
|
--audio-encoder=
|
||||||
--audio-source=
|
--audio-source=
|
||||||
--audio-output-buffer=
|
--audio-output-buffer=
|
||||||
|
@ -13,6 +13,7 @@ arguments=(
|
|||||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
'--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-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-encoder=[Use a specific MediaCodec audio encoder]'
|
||||||
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
||||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
'--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>
|
<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
|
.TP
|
||||||
.BI "\-\-audio\-encoder " name
|
.BI "\-\-audio\-encoder " name
|
||||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
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_NO_WINDOW,
|
||||||
OPT_MOUSE_BIND,
|
OPT_MOUSE_BIND,
|
||||||
OPT_NO_MOUSE_HOVER,
|
OPT_NO_MOUSE_HOVER,
|
||||||
|
OPT_AUDIO_DUP,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -177,6 +178,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_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_id = OPT_AUDIO_ENCODER,
|
||||||
.longopt = "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:
|
case OPT_NO_WINDOW:
|
||||||
opts->window = false;
|
opts->window = false;
|
||||||
break;
|
break;
|
||||||
|
case OPT_AUDIO_DUP:
|
||||||
|
opts->audio_dup = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
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) {
|
if (opts->record_format && !opts->record_filename) {
|
||||||
LOGE("Record format specified without recording");
|
LOGE("Record format specified without recording");
|
||||||
return false;
|
return false;
|
||||||
|
@ -101,6 +101,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.list = 0,
|
.list = 0,
|
||||||
.window = true,
|
.window = true,
|
||||||
.mouse_hover = true,
|
.mouse_hover = true,
|
||||||
|
.audio_dup = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_orientation
|
enum sc_orientation
|
||||||
|
@ -297,6 +297,7 @@ struct scrcpy_options {
|
|||||||
uint8_t list;
|
uint8_t list;
|
||||||
bool window;
|
bool window;
|
||||||
bool mouse_hover;
|
bool mouse_hover;
|
||||||
|
bool audio_dup;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
@ -394,6 +394,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
.video = options->video,
|
.video = options->video,
|
||||||
.audio = options->audio,
|
.audio = options->audio,
|
||||||
|
.audio_dup = options->audio_dup,
|
||||||
.show_touches = options->show_touches,
|
.show_touches = options->show_touches,
|
||||||
.stay_awake = options->stay_awake,
|
.stay_awake = options->stay_awake,
|
||||||
.video_codec_options = options->video_codec_options,
|
.video_codec_options = options->video_codec_options,
|
||||||
|
@ -292,6 +292,9 @@ execute_server(struct sc_server *server,
|
|||||||
ADD_PARAM("audio_source=%s",
|
ADD_PARAM("audio_source=%s",
|
||||||
sc_server_get_audio_source_name(params->audio_source));
|
sc_server_get_audio_source_name(params->audio_source));
|
||||||
}
|
}
|
||||||
|
if (params->audio_dup) {
|
||||||
|
ADD_PARAM("audio_dup=true");
|
||||||
|
}
|
||||||
if (params->max_size) {
|
if (params->max_size) {
|
||||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ struct sc_server_params {
|
|||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
bool video;
|
bool video;
|
||||||
bool audio;
|
bool audio;
|
||||||
|
bool audio_dup;
|
||||||
bool show_touches;
|
bool show_touches;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
|
@ -26,6 +26,7 @@ public class Options {
|
|||||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
private AudioCodec audioCodec = AudioCodec.OPUS;
|
||||||
private VideoSource videoSource = VideoSource.DISPLAY;
|
private VideoSource videoSource = VideoSource.DISPLAY;
|
||||||
private AudioSource audioSource = AudioSource.OUTPUT;
|
private AudioSource audioSource = AudioSource.OUTPUT;
|
||||||
|
private boolean audioDup;
|
||||||
private int videoBitRate = 8000000;
|
private int videoBitRate = 8000000;
|
||||||
private int audioBitRate = 128000;
|
private int audioBitRate = 128000;
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
@ -100,6 +101,10 @@ public class Options {
|
|||||||
return audioSource;
|
return audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getAudioDup() {
|
||||||
|
return audioDup;
|
||||||
|
}
|
||||||
|
|
||||||
public int getVideoBitRate() {
|
public int getVideoBitRate() {
|
||||||
return videoBitRate;
|
return videoBitRate;
|
||||||
}
|
}
|
||||||
@ -303,6 +308,9 @@ public class Options {
|
|||||||
}
|
}
|
||||||
options.audioSource = audioSource;
|
options.audioSource = audioSource;
|
||||||
break;
|
break;
|
||||||
|
case "audio_dup":
|
||||||
|
options.audioDup = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
case "max_size":
|
case "max_size":
|
||||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||||
break;
|
break;
|
||||||
|
@ -167,7 +167,13 @@ public final class Server {
|
|||||||
if (audio) {
|
if (audio) {
|
||||||
AudioCodec audioCodec = options.getAudioCodec();
|
AudioCodec audioCodec = options.getAudioCodec();
|
||||||
AudioSource audioSource = options.getAudioSource();
|
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());
|
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
||||||
AsyncProcessor audioRecorder;
|
AsyncProcessor audioRecorder;
|
||||||
if (audioCodec == AudioCodec.RAW) {
|
if (audioCodec == AudioCodec.RAW) {
|
||||||
|
@ -18,9 +18,15 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public final class AudioPlaybackCapture implements AudioCapture {
|
public final class AudioPlaybackCapture implements AudioCapture {
|
||||||
|
|
||||||
|
private final boolean keepPlayingOnDevice;
|
||||||
|
|
||||||
private AudioRecord recorder;
|
private AudioRecord recorder;
|
||||||
private AudioRecordReader reader;
|
private AudioRecordReader reader;
|
||||||
|
|
||||||
|
public AudioPlaybackCapture(boolean keepPlayingOnDevice) {
|
||||||
|
this.keepPlayingOnDevice = keepPlayingOnDevice;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
@SuppressLint("PrivateApi")
|
||||||
private AudioRecord createAudioRecord() throws AudioCaptureException {
|
private AudioRecord createAudioRecord() throws AudioCaptureException {
|
||||||
// See <https://github.com/Genymobile/scrcpy/issues/4380>
|
// 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);
|
Method setFormat = audioMixBuilder.getClass().getMethod("setFormat", AudioFormat.class);
|
||||||
setFormat.invoke(audioMixBuilder, AudioConfig.createAudioFormat());
|
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);
|
// audioMixBuilder.setRouteFlags(routeFlag);
|
||||||
Method setRouteFlags = audioMixBuilder.getClass().getMethod("setRouteFlags", int.class);
|
Method setRouteFlags = audioMixBuilder.getClass().getMethod("setRouteFlags", int.class);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user