mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-29 19:20:28 +00:00
Added the ability to select desired output device for all output
plugins, across all platforms. 1. Introcued new IDevice and IDeviceList SDK methods 2. Added GetDeviceList(), GetDefaultDevice(), and SetDefaultDevice() methods to IOutput 3. Bumped SdkVersion to 11 4. Integrated the above with the musikcube app.
This commit is contained in:
parent
6596701187
commit
4fc8665cca
@ -191,6 +191,7 @@
|
||||
<ClInclude Include="sdk\IBuffer.h" />
|
||||
<ClInclude Include="sdk\IDecoder.h" />
|
||||
<ClInclude Include="sdk\IDecoderFactory.h" />
|
||||
<ClInclude Include="sdk\IDevice.h" />
|
||||
<ClInclude Include="sdk\IDSP.h" />
|
||||
<ClInclude Include="sdk\IDataStream.h" />
|
||||
<ClInclude Include="sdk\IDataStreamFactory.h" />
|
||||
|
@ -471,5 +471,8 @@
|
||||
<ClInclude Include="sdk\IMetadataValueList.h">
|
||||
<Filter>src\sdk\library</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sdk\IDevice.h">
|
||||
<Filter>src\sdk\audio</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
89
src/core/sdk/IDevice.h
Normal file
89
src/core/sdk/IDevice.h
Normal file
@ -0,0 +1,89 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 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 <string>
|
||||
#include <string.h>
|
||||
|
||||
namespace musik { namespace core { namespace sdk {
|
||||
|
||||
class IDevice {
|
||||
public:
|
||||
virtual void Destroy() = 0;
|
||||
virtual const char* Name() const = 0;
|
||||
virtual const char* Id() const = 0;
|
||||
};
|
||||
|
||||
class IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() = 0;
|
||||
virtual size_t Count() const = 0;
|
||||
virtual const IDevice* At(size_t index) const = 0;
|
||||
};
|
||||
|
||||
template <typename Device, typename Output>
|
||||
IDevice* findDeviceById(Output* output, const std::string& deviceId) {
|
||||
IDevice* result = nullptr;
|
||||
auto deviceList = output->GetDeviceList();
|
||||
if (deviceList) {
|
||||
for (size_t i = 0; i < deviceList->Count(); i++) {
|
||||
auto device = deviceList->At(i);
|
||||
if (device->Id() == deviceId) {
|
||||
return new Device(device->Id(), device->Name());
|
||||
}
|
||||
}
|
||||
deviceList->Destroy();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Prefs, typename Device, typename Output>
|
||||
bool setDefaultDevice(Prefs* prefs, Output* output, const char* key, const char* deviceId) {
|
||||
if (!prefs || !deviceId || !strlen(deviceId)) {
|
||||
prefs->SetString(key, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto device = findDeviceById<Device, Output>(output, deviceId);
|
||||
if (device) {
|
||||
device->Destroy();
|
||||
prefs->SetString(key, deviceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} } }
|
@ -38,6 +38,7 @@
|
||||
#include "IDataStream.h"
|
||||
#include "IBuffer.h"
|
||||
#include "IBufferProvider.h"
|
||||
#include "IDevice.h"
|
||||
|
||||
namespace musik { namespace core { namespace sdk {
|
||||
|
||||
@ -53,6 +54,9 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual void Drain() = 0;
|
||||
virtual double Latency() = 0;
|
||||
virtual const char* Name() = 0;
|
||||
virtual IDeviceList* GetDeviceList() = 0;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) = 0;
|
||||
virtual IDevice* GetDefaultDevice() = 0;
|
||||
};
|
||||
|
||||
} } }
|
||||
|
@ -51,5 +51,20 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual void Save() = 0;
|
||||
};
|
||||
|
||||
template <typename String>
|
||||
String getPreferenceString(IPreferences* prefs, const char* key, const char* defaultValue) {
|
||||
if (prefs) {
|
||||
size_t count = prefs->GetString(key, nullptr, 0, defaultValue);
|
||||
if (count) {
|
||||
char* buffer = new char[count];
|
||||
prefs->GetString(key, buffer, count, defaultValue);
|
||||
String result = buffer;
|
||||
delete[] buffer;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} } }
|
||||
|
||||
|
@ -133,5 +133,5 @@ namespace musik {
|
||||
static const char* ExternalId = "external_id";
|
||||
}
|
||||
|
||||
static const int SdkVersion = 10;
|
||||
static const int SdkVersion = 11;
|
||||
} } }
|
||||
|
@ -99,6 +99,21 @@ static bool showDotfiles = false;
|
||||
|
||||
static UpdateCheck updateCheck;
|
||||
|
||||
static std::string getOutputDeviceName() {
|
||||
std::string deviceName = _TSTR("settings_output_device_default");
|
||||
|
||||
std::shared_ptr<IOutput> output = outputs::SelectedOutput();
|
||||
if (output) {
|
||||
IDevice* device = output->GetDefaultDevice();
|
||||
if (device) {
|
||||
deviceName = device->Name();
|
||||
device->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
SettingsLayout::SettingsLayout(
|
||||
cursespp::App& app,
|
||||
musik::core::ILibraryPtr library,
|
||||
@ -168,12 +183,12 @@ void SettingsLayout::OnLocaleDropdownActivate(cursespp::TextLabel* label) {
|
||||
LocaleOverlay::Show([this](){ this->LoadPreferences(); });
|
||||
}
|
||||
|
||||
void SettingsLayout::OnOutputDropdownActivated(cursespp::TextLabel* label) {
|
||||
void SettingsLayout::OnOutputDriverDropdownActivated(cursespp::TextLabel* label) {
|
||||
std::string currentName;
|
||||
std::shared_ptr<IOutput> currentPlugin = outputs::SelectedOutput();
|
||||
currentName = currentPlugin ? currentPlugin->Name() : currentName;
|
||||
|
||||
PlaybackOverlays::ShowOutputOverlay(
|
||||
PlaybackOverlays::ShowOutputDriverOverlay(
|
||||
this->transport.GetType(),
|
||||
[this, currentName] {
|
||||
std::string newName;
|
||||
@ -187,6 +202,17 @@ void SettingsLayout::OnOutputDropdownActivated(cursespp::TextLabel* label) {
|
||||
});
|
||||
}
|
||||
|
||||
void SettingsLayout::OnOutputDeviceDropdownActivated(cursespp::TextLabel* label) {
|
||||
std::string currentName = getOutputDeviceName();
|
||||
PlaybackOverlays::ShowOutputDeviceOverlay([this, currentName] {
|
||||
std::string newName = getOutputDeviceName();
|
||||
if (currentName != newName) {
|
||||
this->LoadPreferences();
|
||||
this->transport.ReloadOutput();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SettingsLayout::OnTransportDropdownActivate(cursespp::TextLabel* label) {
|
||||
const MasterTransport::Type current = this->transport.GetType();
|
||||
|
||||
@ -256,7 +282,8 @@ void SettingsLayout::OnLayout() {
|
||||
|
||||
y = BOTTOM(this->browseList);
|
||||
this->localeDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
this->outputDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
this->outputDriverDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
this->outputDeviceDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
this->transportDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
this->themeDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
this->hotkeyDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
|
||||
@ -342,8 +369,11 @@ void SettingsLayout::InitializeWindows() {
|
||||
this->localeDropdown.reset(new TextLabel());
|
||||
this->localeDropdown->Activated.connect(this, &SettingsLayout::OnLocaleDropdownActivate);
|
||||
|
||||
this->outputDropdown.reset(new TextLabel());
|
||||
this->outputDropdown->Activated.connect(this, &SettingsLayout::OnOutputDropdownActivated);
|
||||
this->outputDriverDropdown.reset(new TextLabel());
|
||||
this->outputDriverDropdown->Activated.connect(this, &SettingsLayout::OnOutputDriverDropdownActivated);
|
||||
|
||||
this->outputDeviceDropdown.reset(new TextLabel());
|
||||
this->outputDeviceDropdown->Activated.connect(this, &SettingsLayout::OnOutputDeviceDropdownActivated);
|
||||
|
||||
this->transportDropdown.reset(new TextLabel());
|
||||
this->transportDropdown->Activated.connect(this, &SettingsLayout::OnTransportDropdownActivate);
|
||||
@ -388,7 +418,8 @@ void SettingsLayout::InitializeWindows() {
|
||||
this->browseList->SetFocusOrder(order++);
|
||||
this->addedPathsList->SetFocusOrder(order++);
|
||||
this->localeDropdown->SetFocusOrder(order++);
|
||||
this->outputDropdown->SetFocusOrder(order++);
|
||||
this->outputDriverDropdown->SetFocusOrder(order++);
|
||||
this->outputDeviceDropdown->SetFocusOrder(order++);
|
||||
this->transportDropdown->SetFocusOrder(order++);
|
||||
this->themeDropdown->SetFocusOrder(order++);
|
||||
this->hotkeyDropdown->SetFocusOrder(order++);
|
||||
@ -418,7 +449,8 @@ void SettingsLayout::InitializeWindows() {
|
||||
this->AddWindow(this->browseList);
|
||||
this->AddWindow(this->addedPathsList);
|
||||
this->AddWindow(this->localeDropdown);
|
||||
this->AddWindow(this->outputDropdown);
|
||||
this->AddWindow(this->outputDriverDropdown);
|
||||
this->AddWindow(this->outputDeviceDropdown);
|
||||
this->AddWindow(this->transportDropdown);
|
||||
this->AddWindow(this->themeDropdown);
|
||||
|
||||
@ -544,12 +576,16 @@ void SettingsLayout::LoadPreferences() {
|
||||
#endif
|
||||
this->autoUpdateCheckbox->SetChecked(this->prefs->GetBool(cube::prefs::keys::AutoUpdateCheck, true));
|
||||
|
||||
/* output plugin */
|
||||
/* output driver */
|
||||
std::shared_ptr<IOutput> output = outputs::SelectedOutput();
|
||||
if (output) {
|
||||
this->outputDropdown->SetText(arrow + _TSTR("settings_output_device") + output->Name());
|
||||
this->outputDriverDropdown->SetText(arrow + _TSTR("settings_output_driver") + output->Name());
|
||||
}
|
||||
|
||||
/* output device */
|
||||
std::string deviceName = getOutputDeviceName();
|
||||
this->outputDeviceDropdown->SetText(arrow + _TSTR("settings_output_device") + deviceName);
|
||||
|
||||
/* transport type */
|
||||
std::string transportName =
|
||||
this->transport.GetType() == MasterTransport::Gapless
|
||||
|
@ -98,7 +98,8 @@ namespace musik {
|
||||
void OnCheckboxChanged(
|
||||
cursespp::Checkbox* checkbox, bool checked);
|
||||
|
||||
void OnOutputDropdownActivated(cursespp::TextLabel* label);
|
||||
void OnOutputDriverDropdownActivated(cursespp::TextLabel* label);
|
||||
void OnOutputDeviceDropdownActivated(cursespp::TextLabel* label);
|
||||
void OnTransportDropdownActivate(cursespp::TextLabel* label);
|
||||
void OnPluginsDropdownActivate(cursespp::TextLabel* label);
|
||||
void OnHotkeyDropdownActivate(cursespp::TextLabel* label);
|
||||
@ -122,7 +123,8 @@ namespace musik {
|
||||
std::shared_ptr<musik::core::Preferences> prefs;
|
||||
|
||||
std::shared_ptr<cursespp::TextLabel> localeDropdown;
|
||||
std::shared_ptr<cursespp::TextLabel> outputDropdown;
|
||||
std::shared_ptr<cursespp::TextLabel> outputDriverDropdown;
|
||||
std::shared_ptr<cursespp::TextLabel> outputDeviceDropdown;
|
||||
std::shared_ptr<cursespp::TextLabel> transportDropdown;
|
||||
std::shared_ptr<cursespp::TextLabel> pluginsDropdown;
|
||||
std::shared_ptr<cursespp::TextLabel> hotkeyDropdown;
|
||||
|
@ -54,6 +54,13 @@ using namespace cursespp;
|
||||
static std::vector<std::shared_ptr<IOutput> > plugins;
|
||||
static std::set<std::string> invalidCrossfadeOutputs = { "WaveOut" };
|
||||
|
||||
template <typename T>
|
||||
struct DestroyDeleter {
|
||||
void operator()(T* t) {
|
||||
if (t) t->Destroy();
|
||||
}
|
||||
};
|
||||
|
||||
static void showNoOutputPluginsMessage() {
|
||||
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
|
||||
|
||||
@ -92,7 +99,7 @@ static void showOutputCannotCrossfadeMessage(const std::string& outputName) {
|
||||
PlaybackOverlays::PlaybackOverlays() {
|
||||
}
|
||||
|
||||
void PlaybackOverlays::ShowOutputOverlay(
|
||||
void PlaybackOverlays::ShowOutputDriverOverlay(
|
||||
MasterTransport::Type transportType,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
@ -147,6 +154,72 @@ void PlaybackOverlays::ShowOutputOverlay(
|
||||
cursespp::App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void PlaybackOverlays::ShowOutputDeviceOverlay(std::function<void()> callback) {
|
||||
auto output = outputs::SelectedOutput();
|
||||
if (!output) {
|
||||
showNoOutputPluginsMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string currentDeviceName = _TSTR("settings_output_device_default");
|
||||
|
||||
std::shared_ptr<IDeviceList> deviceList = std::shared_ptr<IDeviceList>(
|
||||
output->GetDeviceList(), DestroyDeleter<IDeviceList>());
|
||||
|
||||
std::shared_ptr<IDevice> device = std::shared_ptr<IDevice>(
|
||||
output->GetDefaultDevice(), DestroyDeleter<IDevice>());
|
||||
|
||||
if (device) {
|
||||
currentDeviceName = device->Name();
|
||||
}
|
||||
|
||||
using Adapter = cursespp::SimpleScrollAdapter;
|
||||
using ListOverlay = cursespp::ListOverlay;
|
||||
|
||||
size_t width = _DIMEN("output_device_overlay_width", 35);
|
||||
size_t selectedIndex = 0;
|
||||
|
||||
std::shared_ptr<Adapter> adapter(new Adapter());
|
||||
adapter->AddEntry(_TSTR("settings_output_device_default"));
|
||||
|
||||
if (deviceList) {
|
||||
for (size_t i = 0; i < deviceList->Count(); i++) {
|
||||
const std::string name = deviceList->At(i)->Name();
|
||||
adapter->AddEntry(name);
|
||||
|
||||
width = std::max(width, u8cols(name));
|
||||
|
||||
if (name == currentDeviceName) {
|
||||
selectedIndex = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter->SetSelectable(true);
|
||||
|
||||
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
|
||||
|
||||
dialog->SetAdapter(adapter)
|
||||
.SetTitle(_TSTR("playback_overlay_output_device_title"))
|
||||
.SetSelectedIndex(selectedIndex)
|
||||
.SetWidth(width)
|
||||
.SetItemSelectedCallback(
|
||||
[output, deviceList, callback](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
|
||||
if (index == 0) {
|
||||
output->SetDefaultDevice("");
|
||||
}
|
||||
else {
|
||||
output->SetDefaultDevice(deviceList->At(index - 1)->Id());
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
cursespp::App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void PlaybackOverlays::ShowTransportOverlay(
|
||||
MasterTransport::Type transportType,
|
||||
std::function<void(int)> callback)
|
||||
|
@ -41,10 +41,12 @@ namespace musik {
|
||||
namespace cube {
|
||||
class PlaybackOverlays {
|
||||
public:
|
||||
static void ShowOutputOverlay(
|
||||
static void ShowOutputDriverOverlay(
|
||||
musik::glue::audio::MasterTransport::Type transportType,
|
||||
std::function<void()> callback);
|
||||
|
||||
static void ShowOutputDeviceOverlay(std::function<void()> callback);
|
||||
|
||||
static void ShowTransportOverlay(
|
||||
musik::glue::audio::MasterTransport::Type transportType,
|
||||
std::function<void(int)> callback);
|
||||
|
@ -206,7 +206,7 @@ void ListOverlay::RecalculateSize() {
|
||||
|
||||
/* constrain to app bounds */
|
||||
this->height = std::max(0, std::min(Screen::GetHeight() - 4, this->height));
|
||||
this->width = std::max(0, std::min(Screen::GetWidth(), this->width));
|
||||
this->width = std::max(0, std::min(Screen::GetWidth() - 4, this->width));
|
||||
|
||||
this->y = VERTICAL_PADDING;
|
||||
this->x = (Screen::GetWidth() / 2) - (this->width / 2);
|
||||
@ -231,7 +231,6 @@ void ListOverlay::Redraw() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ListOverlay::RefreshAdapter() {
|
||||
this->listWindow->OnAdapterChanged();
|
||||
}
|
||||
|
@ -30,7 +30,9 @@
|
||||
"settings_enable_disable_plugins": "plugin manager",
|
||||
"settings_color_theme": "color theme: ",
|
||||
"settings_hotkey_tester": "hotkey tester",
|
||||
"settings_output_driver": "output driver: ",
|
||||
"settings_output_device": "output device: ",
|
||||
"settings_output_device_default": "default",
|
||||
"settings_transport_type": "playback mode: ",
|
||||
"settings_degrade_256": "degrade to 256 color palette",
|
||||
"settings_show_dotfiles": "show dotfiles in directory browser",
|
||||
@ -68,8 +70,9 @@
|
||||
"color_theme_256_overlay_message": "disabling 256 color degradation will enable RGB color mode, which will replace colors in the stock palette. disabling this option results in higher fidelity themes, but it may cause display issues in other applications until the terminal is reset.\n\nare you sure you want to disable 256 color degradation?",
|
||||
|
||||
"playback_overlay_transport_title": "playback mode",
|
||||
"playback_overlay_output_plugins_title": "output plugins",
|
||||
"playback_overlay_invalid_transport": "the selected output device (%s) doesn't support crossfading.",
|
||||
"playback_overlay_output_plugins_title": "output driver plugins",
|
||||
"playback_overlay_output_device_title": "output device",
|
||||
"playback_overlay_invalid_transport": "the selected output driver (%s) doesn't support crossfading.",
|
||||
"playback_overlay_no_output_plugins_mesage": "no output plugins found!",
|
||||
|
||||
"playqueue_title": "play queue",
|
||||
@ -136,6 +139,7 @@
|
||||
},
|
||||
|
||||
"dimensions": {
|
||||
"output_device_overlay_width": 35,
|
||||
"playqueue_album_header_overlay": 35,
|
||||
"playqueue_playlist_add_to_queue_overlay": 35,
|
||||
"playqueue_playlist_list_overlay": 35,
|
||||
|
@ -33,11 +33,16 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "AlsaOut.h"
|
||||
|
||||
#include <core/sdk/constants.h>
|
||||
#include <core/sdk/IPreferences.h>
|
||||
|
||||
static musik::core::sdk::IPreferences* prefs;
|
||||
|
||||
#define BUFFER_COUNT 16
|
||||
#define PCM_ACCESS_TYPE SND_PCM_ACCESS_RW_INTERLEAVED
|
||||
#define PCM_FORMAT SND_PCM_FORMAT_FLOAT_LE
|
||||
#define PREF_DEVICE_ID "device_id"
|
||||
|
||||
#define LOCK(x) \
|
||||
/*std::cerr << "locking " << x << "\n";*/ \
|
||||
@ -53,7 +58,6 @@
|
||||
err = snd_pcm_writei(handle, context->buffer->BufferPointer(), samples); \
|
||||
if (err < 0) { PRINT_ERROR(err); }
|
||||
|
||||
|
||||
static inline bool playable(snd_pcm_t* pcm) {
|
||||
if (!pcm) {
|
||||
return false;
|
||||
@ -73,6 +77,61 @@ static inline bool playable(snd_pcm_t* pcm) {
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
class AlsaDevice : public IDevice {
|
||||
public:
|
||||
AlsaDevice(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
virtual void Destroy() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return name.c_str();
|
||||
}
|
||||
|
||||
virtual const char* Id() const override {
|
||||
return id.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name, id;
|
||||
};
|
||||
|
||||
class AlsaDeviceList : public musik::core::sdk::IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual size_t Count() const override {
|
||||
return devices.size();
|
||||
}
|
||||
|
||||
virtual const IDevice* At(size_t index) const override {
|
||||
return &devices.at(index);
|
||||
}
|
||||
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(AlsaDevice(id, name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<AlsaDevice> devices;
|
||||
};
|
||||
|
||||
extern "C" void SetPreferences(musik::core::sdk::IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
return getPreferenceString<std::string>(prefs, PREF_DEVICE_ID, "");
|
||||
}
|
||||
|
||||
AlsaOut::AlsaOut()
|
||||
: pcmHandle(nullptr)
|
||||
, device("default")
|
||||
@ -114,11 +173,78 @@ void AlsaOut::CloseDevice() {
|
||||
}
|
||||
}
|
||||
|
||||
musik::core::sdk::IDevice* AlsaOut::GetDefaultDevice() {
|
||||
return findDeviceById<AlsaDevice, IOutput>(this, getDeviceId());
|
||||
}
|
||||
|
||||
bool AlsaOut::SetDefaultDevice(const char* deviceId) {
|
||||
return setDefaultDevice<IPreferences, AlsaDevice, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
|
||||
}
|
||||
|
||||
IDeviceList* AlsaOut::GetDeviceList() {
|
||||
AlsaDeviceList* result = new AlsaDeviceList();
|
||||
|
||||
/* https://stackoverflow.com/a/6870226 */
|
||||
char** hints;
|
||||
if (snd_device_name_hint(-1, "pcm", (void***)&hints) == 0) {
|
||||
char** n = hints;
|
||||
while (*n != nullptr) {
|
||||
char *name = snd_device_name_get_hint(*n, "NAME");
|
||||
if (name) {
|
||||
std::string stdName = name;
|
||||
if (stdName != "default") {
|
||||
result->Add(stdName, stdName);
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
++n;
|
||||
}
|
||||
|
||||
snd_device_name_free_hint((void**) hints);
|
||||
}
|
||||
|
||||
size_t n = result->Count();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string AlsaOut::GetPreferredDeviceId() {
|
||||
std::string result;
|
||||
|
||||
if (prefs) {
|
||||
std::string storedDeviceId = getDeviceId();
|
||||
|
||||
auto deviceList = GetDeviceList();
|
||||
if (deviceList) {
|
||||
for (size_t i = 0; i < deviceList->Count(); i++) {
|
||||
if (deviceList->At(i)->Id() == storedDeviceId) {
|
||||
result = storedDeviceId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
deviceList->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AlsaOut::InitDevice() {
|
||||
int err, dir;
|
||||
unsigned int rate = (unsigned int) this->rate;
|
||||
|
||||
if ((err = snd_pcm_open(&this->pcmHandle, this->device.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
||||
std::string preferredDeviceId = this->GetPreferredDeviceId();
|
||||
bool preferredOk = false;
|
||||
|
||||
if (preferredDeviceId.size() > 0) {
|
||||
if ((err = snd_pcm_open(&this->pcmHandle, preferredDeviceId.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
||||
std::cerr << "AlsaOut: cannot opened preferred device id " << preferredDeviceId << ": " << snd_strerror(err) << std::endl;
|
||||
}
|
||||
else {
|
||||
preferredOk = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!preferredOk && (err = snd_pcm_open(&this->pcmHandle, this->device.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
||||
std::cerr << "AlsaOut: cannot open audio device 'default' :" << snd_strerror(err) << std::endl;
|
||||
goto error;
|
||||
}
|
||||
|
@ -39,6 +39,8 @@
|
||||
#include <boost/thread/thread.hpp>
|
||||
|
||||
#include <core/sdk/IOutput.h>
|
||||
#include <core/sdk/IDevice.h>
|
||||
|
||||
#include <boost/thread/recursive_mutex.hpp>
|
||||
#include <boost/thread/condition.hpp>
|
||||
#include <list>
|
||||
@ -49,21 +51,25 @@ class AlsaOut : public musik::core::sdk::IOutput {
|
||||
virtual ~AlsaOut();
|
||||
|
||||
/* IPlugin */
|
||||
virtual const char* Name() { return "AlsaOut"; }
|
||||
virtual const char* Name() override { return "AlsaOut"; }
|
||||
|
||||
/* IOutput */
|
||||
virtual void Destroy();
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual double Latency();
|
||||
virtual void Drain();
|
||||
virtual void Destroy() override;
|
||||
virtual void Pause() override ;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual double Latency() override;
|
||||
virtual void Drain() override;
|
||||
|
||||
virtual int Play(
|
||||
musik::core::sdk::IBuffer *buffer,
|
||||
musik::core::sdk::IBufferProvider *provider);
|
||||
musik::core::sdk::IBufferProvider *provider) override;
|
||||
|
||||
virtual musik::core::sdk::IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual musik::core::sdk::IDevice* GetDefaultDevice() override;
|
||||
|
||||
private:
|
||||
struct BufferContext {
|
||||
@ -76,6 +82,7 @@ class AlsaOut : public musik::core::sdk::IOutput {
|
||||
void InitDevice();
|
||||
void CloseDevice();
|
||||
void WriteLoop();
|
||||
std::string GetPreferredDeviceId();
|
||||
|
||||
std::string device;
|
||||
snd_pcm_t* pcmHandle;
|
||||
|
@ -44,7 +44,7 @@ class AlsaPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "AlsaOut IOutput"; }
|
||||
virtual const char* Version() { return "0.3"; }
|
||||
virtual const char* Version() { return "0.5.0"; }
|
||||
virtual const char* Author() { return "Julian Cromarty, clangen"; }
|
||||
virtual const char* Guid() { return "668a75e6-1816-4c75-a361-a9d48906f23f"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
@ -34,12 +34,56 @@
|
||||
|
||||
#include "CoreAudioOut.h"
|
||||
#include <core/sdk/constants.h>
|
||||
#include <core/sdk/IPreferences.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#define BUFFER_COUNT 24
|
||||
#define PREF_DEVICE_ID "device_id"
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
class CoreAudioDevice : public IDevice {
|
||||
public:
|
||||
CoreAudioDevice(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() const { return name.c_str(); }
|
||||
virtual const char* Id() const { return id.c_str(); }
|
||||
|
||||
private:
|
||||
std::string name, id;
|
||||
};
|
||||
|
||||
class CoreAudioDeviceList : public IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual size_t Count() const { return devices.size(); }
|
||||
virtual const IDevice* At(size_t index) const { return &devices.at(index); }
|
||||
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(CoreAudioDevice(id, name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<CoreAudioDevice> devices;
|
||||
};
|
||||
|
||||
static musik::core::sdk::IPreferences* prefs = nullptr;
|
||||
|
||||
extern "C" void SetPreferences(musik::core::sdk::IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
return getPreferenceString<std::string>(prefs, PREF_DEVICE_ID, "");
|
||||
}
|
||||
|
||||
void audioCallback(void *customData, AudioQueueRef queue, AudioQueueBufferRef buffer) {
|
||||
CoreAudioOut* output = (CoreAudioOut *) customData;
|
||||
CoreAudioOut::BufferContext* context = (CoreAudioOut::BufferContext *) buffer->mUserData;
|
||||
@ -117,6 +161,7 @@ int CoreAudioOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
this->Stop();
|
||||
lock.lock();
|
||||
|
||||
/* create the queue */
|
||||
result = AudioQueueNewOutput(
|
||||
&this->audioFormat,
|
||||
audioCallback,
|
||||
@ -131,6 +176,31 @@ int CoreAudioOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
return OutputInvalidState;
|
||||
}
|
||||
|
||||
/* after the queue is created, but before it's started, let's make
|
||||
sure the correct output device is selected */
|
||||
auto device = this->GetDefaultDevice();
|
||||
if (device) {
|
||||
std::string deviceId = device->Id();
|
||||
if (deviceId.c_str()) {
|
||||
CFStringRef deviceUid = CFStringCreateWithBytes(
|
||||
kCFAllocatorDefault,
|
||||
(const UInt8*) deviceId.c_str(),
|
||||
deviceId.size(),
|
||||
kCFStringEncodingUTF8,
|
||||
false);
|
||||
|
||||
AudioQueueSetProperty(
|
||||
this->audioQueue,
|
||||
kAudioQueueProperty_CurrentDevice,
|
||||
&deviceUid,
|
||||
sizeof(deviceUid));
|
||||
|
||||
CFRelease(deviceUid);
|
||||
}
|
||||
device->Destroy();
|
||||
}
|
||||
|
||||
/* get it running! */
|
||||
result = AudioQueueStart(this->audioQueue, nullptr);
|
||||
|
||||
this->SetVolume(volume);
|
||||
@ -257,3 +327,91 @@ void CoreAudioOut::Stop() {
|
||||
AudioQueueDispose(queue, true);
|
||||
}
|
||||
}
|
||||
|
||||
IDeviceList* CoreAudioOut::GetDeviceList() {
|
||||
CoreAudioDeviceList* result = new CoreAudioDeviceList();
|
||||
|
||||
AudioObjectPropertyAddress address = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 propsize;
|
||||
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, NULL, &propsize) == 0) {
|
||||
int deviceCount = propsize / sizeof(AudioDeviceID);
|
||||
AudioDeviceID *deviceIds = new AudioDeviceID[deviceCount];
|
||||
|
||||
char buffer[2048];
|
||||
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &propsize, deviceIds) == 0) {
|
||||
for (int i = 0; i < deviceCount; ++i) {
|
||||
auto deviceId = deviceIds[i];
|
||||
|
||||
AudioObjectPropertyAddress outputAddress = {
|
||||
kAudioDevicePropertyStreamConfiguration,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
0
|
||||
};
|
||||
|
||||
/* see if this device has output channels. if it doesn't, it's a device
|
||||
that can only do input */
|
||||
size_t outputChannels = 0;
|
||||
if (AudioObjectGetPropertyDataSize(deviceId, &outputAddress, 0, NULL, &propsize) == 0) {
|
||||
AudioBufferList *bufferList = (AudioBufferList *) malloc(propsize);
|
||||
if (AudioObjectGetPropertyData(deviceId, &outputAddress, 0, NULL, &propsize, bufferList) == 0) {
|
||||
for (UInt32 j = 0; j < bufferList->mNumberBuffers; ++j) {
|
||||
outputChannels += bufferList->mBuffers[j].mNumberChannels;
|
||||
}
|
||||
}
|
||||
free(bufferList);
|
||||
}
|
||||
|
||||
if (outputChannels > 0) { /* device has an output! */
|
||||
std::string deviceNameStr, deviceIdStr;
|
||||
|
||||
/* get the device name */
|
||||
|
||||
AudioObjectPropertyAddress nameAddress = {
|
||||
kAudioDevicePropertyDeviceName,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
0
|
||||
};
|
||||
|
||||
UInt32 maxLength = 2048;
|
||||
if (AudioObjectGetPropertyData(deviceId, &nameAddress, 0, NULL, &maxLength, buffer) == 0) {
|
||||
deviceNameStr = buffer;
|
||||
}
|
||||
|
||||
/* get the device id */
|
||||
|
||||
AudioObjectPropertyAddress uidAddress = {
|
||||
kAudioDevicePropertyDeviceUID,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
CFStringRef deviceUid;
|
||||
if (AudioObjectGetPropertyData(deviceId, &uidAddress, 0, NULL, &propsize, &deviceUid) == 0) {
|
||||
deviceIdStr = CFStringGetCStringPtr(deviceUid, kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
if (deviceNameStr.size() && deviceIdStr.size()) {
|
||||
result->Add(deviceIdStr, deviceNameStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] deviceIds;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CoreAudioOut::SetDefaultDevice(const char* deviceId) {
|
||||
return setDefaultDevice<IPreferences, CoreAudioDevice, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
|
||||
}
|
||||
|
||||
IDevice* CoreAudioOut::GetDefaultDevice() {
|
||||
return findDeviceById<CoreAudioDevice, IOutput>(this, getDeviceId());
|
||||
}
|
@ -41,6 +41,8 @@
|
||||
#include <AudioToolbox/AudioQueue.h>
|
||||
#include <CoreAudio/CoreAudioTypes.h>
|
||||
#include <CoreFoundation/CFRunLoop.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
class CoreAudioOut : public musik::core::sdk::IOutput {
|
||||
public:
|
||||
@ -53,21 +55,25 @@ class CoreAudioOut : public musik::core::sdk::IOutput {
|
||||
virtual ~CoreAudioOut();
|
||||
|
||||
/* IPlugin */
|
||||
virtual const char* Name() { return "CoreAudio"; }
|
||||
virtual const char* Name() override { return "CoreAudio"; }
|
||||
|
||||
/* IOutput */
|
||||
virtual void Destroy();
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual double Latency() { return 0.0; }
|
||||
virtual void Drain();
|
||||
virtual void Destroy() override;
|
||||
virtual void Pause() override;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual double Latency() override { return 0.0; }
|
||||
virtual void Drain() override;
|
||||
|
||||
virtual int Play(
|
||||
musik::core::sdk::IBuffer *buffer,
|
||||
musik::core::sdk::IBufferProvider *provider);
|
||||
musik::core::sdk::IBufferProvider *provider) override;
|
||||
|
||||
virtual musik::core::sdk::IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual musik::core::sdk::IDevice* GetDefaultDevice() override;
|
||||
|
||||
void NotifyBufferCompleted(BufferContext *context);
|
||||
|
||||
|
@ -42,7 +42,7 @@ class CoreAudioPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "CoreAudio IOutput"; }
|
||||
virtual const char* Version() { return "0.3"; }
|
||||
virtual const char* Version() { return "0.5.0"; }
|
||||
virtual const char* Author() { return "clangen"; }
|
||||
virtual const char* Guid() { return "7277a19f-a5f7-4123-ac2d-c36273097b72"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
@ -35,11 +35,62 @@
|
||||
#include "DirectSoundOut.h"
|
||||
|
||||
#include <core/sdk/constants.h>
|
||||
#include <core/sdk/IDevice.h>
|
||||
#include <core/sdk/IPreferences.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#define MAX_BUFFERS_PER_OUTPUT 16
|
||||
#define READ_CURSOR_INITIAL_OFFSET 128
|
||||
#define DEVICE_ID "device_id"
|
||||
|
||||
#define BUFFER_SIZE_BYTES_PER_CHANNEL \
|
||||
(2048 * sizeof(float) * MAX_BUFFERS_PER_OUTPUT)
|
||||
|
||||
musik::core::sdk::IPreferences* prefs = nullptr;
|
||||
|
||||
extern "C" __declspec(dllexport) void SetPreferences(musik::core::sdk::IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetString(DEVICE_ID, nullptr, 0, "");
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
return getPreferenceString<std::string>(prefs, DEVICE_ID, "");
|
||||
}
|
||||
|
||||
class DxDevice : public musik::core::sdk::IDevice {
|
||||
public:
|
||||
DxDevice(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
virtual void Destroy() override { delete this; }
|
||||
virtual const char* Name() const override { return name.c_str(); }
|
||||
virtual const char* Id() const override { return id.c_str(); }
|
||||
|
||||
private:
|
||||
std::string name, id;
|
||||
};
|
||||
|
||||
class DxDeviceList : public musik::core::sdk::IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual size_t Count() const override { return devices.size(); }
|
||||
virtual const IDevice* At(size_t index) const override { return &devices.at(index); }
|
||||
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(DxDevice(id, name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<DxDevice> devices;
|
||||
};
|
||||
|
||||
class DrainBuffer :
|
||||
public musik::core::sdk::IBuffer,
|
||||
@ -77,12 +128,6 @@ class DrainBuffer :
|
||||
float *buffer;
|
||||
};
|
||||
|
||||
#define MAX_BUFFERS_PER_OUTPUT 16
|
||||
#define READ_CURSOR_INITIAL_OFFSET 128
|
||||
|
||||
#define BUFFER_SIZE_BYTES_PER_CHANNEL \
|
||||
(2048 * sizeof(float) * MAX_BUFFERS_PER_OUTPUT)
|
||||
|
||||
using Lock = std::unique_lock<std::recursive_mutex>;
|
||||
|
||||
inline DWORD getAvailableBytes(
|
||||
@ -189,6 +234,42 @@ void DirectSoundOut::Stop() {
|
||||
this->state = StateStopped;
|
||||
}
|
||||
|
||||
static inline std::string utf16to8(const wchar_t* utf16) {
|
||||
if (!utf16) return "";
|
||||
int size = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, 0, 0, 0, 0);
|
||||
char* buffer = new char[size];
|
||||
WideCharToMultiByte(CP_UTF8, 0, utf16, -1, buffer, size, 0, 0);
|
||||
std::string utf8str(buffer);
|
||||
delete[] buffer;
|
||||
return utf8str;
|
||||
}
|
||||
|
||||
static inline std::wstring utf8to16(const char* utf8) {
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, 0, 0);
|
||||
wchar_t* buffer = new wchar_t[size];
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, buffer, size);
|
||||
std::wstring utf16fn(buffer);
|
||||
delete[] buffer;
|
||||
return utf16fn;
|
||||
}
|
||||
|
||||
static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCWSTR description, LPCWSTR module, LPVOID context) {
|
||||
DxDeviceList* list = static_cast<DxDeviceList*>(context);
|
||||
|
||||
std::string utf8Id = "";
|
||||
std::string utf8Desc = utf16to8(description);
|
||||
|
||||
if (lpGuid) {
|
||||
OLECHAR* guidString;
|
||||
StringFromCLSID(*lpGuid, &guidString);
|
||||
utf8Id = utf16to8(guidString);
|
||||
CoTaskMemFree(guidString);
|
||||
list->Add(utf8Id, utf8Desc);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DirectSoundOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
Lock lock(this->stateMutex);
|
||||
|
||||
@ -329,7 +410,51 @@ void DirectSoundOut::ResetBuffers() {
|
||||
}
|
||||
|
||||
double DirectSoundOut::Latency() {
|
||||
return (double) latency;
|
||||
return (double)latency;
|
||||
}
|
||||
|
||||
IDeviceList* DirectSoundOut::GetDeviceList() {
|
||||
DxDeviceList* list = new DxDeviceList();
|
||||
DirectSoundEnumerate((LPDSENUMCALLBACKW)DSEnumCallback, (LPVOID)list);
|
||||
return list;
|
||||
}
|
||||
|
||||
bool DirectSoundOut::SetDefaultDevice(const char* deviceId) {
|
||||
return setDefaultDevice<IPreferences, DxDevice, IOutput>(prefs, this, DEVICE_ID, deviceId);
|
||||
}
|
||||
|
||||
IDevice* DirectSoundOut::GetDefaultDevice() {
|
||||
return findDeviceById<DxDevice, IOutput>(this, getDeviceId());
|
||||
}
|
||||
|
||||
LPCGUID DirectSoundOut::GetPreferredDeviceId() {
|
||||
GUID* guid = nullptr;
|
||||
|
||||
if (prefs) {
|
||||
std::string storedDeviceId = getDeviceId();
|
||||
auto devices = GetDeviceList();
|
||||
|
||||
/* if we have a stored device id, see if we can find it in the CURRENT
|
||||
devices! otherwise we'll return null for the primary device */
|
||||
if (storedDeviceId.size()) {
|
||||
auto devices = GetDeviceList();
|
||||
for (size_t i = 0; i < devices->Count(); i++) {
|
||||
if (storedDeviceId == devices->At(i)->Id()) {
|
||||
std::wstring guidW = utf8to16(storedDeviceId.c_str());
|
||||
guid = new GUID();
|
||||
HRESULT result = CLSIDFromString(guidW.c_str(), guid);
|
||||
if (result != S_OK) {
|
||||
delete guid;
|
||||
guid = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
devices->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
bool DirectSoundOut::Configure(IBuffer *buffer) {
|
||||
@ -349,7 +474,15 @@ bool DirectSoundOut::Configure(IBuffer *buffer) {
|
||||
HRESULT result;
|
||||
|
||||
if (!this->outputContext) {
|
||||
result = DirectSoundCreate8(nullptr, &this->outputContext, nullptr);
|
||||
/* first, let's try the preferred device */
|
||||
LPCGUID guid = this->GetPreferredDeviceId();
|
||||
result = DirectSoundCreate8(guid, &this->outputContext, nullptr);
|
||||
delete guid;
|
||||
|
||||
/* if it failed, let's output to the default device. */
|
||||
if (result != S_OK) {
|
||||
result = DirectSoundCreate8(nullptr, &this->outputContext, nullptr);
|
||||
}
|
||||
|
||||
if (result != DS_OK) {
|
||||
return false;
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include <dsound.h>
|
||||
|
||||
#include <core/sdk/IOutput.h>
|
||||
#include <core/sdk/IDevice.h>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
@ -61,14 +62,17 @@ class DirectSoundOut : public IOutput {
|
||||
virtual void Destroy();
|
||||
|
||||
/* IOutput */
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency();
|
||||
virtual void Drain();
|
||||
virtual void Pause() override;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider) override;
|
||||
virtual double Latency() override;
|
||||
virtual void Drain() override;
|
||||
virtual IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual IDevice* GetDefaultDevice() override;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
@ -80,6 +84,7 @@ class DirectSoundOut : public IOutput {
|
||||
bool Configure(IBuffer *buffer);
|
||||
void Reset();
|
||||
void ResetBuffers();
|
||||
LPCGUID GetPreferredDeviceId();
|
||||
|
||||
std::atomic<State> state;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class DirectSoundPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "DirectSound IOutput"; };
|
||||
virtual const char* Version() { return "0.4.0"; };
|
||||
virtual const char* Version() { return "0.5.0"; };
|
||||
virtual const char* Author() { return "clangen"; };
|
||||
virtual const char* Guid() { return "731ad687-c52d-47b0-90b4-5483399640b5"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
@ -81,6 +81,18 @@ void NullOut::Drain() {
|
||||
|
||||
}
|
||||
|
||||
IDeviceList* NullOut::GetDeviceList() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NullOut::SetDefaultDevice(const char* deviceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IDevice* NullOut::GetDefaultDevice() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int NullOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
if (this->state == StatePaused) {
|
||||
return OutputInvalidState;
|
||||
|
@ -45,18 +45,21 @@ class NullOut : public IOutput {
|
||||
~NullOut();
|
||||
|
||||
/* IPlugin */
|
||||
virtual const char* Name() { return "Null"; };
|
||||
virtual void Destroy();
|
||||
virtual const char* Name() override { return "Null"; };
|
||||
virtual void Destroy() override;
|
||||
|
||||
/* IOutput */
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency();
|
||||
virtual void Drain();
|
||||
virtual void Pause() override;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider) override;
|
||||
virtual double Latency() override;
|
||||
virtual void Drain() override;
|
||||
virtual IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual IDevice* GetDefaultDevice() override;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
|
@ -48,7 +48,7 @@ class NullPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "Null IOutput"; }
|
||||
virtual const char* Version() { return "0.1.0"; }
|
||||
virtual const char* Version() { return "0.2.0"; }
|
||||
virtual const char* Author() { return "clangen"; }
|
||||
virtual const char* Guid() { return "0d45a986-24f1-4253-9fc2-b432353a1eea"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
@ -36,16 +36,89 @@
|
||||
#include <core/sdk/constants.h>
|
||||
#include <core/sdk/IPreferences.h>
|
||||
#include <pulse/volume.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <pulse/thread-mainloop.h>
|
||||
#include <math.h>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
typedef std::unique_lock<std::recursive_mutex> Lock;
|
||||
typedef musik::core::sdk::IOutput IOutput;
|
||||
|
||||
static musik::core::sdk::IPreferences* prefs = nullptr;
|
||||
|
||||
#define PREF_FORCE_LINEAR_VOLUME "force_linear_volume"
|
||||
#define PREF_DEVICE_ID "device_id"
|
||||
|
||||
class PulseDevice : public musik::core::sdk::IDevice {
|
||||
public:
|
||||
PulseDevice(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
virtual void Destroy() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const char* Name() const {
|
||||
return name.c_str();
|
||||
}
|
||||
|
||||
virtual const char* Id() const {
|
||||
return id.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name, id;
|
||||
};
|
||||
|
||||
class PulseDeviceList : public musik::core::sdk::IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual size_t Count() const {
|
||||
return devices.size();
|
||||
}
|
||||
|
||||
virtual const IDevice* At(size_t index) const {
|
||||
return &devices.at(index);
|
||||
}
|
||||
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(PulseDevice(id, name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<PulseDevice> devices;
|
||||
};
|
||||
|
||||
struct DeviceListContext {
|
||||
pa_threaded_mainloop* mainLoop;
|
||||
PulseDeviceList* devices;
|
||||
};
|
||||
|
||||
static void deviceEnumerator(pa_context* context, const pa_sink_info* info, int eol, void* userdata) {
|
||||
DeviceListContext* deviceListContext = (DeviceListContext*) userdata;
|
||||
if (info) {
|
||||
deviceListContext->devices->Add(info->name, info->description);
|
||||
}
|
||||
|
||||
if (eol) {
|
||||
pa_threaded_mainloop_signal(deviceListContext->mainLoop, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
return getPreferenceString<std::string>(prefs, PREF_DEVICE_ID, "");
|
||||
}
|
||||
|
||||
extern "C" void SetPreferences(musik::core::sdk::IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetBool("force_linear_volume", false);
|
||||
prefs->GetBool(PREF_FORCE_LINEAR_VOLUME, false);
|
||||
prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
@ -89,6 +162,81 @@ void PulseOut::Drain() {
|
||||
}
|
||||
}
|
||||
|
||||
musik::core::sdk::IDevice* PulseOut::GetDefaultDevice() {
|
||||
return findDeviceById<PulseDevice, IOutput>(this, getDeviceId());
|
||||
}
|
||||
|
||||
bool PulseOut::SetDefaultDevice(const char* deviceId) {
|
||||
return setDefaultDevice<IPreferences, PulseDevice, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
|
||||
}
|
||||
|
||||
musik::core::sdk::IDeviceList* PulseOut::GetDeviceList() {
|
||||
PulseDeviceList* result = new PulseDeviceList();
|
||||
|
||||
auto mainLoop = pa_threaded_mainloop_new();
|
||||
if (mainLoop) {
|
||||
auto context = pa_context_new(pa_threaded_mainloop_get_api(mainLoop), "musikcube");
|
||||
if (context) {
|
||||
if (pa_context_connect(context, nullptr, (pa_context_flags_t) 0, nullptr) >= 0) {
|
||||
if (pa_threaded_mainloop_start(mainLoop) >= 0) {
|
||||
bool contextOk = false;
|
||||
for (;;) {
|
||||
pa_context_state_t state;
|
||||
state = pa_context_get_state(context);
|
||||
|
||||
if (state == PA_CONTEXT_READY) {
|
||||
contextOk = true;
|
||||
break;
|
||||
}
|
||||
else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
|
||||
break;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_wait(mainLoop);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(mainLoop);
|
||||
|
||||
if (contextOk) {
|
||||
DeviceListContext dlc;
|
||||
dlc.mainLoop = mainLoop;
|
||||
dlc.devices = result;
|
||||
|
||||
auto op = pa_context_get_sink_info_list(context, deviceEnumerator, (void*) &dlc);
|
||||
if (op) {
|
||||
while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
|
||||
pa_threaded_mainloop_wait(mainLoop);
|
||||
}
|
||||
|
||||
pa_operation_unref(op);
|
||||
}
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(mainLoop);
|
||||
}
|
||||
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
}
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_stop(mainLoop);
|
||||
pa_threaded_mainloop_free(mainLoop);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PulseOut::GetPreferredDeviceId() {
|
||||
std::string deviceId = getDeviceId();
|
||||
auto device = findDeviceById<PulseDevice>(this, deviceId);
|
||||
if (device) {
|
||||
device->Destroy();
|
||||
return deviceId;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void PulseOut::OpenDevice(musik::core::sdk::IBuffer* buffer) {
|
||||
if (!this->audioConnection ||
|
||||
this->rate != buffer->SampleRate() ||
|
||||
@ -102,22 +250,40 @@ void PulseOut::OpenDevice(musik::core::sdk::IBuffer* buffer) {
|
||||
spec.rate = buffer->SampleRate();
|
||||
|
||||
std::cerr << "PulseOut: opening device\n";
|
||||
|
||||
std::string deviceId = this->GetPreferredDeviceId();
|
||||
|
||||
/* output to preferred device id, as specified in prefs */
|
||||
this->audioConnection = pa_blocking_new(
|
||||
nullptr,
|
||||
"musikbox",
|
||||
"musikcube",
|
||||
PA_STREAM_PLAYBACK,
|
||||
nullptr,
|
||||
deviceId.size() ? deviceId.c_str() : nullptr,
|
||||
"music",
|
||||
&spec,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
if (!this->audioConnection && deviceId.size()) {
|
||||
/* fall back to default if preferred is not found */
|
||||
this->audioConnection = pa_blocking_new(
|
||||
nullptr,
|
||||
"musikcube",
|
||||
PA_STREAM_PLAYBACK,
|
||||
nullptr,
|
||||
"music",
|
||||
&spec,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
}
|
||||
|
||||
if (this->audioConnection) {
|
||||
this->rate = buffer->SampleRate();
|
||||
this->channels = buffer->Channels();
|
||||
this->state = StatePlaying;
|
||||
this->linearVolume = ::prefs->GetBool("force_linear_volume", false);
|
||||
this->linearVolume = ::prefs->GetBool(PREF_FORCE_LINEAR_VOLUME, false);
|
||||
this->SetVolume(this->volume);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <core/sdk/IOutput.h>
|
||||
#include <core/sdk/IDevice.h>
|
||||
|
||||
#include <mutex>
|
||||
#include "pulse_blocking_stream.h"
|
||||
|
||||
@ -45,21 +47,25 @@ class PulseOut : public musik::core::sdk::IOutput {
|
||||
virtual ~PulseOut();
|
||||
|
||||
/* IPlugin */
|
||||
virtual const char* Name() { return "PulseAudio"; }
|
||||
virtual const char* Name() override { return "PulseAudio"; }
|
||||
|
||||
/* IOutput */
|
||||
virtual void Destroy();
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual double Latency();
|
||||
virtual void Drain();
|
||||
virtual void Destroy() override;
|
||||
virtual void Pause() override;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual double Latency() override;
|
||||
virtual void Drain() override;
|
||||
|
||||
virtual int Play(
|
||||
musik::core::sdk::IBuffer *buffer,
|
||||
musik::core::sdk::IBufferProvider *provider);
|
||||
musik::core::sdk::IBufferProvider *provider) override;
|
||||
|
||||
virtual musik::core::sdk::IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual musik::core::sdk::IDevice* GetDefaultDevice() override;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
@ -70,6 +76,7 @@ class PulseOut : public musik::core::sdk::IOutput {
|
||||
|
||||
void OpenDevice(musik::core::sdk::IBuffer *buffer);
|
||||
void CloseDevice();
|
||||
std::string GetPreferredDeviceId();
|
||||
|
||||
std::recursive_mutex stateMutex;
|
||||
pa_blocking* audioConnection;
|
||||
|
@ -44,7 +44,7 @@ class PulsePlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "PulseAudio IOutput"; }
|
||||
virtual const char* Version() { return "0.4.0"; }
|
||||
virtual const char* Version() { return "0.5.0"; }
|
||||
virtual const char* Author() { return "clangen"; }
|
||||
virtual const char* Guid() { return "67c7e90b-5123-41c0-b03a-838ecd6cb8b5"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
@ -36,13 +36,18 @@
|
||||
#include <core/sdk/constants.h>
|
||||
#include <core/sdk/IPreferences.h>
|
||||
#include <AudioSessionTypes.h>
|
||||
#include <Functiondiscoverykeys_devpkey.h>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#define MAX_BUFFERS_PER_OUTPUT 16
|
||||
|
||||
#define PREF_DEVICE_ID "device_id"
|
||||
#define PREF_ENDPOINT_ROUTING "enable_audio_endpoint_routing"
|
||||
|
||||
/* NOTE! device init and deinit logic was stolen and modified from
|
||||
QMMP's WASAPI output plugin! http://qmmp.ylsoftware.com/ */
|
||||
|
||||
@ -53,12 +58,74 @@ QMMP's WASAPI output plugin! http://qmmp.ylsoftware.com/ */
|
||||
using Lock = std::unique_lock<std::recursive_mutex>;
|
||||
musik::core::sdk::IPreferences* prefs = nullptr;
|
||||
|
||||
static inline std::string utf16to8(const wchar_t* utf16) {
|
||||
if (!utf16) return "";
|
||||
int size = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, 0, 0, 0, 0);
|
||||
char* buffer = new char[size];
|
||||
WideCharToMultiByte(CP_UTF8, 0, utf16, -1, buffer, size, 0, 0);
|
||||
std::string utf8str(buffer);
|
||||
delete[] buffer;
|
||||
return utf8str;
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
return getPreferenceString<std::string>(prefs, PREF_DEVICE_ID, "");;
|
||||
}
|
||||
|
||||
class WasapiDevice : public musik::core::sdk::IDevice {
|
||||
public:
|
||||
WasapiDevice(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
virtual void Destroy() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return name.c_str();
|
||||
}
|
||||
|
||||
virtual const char* Id() const override {
|
||||
return id.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name, id;
|
||||
};
|
||||
|
||||
class WasapiDeviceList : public musik::core::sdk::IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual size_t Count() const override {
|
||||
return devices.size();
|
||||
}
|
||||
|
||||
virtual const IDevice* At(size_t index) const override {
|
||||
return &devices.at(index);
|
||||
}
|
||||
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(WasapiDevice(id, name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<WasapiDevice> devices;
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) void SetPreferences(musik::core::sdk::IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
|
||||
prefs->GetBool(PREF_ENDPOINT_ROUTING, false);
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
static bool audioRoutingEnabled() {
|
||||
return ::prefs && prefs->GetBool("enable_audio_endpoint_routing", false);
|
||||
return ::prefs && prefs->GetBool(PREF_ENDPOINT_ROUTING, false);
|
||||
}
|
||||
|
||||
class NotificationClient : public IMMNotificationClient {
|
||||
@ -341,6 +408,121 @@ double WasapiOut::Latency() {
|
||||
return this->latency;
|
||||
}
|
||||
|
||||
bool WasapiOut::SetDefaultDevice(const char* deviceId) {
|
||||
return setDefaultDevice<IPreferences, WasapiDevice, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
|
||||
}
|
||||
|
||||
IDevice* WasapiOut::GetDefaultDevice() {
|
||||
return findDeviceById<WasapiDevice, IOutput>(this, getDeviceId());
|
||||
}
|
||||
|
||||
IDeviceList* WasapiOut::GetDeviceList() {
|
||||
WasapiDeviceList* result = new WasapiDeviceList();
|
||||
IMMDeviceEnumerator *deviceEnumerator = nullptr;
|
||||
IMMDeviceCollection *deviceCollection = nullptr;
|
||||
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
HRESULT hr = CoCreateInstance(
|
||||
__uuidof(MMDeviceEnumerator),
|
||||
NULL,
|
||||
CLSCTX_ALL,
|
||||
__uuidof(IMMDeviceEnumerator),
|
||||
(void**) &deviceEnumerator);
|
||||
|
||||
if (hr == S_OK) {
|
||||
hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
if (hr == S_OK) {
|
||||
UINT deviceCount = 0;
|
||||
if (deviceCollection->GetCount(&deviceCount) == S_OK) {
|
||||
for (UINT i = 0; i < deviceCount; i++) {
|
||||
IMMDevice* device = nullptr;
|
||||
LPWSTR deviceIdPtr;
|
||||
std::string deviceId, deviceName;
|
||||
|
||||
hr = deviceCollection->Item(i, &device);
|
||||
if (hr == S_OK) {
|
||||
if (device->GetId(&deviceIdPtr) == S_OK) {
|
||||
deviceId = utf16to8(deviceIdPtr);
|
||||
CoTaskMemFree(deviceIdPtr);
|
||||
}
|
||||
|
||||
IPropertyStore *propertyStore;
|
||||
if (device->OpenPropertyStore(STGM_READ, &propertyStore) == S_OK) {
|
||||
PROPVARIANT friendlyName;
|
||||
PropVariantInit(&friendlyName);
|
||||
|
||||
if (propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName) == S_OK) {
|
||||
deviceName = utf16to8(friendlyName.pwszVal);
|
||||
PropVariantClear(&friendlyName);
|
||||
}
|
||||
|
||||
propertyStore->Release();
|
||||
}
|
||||
|
||||
if (deviceId.size() || deviceName.size()) {
|
||||
result->Add(deviceId, deviceName);
|
||||
}
|
||||
|
||||
device->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviceCollection->Release();
|
||||
}
|
||||
|
||||
deviceEnumerator->Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
IMMDevice* WasapiOut::GetPreferredDevice() {
|
||||
IMMDevice* result = nullptr;
|
||||
|
||||
std::string storedDeviceId = getDeviceId();
|
||||
if (storedDeviceId.size() > 0) {
|
||||
IMMDeviceCollection *deviceCollection = nullptr;
|
||||
|
||||
if (this->enumerator) {
|
||||
HRESULT hr = this->enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
if (hr == S_OK) {
|
||||
UINT deviceCount = 0;
|
||||
if (deviceCollection->GetCount(&deviceCount) == S_OK) {
|
||||
for (UINT i = 0; i < deviceCount; i++) {
|
||||
IMMDevice* device = nullptr;
|
||||
LPWSTR deviceIdPtr;
|
||||
std::string deviceId, deviceName;
|
||||
|
||||
hr = deviceCollection->Item(i, &device);
|
||||
if (hr == S_OK) {
|
||||
if (device->GetId(&deviceIdPtr) == S_OK) {
|
||||
if (storedDeviceId == utf16to8(deviceIdPtr)) {
|
||||
result = device;
|
||||
}
|
||||
|
||||
CoTaskMemFree(deviceIdPtr);
|
||||
|
||||
if (result == device) { /* found it! */
|
||||
goto found_or_done;
|
||||
}
|
||||
}
|
||||
|
||||
device->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
found_or_done:
|
||||
deviceCollection->Release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WasapiOut::Configure(IBuffer *buffer) {
|
||||
HRESULT result;
|
||||
|
||||
@ -377,8 +559,24 @@ bool WasapiOut::Configure(IBuffer *buffer) {
|
||||
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
if ((result = this->enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &this->device)) != S_OK) {
|
||||
return false;
|
||||
bool preferredDeviceOk = false;
|
||||
|
||||
IMMDevice* preferredDevice = this->GetPreferredDevice();
|
||||
if (preferredDevice) {
|
||||
if ((result = preferredDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**) &this->audioClient)) == S_OK) {
|
||||
preferredDeviceOk = true;
|
||||
this->device = preferredDevice;
|
||||
}
|
||||
}
|
||||
|
||||
if (!preferredDeviceOk) {
|
||||
if (preferredDevice) {
|
||||
preferredDevice->Release();
|
||||
}
|
||||
|
||||
if ((result = this->enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &this->device)) != S_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((result = this->device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**) &this->audioClient)) != S_OK) {
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include <Audioclient.h>
|
||||
|
||||
#include <core/sdk/IOutput.h>
|
||||
#include <core/sdk/IDevice.h>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
@ -59,14 +60,17 @@ class WasapiOut : public IOutput {
|
||||
virtual void Destroy();
|
||||
|
||||
/* IOutput */
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency();
|
||||
virtual void Drain();
|
||||
virtual void Pause() override;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider) override;
|
||||
virtual double Latency() override;
|
||||
virtual void Drain() override;
|
||||
virtual IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual IDevice* GetDefaultDevice() override;
|
||||
|
||||
void OnDeviceChanged() { this->deviceChanged = true; }
|
||||
|
||||
@ -79,6 +83,7 @@ class WasapiOut : public IOutput {
|
||||
|
||||
bool Configure(IBuffer *buffer);
|
||||
void Reset();
|
||||
IMMDevice* GetPreferredDevice();
|
||||
|
||||
IMMDeviceEnumerator *enumerator;
|
||||
IMMDevice *device;
|
||||
|
@ -43,7 +43,7 @@ class WasapiPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "Wasapi IOutput"; }
|
||||
virtual const char* Version() { return "0.4.0"; }
|
||||
virtual const char* Version() { return "0.5.0"; }
|
||||
virtual const char* Author() { return "clangen"; }
|
||||
virtual const char* Guid() { return "871cb3c2-0002-49cd-9410-5207cb3cfd4a"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
@ -34,13 +34,94 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "WaveOut.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <core/sdk/constants.h>
|
||||
#include <core/sdk/IPreferences.h>
|
||||
|
||||
#define MAX_VOLUME 0xFFFF
|
||||
#define MAX_BUFFERS_PER_OUTPUT 16
|
||||
#define PREF_DEVICE_ID "device_id"
|
||||
|
||||
musik::core::sdk::IPreferences* prefs = nullptr;
|
||||
|
||||
using LockT = std::unique_lock<std::recursive_mutex>;
|
||||
|
||||
class WaveOutDevice : public musik::core::sdk::IDevice {
|
||||
public:
|
||||
WaveOutDevice(const std::string& id, const std::string& name) {
|
||||
this->id = id;
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
virtual void Destroy() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return name.c_str();
|
||||
}
|
||||
|
||||
virtual const char* Id() const override {
|
||||
return id.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name, id;
|
||||
};
|
||||
|
||||
class WaveOutDeviceList : public musik::core::sdk::IDeviceList {
|
||||
public:
|
||||
virtual void Destroy() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual size_t Count() const override {
|
||||
return devices.size();
|
||||
}
|
||||
|
||||
virtual const IDevice* At(size_t index) const override {
|
||||
return &devices.at(index);
|
||||
}
|
||||
|
||||
void Add(const std::string& id, const std::string& name) {
|
||||
devices.push_back(WaveOutDevice(id, name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<WaveOutDevice> devices;
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) void SetPreferences(musik::core::sdk::IPreferences* prefs) {
|
||||
::prefs = prefs;
|
||||
prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
|
||||
prefs->Save();
|
||||
}
|
||||
|
||||
static inline std::string utf16to8(const wchar_t* utf16) {
|
||||
if (!utf16) return "";
|
||||
int size = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, 0, 0, 0, 0);
|
||||
char* buffer = new char[size];
|
||||
WideCharToMultiByte(CP_UTF8, 0, utf16, -1, buffer, size, 0, 0);
|
||||
std::string utf8str(buffer);
|
||||
delete[] buffer;
|
||||
return utf8str;
|
||||
}
|
||||
|
||||
static std::string deviceCapsToId(WAVEOUTCAPS& caps, UINT index) {
|
||||
std::string name = utf16to8(caps.szPname);
|
||||
return std::to_string(index) + ":" + name;
|
||||
}
|
||||
|
||||
static std::string getDeviceId() {
|
||||
char buffer[4096] = { 0 };
|
||||
std::string storedDeviceId;
|
||||
if (prefs && prefs->GetString(PREF_DEVICE_ID, buffer, 4096, "") > 0) {
|
||||
storedDeviceId.assign(buffer);
|
||||
}
|
||||
return storedDeviceId;
|
||||
}
|
||||
|
||||
WaveOut::WaveOut()
|
||||
: waveHandle(NULL)
|
||||
, currentVolume(1.0)
|
||||
@ -254,6 +335,44 @@ int WaveOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
return OutputBufferFull;
|
||||
}
|
||||
|
||||
bool WaveOut::SetDefaultDevice(const char* deviceId) {
|
||||
return setDefaultDevice<IPreferences, WaveOutDevice, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
|
||||
}
|
||||
|
||||
IDevice* WaveOut::GetDefaultDevice() {
|
||||
return findDeviceById<WaveOutDevice, IOutput>(this, getDeviceId());
|
||||
}
|
||||
|
||||
IDeviceList* WaveOut::GetDeviceList() {
|
||||
WaveOutDeviceList* result = new WaveOutDeviceList();
|
||||
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
|
||||
WAVEOUTCAPS caps = { 0 };
|
||||
if (waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
|
||||
std::string name = utf16to8(caps.szPname);
|
||||
std::string id = deviceCapsToId(caps, i);
|
||||
result->Add(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UINT WaveOut::GetPreferredDeviceId() {
|
||||
std::string storedDeviceId = getDeviceId();
|
||||
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
|
||||
WAVEOUTCAPS caps = { 0 };
|
||||
if (waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
|
||||
if (storedDeviceId == deviceCapsToId(caps, i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WAVE_MAPPER;
|
||||
}
|
||||
|
||||
void WaveOut::SetFormat(IBuffer *buffer) {
|
||||
if (this->currentChannels != buffer->Channels() ||
|
||||
this->currentSampleRate != buffer->SampleRate() ||
|
||||
@ -268,7 +387,7 @@ void WaveOut::SetFormat(IBuffer *buffer) {
|
||||
this->StartWaveOutThread();
|
||||
|
||||
/* reset, and configure speaker output */
|
||||
ZeroMemory(&this->waveFormat, sizeof(this->waveFormat));
|
||||
ZeroMemory(&this->waveFormat, sizeof(this->waveFormat));
|
||||
|
||||
DWORD speakerConfig = 0;
|
||||
|
||||
@ -313,7 +432,7 @@ void WaveOut::SetFormat(IBuffer *buffer) {
|
||||
output device, making it impossible to reach this condition. */
|
||||
int openResult = waveOutOpen(
|
||||
&this->waveHandle,
|
||||
WAVE_MAPPER,
|
||||
this->GetPreferredDeviceId(),
|
||||
(WAVEFORMATEX*) &this->waveFormat,
|
||||
this->threadId,
|
||||
(DWORD_PTR) this,
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <mutex>
|
||||
#include "WaveOutBuffer.h"
|
||||
#include <core/sdk/IOutput.h>
|
||||
#include <core/sdk/IDevice.h>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
@ -54,14 +55,17 @@ class WaveOut : public IOutput {
|
||||
virtual void Destroy();
|
||||
|
||||
/* IOutput */
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency() { return 0.0; }
|
||||
virtual void Drain() { }
|
||||
virtual void Pause() override;
|
||||
virtual void Resume() override;
|
||||
virtual void SetVolume(double volume) override;
|
||||
virtual double GetVolume() override;
|
||||
virtual void Stop() override;
|
||||
virtual int Play(IBuffer *buffer, IBufferProvider *provider) override;
|
||||
virtual double Latency() override { return 0.0; }
|
||||
virtual void Drain() override { }
|
||||
virtual IDeviceList* GetDeviceList() override;
|
||||
virtual bool SetDefaultDevice(const char* deviceId) override;
|
||||
virtual IDevice* GetDefaultDevice() override;
|
||||
|
||||
void OnBufferWrittenToOutput(WaveOutBuffer *buffer);
|
||||
|
||||
@ -76,6 +80,8 @@ class WaveOut : public IOutput {
|
||||
void ClearBufferQueue();
|
||||
void NotifyBufferProcessed(WaveOutBufferPtr buffer);
|
||||
|
||||
UINT GetPreferredDeviceId();
|
||||
|
||||
WaveOutBufferPtr GetEmptyBuffer();
|
||||
|
||||
/* note we apparently use a std::list<> here, and not std::set<> because
|
||||
|
@ -41,7 +41,7 @@ class WaveOutPlugin : public musik::core::sdk::IPlugin {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual const char* Name() { return "WaveOut IOutput"; }
|
||||
virtual const char* Version() { return "0.6.0"; }
|
||||
virtual const char* Version() { return "0.7.0"; }
|
||||
virtual const char* Author() { return "Bj\xC3\xB6rn Olievier, clangen"; }
|
||||
virtual const char* Guid() { return "bec5bf30-0208-4db0-af0a-2722d9de8421"; }
|
||||
virtual bool Configurable() { return false; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user