[client] audio: preserve native playback formats

This commit is contained in:
Geoffrey McRae
2026-08-10 03:45:52 +10:00
parent ff665c2031
commit fa8a95569a
6 changed files with 267 additions and 65 deletions

View File

@@ -156,6 +156,7 @@ static enum spa_audio_format pipewire_sampleFormat(
case LG_AUDIO_FMT_S24_LE : return SPA_AUDIO_FORMAT_S24_LE; case LG_AUDIO_FMT_S24_LE : return SPA_AUDIO_FORMAT_S24_LE;
case LG_AUDIO_FMT_S32_LE : return SPA_AUDIO_FORMAT_S32_LE; case LG_AUDIO_FMT_S32_LE : return SPA_AUDIO_FORMAT_S32_LE;
case LG_AUDIO_FMT_F32_LE : return SPA_AUDIO_FORMAT_F32_LE; case LG_AUDIO_FMT_F32_LE : return SPA_AUDIO_FORMAT_F32_LE;
case LG_AUDIO_FMT_F32_NE : return SPA_AUDIO_FORMAT_F32;
case LG_AUDIO_FMT_F64_LE : return SPA_AUDIO_FORMAT_F64_LE; case LG_AUDIO_FMT_F64_LE : return SPA_AUDIO_FORMAT_F64_LE;
} }
@@ -170,7 +171,8 @@ static int pipewire_sampleSize(LG_AudioSampleFormat format)
case LG_AUDIO_FMT_S16_LE : return 2; case LG_AUDIO_FMT_S16_LE : return 2;
case LG_AUDIO_FMT_S24_LE : return 3; case LG_AUDIO_FMT_S24_LE : return 3;
case LG_AUDIO_FMT_S32_LE : case LG_AUDIO_FMT_S32_LE :
case LG_AUDIO_FMT_F32_LE : return 4; case LG_AUDIO_FMT_F32_LE :
case LG_AUDIO_FMT_F32_NE : return 4;
case LG_AUDIO_FMT_F64_LE : return 8; case LG_AUDIO_FMT_F64_LE : return 8;
} }
@@ -278,15 +280,15 @@ static inline void pipewire_updatePlaybackLatency(void)
} }
#if PW_CHECK_VERSION(1, 4, 0) #if PW_CHECK_VERSION(1, 4, 0)
static bool pipewire_playbackSetRate(double ratio) static bool pipewire_playbackSetRate(double * ratio)
{ {
if (ratio <= 0.0 || if (!ratio || *ratio <= 0.0 ||
!pw.playback.resamplerEnabled) !pw.playback.resamplerEnabled)
return false; return false;
if (ratio == pw.playback.appliedResampleRatio) if (*ratio == pw.playback.appliedResampleRatio)
return true; return true;
const int result = pw_stream_set_rate(pw.playback.stream, ratio); const int result = pw_stream_set_rate(pw.playback.stream, *ratio);
if (result < 0) if (result < 0)
{ {
int expected = 0; int expected = 0;
@@ -296,7 +298,7 @@ static bool pipewire_playbackSetRate(double ratio)
return false; return false;
} }
pw.playback.appliedResampleRatio = ratio; pw.playback.appliedResampleRatio = *ratio;
return true; return true;
} }
@@ -507,6 +509,12 @@ static bool pipewire_playbackSetup(const LG_AudioFormat * format,
const int channels = format->channelCount; const int channels = format->channelCount;
const int sampleRate = format->sampleRate; const int sampleRate = format->sampleRate;
const int sampleSize =
pipewire_sampleSize(format->sampleFormat);
const enum spa_audio_format sampleFormat =
pipewire_sampleFormat(format->sampleFormat);
if (!sampleSize || sampleFormat == SPA_AUDIO_FORMAT_UNKNOWN)
return false;
const struct spa_pod * params[1]; const struct spa_pod * params[1];
uint8_t buffer[1024]; uint8_t buffer[1024];
@@ -541,7 +549,7 @@ static bool pipewire_playbackSetup(const LG_AudioFormat * format,
requestedPeriodFrames, sampleRate); requestedPeriodFrames, sampleRate);
pw.playback.format = *format; pw.playback.format = *format;
pw.playback.stride = sizeof(float) * channels; pw.playback.stride = sampleSize * channels;
pw.playback.pullFn = pullFn; pw.playback.pullFn = pullFn;
pw_thread_loop_lock(pw.thread); pw_thread_loop_lock(pw.thread);
@@ -627,7 +635,7 @@ static bool pipewire_playbackSetup(const LG_AudioFormat * format,
*startFrames = pw.playback.startFrames; *startFrames = pw.playback.startFrames;
struct spa_audio_info_raw info = struct spa_audio_info_raw info =
pipewire_audioInfo(format, SPA_AUDIO_FORMAT_F32); pipewire_audioInfo(format, sampleFormat);
params[0] = spa_format_audio_raw_build( params[0] = spa_format_audio_raw_build(
&b, SPA_PARAM_EnumFormat, &info); &b, SPA_PARAM_EnumFormat, &info);

View File

@@ -40,10 +40,17 @@ struct PulseAudio
bool sinkCorked; bool sinkCorked;
bool sinkMuted; bool sinkMuted;
bool sinkStarting; bool sinkStarting;
bool sinkResamplerEnabled;
bool sinkRateFailed;
int sinkMaxPeriodFrames; int sinkMaxPeriodFrames;
int sinkStartFrames; int sinkStartFrames;
LG_AudioFormat sinkFormat; LG_AudioFormat sinkFormat;
int sinkStride; int sinkStride;
uint32_t sinkNominalRate;
uint32_t sinkAppliedRate;
uint32_t sinkPendingRate;
uint32_t sinkRequestedRate;
pa_operation * sinkRateOperation;
LG_AudioPullFn sinkPullFn; LG_AudioPullFn sinkPullFn;
_Atomic(int64_t) sinkPresentationDeadline; _Atomic(int64_t) sinkPresentationDeadline;
}; };
@@ -61,6 +68,23 @@ static bool pulseaudio_audioFormatEqual(const LG_AudioFormat * a,
a->channelCount * sizeof(*a->channels)) == 0; a->channelCount * sizeof(*a->channels)) == 0;
} }
static pa_sample_format_t pulseaudio_sampleFormat(
LG_AudioSampleFormat format)
{
switch (format)
{
case LG_AUDIO_FMT_U8 : return PA_SAMPLE_U8;
case LG_AUDIO_FMT_S16_LE : return PA_SAMPLE_S16LE;
case LG_AUDIO_FMT_S24_LE : return PA_SAMPLE_S24LE;
case LG_AUDIO_FMT_S32_LE : return PA_SAMPLE_S32LE;
case LG_AUDIO_FMT_F32_LE : return PA_SAMPLE_FLOAT32LE;
case LG_AUDIO_FMT_F32_NE : return PA_SAMPLE_FLOAT32;
case LG_AUDIO_FMT_F64_LE : return PA_SAMPLE_INVALID;
}
return PA_SAMPLE_INVALID;
}
static pa_channel_position_t pulseaudio_channel( static pa_channel_position_t pulseaudio_channel(
LG_AudioChannel channel, uint8_t index) LG_AudioChannel channel, uint8_t index)
{ {
@@ -118,6 +142,54 @@ static void pulseaudio_unrefOperation(pa_operation * operation)
pa_operation_unref(operation); pa_operation_unref(operation);
} }
static bool pulseaudio_submitRateUpdate(void);
static void pulseaudio_rateUpdate_cb(pa_stream * stream, int success,
void * userdata)
{
if (stream != pa.sink || !pa.sinkRateOperation)
return;
pa_operation * operation = pa.sinkRateOperation;
pa.sinkRateOperation = NULL;
pa_operation_unref(operation);
if (!success)
{
DEBUG_ERROR("Failed to update PulseAudio sample rate: %s",
pa_strerror(pa_context_errno(pa.context)));
pa.sinkRateFailed = true;
return;
}
pa.sinkAppliedRate = pa.sinkPendingRate;
if (pa.sinkCorked)
pulseaudio_submitRateUpdate();
}
static bool pulseaudio_submitRateUpdate(void)
{
if (pa.sinkRateFailed)
return false;
if (pa.sinkRateOperation ||
pa.sinkRequestedRate == pa.sinkAppliedRate)
return true;
pa.sinkPendingRate = pa.sinkRequestedRate;
pa.sinkRateOperation = pa_stream_update_sample_rate(pa.sink,
pa.sinkPendingRate, pulseaudio_rateUpdate_cb, NULL);
if (!pa.sinkRateOperation)
{
DEBUG_ERROR("Failed to request a PulseAudio sample rate update: %s",
pa_strerror(pa_context_errno(pa.context)));
pa.sinkRateFailed = true;
return false;
}
return true;
}
static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i, static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i,
int eol, void *userdata) int eol, void *userdata)
{ {
@@ -268,9 +340,22 @@ static void pulseaudio_sink_close_nl(void)
pa_stream_set_write_callback(pa.sink, NULL, NULL); pa_stream_set_write_callback(pa.sink, NULL, NULL);
pa_stream_set_underflow_callback(pa.sink, NULL, NULL); pa_stream_set_underflow_callback(pa.sink, NULL, NULL);
pa_stream_set_overflow_callback(pa.sink, NULL, NULL); pa_stream_set_overflow_callback(pa.sink, NULL, NULL);
if (pa.sinkRateOperation)
{
pa_operation * operation = pa.sinkRateOperation;
pa.sinkRateOperation = NULL;
pa_operation_cancel(operation);
pa_operation_unref(operation);
}
pulseaudio_unrefOperation(pa_stream_flush(pa.sink, NULL, NULL)); pulseaudio_unrefOperation(pa_stream_flush(pa.sink, NULL, NULL));
pa_stream_unref(pa.sink); pa_stream_unref(pa.sink);
pa.sink = NULL; pa.sink = NULL;
pa.sinkResamplerEnabled = false;
pa.sinkRateFailed = false;
pa.sinkNominalRate = 0;
pa.sinkAppliedRate = 0;
pa.sinkPendingRate = 0;
pa.sinkRequestedRate = 0;
atomic_store_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, 0, memory_order_release); &pa.sinkPresentationDeadline, 0, memory_order_release);
} }
@@ -340,6 +425,11 @@ static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
return; return;
} }
/* Queue rate changes after the current audio block. This keeps the rate
* reported by the preceding pull aligned with the block PulseAudio has
* already received. */
pulseaudio_submitRateUpdate();
pa_usec_t latency; pa_usec_t latency;
int negative; int negative;
if (pa_stream_get_latency(p, &latency, &negative) == 0) if (pa_stream_get_latency(p, &latency, &negative) == 0)
@@ -370,23 +460,24 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
const int channels = format->channelCount; const int channels = format->channelCount;
const int sampleRate = format->sampleRate; const int sampleRate = format->sampleRate;
if (pa.sink && pulseaudio_audioFormatEqual(&pa.sinkFormat, format)) const pa_sample_format_t sampleFormat =
{ pulseaudio_sampleFormat(format->sampleFormat);
*maxPeriodFrames = pa.sinkMaxPeriodFrames; if (sampleFormat == PA_SAMPLE_INVALID)
*startFrames = pa.sinkStartFrames; return false;
return true;
}
pa_sample_spec spec = { pa_sample_spec spec = {
.format = PA_SAMPLE_FLOAT32, .format = sampleFormat,
.rate = sampleRate, .rate = sampleRate,
.channels = channels .channels = channels
}; };
if (!pa_sample_spec_valid(&spec))
return false;
pa_channel_map channelMap = { .channels = channels }; pa_channel_map channelMap = { .channels = channels };
for (uint8_t i = 0; i < format->channelCount; ++i) for (uint8_t i = 0; i < format->channelCount; ++i)
channelMap.map[i] = pulseaudio_channel(format->channels[i], i); channelMap.map[i] = pulseaudio_channel(format->channels[i], i);
int stride = channels * sizeof(float); const int stride = (int)pa_frame_size(&spec);
int bufferSize = requestedPeriodFrames * 2 * stride; int bufferSize = requestedPeriodFrames * 2 * stride;
pa_buffer_attr attribs = pa_buffer_attr attribs =
{ {
@@ -397,13 +488,36 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
}; };
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
/* pa_stream_update_sample_rate requires protocol version 12. */
const bool enableResampler = requestResampler &&
pa_context_get_server_protocol_version(pa.context) >= 12;
if (pa.sink && !pa.sinkRateFailed &&
pa.sinkResamplerEnabled == enableResampler &&
!pa.sinkRateOperation &&
pa.sinkAppliedRate == pa.sinkNominalRate &&
pa.sinkRequestedRate == pa.sinkNominalRate &&
pulseaudio_audioFormatEqual(&pa.sinkFormat, format))
{
*resamplerEnabled = pa.sinkResamplerEnabled;
*maxPeriodFrames = pa.sinkMaxPeriodFrames;
*startFrames = pa.sinkStartFrames;
pa_threaded_mainloop_unlock(pa.loop);
return true;
}
pulseaudio_sink_close_nl(); pulseaudio_sink_close_nl();
pa.sinkFormat = *format; pa.sinkFormat = *format;
pa.sinkStride = stride; pa.sinkStride = stride;
pa.sinkPullFn = pullFn; pa.sinkPullFn = pullFn;
pa.sinkCorked = true; pa.sinkCorked = true;
pa.sinkStarting = false; pa.sinkStarting = false;
pa.sinkResamplerEnabled = enableResampler;
pa.sinkRateFailed = false;
pa.sinkNominalRate = sampleRate;
pa.sinkAppliedRate = sampleRate;
pa.sinkPendingRate = sampleRate;
pa.sinkRequestedRate = sampleRate;
pa.sink = pa_stream_new( pa.sink = pa_stream_new(
pa.context, "Looking Glass", &spec, &channelMap); pa.context, "Looking Glass", &spec, &channelMap);
@@ -424,7 +538,8 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
PA_STREAM_START_CORKED | PA_STREAM_START_CORKED |
PA_STREAM_ADJUST_LATENCY | PA_STREAM_ADJUST_LATENCY |
PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_AUTO_TIMING_UPDATE; PA_STREAM_AUTO_TIMING_UPDATE |
(enableResampler ? PA_STREAM_VARIABLE_RATE : 0);
if (pa_stream_connect_playback( if (pa_stream_connect_playback(
pa.sink, NULL, &attribs, flags, NULL, NULL) < 0) pa.sink, NULL, &attribs, flags, NULL, NULL) < 0)
{ {
@@ -464,8 +579,9 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
pa.sinkMaxPeriodFrames = actualMinRequest; pa.sinkMaxPeriodFrames = actualMinRequest;
pa.sinkStartFrames = actualTarget; pa.sinkStartFrames = actualTarget;
*maxPeriodFrames = pa.sinkMaxPeriodFrames; *maxPeriodFrames = pa.sinkMaxPeriodFrames;
*startFrames = pa.sinkStartFrames; *startFrames = pa.sinkStartFrames;
*resamplerEnabled = pa.sinkResamplerEnabled;
atomic_store_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, 0, memory_order_release); &pa.sinkPresentationDeadline, 0, memory_order_release);
@@ -504,6 +620,11 @@ static void pulseaudio_stop(void)
pulseaudio_unrefOperation(pa_stream_cork(pa.sink, 1, NULL, NULL)); pulseaudio_unrefOperation(pa_stream_cork(pa.sink, 1, NULL, NULL));
pa.sinkCorked = true; pa.sinkCorked = true;
pa.sinkStarting = false; pa.sinkStarting = false;
if (pa.sinkResamplerEnabled)
{
pa.sinkRequestedRate = pa.sinkNominalRate;
pulseaudio_submitRateUpdate();
}
atomic_store_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, 0, memory_order_release); &pa.sinkPresentationDeadline, 0, memory_order_release);
@@ -539,6 +660,26 @@ static void pulseaudio_mute(bool mute)
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
} }
static bool pulseaudio_setRate(double * ratio)
{
if (!pa.sink || !pa.sinkResamplerEnabled ||
pa.sinkRateFailed || !ratio || !(*ratio > 0.0))
return false;
const double requestedRate = pa.sinkNominalRate / *ratio;
if (requestedRate < 1.0 || requestedRate > PA_RATE_MAX)
return false;
pa.sinkRequestedRate = (uint32_t)llround(requestedRate);
const uint32_t scheduledRate = pa.sinkRateOperation ?
pa.sinkPendingRate : pa.sinkRequestedRate;
if (!scheduledRate)
return false;
*ratio = (double)pa.sinkNominalRate / scheduledRate;
return true;
}
static uint64_t pulseaudio_latency(void) static uint64_t pulseaudio_latency(void)
{ {
const int64_t deadline = atomic_load_explicit( const int64_t deadline = atomic_load_explicit(
@@ -556,11 +697,12 @@ struct LG_AudioDevOps LGAD_PulseAudio =
.free = pulseaudio_free, .free = pulseaudio_free,
.playback = .playback =
{ {
.setup = pulseaudio_setup, .setup = pulseaudio_setup,
.start = pulseaudio_start, .start = pulseaudio_start,
.stop = pulseaudio_stop, .stop = pulseaudio_stop,
.volume = pulseaudio_volume, .volume = pulseaudio_volume,
.mute = pulseaudio_mute, .mute = pulseaudio_mute,
.setRate = pulseaudio_setRate,
.latency = pulseaudio_latency .latency = pulseaudio_latency
} }
}; };

View File

@@ -37,6 +37,8 @@ typedef enum LG_AudioSampleFormat
/* IEEE 754 little-endian floating-point samples. */ /* IEEE 754 little-endian floating-point samples. */
LG_AUDIO_FMT_F32_LE, LG_AUDIO_FMT_F32_LE,
LG_AUDIO_FMT_F64_LE, LG_AUDIO_FMT_F64_LE,
/* Native-endian floating point for client-side processing. */
LG_AUDIO_FMT_F32_NE,
} }
LG_AudioSampleFormat; LG_AudioSampleFormat;

View File

@@ -46,12 +46,11 @@ struct LG_AudioDevOps
struct struct
{ {
/* setup the stream for playback but don't start it yet, returning false /* Setup the stream for playback but don't start it yet, returning false
* if the stream could not be configured. If backend resampling is * if the stream could not be configured. The pull function returns the
* exact interleaved format supplied here. If backend resampling is
* requested, resamplerEnabled reports whether it was activated for this * requested, resamplerEnabled reports whether it was activated for this
* stream. * stream. */
* Note: the pull function returns f32 samples
*/
bool (*setup)(const LG_AudioFormat * format, int requestedPeriodFrames, bool (*setup)(const LG_AudioFormat * format, int requestedPeriodFrames,
bool requestResampler, bool * resamplerEnabled, bool requestResampler, bool * resamplerEnabled,
int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn); int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn);
@@ -69,8 +68,10 @@ struct LG_AudioDevOps
void (*mute)(bool mute); void (*mute)(bool mute);
/* [optional] update the active backend resampler's output/input ratio. /* [optional] update the active backend resampler's output/input ratio.
* Called from the backend's playback callback and must be realtime safe. */ * The backend replaces ratio with the value it selected for the next
bool (*setRate)(double ratio); * pull. Called from the backend's playback callback and must be realtime
* safe. */
bool (*setRate)(double * ratio);
/* return the current total playback latency in microseconds */ /* return the current total playback latency in microseconds */
uint64_t (*latency)(void); uint64_t (*latency)(void);

View File

@@ -217,6 +217,7 @@ typedef struct
int channels; int channels;
int sampleRate; int sampleRate;
int stride; int stride;
bool convertToFloat;
int deviceMaxPeriodFrames; int deviceMaxPeriodFrames;
int deviceStartFrames; int deviceStartFrames;
int targetStartFrames; int targetStartFrames;
@@ -288,7 +289,8 @@ static size_t audioSampleSize(LG_AudioSampleFormat format)
case LG_AUDIO_FMT_S16_LE: return 2; case LG_AUDIO_FMT_S16_LE: return 2;
case LG_AUDIO_FMT_S24_LE: return 3; case LG_AUDIO_FMT_S24_LE: return 3;
case LG_AUDIO_FMT_S32_LE: case LG_AUDIO_FMT_S32_LE:
case LG_AUDIO_FMT_F32_LE: return 4; case LG_AUDIO_FMT_F32_LE:
case LG_AUDIO_FMT_F32_NE: return 4;
case LG_AUDIO_FMT_F64_LE: return 8; case LG_AUDIO_FMT_F64_LE: return 8;
} }
@@ -398,6 +400,10 @@ static bool audioConvertToFloat(float * dst, const void * src,
return true; return true;
} }
case LG_AUDIO_FMT_F32_NE:
memcpy(dst, src, samples * sizeof(*dst));
return true;
case LG_AUDIO_FMT_F64_LE: case LG_AUDIO_FMT_F64_LE:
{ {
const uint8_t * in = src; const uint8_t * in = src;
@@ -923,7 +929,7 @@ static int playbackPullFrames(uint8_t * dst, int frames)
{ {
nextRatio = atomic_load_explicit( nextRatio = atomic_load_explicit(
&audio.playback.backendResampleRatio, memory_order_acquire); &audio.playback.backendResampleRatio, memory_order_acquire);
if (!audio.audioDev->playback.setRate(nextRatio)) if (!audio.audioDev->playback.setRate(&nextRatio))
{ {
atomic_store_explicit( atomic_store_explicit(
&audio.playback.backendResamplerFailed, true, &audio.playback.backendResamplerFailed, true,
@@ -1038,6 +1044,22 @@ static int playbackPullFrames(uint8_t * dst, int frames)
return frames; return frames;
} }
static bool playbackSetupDevice(const LG_AudioFormat * format,
int requestedPeriodFrames, bool requestResampler)
{
audio.playback.backendResampler = false;
audio.playback.deviceMaxPeriodFrames = 0;
audio.playback.deviceStartFrames = 0;
return audio.audioDev->playback.setup(format,
requestedPeriodFrames, requestResampler,
&audio.playback.backendResampler,
&audio.playback.deviceMaxPeriodFrames,
&audio.playback.deviceStartFrames, playbackPullFrames) &&
audio.playback.deviceMaxPeriodFrames > 0 &&
audio.playback.deviceStartFrames >= 0;
}
static void playbackStart(const LG_AudioFormat * format, static void playbackStart(const LG_AudioFormat * format,
const LG_AudioClock * sourceClock) const LG_AudioClock * sourceClock)
{ {
@@ -1076,19 +1098,12 @@ static void playbackStart(const LG_AudioFormat * format,
if (state != STREAM_STATE_STOP) if (state != STREAM_STATE_STOP)
playbackStop(); playbackStop();
const int bufferFrames = sampleRate;
audio.playback.buffer = ringbuffer_newUnbounded(bufferFrames,
channels * sizeof(float));
if (!audio.playback.buffer)
return;
audio.playback.format = *format; audio.playback.format = *format;
audio.playback.lastFormat = *format; audio.playback.lastFormat = *format;
audio.playback.lastFormatValid = true; audio.playback.lastFormatValid = true;
audio.playback.channels = channels; audio.playback.channels = channels;
audio.playback.sampleRate = sampleRate; audio.playback.sampleRate = sampleRate;
audio.playback.stride = channels * sizeof(float);
playbackSetState(STREAM_STATE_SETUP_SOURCE); playbackSetState(STREAM_STATE_SETUP_SOURCE);
audio.playback.deviceData.nextPosition = 0; audio.playback.deviceData.nextPosition = 0;
@@ -1142,8 +1157,6 @@ static void playbackStart(const LG_AudioFormat * format,
const int requestedPeriodFrames = g_params.audioPeriodSize > 0 ? const int requestedPeriodFrames = g_params.audioPeriodSize > 0 ?
clamp(g_params.audioPeriodSize, 1, sampleRate) : clamp(g_params.audioPeriodSize, 1, sampleRate) :
max(sampleRate / 100, 1); max(sampleRate / 100, 1);
audio.playback.deviceMaxPeriodFrames = 0;
audio.playback.deviceStartFrames = 0;
audio.playback.targetStartFrames = 0; audio.playback.targetStartFrames = 0;
audio.playback.startupLowWaterFrames = 0; audio.playback.startupLowWaterFrames = 0;
audio.playback.startupPacketDeadline = 0; audio.playback.startupPacketDeadline = 0;
@@ -1151,20 +1164,48 @@ static void playbackStart(const LG_AudioFormat * format,
const bool requestBackendResampler = const bool requestBackendResampler =
g_params.audioResampler != AUDIO_RESAMPLER_LIBSAMPLERATE; g_params.audioResampler != AUDIO_RESAMPLER_LIBSAMPLERATE;
LG_AudioFormat deviceFormat = *format; LG_AudioFormat deviceFormat = *format;
deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_LE;
if (!audio.audioDev->playback.setup(&deviceFormat, /* The ring generates zero-filled silence. Keep unsigned PCM on the float
requestedPeriodFrames, requestBackendResampler, * path because its silence level is biased rather than zero. */
&audio.playback.backendResampler, if (!requestBackendResampler ||
&audio.playback.deviceMaxPeriodFrames, deviceFormat.sampleFormat == LG_AUDIO_FMT_U8)
&audio.playback.deviceStartFrames, playbackPullFrames) || deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_NE;
audio.playback.deviceMaxPeriodFrames <= 0 ||
audio.playback.deviceStartFrames < 0) bool deviceConfigured = playbackSetupDevice(
&deviceFormat, requestedPeriodFrames, requestBackendResampler);
/* Native samples require the backend to own rate correction. If it cannot,
* reconnect using float samples for the libsamplerate path. This also
* provides a float fallback for formats unsupported by the backend. */
if ((!deviceConfigured || !audio.playback.backendResampler) &&
deviceFormat.sampleFormat != LG_AUDIO_FMT_F32_NE)
{
deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_NE;
deviceConfigured = playbackSetupDevice(
&deviceFormat, requestedPeriodFrames, requestBackendResampler);
}
if (!deviceConfigured)
{ {
DEBUG_ERROR("Failed to configure audio playback device"); DEBUG_ERROR("Failed to configure audio playback device");
playbackStop(); playbackStop();
return; return;
} }
audio.playback.stride = channels *
audioSampleSize(deviceFormat.sampleFormat);
audio.playback.convertToFloat =
!audio.playback.backendResampler ||
deviceFormat.sampleFormat != format->sampleFormat;
audio.playback.buffer = ringbuffer_newUnbounded(
sampleRate, audio.playback.stride);
if (!audio.playback.buffer)
{
playbackStop();
return;
}
if (g_params.audioResampler == AUDIO_RESAMPLER_BACKEND && if (g_params.audioResampler == AUDIO_RESAMPLER_BACKEND &&
!audio.playback.backendResampler) !audio.playback.backendResampler)
DEBUG_WARN("%s could not activate backend resampling; " DEBUG_WARN("%s could not activate backend resampling; "
@@ -1291,10 +1332,11 @@ static double computeDevicePosition(int64_t curTime)
static bool playbackEnsureConversionBuffers( static bool playbackEnsureConversionBuffers(
PlaybackSourceData * sourceData, int frames) PlaybackSourceData * sourceData, int frames)
{ {
if (frames > sourceData->framesInSize) if (audio.playback.convertToFloat &&
frames > sourceData->framesInSize)
{ {
float * framesIn = realloc(sourceData->framesIn, float * framesIn = realloc(sourceData->framesIn,
(size_t)frames * audio.playback.stride); (size_t)frames * audio.playback.channels * sizeof(float));
if (!framesIn) if (!framesIn)
{ {
DEBUG_ERROR("Failed to grow playback input buffer"); DEBUG_ERROR("Failed to grow playback input buffer");
@@ -1312,7 +1354,7 @@ static bool playbackEnsureConversionBuffers(
if (framesOut > sourceData->framesOutSize) if (framesOut > sourceData->framesOutSize)
{ {
float * output = realloc(sourceData->framesOut, float * output = realloc(sourceData->framesOut,
(size_t)framesOut * audio.playback.stride); (size_t)framesOut * audio.playback.channels * sizeof(float));
if (!output) if (!output)
{ {
DEBUG_ERROR("Failed to grow playback output buffer"); DEBUG_ERROR("Failed to grow playback output buffer");
@@ -1418,13 +1460,19 @@ static void playbackData(const void * data, size_t frameCount,
playbackStop(); playbackStop();
return; return;
} }
if (!audioConvertToFloat(sourceData->framesIn, data,
(size_t)frames * audio.playback.channels, const void * inputFrames = data;
audio.playback.format.sampleFormat)) if (audio.playback.convertToFloat)
{ {
DEBUG_ERROR("Failed to convert playback samples"); if (!audioConvertToFloat(sourceData->framesIn, data,
playbackStop(); (size_t)frames * audio.playback.channels,
return; audio.playback.format.sampleFormat))
{
DEBUG_ERROR("Failed to convert playback samples");
playbackStop();
return;
}
inputFrames = sourceData->framesIn;
} }
bool discontinuity = sourceClock && sourceClock->discontinuity; bool discontinuity = sourceClock && sourceClock->discontinuity;
@@ -1769,7 +1817,7 @@ static void playbackData(const void * data, size_t frameCount,
&audio.playback.backendResampleRatio, ratio, &audio.playback.backendResampleRatio, ratio,
memory_order_release); memory_order_release);
const int outputFrames = const int outputFrames =
playbackAppendFrames(sourceData, sourceData->framesIn, frames); playbackAppendFrames(sourceData, inputFrames, frames);
sourceData->outputPosition += outputFrames; sourceData->outputPosition += outputFrames;
} }
else else

View File

@@ -252,6 +252,7 @@ static size_t sampleSize(LG_AudioSampleFormat format)
case LG_AUDIO_FMT_S24_LE: return 3; case LG_AUDIO_FMT_S24_LE: return 3;
case LG_AUDIO_FMT_S32_LE: return 4; case LG_AUDIO_FMT_S32_LE: return 4;
case LG_AUDIO_FMT_F32_LE: return 4; case LG_AUDIO_FMT_F32_LE: return 4;
case LG_AUDIO_FMT_F32_NE: return 4;
case LG_AUDIO_FMT_F64_LE: return 8; case LG_AUDIO_FMT_F64_LE: return 8;
} }