mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-12-19 22:44:19 +01:00
The audio player had 2 roles: - handle the SDL audio output device; - resample input samples to maintain a target latency. Extract the latter to a separate component (an "audio regulator"), independent of SDL.
119 lines
3.7 KiB
C
119 lines
3.7 KiB
C
#include "audio_player.h"
|
|
|
|
#include "util/log.h"
|
|
|
|
/** Downcast frame_sink to sc_audio_player */
|
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
|
|
|
|
#define SC_SDL_SAMPLE_FMT AUDIO_F32
|
|
|
|
static void SDLCALL
|
|
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|
struct sc_audio_player *ap = userdata;
|
|
|
|
assert(len_int > 0);
|
|
size_t len = len_int;
|
|
|
|
assert(len % ap->audioreg.sample_size == 0);
|
|
uint32_t out_samples = len / ap->audioreg.sample_size;
|
|
|
|
sc_audio_regulator_pull(&ap->audioreg, stream, out_samples);
|
|
}
|
|
|
|
static bool
|
|
sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|
const AVFrame *frame) {
|
|
struct sc_audio_player *ap = DOWNCAST(sink);
|
|
|
|
return sc_audio_regulator_push(&ap->audioreg, frame);
|
|
}
|
|
|
|
static bool
|
|
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
|
const AVCodecContext *ctx) {
|
|
struct sc_audio_player *ap = DOWNCAST(sink);
|
|
|
|
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
|
assert(ctx->ch_layout.nb_channels > 0 && ctx->ch_layout.nb_channels < 256);
|
|
uint8_t nb_channels = ctx->ch_layout.nb_channels;
|
|
#else
|
|
int tmp = av_get_channel_layout_nb_channels(ctx->channel_layout);
|
|
assert(tmp > 0 && tmp < 256);
|
|
uint8_t nb_channels = tmp;
|
|
#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);
|
|
|
|
uint32_t target_buffering_samples =
|
|
ap->target_buffering_delay * ctx->sample_rate / SC_TICK_FREQ;
|
|
|
|
size_t sample_size = nb_channels * out_bytes_per_sample;
|
|
bool ok = sc_audio_regulator_init(&ap->audioreg, sample_size, ctx,
|
|
target_buffering_samples);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
uint64_t aout_samples = ap->output_buffer_duration * ctx->sample_rate
|
|
/ SC_TICK_FREQ;
|
|
assert(aout_samples <= 0xFFFF);
|
|
|
|
SDL_AudioSpec desired = {
|
|
.freq = ctx->sample_rate,
|
|
.format = SC_SDL_SAMPLE_FMT,
|
|
.channels = nb_channels,
|
|
.samples = aout_samples,
|
|
.callback = sc_audio_player_sdl_callback,
|
|
.userdata = ap,
|
|
};
|
|
SDL_AudioSpec obtained;
|
|
|
|
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
|
if (!ap->device) {
|
|
LOGE("Could not open audio device: %s", SDL_GetError());
|
|
sc_audio_regulator_destroy(&ap->audioreg);
|
|
return false;
|
|
}
|
|
|
|
// The thread calling open() is the thread calling push(), which fills the
|
|
// audio buffer consumed by the SDL audio thread.
|
|
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_TIME_CRITICAL);
|
|
if (!ok) {
|
|
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_HIGH);
|
|
(void) ok; // We don't care if it worked, at least we tried
|
|
}
|
|
|
|
SDL_PauseAudioDevice(ap->device, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
|
struct sc_audio_player *ap = DOWNCAST(sink);
|
|
|
|
assert(ap->device);
|
|
SDL_PauseAudioDevice(ap->device, 1);
|
|
SDL_CloseAudioDevice(ap->device);
|
|
|
|
sc_audio_regulator_destroy(&ap->audioreg);
|
|
}
|
|
|
|
void
|
|
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->output_buffer_duration = output_buffer_duration;
|
|
|
|
static const struct sc_frame_sink_ops ops = {
|
|
.open = sc_audio_player_frame_sink_open,
|
|
.close = sc_audio_player_frame_sink_close,
|
|
.push = sc_audio_player_frame_sink_push,
|
|
};
|
|
|
|
ap->frame_sink.ops = &ops;
|
|
}
|