mirror of
https://github.com/clangen/musikcube.git
synced 2024-10-02 04:52:32 +00:00
Merge pull request #421 from clangen/clangen/pipewire
PipeWire output support
This commit is contained in:
commit
f416a1dbd7
@ -42,6 +42,20 @@ jobs:
|
||||
- run: mkdir -p /root/debs && mv /root/project/*.deb /root/debs/
|
||||
- store_artifacts:
|
||||
path: /root/debs/
|
||||
build_ubuntu_hirsute:
|
||||
docker:
|
||||
- image: ubuntu:hirsute
|
||||
steps:
|
||||
- run: apt-get update
|
||||
- run: DEBIAN_FRONTEND="noninteractive" TZ="America/Los_Angeles" apt-get install -y git ssh sshpass build-essential clang cmake libboost-thread1.71-dev libboost-system1.71-dev libboost-filesystem1.71-dev libboost-date-time1.71-dev libboost-atomic1.71-dev libboost-chrono1.71-dev libogg-dev libvorbis-dev libavutil-dev libavformat-dev libswresample-dev libncursesw5-dev libasound2-dev libpulse-dev pulseaudio libmicrohttpd-dev libmp3lame-dev libcurl4-openssl-dev libev-dev libssl-dev libtag1-dev libsystemd-dev libpipewire-0.3-dev libspa-0.2-dev
|
||||
- checkout
|
||||
- run: cmake -DENABLE_PCH=true -DENABLE_PIPEWIRE=true -DGENERATE_DEB=1 -DDEB_ARCHITECTURE=amd64 -DDEB_PLATFORM=ubuntu -DDEB_DISTRO=groovy -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release .
|
||||
- run: make -j2
|
||||
- run: make package
|
||||
- run: ./script/copy-artifacts-to-host.sh /root/project/*.deb
|
||||
- run: mkdir -p /root/debs && mv /root/project/*.deb /root/debs/
|
||||
- store_artifacts:
|
||||
path: /root/debs/
|
||||
build_fedora_31:
|
||||
docker:
|
||||
- image: fedora:31
|
||||
|
@ -120,7 +120,7 @@ else()
|
||||
add_subdirectory(src/plugins/libopenmptdecoder)
|
||||
endif()
|
||||
|
||||
if (${FFMPEG_ENABLED} MATCHES "false")
|
||||
if (${ENABLE_FFMPEG} MATCHES "false")
|
||||
message(STATUS "[ffmpeg] enabled = false")
|
||||
add_subdirectory(src/plugins/m4adecoder)
|
||||
add_subdirectory(src/plugins/oggdecoder)
|
||||
@ -155,6 +155,10 @@ add_dependencies(musikcubed musikcube)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
add_subdirectory(src/plugins/alsaout)
|
||||
add_subdirectory(src/plugins/pulseout)
|
||||
if (${ENABLE_PIPEWIRE} MATCHES "true")
|
||||
add_subdirectory(src/plugins/pipewireout)
|
||||
add_dependencies(musikcube pipewireout)
|
||||
endif()
|
||||
if (${ENABLE_MPRIS} MATCHES "true")
|
||||
add_subdirectory(src/plugins/mpris)
|
||||
add_dependencies(musikcube mpris)
|
||||
@ -295,7 +299,7 @@ if (GENERATE_DEB MATCHES "1")
|
||||
|
||||
set(DEPENDENCIES "libboost-thread${DEB_BOOST_VERSION}, libboost-system${DEB_BOOST_VERSION}, libboost-chrono${DEB_BOOST_VERSION}, libboost-filesystem${DEB_BOOST_VERSION}, libboost-date-time${DEB_BOOST_VERSION}, libmicrohttpd${DEB_MICROHTTPD_VERSION}, libcurl${DEB_LIBCURL_VERSION}, libogg0, libvorbis0a, libvorbisfile3, libncursesw${DEB_NCURSES_VERSION}, libasound2, libpulse0, pulseaudio, libmp3lame0, libev4, libopenmpt0, libssl1.1")
|
||||
|
||||
if (${FFMPEG_ENABLED} MATCHES "false")
|
||||
if (${ENABLE_FFMPEG} MATCHES "false")
|
||||
set(DEPENDENCIES "${DEPENDENCIES}, libflac8, libfaad2")
|
||||
else()
|
||||
set(DEPENDENCIES "${DEPENDENCIES}, libavcodec-extra, libavutil${DEB_AVUTIL_VERSION}, libavformat${DEB_AVFORMAT_VERSION}, libswresample${DEB_SWRESAMPLE_VERSION}")
|
||||
|
@ -12,7 +12,7 @@ SCRIPTDIR=`dirname "$0"`
|
||||
rm -rf bin/
|
||||
|
||||
${SCRIPTDIR}/clean-nix.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLINK_STATICALLY=true -DFFMPEG_ENABLED=false .
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLINK_STATICALLY=true -DENABLE_FFMPEG=false .
|
||||
make -j7
|
||||
|
||||
DIRNAME="musikcube_macos_$VERSION"
|
||||
|
@ -73,10 +73,10 @@ Crossfader::Crossfader(ITransport& transport)
|
||||
}
|
||||
|
||||
Crossfader::~Crossfader() {
|
||||
this->messageQueue.Unregister(this);
|
||||
this->quit = true;
|
||||
this->messageQueue.Post(Message::Create(this, MESSAGE_QUIT, 0, 0));
|
||||
this->thread->join();
|
||||
this->messageQueue.Unregister(this);
|
||||
}
|
||||
|
||||
void Crossfader::Fade(
|
||||
|
@ -454,8 +454,8 @@ void PlaybackService::ProcessMessage(IMessage &message) {
|
||||
if (masterTransport) {
|
||||
const TransportType transportType = static_cast<TransportType>(
|
||||
playbackPrefs->GetInt(
|
||||
keys::Transport.c_str()),
|
||||
static_cast<int>(TransportType::Gapless));
|
||||
keys::Transport,
|
||||
static_cast<int>(TransportType::Gapless)));
|
||||
|
||||
if (masterTransport->GetType() != transportType) {
|
||||
masterTransport->SwitchTo(transportType);
|
||||
|
@ -123,6 +123,14 @@ static class Debug: public IDebug {
|
||||
}
|
||||
} debugger;
|
||||
|
||||
static class NullDebug: public IDebug { /* used during shutdown */
|
||||
public:
|
||||
void Verbose(const char* tag, const char* message) override {}
|
||||
void Info(const char* tag, const char* message) override {}
|
||||
void Warning(const char* tag, const char* message) override {}
|
||||
void Error(const char* tag, const char* message) override {}
|
||||
} nullDebugger;
|
||||
|
||||
static class Environment: public IEnvironment {
|
||||
public:
|
||||
virtual size_t GetPath(PathType type, char* dst, int size) override {
|
||||
@ -439,7 +447,7 @@ namespace musik { namespace core { namespace plugin {
|
||||
PluginFactory::Instance().QueryFunction<SetDebug>(
|
||||
"SetDebug",
|
||||
[](musik::core::sdk::IPlugin* plugin, SetDebug func) {
|
||||
func(nullptr);
|
||||
func(&nullDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -379,7 +379,7 @@ struct Theme {
|
||||
/* main */
|
||||
init_pair(Color::ContentColorDefault, foregroundId, backgroundId);
|
||||
init_pair(Color::FrameDefault, foregroundId, backgroundId);
|
||||
init_pair(Color::FrameFocused, focusedFrame.Id(mode, COLOR_RED),backgroundId);
|
||||
init_pair(Color::FrameFocused, focusedFrame.Id(mode, COLOR_RED), backgroundId);
|
||||
init_pair(Color::FrameImportant, importantFrame.Id(mode, COLOR_BLUE), backgroundId);
|
||||
|
||||
/* text */
|
||||
|
@ -419,11 +419,10 @@ void Window::Redraw() {
|
||||
}
|
||||
|
||||
if (this->frame) {
|
||||
#ifdef __FreeBSD__
|
||||
/* somehow related to curses version, but i don't know how;
|
||||
if we don't do this on FreeBSD we get missing background
|
||||
colors for things like overlays. if we do this on other
|
||||
platforms we get weird repainting artifacts. */
|
||||
#if defined(__FreeBSD__) || (NCURSES_VERSION_PATCH >= 20200301)
|
||||
/* but depending on curses version we'll get redraw artifacts if we do or don't
|
||||
repaint the background. the changelog mentions changes to wbkgd() and wbkgrnd()
|
||||
at revision 20200301, but it's unclear if this is the actual root cause or not. */
|
||||
this->RepaintBackground();
|
||||
#endif
|
||||
this->OnRedraw();
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <musikcore/sdk/IPlugin.h>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
|
||||
extern "C" {
|
||||
#include <systemd/sd-bus.h>
|
||||
|
14
src/plugins/pipewireout/CMakeLists.txt
Normal file
14
src/plugins/pipewireout/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
set (pipewireout_SOURCES
|
||||
pipewireout_plugin.cpp
|
||||
PipeWireOut.cpp
|
||||
)
|
||||
|
||||
message(STATUS "[pipewireout] plugin enabled")
|
||||
|
||||
include_directories("/usr/include/spa-0.2")
|
||||
include_directories("/usr/local/include/spa-0.2")
|
||||
include_directories("/usr/include/pipewire-0.3")
|
||||
include_directories("/usr/local/include/pipewire-0.3")
|
||||
|
||||
add_library(pipewireout SHARED ${pipewireout_SOURCES})
|
||||
target_link_libraries(pipewireout ${musikcube_LINK_LIBS} pipewire-0.3)
|
515
src/plugins/pipewireout/PipeWireOut.cpp
Normal file
515
src/plugins/pipewireout/PipeWireOut.cpp
Normal file
@ -0,0 +1,515 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2004-2020 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "PipeWireOut.h"
|
||||
|
||||
#include <musikcore/sdk/constants.h>
|
||||
#include <musikcore/sdk/IPreferences.h>
|
||||
#include <musikcore/sdk/ISchema.h>
|
||||
#include <musikcore/sdk/IDebug.h>
|
||||
#include <musikcore/sdk/String.h>
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
#include <spa/utils/result.h>
|
||||
#include <unistd.h>
|
||||
#include <chrono>
|
||||
|
||||
constexpr const char* TAG = "PipeWireOut";
|
||||
constexpr size_t SAMPLES_PER_BUFFER = 2048;
|
||||
constexpr size_t SAMPLE_SIZE_BYTES = sizeof(float);
|
||||
constexpr size_t MAX_BUFFERS = 16;
|
||||
constexpr const char* PREF_DEVICE_ID = "device_id";
|
||||
|
||||
static std::atomic<bool> pipeWireInitialized(false);
|
||||
|
||||
static IPreferences* prefs = nullptr;
|
||||
static IDebug* debug = nullptr;
|
||||
|
||||
extern "C" void SetDebug(IDebug* debug) {
|
||||
::debug = debug;
|
||||
}
|
||||
|
||||
extern "C" void SetPreferences(IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
extern "C" musik::core::sdk::ISchema* GetSchema() {
|
||||
auto schema = new TSchema<>();
|
||||
return schema;
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
return getPreferenceString<std::string>(prefs, PREF_DEVICE_ID, "");
|
||||
}
|
||||
|
||||
void PipeWireOut::OnStreamStateChanged(void* data, enum pw_stream_state old, enum pw_stream_state state, const char* error) {
|
||||
::debug->Info(TAG, str::format("state changed from %d to %d", old, state).c_str());
|
||||
}
|
||||
|
||||
void PipeWireOut::OnCoreDone(void* userdata, uint32_t id, int seq) {
|
||||
auto context = static_cast<DeviceListContext*>(userdata);
|
||||
if (seq == context->eventId) {
|
||||
::debug->Info(TAG, "device refresh finished");
|
||||
pw_main_loop_quit(context->loop);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireOut::OnCoreError(void* userdata, uint32_t id, int seq, int res, const char *message) {
|
||||
auto context = static_cast<DeviceListContext*>(userdata);
|
||||
pw_main_loop_quit(context->loop);
|
||||
}
|
||||
|
||||
void PipeWireOut::OnRegistryGlobal(void *userdata, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) {
|
||||
auto context = static_cast<DeviceListContext*>(userdata);
|
||||
if (std::string(type) == "PipeWire:Interface:Device") {
|
||||
std::string formattedName = "unknown device";
|
||||
if (props) {
|
||||
auto dict = pw_properties_new_dict(props);
|
||||
auto name = pw_properties_get(dict, "device.nick");
|
||||
if (!name || !strlen(name)) name = pw_properties_get(dict, "alsa.card_name");
|
||||
if (!name || !strlen(name)) name = pw_properties_get(dict, "api.alsa.card.name");
|
||||
if (!name || !strlen(name)) name = "unknown device";
|
||||
auto addr = pw_properties_get(dict, "object.path");
|
||||
if (!addr || !strlen(addr)) addr = pw_properties_get(dict, "device.bus-path");
|
||||
formattedName = name;
|
||||
if (addr && strlen(addr)) {
|
||||
formattedName += " (" + std::string(addr) + ")";
|
||||
}
|
||||
pw_properties_free(dict);
|
||||
}
|
||||
::debug->Info(TAG, str::format("detected PipeWire:Interface:Device with id=%d", id).c_str());
|
||||
context->instance->deviceList.Add(std::to_string(id), formattedName);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireOut::OnDrained(void* data) {
|
||||
::debug->Info(TAG, "drained");
|
||||
PipeWireOut* self = static_cast<PipeWireOut*>(data);
|
||||
self->drainCondition.notify_all();
|
||||
}
|
||||
|
||||
void PipeWireOut::OnStreamProcess(void* data) {
|
||||
PipeWireOut* self = static_cast<PipeWireOut*>(data);
|
||||
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(self->mutex);
|
||||
|
||||
if (self->state != State::Playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
OutBufferContext& outContext = self->outBufferContext;
|
||||
|
||||
if (!outContext.Valid()) {
|
||||
outContext.Initialize(pw_stream_dequeue_buffer(self->pwStream));
|
||||
if (!outContext.Valid()) {
|
||||
// std::cerr << "[PipeWire] no more output buffers available to fill\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t channelCount;
|
||||
|
||||
while (outContext.remaining > 0 && !self->buffers.empty()) {
|
||||
//std::cerr << "[PipeWire] " << outContext.remaining << " bytes still need to be filled...\n";
|
||||
|
||||
InBufferContext* inContext = self->buffers.front();
|
||||
channelCount = (uint32_t) inContext->buffer->Channels();
|
||||
uint32_t inBufferSize = (uint32_t) inContext->buffer->Bytes();
|
||||
uint32_t bytesToCopy = std::min(outContext.remaining, inContext->remaining);
|
||||
|
||||
if (outContext.remaining >= inContext->remaining) {
|
||||
self->buffers.pop_front();
|
||||
self->bufferCondition.notify_all();
|
||||
}
|
||||
|
||||
memcpy(outContext.writePtr, inContext->readPtr, bytesToCopy);
|
||||
inContext->Advance(bytesToCopy); /* will `delete this` if emptied */
|
||||
outContext.Advance(bytesToCopy);
|
||||
}
|
||||
|
||||
if (outContext.remaining == 0) {
|
||||
outContext.Finalize(self->pwStream, SAMPLE_SIZE_BYTES * channelCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PipeWireOut::PipeWireOut() {
|
||||
this->pwStreamEvents = { PW_VERSION_STREAM_EVENTS };
|
||||
this->pwStreamEvents.state_changed = PipeWireOut::OnStreamStateChanged;
|
||||
this->pwStreamEvents.process = PipeWireOut::OnStreamProcess;
|
||||
this->pwStreamEvents.drained = PipeWireOut::OnDrained;
|
||||
}
|
||||
|
||||
PipeWireOut::~PipeWireOut() {
|
||||
this->StopPipeWire();
|
||||
}
|
||||
|
||||
void PipeWireOut::Release() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void PipeWireOut::Pause() {
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
this->state = State::Paused;
|
||||
}
|
||||
{
|
||||
if (this->pwThreadLoop && this->pwStream) {
|
||||
pw_thread_loop_lock(this->pwThreadLoop);
|
||||
pw_stream_set_active(this->pwStream, false);
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireOut::Resume() {
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
this->state = State::Playing;
|
||||
}
|
||||
{
|
||||
if (this->pwThreadLoop && this->pwStream) {
|
||||
pw_thread_loop_lock(this->pwThreadLoop);
|
||||
pw_stream_set_active(this->pwStream, true);
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireOut::SetVolume(double volume) {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
if (this->pwThreadLoop && this->pwStream) {
|
||||
pw_thread_loop_lock(this->pwThreadLoop);
|
||||
float* channelVolumes = new float[this->channelCount];
|
||||
for (long i = 0; i < this->channelCount; i++) {
|
||||
channelVolumes[i] = volume;
|
||||
}
|
||||
pw_stream_set_control(
|
||||
this->pwStream,
|
||||
SPA_PROP_channelVolumes,
|
||||
this->channelCount,
|
||||
channelVolumes,
|
||||
0);
|
||||
delete[] channelVolumes;
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
}
|
||||
this->volume = volume;
|
||||
}
|
||||
|
||||
double PipeWireOut::GetVolume() {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
return this->volume;
|
||||
}
|
||||
|
||||
void PipeWireOut::Stop() {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
this->DiscardInputBuffers();
|
||||
this->state = State::Stopped;
|
||||
if (this->pwThreadLoop && this->pwStream) {
|
||||
pw_thread_loop_lock(this->pwThreadLoop);
|
||||
pw_stream_set_active(this->pwStream, false);
|
||||
pw_stream_flush(this->pwStream, false);
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireOut::DiscardInputBuffers() {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
for (auto& buffer : this->buffers) {
|
||||
buffer->Discard();
|
||||
}
|
||||
this->buffers.clear();
|
||||
this->bufferCondition.notify_all();
|
||||
}
|
||||
|
||||
void PipeWireOut::Drain() {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
while (this->buffers.size()) {
|
||||
bufferCondition.wait(lock);
|
||||
}
|
||||
if (this->pwThreadLoop && this->pwStream) {
|
||||
pw_thread_loop_lock(this->pwThreadLoop);
|
||||
pw_stream_flush(this->pwStream, true);
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
drainCondition.wait_for(lock, std::chrono::milliseconds(10000));
|
||||
}
|
||||
}
|
||||
|
||||
IDeviceList* PipeWireOut::GetDeviceList() {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
this->RefreshDeviceList();
|
||||
return this->deviceList.Clone();
|
||||
}
|
||||
|
||||
bool PipeWireOut::SetDefaultDevice(const char* deviceId) {
|
||||
if (getDeviceId() != deviceId) {
|
||||
setDefaultDevice<IPreferences, Device, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IDevice* PipeWireOut::GetDefaultDevice() {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
this->RefreshDeviceList();
|
||||
return this->deviceList.ResolveDevice(getDeviceId());
|
||||
}
|
||||
|
||||
void PipeWireOut::StopPipeWire() {
|
||||
::debug->Info(TAG, "shutdown started");
|
||||
|
||||
this->Stop();
|
||||
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
this->state = State::Shutdown;
|
||||
}
|
||||
|
||||
if (this->pwThreadLoop) {
|
||||
pw_thread_loop_stop(this->pwThreadLoop);
|
||||
|
||||
if (this->pwStream) {
|
||||
this->outBufferContext.Finalize(
|
||||
this->pwStream,
|
||||
SAMPLE_SIZE_BYTES * this->channelCount);
|
||||
pw_stream_destroy(this->pwStream);
|
||||
this->pwStream = nullptr;
|
||||
}
|
||||
|
||||
pw_thread_loop_destroy(this->pwThreadLoop);
|
||||
this->pwThreadLoop = nullptr;
|
||||
}
|
||||
|
||||
this->initialized = false;
|
||||
this->channelCount = 0;
|
||||
this->sampleRate = 0;
|
||||
|
||||
::debug->Info(TAG, "shutdown complete");
|
||||
}
|
||||
|
||||
bool PipeWireOut::StartPipeWire(IBuffer* buffer) {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
|
||||
this->pwThreadLoop = pw_thread_loop_new("musikcube", nullptr);
|
||||
if (this->pwThreadLoop) {
|
||||
int result;
|
||||
|
||||
if ((result = pw_thread_loop_start(this->pwThreadLoop)) != 0) {
|
||||
::debug->Error(TAG, str::format("error starting thread loop: %s", spa_strerror(result)).c_str());
|
||||
goto cleanup;
|
||||
};
|
||||
|
||||
pw_thread_loop_lock(this->pwThreadLoop);
|
||||
|
||||
this->pwStream = pw_stream_new_simple(
|
||||
pw_thread_loop_get_loop(this->pwThreadLoop),
|
||||
"musikcube",
|
||||
pw_properties_new(
|
||||
PW_KEY_MEDIA_TYPE, "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE, "Music",
|
||||
NULL),
|
||||
&this->pwStreamEvents,
|
||||
this);
|
||||
|
||||
if (this->pwStream) {
|
||||
uint8_t builderBuffer[4096];
|
||||
spa_pod_builder builder = SPA_POD_BUILDER_INIT(builderBuffer, sizeof(builderBuffer));
|
||||
const spa_pod *params[1];
|
||||
|
||||
this->channelCount = buffer->Channels();
|
||||
this->sampleRate = buffer->SampleRate();
|
||||
|
||||
spa_audio_info_raw audioInfo;
|
||||
spa_zero(audioInfo);
|
||||
audioInfo.format = SPA_AUDIO_FORMAT_F32;
|
||||
audioInfo.channels = (uint32_t) this->channelCount;
|
||||
audioInfo.rate = (uint32_t) this->sampleRate;
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &audioInfo);
|
||||
|
||||
if (!params[0]) {
|
||||
::debug->Error(TAG, str::format(
|
||||
"failed to create audio format. channels=%d, rate=%d",
|
||||
this->channelCount,
|
||||
this->sampleRate).c_str());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// params[1] = (spa_pod*) spa_pod_builder_add_object(
|
||||
// &builder,
|
||||
// SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
||||
// SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(MAX_BUFFERS),
|
||||
// SPA_PARAM_BUFFERS_size, SPA_POD_Int(SAMPLES_PER_BUFFER * SAMPLE_SIZE_BYTES * buffer->Channels()),
|
||||
// SPA_PARAM_BUFFERS_stride, SPA_POD_Int(SAMPLE_SIZE_BYTES * audioInfo.channels));
|
||||
|
||||
pw_stream_flags streamFlags = (pw_stream_flags)(
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS);
|
||||
|
||||
result = pw_stream_connect(
|
||||
this->pwStream,
|
||||
PW_DIRECTION_OUTPUT,
|
||||
this->deviceList.ResolveId(getDeviceId()),
|
||||
streamFlags,
|
||||
params,
|
||||
1);
|
||||
|
||||
if (result == 0) {
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
::debug->Info(TAG, "new stream created and connected successfully");
|
||||
this->initialized = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
::debug->Error(TAG, str::format(
|
||||
"error starting stream: %s",
|
||||
spa_strerror(result)).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
pw_thread_loop_unlock(this->pwThreadLoop);
|
||||
::debug->Error(TAG, "stream not initialized");
|
||||
this->StopPipeWire();
|
||||
return false;
|
||||
}
|
||||
|
||||
OutputState PipeWireOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
if (!this->initialized) {
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
if (!pipeWireInitialized) {
|
||||
pw_init(nullptr, nullptr);
|
||||
pipeWireInitialized = true;
|
||||
}
|
||||
this->RefreshDeviceList();
|
||||
if (!this->StartPipeWire(buffer)) {
|
||||
return OutputState::InvalidState;
|
||||
}
|
||||
this->SetVolume(this->volume);
|
||||
}
|
||||
|
||||
if (this->channelCount != buffer->Channels() || this->sampleRate != buffer->SampleRate()) {
|
||||
State lastState = this->state;
|
||||
this->Drain();
|
||||
this->StopPipeWire();
|
||||
if (!this->StartPipeWire(buffer)) {
|
||||
return OutputState::InvalidState;
|
||||
}
|
||||
this->state = lastState;
|
||||
}
|
||||
|
||||
if (this->state != State::Playing) {
|
||||
return OutputState::InvalidState;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(this->mutex);
|
||||
if (this->buffers.size() >= MAX_BUFFERS) {
|
||||
return OutputState::BufferFull;
|
||||
}
|
||||
this->buffers.push_back(new InBufferContext(buffer, provider));
|
||||
bufferCondition.notify_all();
|
||||
}
|
||||
|
||||
return OutputState::BufferWritten;
|
||||
}
|
||||
|
||||
double PipeWireOut::Latency() {
|
||||
/* CAL TODO: i see how to set latency, but not a good way to query
|
||||
PipeWire to see what it actually is */
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void PipeWireOut::RefreshDeviceList() {
|
||||
/* https://github.com/PipeWire/pipewire/blob/master/src/tools/pw-dump.c */
|
||||
|
||||
if (!pipeWireInitialized) {
|
||||
pw_init(nullptr, nullptr);
|
||||
pipeWireInitialized = true;
|
||||
}
|
||||
|
||||
DeviceListContext deviceListContext(this);
|
||||
|
||||
deviceListContext.loop = pw_main_loop_new(nullptr);
|
||||
if (!deviceListContext.loop) {
|
||||
::debug->Error(TAG, "RefreshDeviceList() could not create main loop");
|
||||
return;
|
||||
}
|
||||
|
||||
auto loop = pw_main_loop_get_loop(deviceListContext.loop);
|
||||
if (!loop) {
|
||||
::debug->Error(TAG, "RefreshDeviceList() could not resolve loop from main_loop??");
|
||||
return;
|
||||
}
|
||||
|
||||
deviceListContext.context = pw_context_new(loop, nullptr, 0);
|
||||
if (!deviceListContext.context) {
|
||||
::debug->Error(TAG, "RefreshDeviceList() could not create context");
|
||||
return;
|
||||
}
|
||||
|
||||
deviceListContext.core = pw_context_connect(deviceListContext.context, nullptr, 0);
|
||||
if (deviceListContext.core == nullptr) {
|
||||
::debug->Error(TAG, "RefreshDeviceList() could not connect to core");
|
||||
return;
|
||||
}
|
||||
|
||||
pw_core_add_listener(
|
||||
deviceListContext.core,
|
||||
&deviceListContext.coreListener,
|
||||
&deviceListContext.coreEvents,
|
||||
&deviceListContext);
|
||||
|
||||
deviceListContext.registry = pw_core_get_registry(
|
||||
deviceListContext.core, PW_VERSION_REGISTRY, 0);
|
||||
|
||||
if (deviceListContext.registry) {
|
||||
pw_registry_add_listener(
|
||||
deviceListContext.registry,
|
||||
&deviceListContext.registryListener,
|
||||
&deviceListContext.registryEvents,
|
||||
&deviceListContext);
|
||||
|
||||
deviceListContext.eventId = pw_core_sync(
|
||||
deviceListContext.core, PW_ID_CORE, 0);
|
||||
|
||||
this->deviceList.Reset();
|
||||
|
||||
pw_main_loop_run(deviceListContext.loop);
|
||||
}
|
||||
}
|
297
src/plugins/pipewireout/PipeWireOut.h
Normal file
297
src/plugins/pipewireout/PipeWireOut.h
Normal file
@ -0,0 +1,297 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2004-2020 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <musikcore/sdk/IOutput.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <condition_variable>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
class PipeWireOut : public IOutput {
|
||||
public:
|
||||
PipeWireOut();
|
||||
~PipeWireOut();
|
||||
|
||||
/* IPlugin */
|
||||
const char* Name() override { return "PipeWire"; };
|
||||
void Release() override;
|
||||
|
||||
/* IOutput */
|
||||
void Pause() override;
|
||||
void Resume() override;
|
||||
void SetVolume(double volume) override;
|
||||
double GetVolume() override;
|
||||
void Stop() override;
|
||||
OutputState Play(IBuffer *buffer, IBufferProvider *provider) override;
|
||||
double Latency() override;
|
||||
void Drain() override;
|
||||
IDeviceList* GetDeviceList() override;
|
||||
bool SetDefaultDevice(const char* deviceId) override;
|
||||
IDevice* GetDefaultDevice() override;
|
||||
|
||||
private:
|
||||
bool StartPipeWire(IBuffer* buffer);
|
||||
void StopPipeWire();
|
||||
void DiscardInputBuffers();
|
||||
void RefreshDeviceList();
|
||||
|
||||
static void OnCoreDone(
|
||||
void* userdata,
|
||||
uint32_t id,
|
||||
int seq);
|
||||
|
||||
static void OnCoreError(
|
||||
void *userdata,
|
||||
uint32_t id,
|
||||
int seq,
|
||||
int res,
|
||||
const char *message
|
||||
);
|
||||
|
||||
static void OnRegistryGlobal(
|
||||
void *userdata,
|
||||
uint32_t id,
|
||||
uint32_t permissions,
|
||||
const char *type,
|
||||
uint32_t version,
|
||||
const struct spa_dict *props);
|
||||
|
||||
static void OnStreamStateChanged(
|
||||
void* userdata,
|
||||
enum pw_stream_state old,
|
||||
enum pw_stream_state state,
|
||||
const char* error);
|
||||
|
||||
static void OnStreamProcess(void* userdata);
|
||||
|
||||
static void OnDrained(void* userdata);
|
||||
|
||||
struct InBufferContext {
|
||||
InBufferContext(IBuffer* buffer, IBufferProvider* provider) {
|
||||
this->buffer = buffer; this->provider = provider;
|
||||
this->readPtr = (char*) buffer->BufferPointer();
|
||||
this->remaining = (uint32_t) buffer->Bytes();
|
||||
}
|
||||
void Advance(int count) {
|
||||
bool release = count >= remaining;
|
||||
this->remaining -= count;
|
||||
this->readPtr += count;
|
||||
if (release) {
|
||||
this->provider->OnBufferProcessed(this->buffer);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
void Discard() {
|
||||
this->provider->OnBufferProcessed(this->buffer);
|
||||
delete this;
|
||||
}
|
||||
IBuffer* buffer{nullptr};
|
||||
IBufferProvider* provider{nullptr};
|
||||
uint32_t remaining{0};
|
||||
char* readPtr;
|
||||
};
|
||||
|
||||
struct OutBufferContext {
|
||||
void Initialize(pw_buffer* buffer) {
|
||||
this->buffer = buffer;
|
||||
if (buffer) {
|
||||
struct spa_buffer* spaBuffer = buffer->buffer;
|
||||
this->writePtr = (char*) spaBuffer->datas[0].data;
|
||||
this->remaining = spaBuffer->datas[0].maxsize;
|
||||
this->total = this->remaining;
|
||||
}
|
||||
else {
|
||||
this->Reset();
|
||||
}
|
||||
}
|
||||
void Reset() {
|
||||
this->buffer = nullptr;
|
||||
this->writePtr = nullptr;
|
||||
this->remaining = 0;
|
||||
this->total = 0;
|
||||
}
|
||||
void Advance(int count) {
|
||||
this->remaining -= count;
|
||||
this->writePtr += count;
|
||||
}
|
||||
void Finalize(pw_stream* stream, uint32_t stride) {
|
||||
if (this->Valid()) {
|
||||
spa_data& data = this->buffer->buffer->datas[0];
|
||||
data.chunk->offset = 0;
|
||||
data.chunk->stride = stride;
|
||||
data.chunk->size = this->total - this->remaining;
|
||||
pw_stream_queue_buffer(stream, this->buffer);
|
||||
this->Reset();
|
||||
}
|
||||
}
|
||||
bool Valid() {
|
||||
return this->buffer != nullptr;
|
||||
}
|
||||
pw_buffer* buffer{nullptr};
|
||||
uint32_t remaining{0};
|
||||
uint32_t total{0};
|
||||
char* writePtr{nullptr};
|
||||
};
|
||||
|
||||
class Device: public musik::core::sdk::IDevice {
|
||||
public:
|
||||
Device(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
void Release() override {
|
||||
delete this;
|
||||
}
|
||||
const char* Name() const override {
|
||||
return name.c_str();
|
||||
}
|
||||
const char* Id() const override {
|
||||
return id.c_str();
|
||||
}
|
||||
Device* Clone() {
|
||||
return new Device(this->id, this->name);
|
||||
}
|
||||
private:
|
||||
std::string id, name;
|
||||
};
|
||||
|
||||
class DeviceList: public musik::core::sdk::IDeviceList {
|
||||
public:
|
||||
void Release() override {
|
||||
delete this;
|
||||
}
|
||||
size_t Count() const override {
|
||||
return devices.size();
|
||||
}
|
||||
const Device* At(size_t index) const override {
|
||||
return &devices.at(index);
|
||||
}
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(Device(id, name));
|
||||
}
|
||||
Device* Default() {
|
||||
return this->devices.empty() ? nullptr : this->devices.at(0).Clone();
|
||||
}
|
||||
void Reset() {
|
||||
this->devices.clear();
|
||||
}
|
||||
uint32_t ResolveId(const std::string& id) {
|
||||
for (auto device: this->devices) {
|
||||
if (device.Id() == id) {
|
||||
try {
|
||||
return (uint32_t) std::stoi(id);
|
||||
}
|
||||
catch(...) {
|
||||
/* return default below... */
|
||||
}
|
||||
}
|
||||
}
|
||||
return PW_ID_ANY;
|
||||
}
|
||||
Device* ResolveDevice(const std::string& id) {
|
||||
for (auto device: this->devices) {
|
||||
if (device.Id() == id) {
|
||||
return device.Clone();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
DeviceList* Clone() {
|
||||
auto result = new DeviceList();
|
||||
result->devices = this->devices;
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
std::vector<Device> devices;
|
||||
};
|
||||
|
||||
struct DeviceListContext {
|
||||
DeviceListContext(PipeWireOut* instance) {
|
||||
this->instance = instance;
|
||||
this->coreEvents = { PW_VERSION_CORE_EVENTS };
|
||||
this->coreEvents.done = PipeWireOut::OnCoreDone;
|
||||
this->coreEvents.error = PipeWireOut::OnCoreError;
|
||||
spa_zero(this->coreListener);
|
||||
this->registryEvents = { PW_VERSION_REGISTRY_EVENTS };
|
||||
this->registryEvents.global = PipeWireOut::OnRegistryGlobal;
|
||||
spa_zero(this->registryListener);
|
||||
}
|
||||
~DeviceListContext() {
|
||||
if (this->registry) {
|
||||
pw_proxy_destroy(reinterpret_cast<pw_proxy*>(this->registry));
|
||||
}
|
||||
if (this->context) {
|
||||
pw_context_destroy(this->context);
|
||||
}
|
||||
if (this->loop) {
|
||||
pw_main_loop_destroy(this->loop);
|
||||
}
|
||||
}
|
||||
pw_main_loop* loop{nullptr};
|
||||
pw_context* context{nullptr};
|
||||
pw_core* core{nullptr};
|
||||
spa_hook coreListener;
|
||||
pw_core_events coreEvents;
|
||||
pw_registry* registry{nullptr};
|
||||
spa_hook registryListener;
|
||||
pw_registry_events registryEvents;
|
||||
int eventId{0};
|
||||
PipeWireOut* instance{nullptr};
|
||||
};
|
||||
|
||||
enum class State {
|
||||
Stopped, Paused, Playing, Shutdown
|
||||
};
|
||||
|
||||
std::deque<InBufferContext*> buffers;
|
||||
std::recursive_mutex mutex;
|
||||
std::atomic<bool> initialized{false};
|
||||
std::atomic<State> state{State::Stopped};
|
||||
std::condition_variable_any bufferCondition, drainCondition;
|
||||
double volume{1.0};
|
||||
pw_stream_events pwStreamEvents;
|
||||
pw_thread_loop* pwThreadLoop {nullptr};
|
||||
pw_stream* pwStream{nullptr};
|
||||
OutBufferContext outBufferContext;
|
||||
long channelCount{0};
|
||||
long sampleRate{0};
|
||||
DeviceList deviceList;
|
||||
};
|
63
src/plugins/pipewireout/pipewireout_plugin.cpp
Normal file
63
src/plugins/pipewireout/pipewireout_plugin.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2004-2020 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <musikcore/sdk/constants.h>
|
||||
#include <musikcore/sdk/IPlugin.h>
|
||||
#include <musikcore/sdk/ISchema.h>
|
||||
#include "PipeWireOut.h"
|
||||
|
||||
class PipeWirePlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
void Release() noexcept override { delete this; }
|
||||
const char* Name() override { return "PipeWire IOutput"; }
|
||||
const char* Version() override { return "0.1.0"; }
|
||||
const char* Author() override { return "clangen"; }
|
||||
const char* Guid() override { return "ab79e0f2-53d8-4774-ad00-266a30c50427"; }
|
||||
bool Configurable() override { return false; }
|
||||
void Configure() override { }
|
||||
void Reload() override { }
|
||||
int SdkVersion() override { return musik::core::sdk::SdkVersion; }
|
||||
};
|
||||
|
||||
extern "C" musik::core::sdk::IPlugin* GetPlugin() {
|
||||
return new PipeWirePlugin();
|
||||
}
|
||||
|
||||
extern "C" musik::core::sdk::IOutput* GetAudioOutput() {
|
||||
return new PipeWireOut();
|
||||
}
|
||||
|
||||
extern "C" musik::core::sdk::ISchema* GetSchema();
|
@ -6,11 +6,11 @@ set (stockencoders_SOURCES
|
||||
|
||||
add_library(stockencoders SHARED ${stockencoders_SOURCES})
|
||||
|
||||
if (${FFMPEG_ENABLED} MATCHES "false")
|
||||
message(STATUS "[stockencoders] *not* defining FFMPEG_ENABLED")
|
||||
if (${ENABLE_FFMPEG} MATCHES "false")
|
||||
message(STATUS "[stockencoders] *not* defining ENABLE_FFMPEG")
|
||||
else()
|
||||
message(STATUS "[stockencoders] defining FFMPEG_ENABLED")
|
||||
add_definitions(-DFFMPEG_ENABLED)
|
||||
message(STATUS "[stockencoders] defining ENABLE_FFMPEG")
|
||||
add_definitions(-DENABLE_FFMPEG)
|
||||
# fedora (and probably other RPM-based distros) put ffmpeg includes here...
|
||||
include_directories("/usr/include/ffmpeg")
|
||||
include_directories("/usr/local/include/ffmpeg")
|
||||
|
@ -34,7 +34,7 @@
|
||||
|
||||
#include "FfmpegEncoder.h"
|
||||
|
||||
#ifdef FFMPEG_ENABLED
|
||||
#ifdef ENABLE_FFMPEG
|
||||
|
||||
#include "shared.h"
|
||||
#include <algorithm>
|
||||
@ -637,4 +637,4 @@ AVFrame* FfmpegEncoder::ReallocFrame(
|
||||
return original;
|
||||
}
|
||||
|
||||
#endif // FFMPEG_ENABLED
|
||||
#endif // ENABLE_FFMPEG
|
@ -35,10 +35,10 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WIN32
|
||||
#define FFMPEG_ENABLED
|
||||
#define ENABLE_FFMPEG
|
||||
#endif
|
||||
|
||||
#ifdef FFMPEG_ENABLED
|
||||
#ifdef ENABLE_FFMPEG
|
||||
|
||||
#include <musikcore/sdk/IBlockingEncoder.h>
|
||||
#include <musikcore/sdk/DataBuffer.h>
|
||||
@ -98,4 +98,4 @@ class FfmpegEncoder : public musik::core::sdk::IBlockingEncoder {
|
||||
int inputSampleRate;
|
||||
};
|
||||
|
||||
#endif // FFMPEG_ENABLED
|
||||
#endif // ENABLE_FFMPEG
|
@ -62,7 +62,7 @@ static IEnvironment* environment = nullptr;
|
||||
|
||||
static std::set<std::string> supportedFormats = {
|
||||
".mp3",
|
||||
#ifdef FFMPEG_ENABLED
|
||||
#ifdef ENABLE_FFMPEG
|
||||
"audio/mpeg",
|
||||
".ogg",
|
||||
"audio/ogg",
|
||||
@ -86,10 +86,10 @@ static class Plugin : public IPlugin {
|
||||
}
|
||||
|
||||
virtual void Release() { }
|
||||
#if defined(FFMPEG_ENABLED) || defined(WIN32)
|
||||
virtual const char* Name() { return "Stock Encoders (MP3 + ffmpeg)"; }
|
||||
#if defined(ENABLE_FFMPEG) || defined(WIN32)
|
||||
virtual const char* Name() { return "Stock Encoders (lame + ffmpeg)"; }
|
||||
#else
|
||||
virtual const char* Name() { return "Stock Encoders (MP3)"; }
|
||||
virtual const char* Name() { return "Stock Encoders (lame)"; }
|
||||
#endif
|
||||
virtual const char* Version() { return "0.7.0"; }
|
||||
virtual const char* Author() { return "clangen"; }
|
||||
@ -116,7 +116,7 @@ static class EncoderFactory: public IEncoderFactory {
|
||||
if (isMp3(lowerType)) {
|
||||
return new LameEncoder();
|
||||
}
|
||||
#ifdef FFMPEG_ENABLED
|
||||
#ifdef ENABLE_FFMPEG
|
||||
else if (supportedFormats.find(lowerType) != supportedFormats.end()) {
|
||||
return new FfmpegEncoder(lowerType);
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ set (taglibreader_SOURCES
|
||||
TaglibMetadataReader.cpp
|
||||
)
|
||||
|
||||
if (${FFMPEG_ENABLED} MATCHES "false")
|
||||
message(STATUS "[taglibmetadatareader] *not* defining FFMPEG_ENABLED")
|
||||
if (${ENABLE_FFMPEG} MATCHES "false")
|
||||
message(STATUS "[taglibmetadatareader] *not* defining ENABLE_FFMPEG")
|
||||
else()
|
||||
message(STATUS "[taglibmetadatareader] defining FFMPEG_ENABLED")
|
||||
add_definitions(-DFFMPEG_ENABLED)
|
||||
message(STATUS "[taglibmetadatareader] defining ENABLE_FFMPEG")
|
||||
add_definitions(-DENABLE_FFMPEG)
|
||||
endif()
|
||||
|
||||
add_library(taglibreader SHARED ${taglibreader_SOURCES})
|
||||
|
@ -93,7 +93,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define FFMPEG_ENABLED
|
||||
#define ENABLE_FFMPEG
|
||||
#endif
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
@ -243,7 +243,7 @@ bool TaglibMetadataReader::CanRead(const char *extension) {
|
||||
if (extension && strlen(extension)) {
|
||||
std::string ext(str::lower(extension[0] == '.' ? &extension[1] : extension));
|
||||
return
|
||||
#ifdef FFMPEG_ENABLED
|
||||
#ifdef ENABLE_FFMPEG
|
||||
ext.compare("opus") == 0 ||
|
||||
ext.compare("wv") == 0 ||
|
||||
ext.compare("wma") == 0 ||
|
||||
|
@ -52,7 +52,7 @@
|
||||
class TaglibPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Release() { delete this; }
|
||||
#if defined(FFMPEG_ENABLED) || defined(WIN32)
|
||||
#if defined(ENABLE_FFMPEG) || defined(WIN32)
|
||||
virtual const char* Name() { return "Taglib 1.11 ITagReader (+ffmpeg)"; }
|
||||
#else
|
||||
virtual const char* Name() { return "Taglib 1.11 ITagReader"; }
|
||||
|
Loading…
Reference in New Issue
Block a user