Get audio device names on OS X too.
This commit is contained in:
parent
6d1dc56a7c
commit
2d7be1502f
@ -860,6 +860,7 @@ optional_source(APPLE
|
||||
core/mac_startup.mm
|
||||
core/scoped_nsautorelease_pool.mm
|
||||
devices/macdevicelister.mm
|
||||
engines/osxdevicefinder.cpp
|
||||
networkremote/bonjour.mm
|
||||
ui/globalshortcutgrabber.mm
|
||||
ui/macscreensaver.cpp
|
||||
@ -1302,6 +1303,7 @@ if (APPLE)
|
||||
${GROWL}
|
||||
/System/Library/Frameworks/AppKit.framework
|
||||
/System/Library/Frameworks/Carbon.framework
|
||||
/System/Library/Frameworks/CoreAudio.framework
|
||||
/System/Library/Frameworks/DiskArbitration.framework
|
||||
/System/Library/Frameworks/Foundation.framework
|
||||
/System/Library/Frameworks/IOKit.framework
|
||||
|
@ -20,3 +20,16 @@
|
||||
DeviceFinder::DeviceFinder(const QString& gstreamer_sink)
|
||||
: gstreamer_sink_(gstreamer_sink) {
|
||||
}
|
||||
|
||||
QString DeviceFinder::GuessIconName(const QString& description) const {
|
||||
QString description_lower = description.toLower();
|
||||
if (description_lower.contains("headset")) {
|
||||
return "audio-headset";
|
||||
}
|
||||
|
||||
if (description_lower.contains("headphone")) {
|
||||
return "audio-headphones";
|
||||
}
|
||||
|
||||
return "audio-card";
|
||||
}
|
||||
|
@ -18,15 +18,15 @@
|
||||
#ifndef DEVICEFINDER_H
|
||||
#define DEVICEFINDER_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
|
||||
// Finds audio output devices that can be used with a given gstreamer sink.
|
||||
class DeviceFinder {
|
||||
public:
|
||||
struct Device {
|
||||
// The value to set as the "device" gstreamer property.
|
||||
QString name;
|
||||
QVariant device_property_value;
|
||||
|
||||
// A human readable description of the device.
|
||||
QString description;
|
||||
@ -35,6 +35,8 @@ class DeviceFinder {
|
||||
QString icon_name;
|
||||
};
|
||||
|
||||
virtual ~DeviceFinder() {}
|
||||
|
||||
// The name of the gstreamer sink element that devices found by this class
|
||||
// can be used with.
|
||||
QString gstreamer_sink() const { return gstreamer_sink_; }
|
||||
@ -49,6 +51,8 @@ class DeviceFinder {
|
||||
protected:
|
||||
explicit DeviceFinder(const QString& gstreamer_sink);
|
||||
|
||||
QString GuessIconName(const QString& description) const;
|
||||
|
||||
private:
|
||||
QString gstreamer_sink_;
|
||||
};
|
||||
|
@ -54,6 +54,10 @@
|
||||
#include "engines/pulsedevicefinder.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "engines/osxdevicefinder.h"
|
||||
#endif
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
|
||||
@ -127,6 +131,9 @@ void GstEngine::InitialiseGstreamer() {
|
||||
#ifdef HAVE_LIBPULSE
|
||||
device_finders.append(new PulseDeviceFinder);
|
||||
#endif
|
||||
#ifdef Q_OS_DARWIN
|
||||
device_finders.append(new OsxDeviceFinder);
|
||||
#endif
|
||||
|
||||
for (DeviceFinder* finder : device_finders) {
|
||||
if (!plugin_names.contains(finder->gstreamer_sink())) {
|
||||
@ -153,7 +160,7 @@ void GstEngine::ReloadSettings() {
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
sink_ = s.value("sink", kAutoSink).toString();
|
||||
device_ = s.value("device").toString();
|
||||
device_ = s.value("device");
|
||||
|
||||
if (sink_.isEmpty()) sink_ = kAutoSink;
|
||||
|
||||
@ -831,7 +838,7 @@ GstEngine::OutputDetailsList GstEngine::GetOutputsList() const {
|
||||
output.description = device.description;
|
||||
output.icon_name = device.icon_name;
|
||||
output.gstreamer_plugin_name = finder->gstreamer_sink();
|
||||
output.device_name = device.name;
|
||||
output.device_property_value = device.device_property_value;
|
||||
ret.append(output);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
||||
QString icon_name;
|
||||
|
||||
QString gstreamer_plugin_name;
|
||||
QString device_name;
|
||||
QVariant device_property_value;
|
||||
};
|
||||
typedef QList<OutputDetails> OutputDetailsList;
|
||||
|
||||
@ -181,7 +181,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
||||
QFuture<void> initialising_;
|
||||
|
||||
QString sink_;
|
||||
QString device_;
|
||||
QVariant device_;
|
||||
|
||||
std::shared_ptr<GstEnginePipeline> current_pipeline_;
|
||||
std::shared_ptr<GstEnginePipeline> fadeout_pipeline_;
|
||||
|
@ -94,7 +94,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
|
||||
}
|
||||
|
||||
void GstEnginePipeline::set_output_device(const QString& sink,
|
||||
const QString& device) {
|
||||
const QVariant& device) {
|
||||
sink_ = sink;
|
||||
device_ = device;
|
||||
}
|
||||
@ -221,10 +221,23 @@ bool GstEnginePipeline::Init() {
|
||||
// Create the sink
|
||||
if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_))) return false;
|
||||
|
||||
if (g_object_class_find_property(G_OBJECT_CLASS(audiosink_), "device") &&
|
||||
!device_.isEmpty()) {
|
||||
g_object_set(G_OBJECT(audiosink_), "device", device_.toUtf8().constData(),
|
||||
nullptr);
|
||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device") &&
|
||||
!device_.toString().isEmpty()) {
|
||||
switch (device_.type()) {
|
||||
case QVariant::Int:
|
||||
g_object_set(G_OBJECT(audiosink_),
|
||||
"device", device_.toInt(),
|
||||
nullptr);
|
||||
break;
|
||||
case QVariant::String:
|
||||
g_object_set(G_OBJECT(audiosink_),
|
||||
"device", device_.toString().toUtf8().constData(),
|
||||
nullptr);
|
||||
break;
|
||||
default:
|
||||
qLog(Warning) << "Unknown device type" << device_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create all the other elements
|
||||
|
@ -50,7 +50,7 @@ class GstEnginePipeline : public QObject {
|
||||
int id() const { return id_; }
|
||||
|
||||
// Call these setters before Init
|
||||
void set_output_device(const QString& sink, const QString& device);
|
||||
void set_output_device(const QString& sink, const QVariant& device);
|
||||
void set_replaygain(bool enabled, int mode, float preamp, bool compression);
|
||||
void set_buffer_duration_nanosec(qint64 duration_nanosec);
|
||||
void set_mono_playback(bool enabled);
|
||||
@ -184,7 +184,7 @@ signals:
|
||||
// General settings for the pipeline
|
||||
bool valid_;
|
||||
QString sink_;
|
||||
QString device_;
|
||||
QVariant device_;
|
||||
|
||||
// These get called when there is a new audio buffer available
|
||||
QList<BufferConsumer*> buffer_consumers_;
|
||||
|
115
src/engines/osxdevicefinder.cpp
Normal file
115
src/engines/osxdevicefinder.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <CoreAudio/AudioHardware.h>
|
||||
|
||||
#include "osxdevicefinder.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/scoped_cftyperef.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
std::unique_ptr<T> GetProperty(const AudioDeviceID& device_id,
|
||||
const AudioObjectPropertyAddress& address,
|
||||
UInt32* size_bytes_out = nullptr) {
|
||||
UInt32 size_bytes = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(
|
||||
device_id, &address, 0, NULL, &size_bytes);
|
||||
if (status != kAudioHardwareNoError) {
|
||||
qLog(Warning) << "AudioObjectGetPropertyDataSize failed:" << status;
|
||||
return std::unique_ptr<T>();
|
||||
}
|
||||
|
||||
std::unique_ptr<T> ret(reinterpret_cast<T*>(malloc(size_bytes)));
|
||||
|
||||
status = AudioObjectGetPropertyData(device_id,
|
||||
&address,
|
||||
0,
|
||||
NULL,
|
||||
&size_bytes,
|
||||
ret.get());
|
||||
if (status != kAudioHardwareNoError) {
|
||||
qLog(Warning) << "AudioObjectGetPropertyData failed:" << status;
|
||||
return std::unique_ptr<T>();
|
||||
}
|
||||
|
||||
if (size_bytes_out) {
|
||||
*size_bytes_out = size_bytes;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
OsxDeviceFinder::OsxDeviceFinder()
|
||||
: DeviceFinder("osxaudiosink") {
|
||||
}
|
||||
|
||||
QList<DeviceFinder::Device> OsxDeviceFinder::ListDevices() {
|
||||
QList<Device> ret;
|
||||
|
||||
AudioObjectPropertyAddress address = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 device_size_bytes = 0;
|
||||
std::unique_ptr<AudioDeviceID> devices =
|
||||
GetProperty<AudioDeviceID>(
|
||||
kAudioObjectSystemObject, address, &device_size_bytes);
|
||||
if (!devices.get()) {
|
||||
return ret;
|
||||
}
|
||||
const int device_count = device_size_bytes / sizeof(AudioDeviceID);
|
||||
|
||||
address.mScope = kAudioDevicePropertyScopeOutput;
|
||||
for (UInt32 i = 0; i < device_count; ++i) {
|
||||
const AudioDeviceID id = devices.get()[i];
|
||||
|
||||
// Query device name
|
||||
address.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
std::unique_ptr<CFStringRef> device_name =
|
||||
GetProperty<CFStringRef>(id, address);
|
||||
ScopedCFTypeRef<CFStringRef> scoped_device_name(*device_name.get());
|
||||
if (!device_name.get()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an output device (it is an output device if
|
||||
// it has output channels)
|
||||
address.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
std::unique_ptr<AudioBufferList> buffer_list =
|
||||
GetProperty<AudioBufferList>(id, address);
|
||||
if (!buffer_list.get() || buffer_list->mNumberBuffers == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Device dev;
|
||||
dev.description = QString::fromUtf8(
|
||||
CFStringGetCStringPtr(*device_name, CFStringGetSystemEncoding()));
|
||||
dev.device_property_value = id;
|
||||
dev.icon_name = GuessIconName(dev.description);
|
||||
ret.append(dev);
|
||||
}
|
||||
return ret;
|
||||
}
|
31
src/engines/osxdevicefinder.h
Normal file
31
src/engines/osxdevicefinder.h
Normal file
@ -0,0 +1,31 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OSXDEVICEFINDER_H
|
||||
#define OSXDEVICEFINDER_H
|
||||
|
||||
#include "engines/devicefinder.h"
|
||||
|
||||
class OsxDeviceFinder : public DeviceFinder {
|
||||
public:
|
||||
OsxDeviceFinder();
|
||||
|
||||
virtual bool Initialise() { return true; }
|
||||
virtual QList<Device> ListDevices();
|
||||
};
|
||||
|
||||
#endif // OSXDEVICEFINDER_H
|
@ -109,7 +109,7 @@ void PulseDeviceFinder::GetSinkInfoCallback(pa_context* c,
|
||||
|
||||
if (info) {
|
||||
Device dev;
|
||||
dev.name = QString::fromUtf8(info->name);
|
||||
dev.device_property_value = QString::fromUtf8(info->name);
|
||||
dev.description = QString::fromUtf8(info->description);
|
||||
dev.icon_name = QString::fromUtf8(
|
||||
pa_proplist_gets(info->proplist, "device.icon_name"));
|
||||
|
@ -88,7 +88,7 @@ void PlaybackSettingsPage::Load() {
|
||||
ui_->gst_output->itemData(i).value<GstEngine::OutputDetails>();
|
||||
|
||||
if (details.gstreamer_plugin_name == sink &&
|
||||
details.device_name == device) {
|
||||
details.device_property_value == device) {
|
||||
ui_->gst_output->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
@ -128,7 +128,7 @@ void PlaybackSettingsPage::Save() {
|
||||
|
||||
s.beginGroup(GstEngine::kSettingsGroup);
|
||||
s.setValue("sink", details.gstreamer_plugin_name);
|
||||
s.setValue("device", details.device_name);
|
||||
s.setValue("device", details.device_property_value);
|
||||
s.setValue("rgenabled", ui_->replaygain->isChecked());
|
||||
s.setValue("rgmode", ui_->replaygain_mode->currentIndex());
|
||||
s.setValue("rgpreamp", float(ui_->replaygain_preamp->value()) / 10 - 15);
|
||||
|
Loading…
x
Reference in New Issue
Block a user