Get audio device names on OS X too.

This commit is contained in:
David Sansome 2014-03-29 20:06:33 +11:00
parent 6d1dc56a7c
commit 2d7be1502f
11 changed files with 201 additions and 16 deletions

View File

@ -860,6 +860,7 @@ optional_source(APPLE
core/mac_startup.mm core/mac_startup.mm
core/scoped_nsautorelease_pool.mm core/scoped_nsautorelease_pool.mm
devices/macdevicelister.mm devices/macdevicelister.mm
engines/osxdevicefinder.cpp
networkremote/bonjour.mm networkremote/bonjour.mm
ui/globalshortcutgrabber.mm ui/globalshortcutgrabber.mm
ui/macscreensaver.cpp ui/macscreensaver.cpp
@ -1302,6 +1303,7 @@ if (APPLE)
${GROWL} ${GROWL}
/System/Library/Frameworks/AppKit.framework /System/Library/Frameworks/AppKit.framework
/System/Library/Frameworks/Carbon.framework /System/Library/Frameworks/Carbon.framework
/System/Library/Frameworks/CoreAudio.framework
/System/Library/Frameworks/DiskArbitration.framework /System/Library/Frameworks/DiskArbitration.framework
/System/Library/Frameworks/Foundation.framework /System/Library/Frameworks/Foundation.framework
/System/Library/Frameworks/IOKit.framework /System/Library/Frameworks/IOKit.framework

View File

@ -20,3 +20,16 @@
DeviceFinder::DeviceFinder(const QString& gstreamer_sink) DeviceFinder::DeviceFinder(const QString& gstreamer_sink)
: gstreamer_sink_(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";
}

View File

@ -18,15 +18,15 @@
#ifndef DEVICEFINDER_H #ifndef DEVICEFINDER_H
#define DEVICEFINDER_H #define DEVICEFINDER_H
#include <QIcon>
#include <QStringList> #include <QStringList>
#include <QVariant>
// Finds audio output devices that can be used with a given gstreamer sink. // Finds audio output devices that can be used with a given gstreamer sink.
class DeviceFinder { class DeviceFinder {
public: public:
struct Device { struct Device {
// The value to set as the "device" gstreamer property. // The value to set as the "device" gstreamer property.
QString name; QVariant device_property_value;
// A human readable description of the device. // A human readable description of the device.
QString description; QString description;
@ -35,6 +35,8 @@ class DeviceFinder {
QString icon_name; QString icon_name;
}; };
virtual ~DeviceFinder() {}
// The name of the gstreamer sink element that devices found by this class // The name of the gstreamer sink element that devices found by this class
// can be used with. // can be used with.
QString gstreamer_sink() const { return gstreamer_sink_; } QString gstreamer_sink() const { return gstreamer_sink_; }
@ -49,6 +51,8 @@ class DeviceFinder {
protected: protected:
explicit DeviceFinder(const QString& gstreamer_sink); explicit DeviceFinder(const QString& gstreamer_sink);
QString GuessIconName(const QString& description) const;
private: private:
QString gstreamer_sink_; QString gstreamer_sink_;
}; };

View File

@ -54,6 +54,10 @@
#include "engines/pulsedevicefinder.h" #include "engines/pulsedevicefinder.h"
#endif #endif
#ifdef Q_OS_DARWIN
#include "engines/osxdevicefinder.h"
#endif
using std::shared_ptr; using std::shared_ptr;
using std::vector; using std::vector;
@ -127,6 +131,9 @@ void GstEngine::InitialiseGstreamer() {
#ifdef HAVE_LIBPULSE #ifdef HAVE_LIBPULSE
device_finders.append(new PulseDeviceFinder); device_finders.append(new PulseDeviceFinder);
#endif #endif
#ifdef Q_OS_DARWIN
device_finders.append(new OsxDeviceFinder);
#endif
for (DeviceFinder* finder : device_finders) { for (DeviceFinder* finder : device_finders) {
if (!plugin_names.contains(finder->gstreamer_sink())) { if (!plugin_names.contains(finder->gstreamer_sink())) {
@ -153,7 +160,7 @@ void GstEngine::ReloadSettings() {
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
sink_ = s.value("sink", kAutoSink).toString(); sink_ = s.value("sink", kAutoSink).toString();
device_ = s.value("device").toString(); device_ = s.value("device");
if (sink_.isEmpty()) sink_ = kAutoSink; if (sink_.isEmpty()) sink_ = kAutoSink;
@ -831,7 +838,7 @@ GstEngine::OutputDetailsList GstEngine::GetOutputsList() const {
output.description = device.description; output.description = device.description;
output.icon_name = device.icon_name; output.icon_name = device.icon_name;
output.gstreamer_plugin_name = finder->gstreamer_sink(); output.gstreamer_plugin_name = finder->gstreamer_sink();
output.device_name = device.name; output.device_property_value = device.device_property_value;
ret.append(output); ret.append(output);
} }
} }

View File

@ -62,7 +62,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
QString icon_name; QString icon_name;
QString gstreamer_plugin_name; QString gstreamer_plugin_name;
QString device_name; QVariant device_property_value;
}; };
typedef QList<OutputDetails> OutputDetailsList; typedef QList<OutputDetails> OutputDetailsList;
@ -181,7 +181,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
QFuture<void> initialising_; QFuture<void> initialising_;
QString sink_; QString sink_;
QString device_; QVariant device_;
std::shared_ptr<GstEnginePipeline> current_pipeline_; std::shared_ptr<GstEnginePipeline> current_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pipeline_; std::shared_ptr<GstEnginePipeline> fadeout_pipeline_;

View File

@ -94,7 +94,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
} }
void GstEnginePipeline::set_output_device(const QString& sink, void GstEnginePipeline::set_output_device(const QString& sink,
const QString& device) { const QVariant& device) {
sink_ = sink; sink_ = sink;
device_ = device; device_ = device;
} }
@ -221,10 +221,23 @@ bool GstEnginePipeline::Init() {
// Create the sink // Create the sink
if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_))) return false; if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_))) return false;
if (g_object_class_find_property(G_OBJECT_CLASS(audiosink_), "device") && if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device") &&
!device_.isEmpty()) { !device_.toString().isEmpty()) {
g_object_set(G_OBJECT(audiosink_), "device", device_.toUtf8().constData(), switch (device_.type()) {
nullptr); 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 // Create all the other elements

View File

@ -50,7 +50,7 @@ class GstEnginePipeline : public QObject {
int id() const { return id_; } int id() const { return id_; }
// Call these setters before Init // 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_replaygain(bool enabled, int mode, float preamp, bool compression);
void set_buffer_duration_nanosec(qint64 duration_nanosec); void set_buffer_duration_nanosec(qint64 duration_nanosec);
void set_mono_playback(bool enabled); void set_mono_playback(bool enabled);
@ -184,7 +184,7 @@ signals:
// General settings for the pipeline // General settings for the pipeline
bool valid_; bool valid_;
QString sink_; QString sink_;
QString device_; QVariant device_;
// These get called when there is a new audio buffer available // These get called when there is a new audio buffer available
QList<BufferConsumer*> buffer_consumers_; QList<BufferConsumer*> buffer_consumers_;

View 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;
}

View 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

View File

@ -109,7 +109,7 @@ void PulseDeviceFinder::GetSinkInfoCallback(pa_context* c,
if (info) { if (info) {
Device dev; Device dev;
dev.name = QString::fromUtf8(info->name); dev.device_property_value = QString::fromUtf8(info->name);
dev.description = QString::fromUtf8(info->description); dev.description = QString::fromUtf8(info->description);
dev.icon_name = QString::fromUtf8( dev.icon_name = QString::fromUtf8(
pa_proplist_gets(info->proplist, "device.icon_name")); pa_proplist_gets(info->proplist, "device.icon_name"));

View File

@ -88,7 +88,7 @@ void PlaybackSettingsPage::Load() {
ui_->gst_output->itemData(i).value<GstEngine::OutputDetails>(); ui_->gst_output->itemData(i).value<GstEngine::OutputDetails>();
if (details.gstreamer_plugin_name == sink && if (details.gstreamer_plugin_name == sink &&
details.device_name == device) { details.device_property_value == device) {
ui_->gst_output->setCurrentIndex(i); ui_->gst_output->setCurrentIndex(i);
break; break;
} }
@ -128,7 +128,7 @@ void PlaybackSettingsPage::Save() {
s.beginGroup(GstEngine::kSettingsGroup); s.beginGroup(GstEngine::kSettingsGroup);
s.setValue("sink", details.gstreamer_plugin_name); 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("rgenabled", ui_->replaygain->isChecked());
s.setValue("rgmode", ui_->replaygain_mode->currentIndex()); s.setValue("rgmode", ui_->replaygain_mode->currentIndex());
s.setValue("rgpreamp", float(ui_->replaygain_preamp->value()) / 10 - 15); s.setValue("rgpreamp", float(ui_->replaygain_preamp->value()) / 10 - 15);