output/pipewire: obey PipeWire's DSD bit order and interleave
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1297
This commit is contained in:
parent
6467502b9d
commit
b8e0855ef3
@ -25,6 +25,7 @@
|
|||||||
#include "mixer/plugins/PipeWireMixerPlugin.hxx"
|
#include "mixer/plugins/PipeWireMixerPlugin.hxx"
|
||||||
#include "pcm/Silence.hxx"
|
#include "pcm/Silence.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
|
#include "util/BitReverse.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
@ -94,7 +95,28 @@ class PipeWireOutput final : AudioOutput {
|
|||||||
SampleFormat sample_format;
|
SampleFormat sample_format;
|
||||||
|
|
||||||
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
/**
|
||||||
|
* Is the "dsd" setting enabled, i.e. is DSD playback allowed?
|
||||||
|
*/
|
||||||
const bool enable_dsd;
|
const bool enable_dsd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we currently playing in native DSD mode?
|
||||||
|
*/
|
||||||
|
bool use_dsd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the 8 bits in each DSD byte? This is necessary if
|
||||||
|
* PipeWire wants LSB (because MPD uses MSB internally).
|
||||||
|
*/
|
||||||
|
bool dsd_reverse_bits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pack this many bytes of each frame together. MPD uses 1
|
||||||
|
* internally, and if PipeWire wants more than one
|
||||||
|
* (e.g. because it uses DSD_U32), we need to reorder bytes.
|
||||||
|
*/
|
||||||
|
uint_least8_t dsd_interleave;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool disconnected;
|
bool disconnected;
|
||||||
@ -216,6 +238,11 @@ private:
|
|||||||
o.ControlInfo(control);
|
o.ControlInfo(control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
void DsdFormatChanged(const struct spa_audio_info_dsd &dsd) noexcept;
|
||||||
|
void DsdFormatChanged(const struct spa_pod ¶m) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
void ParamChanged(uint32_t id, const struct spa_pod *param) noexcept;
|
void ParamChanged(uint32_t id, const struct spa_pod *param) noexcept;
|
||||||
|
|
||||||
static void ParamChanged(void *data,
|
static void ParamChanged(void *data,
|
||||||
@ -481,8 +508,10 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
|||||||
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
/* this needs to be determined before ToPipeWireAudioFormat()
|
/* this needs to be determined before ToPipeWireAudioFormat()
|
||||||
switches DSD to S16 */
|
switches DSD to S16 */
|
||||||
const bool use_dsd = enable_dsd &&
|
use_dsd = enable_dsd &&
|
||||||
audio_format.format == SampleFormat::DSD;
|
audio_format.format == SampleFormat::DSD;
|
||||||
|
dsd_reverse_bits = false;
|
||||||
|
dsd_interleave = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto raw = ToPipeWireAudioFormat(audio_format);
|
auto raw = ToPipeWireAudioFormat(audio_format);
|
||||||
@ -574,6 +603,33 @@ PipeWireOutput::StateChanged(enum pw_stream_state state,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
|
||||||
|
inline void
|
||||||
|
PipeWireOutput::DsdFormatChanged(const struct spa_audio_info_dsd &dsd) noexcept
|
||||||
|
{
|
||||||
|
/* MPD uses MSB internally, which means if PipeWire asks LSB
|
||||||
|
from us, we need to reverse the bits in each DSD byte */
|
||||||
|
dsd_reverse_bits = dsd.bitorder == SPA_PARAM_BITORDER_lsb;
|
||||||
|
|
||||||
|
dsd_interleave = dsd.interleave;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
PipeWireOutput::DsdFormatChanged(const struct spa_pod ¶m) noexcept
|
||||||
|
{
|
||||||
|
uint32_t media_type, media_subtype;
|
||||||
|
struct spa_audio_info_dsd dsd;
|
||||||
|
|
||||||
|
if (spa_format_parse(¶m, &media_type, &media_subtype) >= 0 &&
|
||||||
|
media_type == SPA_MEDIA_TYPE_audio &&
|
||||||
|
media_subtype == SPA_MEDIA_SUBTYPE_dsd &&
|
||||||
|
spa_format_audio_dsd_parse(¶m, &dsd) >= 0)
|
||||||
|
DsdFormatChanged(dsd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
PipeWireOutput::ParamChanged([[maybe_unused]] uint32_t id,
|
PipeWireOutput::ParamChanged([[maybe_unused]] uint32_t id,
|
||||||
[[maybe_unused]] const struct spa_pod *param) noexcept
|
[[maybe_unused]] const struct spa_pod *param) noexcept
|
||||||
@ -582,8 +638,75 @@ PipeWireOutput::ParamChanged([[maybe_unused]] uint32_t id,
|
|||||||
SetVolume(volume);
|
SetVolume(volume);
|
||||||
restore_volume = false;
|
restore_volume = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
if (use_dsd && id == SPA_PARAM_Format && param != nullptr)
|
||||||
|
DsdFormatChanged(*param);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
|
||||||
|
static void
|
||||||
|
Interleave(std::byte *data, std::byte *end,
|
||||||
|
std::size_t channels, std::size_t interleave) noexcept
|
||||||
|
{
|
||||||
|
assert(channels > 1);
|
||||||
|
assert(channels <= MAX_CHANNELS);
|
||||||
|
|
||||||
|
constexpr std::size_t MAX_INTERLEAVE = 8;
|
||||||
|
assert(interleave > 1);
|
||||||
|
assert(interleave <= MAX_INTERLEAVE);
|
||||||
|
|
||||||
|
std::array<std::byte, MAX_CHANNELS * MAX_INTERLEAVE> buffer;
|
||||||
|
std::size_t buffer_size = channels * interleave;
|
||||||
|
|
||||||
|
while (data < end) {
|
||||||
|
std::copy_n(data, buffer_size, buffer.data());
|
||||||
|
|
||||||
|
const std::byte *src0 = buffer.data();
|
||||||
|
for (std::size_t channel = 0; channel < channels;
|
||||||
|
++channel, ++src0) {
|
||||||
|
const std::byte *src = src0;
|
||||||
|
for (std::size_t i = 0; i < interleave;
|
||||||
|
++i, src += channels)
|
||||||
|
*data++ = *src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
BitReverse(uint8_t *data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
while (n-- > 0)
|
||||||
|
*data = bit_reverse(*data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
BitReverse(std::byte *data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
BitReverse((uint8_t *)data, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
PostProcessDsd(std::byte *data, struct spa_chunk &chunk, unsigned channels,
|
||||||
|
bool reverse_bits, unsigned interleave) noexcept
|
||||||
|
{
|
||||||
|
assert(chunk.size % channels == 0);
|
||||||
|
|
||||||
|
if (interleave > 1 && channels > 1) {
|
||||||
|
assert(chunk.size % (channels * interleave) == 0);
|
||||||
|
|
||||||
|
Interleave(data, data + chunk.size, channels, interleave);
|
||||||
|
chunk.stride *= interleave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reverse_bits)
|
||||||
|
BitReverse(data, chunk.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
PipeWireOutput::Process() noexcept
|
PipeWireOutput::Process() noexcept
|
||||||
{
|
{
|
||||||
@ -600,10 +723,25 @@ PipeWireOutput::Process() noexcept
|
|||||||
if (dest == nullptr)
|
if (dest == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::size_t max_frames = d.maxsize / frame_size;
|
std::size_t max_frames = d.maxsize / frame_size;
|
||||||
const std::size_t max_size = max_frames * frame_size;
|
|
||||||
|
|
||||||
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
if (use_dsd && dsd_interleave > 1) {
|
||||||
|
/* make sure we don't get partial interleave frames */
|
||||||
|
std::size_t interleave_size = frame_size * dsd_interleave;
|
||||||
|
std::size_t available_bytes = ring_buffer->read_available();
|
||||||
|
std::size_t available_interleaves =
|
||||||
|
available_bytes / interleave_size;
|
||||||
|
std::size_t available_frames =
|
||||||
|
available_interleaves * dsd_interleave;
|
||||||
|
if (max_frames > available_frames)
|
||||||
|
max_frames = available_frames;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const std::size_t max_size = max_frames * frame_size;
|
||||||
size_t nbytes = ring_buffer->pop(dest, max_size);
|
size_t nbytes = ring_buffer->pop(dest, max_size);
|
||||||
|
assert(nbytes % frame_size == 0);
|
||||||
if (nbytes == 0) {
|
if (nbytes == 0) {
|
||||||
if (drain_requested) {
|
if (drain_requested) {
|
||||||
pw_stream_flush(stream, true);
|
pw_stream_flush(stream, true);
|
||||||
@ -622,6 +760,12 @@ PipeWireOutput::Process() noexcept
|
|||||||
chunk.stride = frame_size;
|
chunk.stride = frame_size;
|
||||||
chunk.size = nbytes;
|
chunk.size = nbytes;
|
||||||
|
|
||||||
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
|
if (use_dsd)
|
||||||
|
PostProcessDsd(dest, chunk, channels,
|
||||||
|
dsd_reverse_bits, dsd_interleave);
|
||||||
|
#endif
|
||||||
|
|
||||||
pw_stream_queue_buffer(stream, b);
|
pw_stream_queue_buffer(stream, b);
|
||||||
|
|
||||||
pw_thread_loop_signal(thread_loop, false);
|
pw_thread_loop_signal(thread_loop, false);
|
||||||
|
Loading…
Reference in New Issue
Block a user