Compare commits
1 Commits
android14
...
audio_outp
Author | SHA1 | Date | |
---|---|---|---|
99a0b13496 |
@ -33,6 +33,14 @@ Lower values decrease the latency, but increase the likelyhood of buffer underru
|
|||||||
|
|
||||||
Default is 50.
|
Default is 50.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-audio\-output\-buffer ms
|
||||||
|
Configure the size of the SDL audio output buffer (in milliseconds).
|
||||||
|
|
||||||
|
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
|
||||||
|
|
||||||
|
Default is 5.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-audio\-codec " name
|
.BI "\-\-audio\-codec " name
|
||||||
Select an audio codec (opus, aac or raw).
|
Select an audio codec (opus, aac or raw).
|
||||||
|
@ -59,8 +59,6 @@
|
|||||||
#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT
|
#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT
|
||||||
#define SC_SDL_SAMPLE_FMT AUDIO_F32
|
#define SC_SDL_SAMPLE_FMT AUDIO_F32
|
||||||
|
|
||||||
#define SC_AUDIO_OUTPUT_BUFFER_MS 5
|
|
||||||
|
|
||||||
#define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ap->buf, (SAMPLES))
|
#define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ap->buf, (SAMPLES))
|
||||||
#define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ap->buf, (BYTES))
|
#define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ap->buf, (BYTES))
|
||||||
|
|
||||||
@ -230,7 +228,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
|
|
||||||
if (played) {
|
if (played) {
|
||||||
uint32_t max_buffered_samples = ap->target_buffering
|
uint32_t max_buffered_samples = ap->target_buffering
|
||||||
+ 12 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000
|
+ 12 * ap->output_buffer
|
||||||
+ ap->target_buffering / 10;
|
+ ap->target_buffering / 10;
|
||||||
if (buffered_samples > max_buffered_samples) {
|
if (buffered_samples > max_buffered_samples) {
|
||||||
uint32_t skip_samples = buffered_samples - max_buffered_samples;
|
uint32_t skip_samples = buffered_samples - max_buffered_samples;
|
||||||
@ -246,7 +244,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
// max_initial_buffering samples, this would cause unnecessary delay
|
// max_initial_buffering samples, this would cause unnecessary delay
|
||||||
// (and glitches to compensate) on start.
|
// (and glitches to compensate) on start.
|
||||||
uint32_t max_initial_buffering = ap->target_buffering
|
uint32_t max_initial_buffering = ap->target_buffering
|
||||||
+ 2 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000;
|
+ 2 * ap->output_buffer;
|
||||||
if (buffered_samples > max_initial_buffering) {
|
if (buffered_samples > max_initial_buffering) {
|
||||||
uint32_t skip_samples = buffered_samples - max_initial_buffering;
|
uint32_t skip_samples = buffered_samples - max_initial_buffering;
|
||||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
sc_audiobuf_skip(&ap->buf, skip_samples);
|
||||||
@ -333,11 +331,28 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
unsigned nb_channels = tmp;
|
unsigned nb_channels = tmp;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
assert(ctx->sample_rate > 0);
|
||||||
|
assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT));
|
||||||
|
int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT);
|
||||||
|
assert(out_bytes_per_sample > 0);
|
||||||
|
|
||||||
|
ap->sample_rate = ctx->sample_rate;
|
||||||
|
ap->nb_channels = nb_channels;
|
||||||
|
ap->out_bytes_per_sample = out_bytes_per_sample;
|
||||||
|
|
||||||
|
ap->target_buffering = ap->target_buffering_delay * ap->sample_rate
|
||||||
|
/ SC_TICK_FREQ;
|
||||||
|
|
||||||
|
uint64_t aout_samples = ap->output_buffer_duration * ap->sample_rate
|
||||||
|
/ SC_TICK_FREQ;
|
||||||
|
assert(aout_samples <= 0xFFFF);
|
||||||
|
ap->output_buffer = (uint16_t) aout_samples;
|
||||||
|
|
||||||
SDL_AudioSpec desired = {
|
SDL_AudioSpec desired = {
|
||||||
.freq = ctx->sample_rate,
|
.freq = ctx->sample_rate,
|
||||||
.format = SC_SDL_SAMPLE_FMT,
|
.format = SC_SDL_SAMPLE_FMT,
|
||||||
.channels = nb_channels,
|
.channels = nb_channels,
|
||||||
.samples = SC_AUDIO_OUTPUT_BUFFER_MS * ctx->sample_rate / 1000,
|
.samples = aout_samples,
|
||||||
.callback = sc_audio_player_sdl_callback,
|
.callback = sc_audio_player_sdl_callback,
|
||||||
.userdata = ap,
|
.userdata = ap,
|
||||||
};
|
};
|
||||||
@ -356,11 +371,6 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
}
|
}
|
||||||
ap->swr_ctx = swr_ctx;
|
ap->swr_ctx = swr_ctx;
|
||||||
|
|
||||||
assert(ctx->sample_rate > 0);
|
|
||||||
assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT));
|
|
||||||
int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT);
|
|
||||||
assert(out_bytes_per_sample > 0);
|
|
||||||
|
|
||||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||||
av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0);
|
av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0);
|
||||||
av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0);
|
av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0);
|
||||||
@ -383,13 +393,6 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
goto error_free_swr_ctx;
|
goto error_free_swr_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
ap->sample_rate = ctx->sample_rate;
|
|
||||||
ap->nb_channels = nb_channels;
|
|
||||||
ap->out_bytes_per_sample = out_bytes_per_sample;
|
|
||||||
|
|
||||||
ap->target_buffering = ap->target_buffering_delay * ap->sample_rate
|
|
||||||
/ SC_TICK_FREQ;
|
|
||||||
|
|
||||||
// Use a ring-buffer of the target buffering size plus 1 second between the
|
// Use a ring-buffer of the target buffering size plus 1 second between the
|
||||||
// producer and the consumer. It's too big on purpose, to guarantee that
|
// producer and the consumer. It's too big on purpose, to guarantee that
|
||||||
// the producer and the consumer will be able to access it in parallel
|
// the producer and the consumer will be able to access it in parallel
|
||||||
@ -458,8 +461,10 @@ sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering) {
|
sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering,
|
||||||
|
sc_tick output_buffer_duration) {
|
||||||
ap->target_buffering_delay = target_buffering;
|
ap->target_buffering_delay = target_buffering;
|
||||||
|
ap->output_buffer_duration = output_buffer_duration;
|
||||||
|
|
||||||
static const struct sc_frame_sink_ops ops = {
|
static const struct sc_frame_sink_ops ops = {
|
||||||
.open = sc_audio_player_frame_sink_open,
|
.open = sc_audio_player_frame_sink_open,
|
||||||
|
@ -27,6 +27,10 @@ struct sc_audio_player {
|
|||||||
sc_tick target_buffering_delay;
|
sc_tick target_buffering_delay;
|
||||||
uint32_t target_buffering; // in samples
|
uint32_t target_buffering; // in samples
|
||||||
|
|
||||||
|
// SDL audio output buffer size.
|
||||||
|
sc_tick output_buffer_duration;
|
||||||
|
uint16_t output_buffer;
|
||||||
|
|
||||||
// Audio buffer to communicate between the receiver and the SDL audio
|
// Audio buffer to communicate between the receiver and the SDL audio
|
||||||
// callback (protected by SDL_AudioDeviceLock())
|
// callback (protected by SDL_AudioDeviceLock())
|
||||||
struct sc_audiobuf buf;
|
struct sc_audiobuf buf;
|
||||||
@ -80,6 +84,7 @@ struct sc_audio_player_callbacks {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering);
|
sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering,
|
||||||
|
sc_tick audio_output_buffer);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -71,6 +71,7 @@ enum {
|
|||||||
OPT_LIST_DISPLAYS,
|
OPT_LIST_DISPLAYS,
|
||||||
OPT_REQUIRE_AUDIO,
|
OPT_REQUIRE_AUDIO,
|
||||||
OPT_AUDIO_BUFFER,
|
OPT_AUDIO_BUFFER,
|
||||||
|
OPT_AUDIO_OUTPUT_BUFFER,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -129,6 +130,16 @@ static const struct sc_option options[] = {
|
|||||||
"likelyhood of buffer underrun (causing audio glitches).\n"
|
"likelyhood of buffer underrun (causing audio glitches).\n"
|
||||||
"Default is 50.",
|
"Default is 50.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_AUDIO_OUTPUT_BUFFER,
|
||||||
|
.longopt = "audio-output-buffer",
|
||||||
|
.argdesc = "ms",
|
||||||
|
.text = "Configure the size of the SDL audio output buffer (in "
|
||||||
|
"milliseconds).\n"
|
||||||
|
"If you get \"robotic\" audio playback, you should test with "
|
||||||
|
"a higher value (10). Do not change this setting otherwise.\n"
|
||||||
|
"Default is 5.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_AUDIO_CODEC,
|
.longopt_id = OPT_AUDIO_CODEC,
|
||||||
.longopt = "audio-codec",
|
.longopt = "audio-codec",
|
||||||
@ -1204,6 +1215,19 @@ parse_buffering_time(const char *s, sc_tick *tick) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_audio_output_buffer(const char *s, sc_tick *tick) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 1000,
|
||||||
|
"audio output buffer");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*tick = SC_TICK_FROM_MS(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_lock_video_orientation(const char *s,
|
parse_lock_video_orientation(const char *s,
|
||||||
enum sc_lock_video_orientation *lock_mode) {
|
enum sc_lock_video_orientation *lock_mode) {
|
||||||
@ -1831,6 +1855,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OPT_AUDIO_OUTPUT_BUFFER:
|
||||||
|
if (!parse_audio_output_buffer(optarg,
|
||||||
|
&opts->audio_output_buffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
@ -44,6 +44,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.display_buffer = 0,
|
.display_buffer = 0,
|
||||||
.v4l2_buffer = 0,
|
.v4l2_buffer = 0,
|
||||||
.audio_buffer = SC_TICK_FROM_MS(50),
|
.audio_buffer = SC_TICK_FROM_MS(50),
|
||||||
|
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
.otg = false,
|
.otg = false,
|
||||||
#endif
|
#endif
|
||||||
|
@ -127,6 +127,7 @@ struct scrcpy_options {
|
|||||||
sc_tick display_buffer;
|
sc_tick display_buffer;
|
||||||
sc_tick v4l2_buffer;
|
sc_tick v4l2_buffer;
|
||||||
sc_tick audio_buffer;
|
sc_tick audio_buffer;
|
||||||
|
sc_tick audio_output_buffer;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
bool otg;
|
bool otg;
|
||||||
#endif
|
#endif
|
||||||
|
@ -688,7 +688,8 @@ aoa_hid_end:
|
|||||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||||
|
|
||||||
if (options->audio) {
|
if (options->audio) {
|
||||||
sc_audio_player_init(&s->audio_player, options->audio_buffer);
|
sc_audio_player_init(&s->audio_player, options->audio_buffer,
|
||||||
|
options->audio_output_buffer);
|
||||||
sc_frame_source_add_sink(&s->audio_decoder.frame_source,
|
sc_frame_source_add_sink(&s->audio_decoder.frame_source,
|
||||||
&s->audio_player.frame_sink);
|
&s->audio_player.frame_sink);
|
||||||
}
|
}
|
||||||
|
11
doc/audio.md
11
doc/audio.md
@ -88,3 +88,14 @@ avoid glitches and smooth the playback:
|
|||||||
```
|
```
|
||||||
scrcpy --display-buffer=200 --audio-buffer=200
|
scrcpy --display-buffer=200 --audio-buffer=200
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It is also possible to configure another audio buffer (the audio output buffer),
|
||||||
|
by default set to 5ms. Don't change it, unless you get some [robotic and glitchy
|
||||||
|
sound][#3793]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Only if absolutely necessary
|
||||||
|
scrcpy --audio-output-buffer=10
|
||||||
|
```
|
||||||
|
|
||||||
|
[#3793]: https://github.com/Genymobile/scrcpy/issues/3793
|
||||||
|
Reference in New Issue
Block a user