Skip to content

Commit

Permalink
Add --audio-dup
Browse files Browse the repository at this point in the history
Add an option to duplicate audio on the device, compatible with the new
audio playback capture (--audio-source=playback).

Fixes #3875 <#3875>
Fixes #4380 <#4380>

Suggested-by: Simon Chan <[email protected]>
  • Loading branch information
rom1v committed Jul 16, 2024
1 parent fe21685 commit 00d2b16
Show file tree
Hide file tree
Showing 12 changed files with 61 additions and 2 deletions.
1 change: 1 addition & 0 deletions app/data/bash-completion/scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ _scrcpy() {
--audio-buffer=
--audio-codec=
--audio-codec-options=
--audio-dup
--audio-encoder=
--audio-source=
--audio-output-buffer=
Expand Down
1 change: 1 addition & 0 deletions app/data/zsh-completion/_scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -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)]'
Expand Down
6 changes: 6 additions & 0 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
23 changes: 23 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ enum {
OPT_NO_WINDOW,
OPT_MOUSE_BIND,
OPT_NO_MOUSE_HOVER,
OPT_AUDIO_DUP,
};

struct sc_option {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions app/src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const struct scrcpy_options scrcpy_options_default = {
.list = 0,
.window = true,
.mouse_hover = true,
.audio_dup = false,
};

enum sc_orientation
Expand Down
1 change: 1 addition & 0 deletions app/src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -100,6 +101,10 @@ public AudioSource getAudioSource() {
return audioSource;
}

public boolean getAudioDup() {
return audioDup;
}

public int getVideoBitRate() {
return videoBitRate;
}
Expand Down Expand Up @@ -303,6 +308,9 @@ public static Options parse(String... args) {
}
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;
Expand Down
8 changes: 7 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,13 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@

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 {
try {
Expand Down Expand Up @@ -61,7 +67,8 @@ private AudioRecord createAudioRecord() throws AudioCaptureException {
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);
Expand Down

0 comments on commit 00d2b16

Please sign in to comment.