aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2021-08-06 21:34:17 -0700
committerChris Robinson <[email protected]>2021-08-06 21:38:23 -0700
commitdc9b39f4192b0ab28bdfb2699fbcef591f4cf290 (patch)
tree56d82160ab861f1ef66a05bed4fb0c188044f4eb
parent4cc820bb5c4c3c93ec85fed5a5bf7978b6bd14b1 (diff)
Implement PipeWire playback
Not yet an auto-selected backend. This doesn't yet support enumeration, or matching the AL device format to the output.
-rw-r--r--alc/alc.cpp2
-rw-r--r--alc/backends/pipewire.cpp505
-rw-r--r--alc/backends/pipewire.h4
3 files changed, 439 insertions, 72 deletions
diff --git a/alc/alc.cpp b/alc/alc.cpp
index c3bbdf29..6c68a995 100644
--- a/alc/alc.cpp
+++ b/alc/alc.cpp
@@ -243,10 +243,10 @@ BackendInfo BackendList[] = {
{ "sdl2", SDL2BackendFactory::getFactory },
#endif
+ { "null", NullBackendFactory::getFactory },
#ifdef HAVE_PIPEWIRE
{ "pipewire", PipeWireBackendFactory::getFactory },
#endif
- { "null", NullBackendFactory::getFactory },
#ifdef HAVE_WAVE
{ "wave", WaveBackendFactory::getFactory },
#endif
diff --git a/alc/backends/pipewire.cpp b/alc/backends/pipewire.cpp
index 00bf7cea..e5c4db72 100644
--- a/alc/backends/pipewire.cpp
+++ b/alc/backends/pipewire.cpp
@@ -22,130 +22,493 @@
#include "pipewire.h"
-#include <exception>
+#include <algorithm>
#include <atomic>
-#include <chrono>
-#include <cstdint>
#include <cstring>
-#include <functional>
-#include <thread>
+#include <cerrno>
+#include <memory>
+#include <mutex>
+#include <stdint.h>
+#include <utility>
-#include "core/device.h"
+#include "albyte.h"
#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/devformat.h"
+#include "core/device.h"
#include "core/helpers.h"
-#include "threads.h"
-
+#include "core/logging.h"
+#include "dynload.h"
+#include "opthelpers.h"
+
+/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */
+_Pragma("GCC diagnostic push")
+_Pragma("GCC diagnostic ignored \"-Weverything\"")
+#include "pipewire/pipewire.h"
+#include "spa/buffer/buffer.h"
+#include "spa/param/audio/format-utils.h"
+#include "spa/param/audio/raw.h"
+#include "spa/param/param.h"
+#include "spa/pod/builder.h"
+_Pragma("GCC diagnostic pop")
namespace {
-using std::chrono::seconds;
-using std::chrono::milliseconds;
-using std::chrono::nanoseconds;
+using uint = unsigned int;
+
+constexpr char pwireDevice[] = "PipeWire Output";
+
+
+#ifdef HAVE_DYNLOAD
+#define PWIRE_FUNCS(MAGIC) \
+ MAGIC(pw_context_destroy) \
+ MAGIC(pw_context_new) \
+ MAGIC(pw_init) \
+ MAGIC(pw_properties_free) \
+ MAGIC(pw_properties_new) \
+ MAGIC(pw_properties_set) \
+ MAGIC(pw_properties_setf) \
+ MAGIC(pw_stream_connect) \
+ MAGIC(pw_stream_dequeue_buffer) \
+ MAGIC(pw_stream_destroy) \
+ MAGIC(pw_stream_get_state) \
+ MAGIC(pw_stream_new_simple) \
+ MAGIC(pw_stream_queue_buffer) \
+ MAGIC(pw_stream_set_active) \
+ MAGIC(pw_thread_loop_new) \
+ MAGIC(pw_thread_loop_destroy) \
+ MAGIC(pw_thread_loop_get_loop) \
+ MAGIC(pw_thread_loop_start) \
+ MAGIC(pw_thread_loop_stop) \
+ MAGIC(pw_thread_loop_lock) \
+ MAGIC(pw_thread_loop_wait) \
+ MAGIC(pw_thread_loop_signal) \
+ MAGIC(pw_thread_loop_unlock) \
+
+void *pwire_handle;
+#define MAKE_FUNC(f) decltype(f) * p##f;
+PWIRE_FUNCS(MAKE_FUNC)
+#undef MAKE_FUNC
+
+#ifndef IN_IDE_PARSER
+#define pw_context_destroy ppw_context_destroy
+#define pw_context_new ppw_context_new
+#define pw_init ppw_init
+#define pw_properties_free ppw_properties_free
+#define pw_properties_new ppw_properties_new
+#define pw_properties_set ppw_properties_set
+#define pw_properties_setf ppw_properties_setf
+#define pw_stream_connect ppw_stream_connect
+#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
+#define pw_stream_destroy ppw_stream_destroy
+#define pw_stream_get_state ppw_stream_get_state
+#define pw_stream_new_simple ppw_stream_new_simple
+#define pw_stream_queue_buffer ppw_stream_queue_buffer
+#define pw_stream_set_active ppw_stream_set_active
+#define pw_thread_loop_destroy ppw_thread_loop_destroy
+#define pw_thread_loop_get_loop ppw_thread_loop_get_loop
+#define pw_thread_loop_lock ppw_thread_loop_lock
+#define pw_thread_loop_new ppw_thread_loop_new
+#define pw_thread_loop_signal ppw_thread_loop_signal
+#define pw_thread_loop_start ppw_thread_loop_start
+#define pw_thread_loop_stop ppw_thread_loop_stop
+#define pw_thread_loop_unlock ppw_thread_loop_unlock
+#define pw_thread_loop_wait ppw_thread_loop_wait
+#endif
+#endif
+
+
+bool pwire_load()
+{
+ bool error{false};
-constexpr char pipeDevice[] = "No Output";
+#ifdef HAVE_DYNLOAD
+ if(!pwire_handle)
+ {
+ static constexpr char pwire_library[] = "libpipewire-0.3.so.0";
+ std::string missing_funcs;
+ pwire_handle = LoadLib(pwire_library);
+ if(!pwire_handle)
+ {
+ WARN("Failed to load %s\n", pwire_library);
+ return false;
+ }
+
+ error = false;
+#define LOAD_FUNC(f) do { \
+ p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
+ if(p##f == nullptr) { \
+ error = true; \
+ missing_funcs += "\n" #f; \
+ } \
+} while(0);
+ PWIRE_FUNCS(LOAD_FUNC)
+#undef LOAD_FUNC
+
+ if(error)
+ {
+ WARN("Missing expected functions:%s\n", missing_funcs.c_str());
+ CloseLib(pwire_handle);
+ pwire_handle = nullptr;
+ }
+ }
+#endif
+
+ return !error;
+}
+
+
+class ThreadMainloop {
+ pw_thread_loop *mLoop{};
+
+public:
+ ThreadMainloop() = default;
+ ThreadMainloop(const ThreadMainloop&) = delete;
+ ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
+ explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { }
+ ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); }
+
+ ThreadMainloop& operator=(const ThreadMainloop&) = delete;
+ ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept
+ { std::swap(mLoop, rhs.mLoop); return *this; }
+
+ operator bool() const noexcept { return mLoop != nullptr; }
+
+ auto start() const { return pw_thread_loop_start(mLoop); }
+ auto stop() const { return pw_thread_loop_stop(mLoop); }
+
+ auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); }
+ auto wait() const { return pw_thread_loop_wait(mLoop); }
+
+ auto getLoop() const { return pw_thread_loop_get_loop(mLoop); }
+
+ auto lock() const { return pw_thread_loop_lock(mLoop); }
+ auto unlock() const { return pw_thread_loop_unlock(mLoop); }
+};
+using MainloopUniqueLock = std::unique_lock<ThreadMainloop>;
+using MainloopLockGuard = std::lock_guard<ThreadMainloop>;
+
+struct PwStreamDeleter {
+ void operator()(pw_stream *stream) const { pw_stream_destroy(stream); }
+};
+using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
+
+
+/* Enums for bitflags... again... *sigh* */
+constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
+{ return static_cast<pw_stream_flags>(lhs | uint{rhs}); }
+
+/* Using PW_ID_ANY causes a compiler warning, so use our own variable with the
+ * same type/value.
+ */
+constexpr uint32_t IdAny{0xffffffff};
+
+/* SPA_POD_BUILDER_INIT causes a compiler warning, so make this function for
+ * the same functionality.
+ */
+inline spa_pod_builder make_pod_builder(void *data, uint32_t size) noexcept
+{
+ spa_pod_builder ret{};
+ spa_pod_builder_init(&ret, data, size);
+ return ret;
+}
+
+
+enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true };
+spa_audio_info_raw make_spa_info(DeviceBase *device, use_f32p_e use_f32p)
+{
+ static const spa_audio_channel MonoMap[]{
+ SPA_AUDIO_CHANNEL_MONO
+ }, StereoMap[] {
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR
+ }, QuadMap[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
+ }, X51Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+ }, X61Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+ }, X71Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+ };
+
+ spa_audio_info_raw info{};
+ if(use_f32p)
+ {
+ device->FmtType = DevFmtFloat;
+ info.format = SPA_AUDIO_FORMAT_F32P;
+ }
+ else switch(device->FmtType)
+ {
+ case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8;
+ case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8;
+ case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16;
+ case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16;
+ case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32;
+ case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32;
+ case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32;
+ }
+
+ info.rate = device->Frequency;
+
+ al::span<const spa_audio_channel> map{};
+ switch(device->FmtChans)
+ {
+ case DevFmtMono: map = MonoMap; break;
+ case DevFmtStereo: map = StereoMap; break;
+ case DevFmtQuad: map = QuadMap; break;
+ case DevFmtX51: map = X51Map; break;
+ case DevFmtX61: map = X61Map; break;
+ case DevFmtX71: map = X71Map; break;
+ case DevFmtAmbi3D:
+ info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
+ info.channels = device->channelsFromFmt();
+ break;
+ }
+ if(!map.empty())
+ {
+ info.channels = static_cast<uint32_t>(map.size());
+ std::copy(map.begin(), map.end(), info.position);
+ }
+
+ return info;
+}
-struct PipeWireBackend final : public BackendBase {
- PipeWireBackend(DeviceBase *device) noexcept : BackendBase{device} { }
+struct PipeWirePlayback final : public BackendBase {
+ PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
+ ~PipeWirePlayback();
- int mixerProc();
+ void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
+ static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
+ const char *error)
+ { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); }
+
+ void outputCallback();
+ static void outputCallbackC(void *data)
+ { static_cast<PipeWirePlayback*>(data)->outputCallback(); }
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
- std::atomic<bool> mKillNow{true};
- std::thread mThread;
+ ThreadMainloop mLoop;
+ PwStreamPtr mStream;
+ std::unique_ptr<float*[]> mChannelPtrs;
+ uint mNumChannels{};
+
+ static const pw_stream_events sEvents;
+ static constexpr pw_stream_events InitEvent()
+ {
+ pw_stream_events ret{};
+ ret.version = PW_VERSION_STREAM_EVENTS;
+ ret.state_changed = &PipeWirePlayback::stateChangedCallbackC;
+ ret.process = &PipeWirePlayback::outputCallbackC;
+ return ret;
+ }
- DEF_NEWDEL(PipeWireBackend)
+ DEF_NEWDEL(PipeWirePlayback)
};
+const pw_stream_events PipeWirePlayback::sEvents{PipeWirePlayback::InitEvent()};
-int PipeWireBackend::mixerProc()
+PipeWirePlayback::~PipeWirePlayback()
{
- const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
+ if(mLoop && mStream)
+ {
+ /* The main loop needs to be locked when accessing/destroying the
+ * stream from user threads.
+ */
+ MainloopLockGuard _{mLoop};
+ mStream = nullptr;
+ }
+}
+
- SetRTPriority();
- althrd_setname(MIXER_THREAD_NAME);
+void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
+{ mLoop.signal(false); }
- int64_t done{0};
- auto start = std::chrono::steady_clock::now();
- while(!mKillNow.load(std::memory_order_acquire)
- && mDevice->Connected.load(std::memory_order_acquire))
+void PipeWirePlayback::outputCallback()
+{
+ /* TODO: Should all buffers be filled? There can be more than one buffer to
+ * dequeue, but example code only ever does one.
+ */
+ pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
+ if UNLIKELY(!pw_buf) return;
+
+ spa_buffer *spa_buf{pw_buf->buffer};
+ uint length{mDevice->UpdateSize};
+ /* For planar formats, each datas[] seems to contain one channel, so store
+ * the pointers in an array. Limit the render length in case the available
+ * buffer length in any one channel is smaller than we wanted (shouldn't
+ * be, but just in case).
+ */
+ const size_t chancount{minu(mNumChannels, spa_buf->n_datas)};
+ for(size_t i{0};i < chancount;++i)
{
- auto now = std::chrono::steady_clock::now();
+ length = minu(length, spa_buf->datas[i].maxsize/sizeof(float));
+ mChannelPtrs[i] = static_cast<float*>(spa_buf->datas[i].data);
+ }
- /* This converts from nanoseconds to nanosamples, then to samples. */
- int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()};
- if(avail-done < mDevice->UpdateSize)
- {
- std::this_thread::sleep_for(restTime);
- continue;
- }
- while(avail-done >= mDevice->UpdateSize)
- {
- mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u);
- done += mDevice->UpdateSize;
- }
+ /* TODO: How many samples should actually be written? 'maxsize' can be 16k
+ * samples, which is excessive (~341ms @ 48khz), but aside from what gets
+ * specified with PW_KEY_NODE_LATENCY, there's nothing here saying how much
+ * is needed to keep the stream healthy.
+ */
+ mDevice->renderSamples({mChannelPtrs.get(), chancount}, length);
- /* For every completed second, increment the start time and reduce the
- * samples done. This prevents the difference between the start time
- * and current time from growing too large, while maintaining the
- * correct number of samples to render.
- */
- if(done >= mDevice->Frequency)
- {
- seconds s{done/mDevice->Frequency};
- start += s;
- done -= mDevice->Frequency*s.count();
- }
+ for(size_t i{0};i < chancount;++i)
+ {
+ spa_buf->datas[i].chunk->offset = 0;
+ spa_buf->datas[i].chunk->stride = sizeof(float);
+ spa_buf->datas[i].chunk->size = length * sizeof(float);
}
-
- return 0;
+ pw_stream_queue_buffer(mStream.get(), pw_buf);
}
-void PipeWireBackend::open(const char *name)
+void PipeWirePlayback::open(const char *name)
{
+ static std::atomic<uint> OpenCount{0};
+
if(!name)
- name = pipeDevice;
- else if(strcmp(name, pipeDevice) != 0)
+ name = pwireDevice;
+ else if(strcmp(name, pwireDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
+ if(!mLoop)
+ {
+ const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
+ const std::string thread_name{"ALSoftP" + std::to_string(count)};
+ mLoop = ThreadMainloop{pw_thread_loop_new(thread_name.c_str(), nullptr)};
+ if(!mLoop)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create PipeWire mainloop (errno: %d)", errno};
+ if(int res{mLoop.start()})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start PipeWire mainloop (res: %d)", res};
+ }
+
mDevice->DeviceName = name;
}
-bool PipeWireBackend::reset()
+bool PipeWirePlayback::reset()
{
+ if(mStream)
+ {
+ MainloopLockGuard _{mLoop};
+ mStream = nullptr;
+ }
+
+ /* TODO: Detect format from output device to avoid unnecessary conversions.
+ * Force planar 32-bit float output for playback. This is what PipeWire
+ * handles internally, and it's easier for us too.
+ */
+ spa_audio_info_raw info{make_spa_info(mDevice, ForceF32Planar)};
+
+ /* TODO: How to tell what an appropriate size is? Examples just use this
+ * magic value.
+ */
+ constexpr uint32_t pod_buffer_size{1024};
+ auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
+ spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
+
+ const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
+ if(!params)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to set PipeWire audio format parameters"};
+
+ pw_properties *props{pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ PW_KEY_MEDIA_ROLE, "Game",
+ PW_KEY_NODE_ALWAYS_PROCESS, "true",
+ nullptr)};
+ if(!props)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create PipeWire stream properties (errno: %d)", errno};
+
+ auto&& binary = GetProcBinary();
+ const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
+ /* TODO: Which properties are actually needed here? Any others that could
+ * be useful?
+ */
+ pw_properties_set(props, PW_KEY_NODE_NAME, appname);
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
+ mDevice->Frequency);
+
+ MainloopUniqueLock plock{mLoop};
+ mStream = PwStreamPtr{pw_stream_new_simple(mLoop.getLoop(), "Playback Stream", props,
+ &sEvents, this)};
+ if(!mStream)
+ {
+ plock.unlock();
+ pw_properties_free(props);
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Failed to create PipeWire stream (errno: %d)", errno};
+ }
+
+ static constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
+ | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
+ if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, IdAny, Flags, &params, 1)})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Error connecting PipeWire stream (res: %d)", res};
+
+ /* Wait for the stream to become paused (ready to start streaming). */
+ pw_stream_state state{};
+ const char *error{};
+ while((state=pw_stream_get_state(mStream.get(), &error)) != PW_STREAM_STATE_PAUSED)
+ {
+ if(state == PW_STREAM_STATE_ERROR)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Error connecting PipeWire stream: \"%s\"", error};
+ mLoop.wait();
+ }
+ plock.unlock();
+
+ mNumChannels = mDevice->channelsFromFmt();
+ mChannelPtrs = std::make_unique<float*[]>(mNumChannels);
+
setDefaultWFXChannelOrder();
+
return true;
}
-void PipeWireBackend::start()
+void PipeWirePlayback::start()
{
- try {
- mKillNow.store(false, std::memory_order_release);
- mThread = std::thread{std::mem_fn(&PipeWireBackend::mixerProc), this};
- }
- catch(std::exception& e) {
+ MainloopLockGuard _{mLoop};
+ if(int res{pw_stream_set_active(mStream.get(), true)})
throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to start mixing thread: %s", e.what()};
- }
+ "Failed to start PipeWire stream (res: %d)", res};
}
-void PipeWireBackend::stop()
+void PipeWirePlayback::stop()
{
- if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
- return;
- mThread.join();
+ MainloopLockGuard _{mLoop};
+ if(int res{pw_stream_set_active(mStream.get(), false)})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to stop PipeWire stream (res: %d)", res};
}
} // namespace
bool PipeWireBackendFactory::init()
-{ return true; }
+{
+ if(!pwire_load())
+ return false;
+
+ pw_init(0, nullptr);
+
+ /* TODO: Check that audio devices are supported. */
+
+ return true;
+}
bool PipeWireBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
@@ -157,7 +520,7 @@ std::string PipeWireBackendFactory::probe(BackendType type)
{
case BackendType::Playback:
/* Includes null char. */
- outnames.append(pipeDevice, sizeof(pipeDevice));
+ outnames.append(pwireDevice, sizeof(pwireDevice));
break;
case BackendType::Capture:
break;
@@ -168,7 +531,7 @@ std::string PipeWireBackendFactory::probe(BackendType type)
BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
- return BackendPtr{new PipeWireBackend{device}};
+ return BackendPtr{new PipeWirePlayback{device}};
return nullptr;
}
diff --git a/alc/backends/pipewire.h b/alc/backends/pipewire.h
index f8d3d5c2..5f930239 100644
--- a/alc/backends/pipewire.h
+++ b/alc/backends/pipewire.h
@@ -1,8 +1,12 @@
#ifndef BACKENDS_PIPEWIRE_H
#define BACKENDS_PIPEWIRE_H
+#include <string>
+
#include "base.h"
+struct DeviceBase;
+
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;