Initial commit.

This commit is contained in:
Jonas Kvinge
2018-02-27 18:06:05 +01:00
parent 85d9664df7
commit b2b1ba7abe
1393 changed files with 177311 additions and 1 deletions

View File

@@ -0,0 +1,118 @@
/*
* Strawberry Music Player
* Copyright 2017, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <QFile>
#include <alsa/asoundlib.h>
#include "alsadevicefinder.h"
#include <core/logging.h>
AlsaDeviceFinder::AlsaDeviceFinder()
: DeviceFinder("alsa") {
}
QList<DeviceFinder::Device> AlsaDeviceFinder::ListDevices() {
QList<Device> ret;
//register int err;
int card = -1;
int dev = -1;
int result = -1;
snd_ctl_card_info_t *cardinfo;
snd_pcm_info_t *pcminfo;
snd_ctl_t *handle;
snd_ctl_card_info_alloca(&cardinfo);
snd_pcm_info_alloca(&pcminfo);
card = -1;
snd_pcm_stream_name(SND_PCM_STREAM_PLAYBACK);
while (true) {
result = snd_card_next(&card);
if (result < 0) {
qLog(Error) << "Unable to get soundcard: " << snd_strerror(result);
return ret;
}
if (card < 0) return ret;
char name[32];
sprintf(name, "hw:%d", card);
result = snd_ctl_open(&handle, name, 0);
if (result < 0) {
qLog(Error) << "Unable to open soundcard " << card << ": " << snd_strerror(result);
continue;
}
result = snd_ctl_card_info(handle, cardinfo);
if (result < 0) {
qLog(Error) << "Control hardware failure for card " << card << ": " << snd_strerror(result);
snd_ctl_close(handle);
continue;
}
dev = -1;
while (true) {
result = snd_ctl_pcm_next_device(handle, &dev);
if (result < 0) {
qLog(Error) << "Unable to get PCM for card " << card << ": " << snd_strerror(result);
continue;
}
if (dev < 0) break;
snd_pcm_info_set_device(pcminfo, dev);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
result = snd_ctl_pcm_info(handle, pcminfo);
if (result < 0) {
if (result != -ENOENT) qLog(Error) << "Unable to get control digital audio info for card " << card << ": " << snd_strerror(result);
continue;
}
snd_pcm_info_get_name(pcminfo);
Device device;
device.card = card;
device.device = dev;
device.description = QString("%1 %2").arg(snd_ctl_card_info_get_name(cardinfo)).arg(snd_pcm_info_get_name(pcminfo));
device.string = QString("hw:%1,%2").arg(card).arg(dev);
device.device_property_value = QString("hw:%1,%2").arg(card).arg(dev);
device.iconname = GuessIconName("", device.description);
ret.append(device);
}
snd_ctl_close(handle);
}
snd_pcm_info_free(pcminfo); pcminfo = NULL;
snd_ctl_card_info_free(cardinfo); cardinfo = NULL;
snd_config_update_free_global();
return ret;
}

View File

@@ -0,0 +1,35 @@
/*
* Strawberry Music Player
* Copyright 2017, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ALSADEVICEFINDER_H
#define ALSADEVICEFINDER_H
#include "config.h"
#include "engine/devicefinder.h"
class AlsaDeviceFinder : public DeviceFinder {
public:
AlsaDeviceFinder();
virtual bool Initialise() { return true; }
virtual QList<Device> ListDevices();
};
#endif // ALSADEVICEFINDER_H

View File

@@ -0,0 +1,40 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BUFFERCONSUMER_H
#define BUFFERCONSUMER_H
#include "config.h"
#include <gst/gstbuffer.h>
class GstEnginePipeline;
class BufferConsumer {
public:
virtual ~BufferConsumer() {}
// This is called in some unspecified GStreamer thread.
// Ownership of the buffer is transferred to the BufferConsumer and it should
// gst_buffer_unref it.
virtual void ConsumeBuffer(GstBuffer* buffer, int pipeline_id) = 0;
};
#endif // BUFFERCONSUMER_H

View File

@@ -0,0 +1,57 @@
/*
* Strawberry Music Player
* Copyright 2017, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "devicefinder.h"
#include "core/logging.h"
DeviceFinder::DeviceFinder(const QString &output): output_(output) {
//qLog(Debug) << __PRETTY_FUNCTION__ << output;
}
QString DeviceFinder::GuessIconName(const QString &name, const QString &description) {
//qLog(Debug) << __PRETTY_FUNCTION__ << name << description;
QString description_lower = description.toLower();
if (description_lower.contains("mcintosh")) {
return "mcintosh";
}
if (description_lower.contains("electrocompaniet")) {
return "electrocompaniet";
}
if (description_lower.contains("intel")) {
return "intel";
}
if (description_lower.contains("realtek")) {
return "realtek";
}
if (description_lower.contains("nvidia")) {
return "nvidia";
}
if (description_lower.contains("headset")) {
return "headset";
}
return "soundcard";
}

64
src/engine/devicefinder.h Normal file
View File

@@ -0,0 +1,64 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DEVICEFINDER_H
#define DEVICEFINDER_H
#include "config.h"
#include <QStringList>
#include <QVariant>
// Finds audio output devices
class DeviceFinder {
public:
struct Device {
int card;
int device;
QVariant device_property_value;
QString string;
QString description;
QString iconname;
};
virtual ~DeviceFinder() {}
// The name of the gstreamer sink element that devices found by this class can be used with.
QString output() const { return output_; }
// Does any necessary setup, returning false if this DeviceFinder cannot be used.
virtual bool Initialise() = 0;
// Returns a list of available devices.
virtual QList<Device> ListDevices() = 0;
protected:
explicit DeviceFinder(const QString &output);
static QString GuessIconName(const QString &, const QString &);
private:
QString output_;
};
#endif // DEVICEFINDER_H

View File

@@ -0,0 +1,58 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef INTERFACE
#undef INTERFACE
#endif
#include "config.h"
#include <dsound.h>
#include <QUuid>
#include "directsounddevicefinder.h"
DirectSoundDeviceFinder::DirectSoundDeviceFinder()
: DeviceFinder("directsoundsink") {
}
QList<DeviceFinder::Device> DirectSoundDeviceFinder::ListDevices() {
State state;
DirectSoundEnumerateA(&DirectSoundDeviceFinder::EnumerateCallback, &state);
return state.devices;
}
BOOL DirectSoundDeviceFinder::EnumerateCallback(LPGUID guid, LPCSTR description, LPCSTR module, LPVOID state_voidptr) {
State *state = reinterpret_cast<State*>(state_voidptr);
if (guid) {
Device dev;
dev.description = QString::fromUtf8(description);
dev.device_property_value = QUuid(*guid).toByteArray();
dev.icon_name = GuessIconName(dev.plugin_name, dev.description);
state->devices.append(dev);
}
return 1;
}

View File

@@ -0,0 +1,49 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DIRECTSOUNDDEVICEFINDER_H
#define DIRECTSOUNDDEVICEFINDER_H
#include "config.h"
#include <rpc.h>
#include "engine/devicefinder.h"
class DirectSoundDeviceFinder : public DeviceFinder {
public:
DirectSoundDeviceFinder();
virtual bool Initialise() { return true; }
virtual QList<Device> ListDevices();
private:
struct State {
QList<Device> devices;
};
static BOOL EnumerateCallback(LPGUID guid,
LPCSTR description,
LPCSTR module,
LPVOID state_voidptr) __attribute__((stdcall));
};
#endif // DIRECTSOUNDDEVICEFINDER_H

42
src/engine/engine_fwd.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef ENGINE_FWD_H
#define ENGINE_FWD_H
#include <QString>
/// Used by eg engineobserver.h, and thus we reduce header dependencies on enginebase.h
namespace Engine {
struct SimpleMetaBundle;
class Base;
/**
* You should return:
* Playing when playing,
* Paused when paused
* Idle when you still have a URL loaded (ie you have not been told to stop())
* Empty when you have been told to stop(),
* Error when an error occurred and you stopped yourself
*
* It is vital to be Idle just after the track has ended!
*/
enum State { Empty, Idle, Playing, Paused, Error };
enum TrackChangeType {
// One of:
First = 0x01,
Manual = 0x02,
Auto = 0x04,
Intro = 0x08,
// Any of:
SameAlbum = 0x10,
};
Q_DECLARE_FLAGS(TrackChangeFlags, TrackChangeType);
}
typedef Engine::Base EngineBase;
#endif

105
src/engine/enginebase.cpp Normal file
View File

@@ -0,0 +1,105 @@
/*
* Strawberry Music Player
* This file was part of Amarok / Clementine.
* Copyright 2003 Mark Kretschmann
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 "config.h"
#include <cmath>
#include <QSettings>
#include "enginebase.h"
#include "enginedevice.h"
#include "core/timeconstants.h"
#include "settings/playbacksettingspage.h"
Engine::Base::Base()
: volume_(50),
beginning_nanosec_(0),
end_nanosec_(0),
scope_(kScopeSize),
fadeout_enabled_(true),
fadeout_duration_nanosec_(2 * kNsecPerSec), // 2s
crossfade_enabled_(true),
autocrossfade_enabled_(false),
crossfade_same_album_(false),
next_background_stream_id_(0),
about_to_end_emitted_(false) {}
Engine::Base::~Base() {}
bool Engine::Base::Load(const QUrl &url, TrackChangeFlags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
Q_UNUSED(force_stop_at_end);
url_ = url;
beginning_nanosec_ = beginning_nanosec;
end_nanosec_ = end_nanosec;
about_to_end_emitted_ = false;
return true;
}
void Engine::Base::SetVolume(uint value) {
volume_ = value;
SetVolumeSW(MakeVolumeLogarithmic(value));
}
uint Engine::Base::MakeVolumeLogarithmic(uint volume) {
// We're using a logarithmic function to make the volume ramp more natural.
return static_cast<uint>( 100 - 100.0 * std::log10( ( 100 - volume ) * 0.09 + 1.0 ) );
}
void Engine::Base::ReloadSettings() {
QSettings s;
s.beginGroup(PlaybackSettingsPage::kSettingsGroup);
fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool();
fadeout_duration_nanosec_ = s.value("FadeoutDuration", 2000).toLongLong() * kNsecPerMsec;
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool();
fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).toBool();
fadeout_pause_duration_nanosec_ = s.value("FadeoutPauseDuration", 250).toLongLong() * kNsecPerMsec;
}
void Engine::Base::EmitAboutToEnd() {
if (about_to_end_emitted_)
return;
about_to_end_emitted_ = true;
emit TrackAboutToEnd();
}
bool Engine::Base::Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
if (!Load(u, c, force_stop_at_end, beginning_nanosec, end_nanosec))
return false;
return Play(0);
}

186
src/engine/enginebase.h Normal file
View File

@@ -0,0 +1,186 @@
/*
* Strawberry Music Player
* This file was part of Amarok / Clementine.
* Copyright 2003 Mark Kretschmann
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 ENGINEBASE_H
#define ENGINEBASE_H
#include "config.h"
#include <stdint.h>
#include <sys/types.h>
#include <vector>
#include <QList>
#include <QObject>
#include <QUrl>
#include <QVariant>
#include "enginetype.h"
#include "engine_fwd.h"
namespace Engine {
typedef std::vector<int16_t> Scope;
class Base : public QObject {
Q_OBJECT
public:
virtual ~Base();
virtual bool Init() = 0;
virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {}
virtual bool Play(quint64 offset_nanosec) = 0;
virtual void Stop(bool stop_after = false) = 0;
virtual void Pause() = 0;
virtual void Unpause() = 0;
virtual void Seek(quint64 offset_nanosec) = 0;
virtual State state() const = 0;
virtual qint64 position_nanosec() const = 0;
virtual qint64 length_nanosec() const = 0;
// Subclasses should respect given markers (beginning and end) which are in miliseconds.
virtual bool Load(const QUrl &url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
// Sets new values for the beginning and end markers of the currently playing song.
// This doesn't change the state of engine or the stream's current position.
virtual void RefreshMarkers(quint64 beginning_nanosec, qint64 end_nanosec) {
beginning_nanosec_ = beginning_nanosec;
end_nanosec_ = end_nanosec;
}
// Plays a media stream represented with the URL 'u' from the given 'beginning'
// to the given 'end' (usually from 0 to a song's length). Both markers
// should be passed in nanoseconds. 'end' can be negative, indicating that the
// real length of 'u' stream is unknown.
bool Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
void SetVolume(uint value);
// Simple accessors
EngineType type() const { return type_; }
inline uint volume() const { return volume_; }
virtual const Scope &scope(int chunk_length) { return scope_; }
bool is_fadeout_enabled() const { return fadeout_enabled_; }
bool is_crossfade_enabled() const { return crossfade_enabled_; }
bool is_autocrossfade_enabled() const { return autocrossfade_enabled_; }
bool crossfade_same_album() const { return crossfade_same_album_; }
static const int kScopeSize = 1024;
struct PluginDetails {
QString name;
QString description;
QString iconname;
};
typedef QList<PluginDetails> PluginDetailsList;
struct OutputDetails {
QString name;
QString description;
QString iconname;
QVariant device_property_value;
};
typedef QList<OutputDetails> OutputDetailsList;
public slots:
virtual void ReloadSettings();
virtual void SetEqualizerEnabled(bool) {}
virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {}
virtual void SetStereoBalance(float value) {}
signals:
// Emitted when crossfading is enabled and the track is crossfade_duration_ away from finishing
void TrackAboutToEnd();
void TrackEnded();
void FadeoutFinishedSignal();
void StatusText(const QString&);
void Error(const QString&);
// Emitted when Engine was unable to play a song with the given QUrl.
void InvalidSongRequested(const QUrl&);
// Emitted when Engine successfully started playing a song with the given QUrl.
void ValidSongRequested(const QUrl&);
void MetaData(const Engine::SimpleMetaBundle&);
// Signals that the engine's state has changed (a stream was stopped for example).
// Always use the state from event, because it's not guaranteed that immediate
// subsequent call to state() won't return a stale value.
void StateChanged(Engine::State);
protected:
Base();
virtual void SetVolumeSW(uint percent) = 0;
static uint MakeVolumeLogarithmic(uint volume);
void EmitAboutToEnd();
protected:
EngineType type_;
uint volume_;
quint64 beginning_nanosec_;
qint64 end_nanosec_;
QUrl url_;
Scope scope_;
bool fadeout_enabled_;
qint64 fadeout_duration_nanosec_;
bool crossfade_enabled_;
bool autocrossfade_enabled_;
bool crossfade_same_album_;
int next_background_stream_id_;
bool fadeout_pause_enabled_;
qint64 fadeout_pause_duration_nanosec_;
private:
bool about_to_end_emitted_;
Q_DISABLE_COPY(Base);
};
//Q_DECLARE_METATYPE(EngineBase::PluginDetails);
Q_DECLARE_METATYPE(EngineBase::OutputDetails);
struct SimpleMetaBundle {
QString title;
QString artist;
QString album;
QString comment;
QString genre;
QString bitrate;
QString samplerate;
QString bitdepth;
QString length;
QString year;
QString tracknr;
};
} // namespace
#endif

View File

@@ -0,0 +1,93 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "enginedevice.h"
#include "devicefinder.h"
#include "core/logging.h"
#include <QMutex>
#include <QList>
#ifdef Q_OS_LINUX
#include "engine/alsadevicefinder.h"
#endif
#ifdef HAVE_LIBPULSE
#include "engine/pulsedevicefinder.h"
#endif
#ifdef Q_OS_DARWIN
#include "engine/osxdevicefinder.h"
#endif
#ifdef Q_OS_WIN32
#include "engine/directsounddevicefinder.h"
#endif
#include "settings/backendsettingspage.h"
EngineDevice::EngineDevice(QObject *parent) : QObject(parent) {
//qLog(Debug) << __PRETTY_FUNCTION__;
}
EngineDevice::~EngineDevice() {
//qLog(Debug) << __PRETTY_FUNCTION__;
qDeleteAll(device_finders_);
}
void EngineDevice::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QList<DeviceFinder*> device_finders;
#ifdef Q_OS_LINUX
device_finders.append(new AlsaDeviceFinder);
#endif
#ifdef HAVE_LIBPULSE
device_finders.append(new PulseDeviceFinder);
#endif
#ifdef Q_OS_DARWIN
device_finders.append(new OsxDeviceFinder);
#endif
#ifdef Q_OS_WIN32
device_finders.append(new DirectSoundDeviceFinder);
#endif
for (DeviceFinder *finder : device_finders) {
if (!finder->Initialise()) {
qLog(Warning) << "Failed to initialise DeviceFinder for" << finder->output();
delete finder;
continue;
}
device_finders_.append(finder);
}
}

54
src/engine/enginedevice.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ENGINEDEVICE_H
#define ENGINEDEVICE_H
#include "config.h"
#include <QStringList>
#include <QVariant>
#include "enginebase.h"
#include "devicefinder.h"
class DeviceFinder;
class EngineDevice : public QObject {
Q_OBJECT
public:
explicit EngineDevice(QObject *parent = nullptr);
~EngineDevice();
void Init();
QList<DeviceFinder*> device_finders_;
protected:
//static QString GuessIconName(const QString &, const QString &);
private:
QString output_;
};
#endif // ENGINEDEVICE_H

53
src/engine/enginetype.cpp Normal file
View File

@@ -0,0 +1,53 @@
/*
* Strawberry Music Player
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QString>
#include <QObject>
#include "enginetype.h"
namespace Engine {
QString EngineNameFromType(Engine::EngineType enginetype) {
switch (enginetype) {
case Engine::Xine: return QObject::tr("Xine");
case Engine::GStreamer: return QObject::tr("GStreamer");
case Engine::Phonon: return QObject::tr("Phonon");
case Engine::VLC: return QObject::tr("VLC");
case Engine::None:
default: return QObject::tr("None");
}
}
Engine::EngineType EngineTypeFromName(QString enginename) {
QString lower = enginename.toLower();
if (lower == "xine") return Engine::Xine;
else if (lower == "gstreamer") return Engine::GStreamer;
else if (lower == "phonon") return Engine::Phonon;
else if (lower == "vlc") return Engine::VLC;
else return Engine::None;
}
}

44
src/engine/enginetype.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* Strawberry Music Player
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ENGINETYPE_H
#define ENGINETYPE_H
#include "config.h"
#include <QString>
#include <QObject>
namespace Engine {
enum EngineType {
None,
GStreamer,
VLC,
Xine,
Phonon
};
Q_DECLARE_METATYPE(Engine::EngineType);
QString EngineNameFromType(Engine::EngineType enginetype);
Engine::EngineType EngineTypeFromName(QString enginename);
}
#endif

View File

@@ -0,0 +1,35 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QtDebug>
#include "gstelementdeleter.h"
GstElementDeleter::GstElementDeleter(QObject *parent) : QObject(parent) {}
void GstElementDeleter::DeleteElementLater(GstElement* element) {
metaObject()->invokeMethod(this, "DeleteElement", Qt::QueuedConnection, Q_ARG(GstElement *, element));
}
void GstElementDeleter::DeleteElement(GstElement *element) {
gst_element_set_state(element, GST_STATE_NULL);
}

View File

@@ -0,0 +1,48 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GSTBINDELETER_H
#define GSTBINDELETER_H
#include "config.h"
#include <gst/gst.h>
#include <QObject>
class GstElementDeleter : public QObject {
Q_OBJECT
public:
GstElementDeleter(QObject *parent = nullptr);
// If you call this function with any gstreamer element, the element will get
// deleted in the main thread. This is useful if you need to delete an
// element from its own callback.
// It's in a separate object so *your* object (GstEnginePipeline) can be
// destroyed, and the element that you scheduled for deletion is still
// deleted later regardless.
void DeleteElementLater(GstElement *element);
private slots:
void DeleteElement(GstElement *element);
};
#endif // GSTBINDELETER_H

983
src/engine/gstengine.cpp Normal file
View File

@@ -0,0 +1,983 @@
/***************************************************************************
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
* *
* This program 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 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <cmath>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <iostream>
#include <memory>
#include <vector>
#include <QTimer>
#include <QRegExp>
#include <QFile>
#include <QSettings>
#include <QCoreApplication>
#include <QTimeLine>
#include <QDir>
#include <QtConcurrentRun>
#include "enginetype.h"
#include "enginebase.h"
#include "gstengine.h"
#include "devicefinder.h"
#include "gstenginepipeline.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/taskmanager.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#ifdef Q_OS_LINUX
#include "engine/alsadevicefinder.h"
#endif
#ifdef HAVE_LIBPULSE
#include "engine/pulsedevicefinder.h"
#endif
#ifdef Q_OS_DARWIN
#include "engine/osxdevicefinder.h"
#endif
#ifdef Q_OS_WIN32
#include "engine/directsounddevicefinder.h"
#endif
#include "settings/backendsettingspage.h"
using std::shared_ptr;
using std::vector;
const char *GstEngine::kAutoSink = "autoaudiosink";
const char *GstEngine::kALSASink = "alsasink";
const char *GstEngine::kOSSSink = "osssink";
const char *GstEngine::kOSS4Sink = "oss4sink";
const char *GstEngine::kJackAudioSink = "jackaudiosink";
const char *GstEngine::kPulseSink = "pulsesink";
const char *GstEngine::kA2DPSink = "a2dpsink";
const char *GstEngine::kAVDTPSink = "avdtpsink";
const char *GstEngine::kEnterprisePipeline =
"audiotestsrc wave=5 ! "
"audiocheblimit mode=0 cutoff=120";
GstEngine::GstEngine(TaskManager *task_manager)
: Engine::Base(),
task_manager_(task_manager),
buffering_task_id_(-1),
latest_buffer_(nullptr),
equalizer_enabled_(false),
stereo_balance_(0.0f),
rg_enabled_(false),
rg_mode_(0),
rg_preamp_(0.0),
rg_compression_(true),
buffer_duration_nanosec_(1 * kNsecPerSec), // 1s
buffer_min_fill_(33),
mono_playback_(false),
seek_timer_(new QTimer(this)),
timer_id_(-1),
next_element_id_(0),
is_fading_out_to_pause_(false),
has_faded_out_(false),
scope_chunk_(0),
have_new_buffer_(false) {
//qLog(Debug) << __PRETTY_FUNCTION__;
seek_timer_->setSingleShot(true);
seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec);
connect(seek_timer_, SIGNAL(timeout()), SLOT(SeekNow()));
ReloadSettings();
#ifdef Q_OS_DARWIN
QDir resources_dir(mac::GetResourcesPath());
QString ca_cert_path = resources_dir.filePath("cacert.pem");
GError* error = nullptr;
tls_database_ = g_tls_file_database_new(ca_cert_path.toUtf8().data(), &error);
#endif
}
GstEngine::~GstEngine() {
//qLog(Debug) << __PRETTY_FUNCTION__;
EnsureInitialised();
current_pipeline_.reset();
// Save configuration
//gst_deinit();
//qDeleteAll(device_finders_);
#ifdef Q_OS_DARWIN
g_object_unref(tls_database_);
#endif
}
bool GstEngine::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
SetEnvironment();
type_ = Engine::GStreamer;
initialising_ = QtConcurrent::run(this, &GstEngine::InitialiseGStreamer);
return true;
}
void GstEngine::InitialiseGStreamer() {
//qLog(Debug) << __PRETTY_FUNCTION__;
#if 0
gst_init(nullptr, nullptr);
gst_pb_utils_init();
#endif
#if 0
QSet<QString> plugin_names;
for (const PluginDetails &plugin : GetPluginList("Sink/Audio")) {
plugin_names.insert(plugin.name);
}
#endif
#if 0
QList<DeviceFinder*> device_finders;
#ifdef Q_OS_LINUX
device_finders.append(new AlsaDeviceFinder);
#endif
#ifdef HAVE_LIBPULSE
device_finders.append(new PulseDeviceFinder);
#endif
#ifdef Q_OS_DARWIN
device_finders.append(new OsxDeviceFinder);
#endif
#ifdef Q_OS_WIN32
device_finders.append(new DirectSoundDeviceFinder);
#endif
for (DeviceFinder *finder : device_finders) {
if (!plugin_names.contains(finder->gstreamer_sink())) {
qLog(Info) << "Skipping DeviceFinder for" << finder->gstreamer_sink() << "known plugins:" << plugin_names;
delete finder;
continue;
}
if (!finder->Initialise()) {
qLog(Warning) << "Failed to initialise DeviceFinder for" << finder->gstreamer_sink();
delete finder;
continue;
}
device_finders_.append(finder);
}
#endif
}
void GstEngine::SetEnvironment() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QString scanner_path;
QString plugin_path;
QString registry_filename;
// On windows and mac we bundle the gstreamer plugins with strawberry
#if defined(Q_OS_DARWIN)
scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner";
plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer";
#elif defined(Q_OS_WIN32)
plugin_path = QCoreApplication::applicationDirPath() + "/gstreamer-plugins";
#endif
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
registry_filename = Utilities::GetConfigPath(Utilities::Path_GstreamerRegistry);
#endif
if (!scanner_path.isEmpty()) Utilities::SetEnv("GST_PLUGIN_SCANNER", scanner_path);
if (!plugin_path.isEmpty()) {
Utilities::SetEnv("GST_PLUGIN_PATH", plugin_path);
// Never load plugins from anywhere else.
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
}
if (!registry_filename.isEmpty()) {
Utilities::SetEnv("GST_REGISTRY", registry_filename);
}
#ifdef Q_OS_DARWIN
Utilities::SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules");
#endif
Utilities::SetEnv("PULSE_PROP_media.role", "music");
}
void GstEngine::ReloadSettings() {
//qLog(Debug) << __PRETTY_FUNCTION__;
Engine::Base::ReloadSettings();
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
sink_ = s.value("output", kAutoSink).toString();
device_ = s.value("device");
if (sink_.isEmpty()) sink_ = kAutoSink;
rg_enabled_ = s.value("rgenabled", false).toBool();
rg_mode_ = s.value("rgmode", 0).toInt();
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
rg_compression_ = s.value("rgcompression", true).toBool();
buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec;
buffer_min_fill_ = s.value("bufferminfill", 33).toInt();
mono_playback_ = s.value("monoplayback", false).toBool();
}
qint64 GstEngine::position_nanosec() const {
if (!current_pipeline_) return 0;
const qint64 result = current_pipeline_->position() - beginning_nanosec_;
return qint64(qMax(0ll, result));
}
qint64 GstEngine::length_nanosec() const {
if (!current_pipeline_) return 0;
const qint64 result = end_nanosec_ - beginning_nanosec_;
if (result > 0) {
return result;
}
else {
// Get the length from the pipeline if we don't know.
return current_pipeline_->length();
}
}
Engine::State GstEngine::state() const {
if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle;
switch (current_pipeline_->state()) {
case GST_STATE_NULL:
return Engine::Empty;
case GST_STATE_READY:
return Engine::Idle;
case GST_STATE_PLAYING:
return Engine::Playing;
case GST_STATE_PAUSED:
return Engine::Paused;
default:
return Engine::Empty;
}
}
void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Schedule this to run in the GUI thread. The buffer gets added to the
// queue and unreffed by UpdateScope.
if (!QMetaObject::invokeMethod(this, "AddBufferToScope", Q_ARG(GstBuffer*, buffer), Q_ARG(int, pipeline_id))) {
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
}
}
void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
gst_buffer_unref(buf);
return;
}
if (latest_buffer_ != nullptr) {
gst_buffer_unref(latest_buffer_);
}
latest_buffer_ = buf;
have_new_buffer_ = true;
}
const Engine::Scope &GstEngine::scope(int chunk_length) {
// the new buffer could have a different size
if (have_new_buffer_) {
if (latest_buffer_ != nullptr) {
scope_chunks_ = ceil(((double)GST_BUFFER_DURATION(latest_buffer_) / (double)(chunk_length * kNsecPerMsec)));
}
// if the buffer is shorter than the chunk length
if (scope_chunks_ <= 0) {
scope_chunks_ = 1;
}
scope_chunk_ = 0;
have_new_buffer_ = false;
}
if (latest_buffer_ != nullptr) {
UpdateScope(chunk_length);
}
return scope_;
}
void GstEngine::UpdateScope(int chunk_length) {
typedef Engine::Scope::value_type sample_type;
// prevent dbz or invalid chunk size
if (!GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(latest_buffer_))) return;
if (GST_BUFFER_DURATION(latest_buffer_) == 0) return;
GstMapInfo map;
gst_buffer_map(latest_buffer_, &map, GST_MAP_READ);
// determine where to split the buffer
int chunk_density = (map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_);
int chunk_size = chunk_length * chunk_density;
// in case a buffer doesn't arrive in time
if (scope_chunk_ >= scope_chunks_) {
scope_chunk_ = 0;
return;
}
const sample_type *source = reinterpret_cast<sample_type*>(map.data);
sample_type *dest = scope_.data();
source += (chunk_size / sizeof(sample_type)) * scope_chunk_;
int bytes = 0;
// make sure we don't go beyond the end of the buffer
if (scope_chunk_ == scope_chunks_ - 1) {
bytes = qMin(static_cast<Engine::Scope::size_type>(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type));
}
else {
bytes = qMin(static_cast<Engine::Scope::size_type>(chunk_size), scope_.size() * sizeof(sample_type));
}
scope_chunk_++;
memcpy(dest, source, bytes);
gst_buffer_unmap(latest_buffer_, &map);
if (scope_chunk_ == scope_chunks_) {
gst_buffer_unref(latest_buffer_);
latest_buffer_ = nullptr;
}
}
void GstEngine::StartPreloading(const QUrl &url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec) {
EnsureInitialised();
QUrl gst_url = FixupUrl(url);
// No crossfading, so we can just queue the new URL in the existing
// pipeline and get gapless playback (hopefully)
if (current_pipeline_)
current_pipeline_->SetNextUrl(gst_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0);
}
QUrl GstEngine::FixupUrl(const QUrl &url) {
QUrl copy = url;
// It's a file:// url with a hostname set. QUrl::fromLocalFile does this
// when given a \\host\share\file path on Windows. Munge it back into a
// path that gstreamer will recognise.
if (url.scheme() == "file" && !url.host().isEmpty()) {
copy.setPath("//" + copy.host() + copy.path());
copy.setHost(QString());
}
return copy;
}
bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
//qLog(Debug) << __PRETTY_FUNCTION__;
EnsureInitialised();
Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
QUrl gst_url = FixupUrl(url);
bool crossfade = current_pipeline_ && ((crossfade_enabled_ && change & Engine::Manual) || (autocrossfade_enabled_ && change & Engine::Auto) || ((crossfade_enabled_ || autocrossfade_enabled_) && change & Engine::Intro));
if (change & Engine::Auto && change & Engine::SameAlbum && !crossfade_same_album_)
crossfade = false;
if (!crossfade && current_pipeline_ && current_pipeline_->url() == gst_url && change & Engine::Auto) {
// We're not crossfading, and the pipeline is already playing the URI we want, so just do nothing.
return true;
}
//SetEqualizerEnabled(equalizer_enabled_);
//SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
//SetStereoBalance(stereo_balance_);
shared_ptr<GstEnginePipeline> pipeline = CreatePipeline(gst_url, force_stop_at_end ? end_nanosec : 0);
if (!pipeline) return false;
if (crossfade) StartFadeout();
BufferingFinished();
current_pipeline_ = pipeline;
SetVolume(volume_);
SetEqualizerEnabled(equalizer_enabled_);
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
SetStereoBalance(stereo_balance_);
// Maybe fade in this track
if (crossfade)
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward);
return true;
}
void GstEngine::StartFadeout() {
if (is_fading_out_to_pause_) return;
fadeout_pipeline_ = current_pipeline_;
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
fadeout_pipeline_->RemoveAllBufferConsumers();
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
}
void GstEngine::StartFadeoutPause() {
fadeout_pause_pipeline_ = current_pipeline_;
disconnect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::EaseInOutCurve, false);
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::LinearCurve, false);
}
connect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutPauseFinished()));
is_fading_out_to_pause_ = true;
}
bool GstEngine::Play(quint64 offset_nanosec) {
//qLog(Debug) << __PRETTY_FUNCTION__;
EnsureInitialised();
if (!current_pipeline_ || current_pipeline_->is_buffering()) return false;
QFuture<GstStateChangeReturn> future = current_pipeline_->SetState(GST_STATE_PLAYING);
NewClosure(future, this, SLOT(PlayDone(QFuture<GstStateChangeReturn>, quint64, int)), future, offset_nanosec, current_pipeline_->id());
if (is_fading_out_to_pause_) {
current_pipeline_->SetState(GST_STATE_PAUSED);
}
return true;
}
void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 offset_nanosec, const int pipeline_id) {
//qLog(Debug) << __PRETTY_FUNCTION__;
GstStateChangeReturn ret = future.result();
if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
return;
}
if (ret == GST_STATE_CHANGE_FAILURE) {
// Failure, but we got a redirection URL - try loading that instead
QUrl redirect_url = current_pipeline_->redirect_url();
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
qLog(Info) << "Redirecting to" << redirect_url;
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
Play(offset_nanosec);
return;
}
// Failure - give up
qLog(Warning) << "Could not set thread to PLAYING.";
current_pipeline_.reset();
BufferingFinished();
return;
}
StartTimers();
// initial offset
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
Seek(offset_nanosec);
}
emit StateChanged(Engine::Playing);
// we've successfully started playing a media stream with this url
emit ValidSongRequested(url_);
}
void GstEngine::Stop(bool stop_after) {
//qLog(Debug) << __PRETTY_FUNCTION__;
StopTimers();
url_ = QUrl(); // To ensure we return Empty from state()
beginning_nanosec_ = end_nanosec_ = 0;
// Check if we started a fade out. If it isn't finished yet and the user
// pressed stop, we cancel the fader and just stop the playback.
if (is_fading_out_to_pause_) {
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
is_fading_out_to_pause_ = false;
has_faded_out_ = true;
fadeout_pause_pipeline_.reset();
fadeout_pipeline_.reset();
}
if (fadeout_enabled_ && current_pipeline_ && !stop_after) StartFadeout();
current_pipeline_.reset();
BufferingFinished();
emit StateChanged(Engine::Empty);
}
void GstEngine::FadeoutFinished() {
fadeout_pipeline_.reset();
emit FadeoutFinishedSignal();
}
void GstEngine::FadeoutPauseFinished() {
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED);
current_pipeline_->SetState(GST_STATE_PAUSED);
emit StateChanged(Engine::Paused);
StopTimers();
is_fading_out_to_pause_ = false;
has_faded_out_ = true;
fadeout_pause_pipeline_.reset();
fadeout_pipeline_.reset();
emit FadeoutFinishedSignal();
}
void GstEngine::Pause() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
// Check if we started a fade out. If it isn't finished yet and the user
// pressed play, we inverse the fader and resume the playback.
if (is_fading_out_to_pause_) {
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QTimeLine::EaseInOutCurve, false);
is_fading_out_to_pause_ = false;
has_faded_out_ = false;
emit StateChanged(Engine::Playing);
return;
}
if (current_pipeline_->state() == GST_STATE_PLAYING) {
if (fadeout_pause_enabled_) {
StartFadeoutPause();
}
else {
current_pipeline_->SetState(GST_STATE_PAUSED);
emit StateChanged(Engine::Paused);
StopTimers();
}
}
}
void GstEngine::Unpause() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
if (current_pipeline_->state() == GST_STATE_PAUSED) {
current_pipeline_->SetState(GST_STATE_PLAYING);
// Check if we faded out last time. If yes, fade in no matter what the
// settings say. If we pause with fadeout, deactivate fadeout and resume
// playback, the player would be muted if not faded in.
if (has_faded_out_) {
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QTimeLine::EaseInOutCurve, false);
has_faded_out_ = false;
}
emit StateChanged(Engine::Playing);
StartTimers();
}
}
void GstEngine::Seek(quint64 offset_nanosec) {
if (!current_pipeline_) return;
seek_pos_ = beginning_nanosec_ + offset_nanosec;
waiting_to_seek_ = true;
if (!seek_timer_->isActive()) {
SeekNow();
seek_timer_->start(); // Stop us from seeking again for a little while
}
}
void GstEngine::SeekNow() {
if (!waiting_to_seek_) return;
waiting_to_seek_ = false;
if (!current_pipeline_) return;
if (!current_pipeline_->Seek(seek_pos_)) {
qLog(Warning) << "Seek failed";
}
}
void GstEngine::SetEqualizerEnabled(bool enabled) {
//qLog(Debug) << "equalizer ENABLED: " << enabled;
equalizer_enabled_ = enabled;
if (current_pipeline_) current_pipeline_->SetEqualizerEnabled(enabled);
}
void GstEngine::SetEqualizerParameters(int preamp, const QList<int> &band_gains) {
equalizer_preamp_ = preamp;
equalizer_gains_ = band_gains;
if (current_pipeline_)
current_pipeline_->SetEqualizerParams(preamp, band_gains);
}
void GstEngine::SetStereoBalance(float value) {
stereo_balance_ = value;
if (current_pipeline_) current_pipeline_->SetStereoBalance(value);
}
void GstEngine::SetVolumeSW(uint percent) {
if (current_pipeline_) current_pipeline_->SetVolume(percent);
}
void GstEngine::StartTimers() {
StopTimers();
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
}
void GstEngine::StopTimers() {
if (timer_id_ != -1) {
killTimer(timer_id_);
timer_id_ = -1;
}
}
void GstEngine::timerEvent(QTimerEvent *e) {
if (e->timerId() != timer_id_) return;
if (current_pipeline_) {
const qint64 current_position = position_nanosec();
const qint64 current_length = length_nanosec();
const qint64 remaining = current_length - current_position;
const qint64 fudge = kTimerIntervalNanosec + 100 * kNsecPerMsec; // Mmm fudge
const qint64 gap = buffer_duration_nanosec_ + (autocrossfade_enabled_ ? fadeout_duration_nanosec_ : kPreloadGapNanosec);
// only if we know the length of the current stream...
if (current_length > 0) {
// emit TrackAboutToEnd when we're a few seconds away from finishing
if (remaining < gap + fudge) {
EmitAboutToEnd();
}
}
}
}
void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
qLog(Warning) << "Gstreamer error:" << message;
current_pipeline_.reset();
BufferingFinished();
emit StateChanged(Engine::Error);
// unable to play media stream with this url
emit InvalidSongRequested(url_);
// TODO: the types of errors listed below won't be shown to user - they will
// get logged and the current song will be skipped; instead of maintaining
// the list we should probably:
// - don't report any engine's errors to user (always just log and skip)
// - come up with a less intrusive error box (not a dialog but a notification
// popup of some kind) and then report all errors
if (!(domain == GST_RESOURCE_ERROR &&
error_code == GST_RESOURCE_ERROR_NOT_FOUND) &&
!(domain == GST_STREAM_ERROR &&
error_code == GST_STREAM_ERROR_TYPE_NOT_FOUND) &&
!(domain == GST_RESOURCE_ERROR &&
error_code == GST_RESOURCE_ERROR_OPEN_READ)) {
emit Error(message);
}
}
void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
if (!has_next_track) {
current_pipeline_.reset();
BufferingFinished();
}
emit TrackEnded();
}
void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
emit MetaData(bundle);
}
GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Make a unique name
QString name = factoryName + "-" + QString::number(next_element_id_++);
GstElement *element = gst_element_factory_make(factoryName.toLatin1().constData(), name.toLatin1().constData());
if (!element) {
emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName));
gst_object_unref(GST_OBJECT(bin));
return nullptr;
}
if (bin) gst_bin_add(GST_BIN(bin), element);
return element;
}
GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname) const {
//qLog(Debug) << __PRETTY_FUNCTION__;
PluginDetailsList ret;
GstRegistry *registry = gst_registry_get();
GList *const features = gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY);
//qLog(Debug) << __PRETTY_FUNCTION__ << registry << features;
GList *p = features;
while (p) {
GstElementFactory *factory = GST_ELEMENT_FACTORY(p->data);
if (QString(gst_element_factory_get_klass(factory)).contains(classname)) {;
PluginDetails details;
details.name = QString::fromUtf8(gst_plugin_feature_get_name(p->data));
details.description = QString::fromUtf8(gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_DESCRIPTION));
ret << details;
//qLog(Debug) << details.name << details.description;
}
p = g_list_next(p);
}
gst_plugin_feature_list_free(features);
return ret;
}
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
//qLog(Debug) << __PRETTY_FUNCTION__ << sink_ << device_;
EnsureInitialised();
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
ret->set_output_device(sink_, device_);
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
ret->set_buffer_min_fill(buffer_min_fill_);
ret->set_mono_playback(mono_playback_);
ret->AddBufferConsumer(this);
for (BufferConsumer *consumer : buffer_consumers_) {
ret->AddBufferConsumer(consumer);
}
connect(ret.get(), SIGNAL(EndOfStreamReached(int, bool)), SLOT(EndOfStreamReached(int, bool)));
connect(ret.get(), SIGNAL(Error(int, QString, int, int)), SLOT(HandlePipelineError(int, QString, int, int)));
connect(ret.get(), SIGNAL(MetadataFound(int, Engine::SimpleMetaBundle)), SLOT(NewMetaData(int, Engine::SimpleMetaBundle)));
connect(ret.get(), SIGNAL(BufferingStarted()), SLOT(BufferingStarted()));
connect(ret.get(), SIGNAL(BufferingProgress(int)), SLOT(BufferingProgress(int)));
connect(ret.get(), SIGNAL(BufferingFinished()), SLOT(BufferingFinished()));
return ret;
}
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl &url, qint64 end_nanosec) {
//qLog(Debug) << __PRETTY_FUNCTION__;
shared_ptr<GstEnginePipeline> ret = CreatePipeline();
if (url.scheme() == "enterprise") {
ret->InitFromString(kEnterprisePipeline);
return ret;
}
if (!ret->InitFromUrl(url, end_nanosec)) ret.reset();
return ret;
}
bool GstEngine::ALSADeviceSupport(const QString &name) {
return (name == kALSASink || name == kOSSSink || name == kPulseSink);
}
void GstEngine::AddBufferConsumer(BufferConsumer *consumer) {
buffer_consumers_ << consumer;
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
}
void GstEngine::RemoveBufferConsumer(BufferConsumer *consumer) {
buffer_consumers_.removeAll(consumer);
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
}
void GstEngine::BufferingStarted() {
if (buffering_task_id_ != -1) {
task_manager_->SetTaskFinished(buffering_task_id_);
}
buffering_task_id_ = task_manager_->StartTask(tr("Buffering"));
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100);
}
void GstEngine::BufferingProgress(int percent) {
task_manager_->SetTaskProgress(buffering_task_id_, percent, 100);
}
void GstEngine::BufferingFinished() {
if (buffering_task_id_ != -1) {
task_manager_->SetTaskFinished(buffering_task_id_);
buffering_task_id_ = -1;
}
}
EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
//const_cast<GstEngine *>(this)->EnsureInitialised();
EngineBase::OutputDetailsList ret;
#if 0
for (DeviceFinder *finder : device_finders_) {
for (const DeviceFinder::Device &device : finder->ListDevices()) {
OutputDetails output;
output.description = device.description;
output.icon_name = device.icon_name;
output.name = finder->gstreamer_sink();
output.device_property_value = device.device_property_value;
ret.append(output);
}
}
#endif
PluginDetailsList plugins = GetPluginList("Sink/Audio");
for (const PluginDetails &plugin : plugins) {
OutputDetails output;
output.name = plugin.name;
output.description = plugin.description;
if (plugin.name == kAutoSink) output.iconname = "soundcard";
else if ((plugin.name == kALSASink) || (plugin.name == kOSS4Sink) || (plugin.name == kOSS4Sink)) output.iconname = "alsa";
else if (plugin.name== kJackAudioSink) output.iconname = "jack";
else if (plugin.name == kPulseSink) output.iconname = "pulseaudio";
else if ((plugin.name == kA2DPSink) || (plugin.name == kAVDTPSink)) output.iconname = "bluetooth";
else output.iconname = "soundcard";
ret.append(output);
}
return ret;
}

220
src/engine/gstengine.h Normal file
View File

@@ -0,0 +1,220 @@
/***************************************************************************
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
* Portions Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
* *
* This program 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 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GSTENGINE_H
#define GSTENGINE_H
#include "config.h"
#include <memory>
#include <gst/gst.h>
#include <QFuture>
#include <QHash>
#include <QList>
#include <QString>
#include <QStringList>
#include <QTimerEvent>
#include "bufferconsumer.h"
#include "enginebase.h"
#include "core/timeconstants.h"
class QTimer;
class QTimerEvent;
class GstEnginePipeline;
class TaskManager;
#ifdef Q_OS_DARWIN
struct _GTlsDatabase;
typedef struct _GTlsDatabase GTlsDatabase;
#endif
/**
* @class GstEngine
* @short GStreamer engine plugin
* @author Mark Kretschmann <markey@web.de>
*/
class GstEngine : public Engine::Base, public BufferConsumer {
Q_OBJECT
public:
GstEngine(TaskManager *task_manager);
~GstEngine();
static const char *kAutoSink;
static const char *kALSASink;
static const char *kOSSSink;
static const char *kOSS4Sink;
static const char *kJackAudioSink;
static const char *kPulseSink;
static const char *kA2DPSink;
static const char *kAVDTPSink;
bool Init();
void EnsureInitialised() { initialising_.waitForFinished(); }
void InitialiseGStreamer();
void SetEnvironment();
OutputDetailsList GetOutputsList() const;
qint64 position_nanosec() const;
qint64 length_nanosec() const;
Engine::State state() const;
const Engine::Scope &scope(int chunk_length);
static bool ALSADeviceSupport(const QString &name);
GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0);
// BufferConsumer
void ConsumeBuffer(GstBuffer *buffer, int pipeline_id);
bool IsEqualizerEnabled() { return equalizer_enabled_; }
public slots:
void StartPreloading(const QUrl &url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec);
bool Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false);
void Pause();
void Unpause();
void Seek(quint64 offset_nanosec);
/** Set whether equalizer is enabled */
void SetEqualizerEnabled(bool);
/** Set equalizer preamp and gains, range -100..100. Gains are 10 values. */
void SetEqualizerParameters(int preamp, const QList<int> &bandGains);
/** Set Stereo balance, range -1.0f..1.0f */
void SetStereoBalance(float value);
void ReloadSettings();
void AddBufferConsumer(BufferConsumer *consumer);
void RemoveBufferConsumer(BufferConsumer *consumer);
#ifdef Q_OS_DARWIN
GTlsDatabase *tls_database() const { return tls_database_; }
#endif
protected:
void SetVolumeSW(uint percent);
void timerEvent(QTimerEvent*);
private slots:
void EndOfStreamReached(int pipeline_id, bool has_next_track);
void HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code);
void NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle);
void AddBufferToScope(GstBuffer *buf, int pipeline_id);
void FadeoutFinished();
void FadeoutPauseFinished();
void SeekNow();
void PlayDone(QFuture<GstStateChangeReturn> future, const quint64, const int);
void BufferingStarted();
void BufferingProgress(int percent);
void BufferingFinished();
private:
PluginDetailsList GetPluginList(const QString &classname) const;
void StartFadeout();
void StartFadeoutPause();
void StartTimers();
void StopTimers();
std::shared_ptr<GstEnginePipeline> CreatePipeline();
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QUrl &url, qint64 end_nanosec);
void UpdateScope(int chunk_length);
static QUrl FixupUrl(const QUrl &url);
private:
static const qint64 kTimerIntervalNanosec = 1000 *kNsecPerMsec; // 1s
static const qint64 kPreloadGapNanosec = 2000 *kNsecPerMsec; // 2s
static const qint64 kSeekDelayNanosec = 100 *kNsecPerMsec; // 100msec
static const char *kHypnotoadPipeline;
static const char *kEnterprisePipeline;
TaskManager *task_manager_;
int buffering_task_id_;
QFuture<void> initialising_;
QString sink_;
QVariant device_;
std::shared_ptr<GstEnginePipeline> current_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pause_pipeline_;
QUrl preloaded_url_;
QList<BufferConsumer*> buffer_consumers_;
GstBuffer *latest_buffer_;
bool equalizer_enabled_;
int equalizer_preamp_;
QList<int> equalizer_gains_;
float stereo_balance_;
bool rg_enabled_;
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
qint64 buffer_duration_nanosec_;
int buffer_min_fill_;
bool mono_playback_;
mutable bool can_decode_success_;
mutable bool can_decode_last_;
// Hack to stop seeks happening too often
QTimer *seek_timer_;
bool waiting_to_seek_;
quint64 seek_pos_;
int timer_id_;
int next_element_id_;
bool is_fading_out_to_pause_;
bool has_faded_out_;
int scope_chunk_;
bool have_new_buffer_;
int scope_chunks_;
};
//Q_DECLARE_METATYPE(GstEngine::OutputDetails)
#endif /* GSTENGINE_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GSTENGINEPIPELINE_H
#define GSTENGINEPIPELINE_H
#include "config.h"
#include <memory>
#include <gst/gst.h>
#include <QBasicTimer>
#include <QFuture>
#include <QMutex>
#include <QObject>
#include <QThreadPool>
#include <QTimeLine>
#include <QUrl>
#include "engine_fwd.h"
class GstElementDeleter;
class GstEngine;
class BufferConsumer;
struct GstQueue;
struct GstURIDecodeBin;
class GstEnginePipeline : public QObject {
Q_OBJECT
public:
GstEnginePipeline(GstEngine *engine);
~GstEnginePipeline();
// Globally unique across all pipelines.
int id() const { return id_; }
// Call these setters before Init
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_buffer_min_fill(int percent);
void set_mono_playback(bool enabled);
// Creates the pipeline, returns false on error
bool InitFromUrl(const QUrl &url, qint64 end_nanosec);
bool InitFromString(const QString &pipeline);
// BufferConsumers get fed audio data. Thread-safe.
void AddBufferConsumer(BufferConsumer *consumer);
void RemoveBufferConsumer(BufferConsumer *consumer);
void RemoveAllBufferConsumers();
// Control the music playback
QFuture<GstStateChangeReturn> SetState(GstState state);
Q_INVOKABLE bool Seek(qint64 nanosec);
void SetEqualizerEnabled(bool enabled);
void SetEqualizerParams(int preamp, const QList<int> &band_gains);
void SetVolume(int percent);
void SetStereoBalance(float value);
void StartFader(qint64 duration_nanosec, QTimeLine::Direction direction = QTimeLine::Forward, QTimeLine::CurveShape shape = QTimeLine::LinearCurve, bool use_fudge_timer = true);
// If this is set then it will be loaded automatically when playback finishes
// for gapless playback
void SetNextUrl(const QUrl &url, qint64 beginning_nanosec, qint64 end_nanosec);
bool has_next_valid_url() const { return next_url_.isValid(); }
// Get information about the music playback
QUrl url() const { return url_; }
bool is_valid() const { return valid_; }
// Please note that this method (unlike GstEngine's.position()) is
// multiple-section media unaware.
qint64 position() const;
// Please note that this method (unlike GstEngine's.length()) is
// multiple-section media unaware.
qint64 length() const;
// Returns this pipeline's state. May return GST_STATE_NULL if the state check
// timed out. The timeout value is a reasonable default.
GstState state() const;
qint64 segment_start() const { return segment_start_; }
// Don't allow the user to change the playback state (playing/paused) while
// the pipeline is buffering.
bool is_buffering() const { return buffering_; }
QUrl redirect_url() const { return redirect_url_; }
QString source_device() const { return source_device_; }
public slots:
void SetVolumeModifier(qreal mod);
signals:
void EndOfStreamReached(int pipeline_id, bool has_next_track);
void MetadataFound(int pipeline_id, const Engine::SimpleMetaBundle &bundle);
// This indicates an error, delegated from GStreamer, in the pipeline.
// The message, domain and error_code are related to GStreamer's GError.
void Error(int pipeline_id, const QString &message, int domain, int error_code);
void FaderFinished();
void BufferingStarted();
void BufferingProgress(int percent);
void BufferingFinished();
protected:
void timerEvent(QTimerEvent*);
private:
// Static callbacks. The GstEnginePipeline instance is passed in the last
// argument.
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
static void NewPadCallback(GstElement*, GstPad*, gpointer);
static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static GstPadProbeReturn DecodebinProbe(GstPad*, GstPadProbeInfo*, gpointer);
static void SourceDrainedCallback(GstURIDecodeBin*, gpointer);
static void SourceSetupCallback(GstURIDecodeBin*, GParamSpec *pspec, gpointer);
static void TaskEnterCallback(GstTask*, GThread*, gpointer);
void TagMessageReceived(GstMessage*);
void ErrorMessageReceived(GstMessage*);
void ElementMessageReceived(GstMessage*);
void StateChangedMessageReceived(GstMessage*);
void BufferingMessageReceived(GstMessage*);
void StreamStatusMessageReceived(GstMessage*);
QString ParseTag(GstTagList *list, const char *tag) const;
bool Init();
GstElement *CreateDecodeBinFromString(const char *pipeline);
void UpdateVolume();
void UpdateEqualizer();
void UpdateStereoBalance();
bool ReplaceDecodeBin(GstElement *new_bin);
bool ReplaceDecodeBin(const QUrl &url);
void TransitionToNext();
// If the decodebin is special (ie. not really a uridecodebin) then it'll have
// a src pad immediately and we can link it after everything's created.
void MaybeLinkDecodeToAudio();
private slots:
void FaderTimelineFinished();
private:
static const int kGstStateTimeoutNanosecs;
static const int kFaderFudgeMsec;
static const int kEqBandCount;
static const int kEqBandFrequencies[];
static GstElementDeleter *sElementDeleter;
GstEngine *engine_;
// Using == to compare two pipelines is a bad idea, because new ones often
// get created in the same address as old ones. This ID will be unique for
// each pipeline.
// Threading warning: access to the static ID field isn't protected by a
// mutex because all pipeline creation is currently done in the main thread.
static int sId;
int id_;
// General settings for the pipeline
bool valid_;
QString sink_;
QVariant device_;
// These get called when there is a new audio buffer available
QList<BufferConsumer*> buffer_consumers_;
QMutex buffer_consumers_mutex_;
qint64 segment_start_;
bool segment_start_received_;
bool emit_track_ended_on_stream_start_;
bool emit_track_ended_on_time_discontinuity_;
qint64 last_buffer_offset_;
// Equalizer
bool eq_enabled_;
int eq_preamp_;
QList<int> eq_band_gains_;
// Stereo balance.
// From -1.0 - 1.0
// -1.0 is left, 1.0 is right.
float stereo_balance_;
// ReplayGain
bool rg_enabled_;
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
// Buffering
quint64 buffer_duration_nanosec_;
int buffer_min_fill_;
bool buffering_;
bool mono_playback_;
// The URL that is currently playing, and the URL that is to be preloaded
// when the current track is close to finishing.
QUrl url_;
QUrl next_url_;
// If this is > 0 then the pipeline will be forced to stop when playback goes
// past this position.
qint64 end_offset_nanosec_;
// We store the beginning and end for the preloading song too, so we can just
// carry on without reloading the file if the sections carry on from each
// other.
qint64 next_beginning_offset_nanosec_;
qint64 next_end_offset_nanosec_;
// Set temporarily when moving to the next contiguous section in a multi-part
// file.
bool ignore_next_seek_;
// Set temporarily when switching out the decode bin, so metadata doesn't
// get sent while the Player still thinks it's playing the last song
bool ignore_tags_;
// When the gstreamer source requests a redirect we store the URL here and
// callers can pick it up after the state change to PLAYING fails.
QUrl redirect_url_;
// When we need to specify the device to use as source (for CD device)
QString source_device_;
// Seeking while the pipeline is in the READY state doesn't work, so we have
// to wait until it goes to PAUSED or PLAYING.
// Also we have to wait for the decodebin to be connected.
bool pipeline_is_initialised_;
bool pipeline_is_connected_;
qint64 pending_seek_nanosec_;
// We can only use gst_element_query_position() when the pipeline is in
// PAUSED nor PLAYING state. Whenever we get a new position (e.g. after a
// correct call to gst_element_query_position() or after a seek), we store
// it here so that we can use it when using gst_element_query_position() is
// not possible.
mutable gint64 last_known_position_ns_;
int volume_percent_;
qreal volume_modifier_;
std::unique_ptr<QTimeLine> fader_;
QBasicTimer fader_fudge_timer_;
bool use_fudge_timer_;
GstElement *pipeline_;
// Bins
// uridecodebin ! audiobin
GstElement *uridecodebin_;
GstElement *audiobin_;
// Elements in the audiobin. See comments in Init()'s definition.
GstElement *queue_;
GstElement *audioconvert_;
GstElement *rgvolume_;
GstElement *rglimiter_;
GstElement *audioconvert2_;
GstElement *equalizer_preamp_;
GstElement *equalizer_;
GstElement *stereo_panorama_;
GstElement *volume_;
GstElement *audioscale_;
GstElement *audiosink_;
uint bus_cb_id_;
QThreadPool set_state_threadpool_;
GstSegment last_decodebin_segment_;
};
#endif // GSTENGINEPIPELINE_H

View File

@@ -0,0 +1,110 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#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,37 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef OSXDEVICEFINDER_H
#define OSXDEVICEFINDER_H
#include "config.h"
#include "engine/devicefinder.h"
class OsxDeviceFinder : public DeviceFinder {
public:
OsxDeviceFinder();
virtual bool Initialise() { return true; }
virtual QList<Device> ListDevices();
};
#endif // OSXDEVICEFINDER_H

159
src/engine/phononengine.cpp Normal file
View File

@@ -0,0 +1,159 @@
/* This file is part of Strawberry.
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <QTimer>
#include "phononengine.h"
#include "core/logging.h"
#include "core/taskmanager.h"
PhononEngine::PhononEngine(TaskManager *task_manager)
: media_object_(new Phonon::MediaObject(this)),
audio_output_(new Phonon::AudioOutput(Phonon::MusicCategory, this)),
state_timer_(new QTimer(this)),
seek_offset_(-1)
{
Phonon::createPath(media_object_, audio_output_);
connect(media_object_, SIGNAL(finished()), SLOT(PhononFinished()));
connect(media_object_, SIGNAL(stateChanged(Phonon::State,Phonon::State)), SLOT(PhononStateChanged(Phonon::State)));
state_timer_->setSingleShot(true);
connect(state_timer_, SIGNAL(timeout()), SLOT(StateTimeoutExpired()));
}
PhononEngine::~PhononEngine() {
delete media_object_;
delete audio_output_;
}
bool PhononEngine::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
type_ = Engine::Phonon;
return true;
}
bool PhononEngine::CanDecode(const QUrl &url) {
// TODO
return true;
}
bool PhononEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
media_object_->setCurrentSource(Phonon::MediaSource(url));
return true;
}
bool PhononEngine::Play(quint64 offset_nanosec) {
// The seek happens in PhononStateChanged - phonon doesn't seem to change
// currentTime() if we seek before we start playing :S
seek_offset_ = offset_nanosec;
media_object_->play();
return true;
}
void PhononEngine::Stop(bool stop_after) {
media_object_->stop();
}
void PhononEngine::Pause() {
media_object_->pause();
}
void PhononEngine::Unpause() {
media_object_->play();
}
Engine::State PhononEngine::state() const {
switch (media_object_->state()) {
case Phonon::LoadingState:
case Phonon::PlayingState:
case Phonon::BufferingState:
return Engine::Playing;
case Phonon::PausedState:
return Engine::Paused;
case Phonon::StoppedState:
case Phonon::ErrorState:
default:
return Engine::Empty;
}
}
uint PhononEngine::position() const {
return media_object_->currentTime();
}
uint PhononEngine::length() const {
return media_object_->totalTime();
}
void PhononEngine::Seek(quint64 offset_nanosec) {
media_object_->seek(offset_nanosec);
}
void PhononEngine::SetVolumeSW(uint volume) {
audio_output_->setVolume(volume);
}
void PhononEngine::PhononFinished() {
emit TrackEnded();
}
void PhononEngine::PhononStateChanged(Phonon::State new_state) {
if (new_state == Phonon::ErrorState) {
emit Error(media_object_->errorString());
}
if (new_state == Phonon::PlayingState && seek_offset_ != -1) {
media_object_->seek(seek_offset_);
seek_offset_ = -1;
}
// Don't emit the state change straight away
state_timer_->start(100);
}
void PhononEngine::StateTimeoutExpired() {
emit StateChanged(state());
}
qint64 PhononEngine::position_nanosec() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
return 0;
}
qint64 PhononEngine::length_nanosec() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
return 0;
}

73
src/engine/phononengine.h Normal file
View File

@@ -0,0 +1,73 @@
/* This file is part of Strawberry.
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PHONONENGINE_H
#define PHONONENGINE_H
#include "config.h"
#include "enginebase.h"
#include <phonon/mediaobject.h>
#include <phonon/audiooutput.h>
class QTimer;
class TaskManager;
class PhononEngine : public Engine::Base {
Q_OBJECT
public:
PhononEngine(TaskManager *task_manager);
~PhononEngine();
bool Init();
bool CanDecode(const QUrl &url);
bool Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false);
void Pause();
void Unpause();
Engine::State state() const;
uint position() const;
uint length() const;
void Seek(quint64 offset_nanosec);
qint64 position_nanosec() const;
qint64 length_nanosec() const;
protected:
void SetVolumeSW( uint percent );
private slots:
void PhononFinished();
void PhononStateChanged(Phonon::State new_state);
void StateTimeoutExpired();
private:
Phonon::MediaObject *media_object_;
Phonon::AudioOutput *audio_output_;
QTimer *state_timer_;
qint64 seek_offset_;
};
#endif // PHONONENGINE_H

View File

@@ -0,0 +1,142 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QMutex>
#include <pulse/context.h>
#include <pulse/introspect.h>
#include <pulse/mainloop.h>
#include "core/logging.h"
#include "engine/pulsedevicefinder.h"
PulseDeviceFinder::PulseDeviceFinder() : DeviceFinder("pulsesink"), mainloop_(nullptr), context_(nullptr) {
}
bool PulseDeviceFinder::Initialise() {
mainloop_ = pa_mainloop_new();
if (!mainloop_) {
qLog(Warning) << "Failed to create pulseaudio mainloop";
return false;
}
return Reconnect();
}
bool PulseDeviceFinder::Reconnect() {
if (context_) {
pa_context_disconnect(context_);
pa_context_unref(context_);
}
context_ = pa_context_new(pa_mainloop_get_api(mainloop_), "Strawberry device finder");
if (!context_) {
qLog(Warning) << "Failed to create pulseaudio context";
return false;
}
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
qLog(Warning) << "Failed to connect pulseaudio context";
return false;
}
// Wait for the context to be connected.
forever {
const pa_context_state state = pa_context_get_state(context_);
if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
qLog(Warning) << "Connection to pulseaudio failed";
return false;
}
if (state == PA_CONTEXT_READY) {
return true;
}
pa_mainloop_iterate(mainloop_, true, nullptr);
}
}
QList<DeviceFinder::Device> PulseDeviceFinder::ListDevices() {
if (!context_ || pa_context_get_state(context_) != PA_CONTEXT_READY) {
return QList<Device>();
}
retry:
ListDevicesState state;
pa_context_get_sink_info_list(context_, &PulseDeviceFinder::GetSinkInfoCallback, &state);
forever {
if (state.finished) {
return state.devices;
}
switch (pa_context_get_state(context_)) {
case PA_CONTEXT_READY:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
// Maybe pulseaudio died. Try reconnecting.
if (Reconnect()) {
goto retry;
}
return state.devices;
default:
return state.devices;
}
pa_mainloop_iterate(mainloop_, true, nullptr);
}
}
void PulseDeviceFinder::GetSinkInfoCallback(pa_context *c, const pa_sink_info *info, int eol, void *state_voidptr) {
ListDevicesState *state = reinterpret_cast<ListDevicesState*>(state_voidptr);
if (info) {
Device dev;
dev.device_property_value = QString::fromUtf8(info->name);
dev.description = QString::fromUtf8(info->description);
dev.iconname = QString::fromUtf8(pa_proplist_gets(info->proplist, "device.icon_name"));
state->devices.append(dev);
}
if (eol) {
state->finished = true;
}
}
PulseDeviceFinder::~PulseDeviceFinder() {
if (context_) {
pa_context_disconnect(context_);
pa_context_unref(context_);
}
if (mainloop_) {
pa_mainloop_free(mainloop_);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PULSEDEVICEFINDER_H
#define PULSEDEVICEFINDER_H
#include "config.h"
#include <QMutex>
#include <QList>
#include <pulse/context.h>
#include <pulse/introspect.h>
#include <pulse/mainloop.h>
#include "engine/devicefinder.h"
class PulseDeviceFinder : public DeviceFinder {
public:
PulseDeviceFinder();
~PulseDeviceFinder();
virtual bool Initialise();
virtual QList<Device> ListDevices();
private:
struct ListDevicesState {
ListDevicesState() : finished(false) {}
bool finished;
QList<Device> devices;
};
bool Reconnect();
static void GetSinkInfoCallback(pa_context *c, const pa_sink_info *info, int eol, void *state_voidptr);
pa_mainloop *mainloop_;
pa_context *context_;
};
#endif // PULSEDEVICEFINDER_H

335
src/engine/vlcengine.cpp Normal file
View File

@@ -0,0 +1,335 @@
/* This file is part of Clementine.
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 "config.h"
#include <boost/bind.hpp>
#include <QTimer>
#include <QtDebug>
#include <QMutexLocker>
#include <QTime>
#include "vlcengine.h"
#include "vlcscopedref.h"
#include "core/taskmanager.h"
#include "core/logging.h"
VLCEngine* VLCEngine::sInstance = NULL;
VLCEngine::VLCEngine(TaskManager *task_manager)
: instance_(NULL),
player_(NULL),
scope_data_(4096),
state_(Engine::Empty)
{
//qLog(Debug) << __PRETTY_FUNCTION__;
#if 1
static const char * const args[] = {
"-I", "dummy", // Don't use any interface
"--ignore-config", // Don't use VLC's config
"--extraintf=logger", // log anything
"--verbose=2", // be much more verbose then normal for debugging purpose
// Our scope plugin
"--audio-filter=clementine_scope",
"--no-plugins-cache",
// Try to stop audio stuttering
"--file-caching=500", // msec
"--http-caching=500",
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
"--aout=alsa", // The default, pulseaudio, is buggy
#endif
};
#endif
// Create the VLC instance
//libvlc_exception_init(&exception_);
instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args);
HandleErrors();
// Create the media player
player_ = libvlc_media_player_new(instance_);
HandleErrors();
// Add event handlers
libvlc_event_manager_t* player_em = libvlc_media_player_event_manager(player_);
HandleErrors();
AttachCallback(player_em, libvlc_MediaPlayerEncounteredError, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerNothingSpecial, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerOpening, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerBuffering, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerPlaying, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerPaused, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerStopped, StateChangedCallback);
AttachCallback(player_em, libvlc_MediaPlayerEndReached, StateChangedCallback);
HandleErrors();
sInstance = this;
}
VLCEngine::~VLCEngine() {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_media_player_stop(player_);
libvlc_media_player_release(player_);
libvlc_release(instance_);
HandleErrors();
}
void VLCEngine::AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback) {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_event_attach(em, type, callback, this);
HandleErrors();
}
void VLCEngine::StateChangedCallback(const libvlc_event_t* e, void* data) {
//qLog(Debug) << __PRETTY_FUNCTION__;
VLCEngine* engine = reinterpret_cast<VLCEngine*>(data);
switch (e->type) {
case libvlc_MediaPlayerNothingSpecial:
case libvlc_MediaPlayerStopped:
case libvlc_MediaPlayerEncounteredError:
engine->state_ = Engine::Empty;
break;
case libvlc_MediaPlayerOpening:
case libvlc_MediaPlayerBuffering:
case libvlc_MediaPlayerPlaying:
engine->state_ = Engine::Playing;
break;
case libvlc_MediaPlayerPaused:
engine->state_ = Engine::Paused;
break;
case libvlc_MediaPlayerEndReached:
engine->state_ = Engine::Idle;
emit engine->TrackEnded();
return; // Don't emit state changed here
}
emit engine->StateChanged(engine->state_);
}
bool VLCEngine::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
type_ = Engine::VLC;
return true;
}
bool VLCEngine::CanDecode(const QUrl &url) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// TODO
return true;
}
bool VLCEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Create the media object
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, url.toEncoded().constData()));
//if (libvlc_exception_raised(&exception_))
//return false;
libvlc_media_player_set_media(player_, media);
//if (libvlc_exception_raised(&exception_))
//return false;
return true;
}
bool VLCEngine::Play(quint64 offset_nanosec) {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_media_player_play(player_);
//if (libvlc_exception_raised(&exception_))
//return false;
Seek(offset_nanosec);
return true;
}
void VLCEngine::Stop(bool stop_after) {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_media_player_stop(player_);
HandleErrors();
}
void VLCEngine::Pause() {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_media_player_pause(player_);
HandleErrors();
}
void VLCEngine::Unpause() {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_media_player_play(player_);
HandleErrors();
}
uint VLCEngine::position() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
//bool is_playing = libvlc_media_player_is_playing(player_, const_cast<libvlc_exception_t*>(&exception_));
bool is_playing = libvlc_media_player_is_playing(player_);
HandleErrors();
if (!is_playing)
return 0;
//float pos = libvlc_media_player_get_position(player_, const_cast<libvlc_exception_t*>(&exception_));
float pos = libvlc_media_player_get_position(player_);
HandleErrors();
return pos * length();
}
uint VLCEngine::length() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
//bool is_playing = libvlc_media_player_is_playing(player_, const_cast<libvlc_exception_t*>(&exception_));
bool is_playing = libvlc_media_player_is_playing(player_);
HandleErrors();
if (!is_playing)
return 0;
//libvlc_time_t len = libvlc_media_player_get_length(player_, const_cast<libvlc_exception_t*>(&exception_));
libvlc_time_t len = libvlc_media_player_get_length(player_);
HandleErrors();
return len;
}
void VLCEngine::Seek(quint64 offset_nanosec) {
//qLog(Debug) << __PRETTY_FUNCTION__;
uint len = length();
if (len == 0)
return;
float pos = float(offset_nanosec) / len;
libvlc_media_player_set_position(player_, pos);
HandleErrors();
}
void VLCEngine::SetVolumeSW(uint percent) {
//qLog(Debug) << __PRETTY_FUNCTION__;
libvlc_audio_set_volume(player_, percent);
HandleErrors();
}
void VLCEngine::HandleErrors() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
//if (libvlc_exception_raised(&exception_)) {
//qFatal("libvlc error: %s", libvlc_exception_get_message(&exception_));
//}
}
void VLCEngine::SetScopeData(float* data, int size) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (!sInstance)
return;
QMutexLocker l(&sInstance->scope_mutex_);
// This gets called by our VLC plugin. Just push the data on to the end of
// the circular buffer and let it get consumed by scope()
for (int i=0 ; i<size ; ++i) {
sInstance->scope_data_.push_back(data[i]);
}
}
const Engine::Scope& VLCEngine::Scope() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QMutexLocker l(&scope_mutex_);
// Leave the scope unchanged if there's not enough data
if (scope_data_.size() < uint(kScopeSize))
return scope_;
// Take the samples off the front of the circular buffer
for (uint i=0 ; i<uint(kScopeSize) ; ++i)
scope_[i] = scope_data_[i] * (1 << 15);
// Remove the samples from the buffer. Unfortunately I think this is O(n) :(
scope_data_.rresize(qMax(0, int(scope_data_.size()) - kScopeSize*2));
return scope_;
}
qint64 VLCEngine::position_nanosec() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
return 0;
}
qint64 VLCEngine::length_nanosec() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
return 0;
}

85
src/engine/vlcengine.h Normal file
View File

@@ -0,0 +1,85 @@
/* This file is part of Clementine.
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 VLCENGINE_H
#define VLCENGINE_H
#include "config.h"
#include "enginebase.h"
#include <vlc/vlc.h>
#include <boost/circular_buffer.hpp>
#include <QMutex>
class QTimer;
class TaskManager;
class VLCEngine : public Engine::Base {
Q_OBJECT
public:
VLCEngine(TaskManager *task_manager);
~VLCEngine();
bool Init();
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
bool CanDecode( const QUrl &url );
bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false);
void Pause();
void Unpause();
Engine::State state() const { return state_; }
uint position() const;
uint length() const;
void Seek(quint64 offset_nanosec);
static void SetScopeData(float* data, int size);
const Engine::Scope& Scope();
protected:
void SetVolumeSW(uint percent);
private:
void HandleErrors() const;
void AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback);
static void StateChangedCallback(const libvlc_event_t* e, void* data);
private:
// The callbacks need access to this
static VLCEngine *sInstance;
// VLC bits and pieces
//libvlc_exception_t exception_;
libvlc_instance_t *instance_;
libvlc_media_player_t *player_;
// Our clementine_scope VLC plugin puts data in here
QMutex scope_mutex_;
boost::circular_buffer<float> scope_data_;
Engine::State state_;
};
#endif // VLCENGINE_H

67
src/engine/vlcscopedref.h Normal file
View File

@@ -0,0 +1,67 @@
/* This file is part of Clementine.
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 VLCSCOPEDREF_H
#define VLCSCOPEDREF_H
#include "config.h"
#include <vlc/vlc.h>
template <typename T>
class VlcScopedRef {
public:
VlcScopedRef(T* ptr);
~VlcScopedRef();
operator T* () const { return ptr_; }
operator bool () const { return ptr_; }
T* operator ->() const { return ptr_; }
private:
VlcScopedRef(VlcScopedRef&) {}
VlcScopedRef& operator =(const VlcScopedRef&) { return *this; }
T* ptr_;
};
#define VLCSCOPEDREF_DEFINE2(type, dtor) \
template <> void VlcScopedRef_Release<libvlc_##type##_t>(libvlc_##type##_t* ptr) { \
dtor(ptr); \
}
#define VLCSCOPEDREF_DEFINE(type) VLCSCOPEDREF_DEFINE2(type, libvlc_##type##_release)
template <typename T>
void VlcScopedRef_Release(T* ptr);
VLCSCOPEDREF_DEFINE2(instance, libvlc_release);
VLCSCOPEDREF_DEFINE(media_player);
VLCSCOPEDREF_DEFINE(media);
template <> void VlcScopedRef_Release<char>(char* ptr) { free(ptr); }
template <typename T>
VlcScopedRef<T>::VlcScopedRef(T* ptr)
: ptr_(ptr) {
}
template <typename T>
VlcScopedRef<T>::~VlcScopedRef() {
VlcScopedRef_Release(ptr_);
}
#endif // VLCSCOPEDREF_H

1437
src/engine/xineengine.cpp Normal file

File diff suppressed because it is too large Load Diff

211
src/engine/xineengine.h Normal file
View File

@@ -0,0 +1,211 @@
/***************************************************************************
* Copyright (C) 2004,5 Max Howell <max.howell@methylblue.com> *
* *
* This program 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef XINE_ENGINE_H
#define XINE_ENGINE_H
#include "config.h"
#include "enginebase.h"
#include <QThread>
#include <QEvent>
#include <QSettings>
#include <QMutex>
extern "C"
{
#include <sys/types.h>
#include <xine.h>
}
class XineConfigDialog;
//class DeviceFinder;
class TaskManager;
class XineEvent : public QEvent {
public:
enum EventType {
PlaybackFinished = QEvent::User + 1,
InfoMessage,
StatusMessage,
MetaInfoChanged,
Redirecting,
LastFMTrackChanged,
};
XineEvent(EventType type, void* data = NULL) : QEvent(QEvent::Type(type)), data_(data) {}
void setData(void* data) { data_ = data; }
void* data() const { return data_; }
private:
void* data_;
};
class PruneScopeThread;
class XineEngine : public Engine::Base {
Q_OBJECT
public:
XineEngine(TaskManager *task_manager);
~XineEngine();
friend class Fader;
friend class OutFader;
friend class PruneScopeThread;
virtual bool Init();
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
virtual bool CanDecode( const QUrl &);
virtual bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
virtual bool Play(quint64 offset_nanosec);
virtual void Stop(bool stop_after = false);
virtual void Pause();
virtual void Unpause();
virtual uint position() const;
virtual uint length() const;
virtual void Seek(quint64 offset_nanosec);
virtual bool metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
virtual bool getAudioCDContents(const QString &device, QList<QUrl> &urls);
virtual bool flushBuffer();
virtual Engine::State state() const;
virtual const Engine::Scope& scope(int chunk_length);
virtual void setEqualizerEnabled( bool );
virtual void setEqualizerParameters( int preamp, const QList<int>& );
virtual void SetVolumeSW( uint );
virtual void fadeOut( uint fadeLength, bool* terminate, bool exiting = false );
static void XineEventListener( void*, const xine_event_t* );
virtual bool event( QEvent* );
virtual void playlistChanged();
virtual void ReloadSettings();
Engine::SimpleMetaBundle fetchMetaData() const;
virtual bool lastFmProxyRequired();
bool makeNewStream();
bool ensureStream();
void determineAndShowErrorMessage(); //call after failure to load/play
//static void SetOutput(QString output, QString device);
xine_t *xine_;
xine_stream_t *stream_;
xine_audio_port_t *audioPort_;
xine_event_queue_t *eventQueue_;
xine_post_t *post_;
int64_t currentVpts_;
float preamp_;
bool stopFader_;
bool fadeOutRunning_;
QString currentAudioPlugin_; //to see if audio plugin has been changed need to save these for when the audio plugin is changed and xine reloaded
QString currentAudioDevice_;
bool equalizerEnabled_;
int intPreamp_;
QList<int> equalizerGains_;
QMutex initMutex_;
//QSettings settings_;
bool fadeoutOnExit_;
bool fadeoutEnabled_;
bool crossfadeEnabled_;
int fadeoutDuration_;
int xfadeLength_;
bool xfadeNextTrack_;
QUrl url_;
PruneScopeThread* prune_;
mutable Engine::SimpleMetaBundle currentBundle_;
XineEngine();
OutputDetailsList GetOutputsList() const;
PluginDetailsList GetPluginList() const;
static bool ALSADeviceSupport(const QString &name);
private:
private slots:
void PruneScope();
signals:
void resetConfig(xine_t *xine);
void InfoMessage(const QString&);
void LastFmTrackChange();
};
class Fader : public QThread {
XineEngine *engine_;
xine_t *xine_;
xine_stream_t *decrease_;
xine_stream_t *increase_;
xine_audio_port_t *port_;
xine_post_t *post_;
uint fadeLength_;
bool paused_;
bool terminated_;
virtual void run();
public:
Fader( XineEngine *, uint fadeLengthMs );
~Fader();
void pause();
void resume();
void finish();
};
class OutFader : public QThread {
XineEngine *engine_;
bool terminated_;
uint fadeLength_;
virtual void run();
public:
OutFader( XineEngine *, uint fadeLengthMs );
~OutFader();
void finish();
};
class PruneScopeThread : public QThread {
public:
PruneScopeThread(XineEngine *parent);
protected:
virtual void run();
private:
XineEngine* engine_;
};
#endif

188
src/engine/xinescope.c Normal file
View File

@@ -0,0 +1,188 @@
/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004
Copyright: See COPYING file that comes with this distribution
This has to be a c file or for some reason it won't link! (GCC 3.4.1)
*/
/* gcc doesn't like inline for me */
#define inline
/* need access to port_ticket */
#define XINE_ENGINE_INTERNAL
#include "config.h"
#include "xinescope.h"
#include <xine/post.h>
#include <xine/xine_internal.h>
typedef struct scope_plugin_s scope_plugin_t;
struct scope_plugin_s
{
post_plugin_t post;
metronom_t metronom;
int channels;
MyNode *list;
};
/*************************
* post plugin functions *
*************************/
static int
scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode )
{
#define port ((post_audio_port_t*)port_gen)
#define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post)
_x_post_rewire( (post_plugin_t*)port->post );
_x_post_inc_usage( port );
port->stream = stream;
port->bits = bits;
port->rate = rate;
port->mode = mode;
this->channels = _x_ao_mode2channels( mode );
return port->original_port->open( port->original_port, stream, bits, rate, mode );
}
static void
scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream )
{
MyNode *node;
/* ensure the buffers are deleted during the next XineEngine::timerEvent() */
for( node = this->list->next; node != this->list; node = node->next )
node->vpts = node->vpts_end = -1;
port->stream = NULL;
port->original_port->close( port->original_port, stream );
_x_post_dec_usage( port );
}
static void
scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream )
{
/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path,
the sample size could be checked like this: if( port->bits == 8 ) */
const int num_samples = buf->num_frames * this->channels;
metronom_t *myMetronom = &this->metronom;
MyNode *new_node;
/* I keep my own metronom because xine wouldn't for some reason */
memcpy( &this->metronom, stream->metronom, sizeof(metronom_t) );
new_node = malloc( sizeof(MyNode) );
new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames );
new_node->num_frames = buf->num_frames;
new_node->mem = malloc( num_samples * 2 );
memcpy( new_node->mem, buf->mem, num_samples * 2 );
{
int64_t
K = myMetronom->pts_per_smpls; /*smpls = 1<<16 samples*/
K *= num_samples;
K /= (1<<16);
K += new_node->vpts;
new_node->vpts_end = K;
}
port->original_port->put_buffer( port->original_port, buf, stream );
/* finally we should append the current buffer to the list
* this is thread-safe due to the way we handle the list in the GUI thread */
new_node->next = this->list->next;
this->list->next = new_node;
#undef port
#undef this
}
static void
scope_dispose( post_plugin_t *this )
{
MyNode *list = ((scope_plugin_t*)this)->list;
MyNode *prev;
MyNode *node = list;
/* Free all elements of the list (a ring buffer) */
do {
prev = node->next;
free( node->mem );
free( node );
node = prev;
}
while( node != list );
free( this );
}
/************************
* plugin init function *
************************/
xine_post_t*
scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target )
{
scope_plugin_t *scope_plugin = calloc( 1, sizeof(scope_plugin_t) );
post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin;
{
post_in_t *input;
post_out_t *output;
post_audio_port_t *port;
_x_post_init( post_plugin, 1, 0 );
port = _x_post_intercept_audio_port( post_plugin, audio_target, &input, &output );
port->new_port.open = scope_port_open;
port->new_port.close = scope_port_close;
port->new_port.put_buffer = scope_port_put_buffer;
post_plugin->xine_post.audio_input[0] = &port->new_port;
post_plugin->xine_post.type = PLUGIN_POST;
post_plugin->dispose = scope_dispose;
}
/* code is straight from xine_init_post()
can't use that function as it only dlopens the plugins
and our plugin is statically linked in */
post_plugin->running_ticket = xine->port_ticket;
post_plugin->xine = xine;
/* scope_plugin_t init */
scope_plugin->list = calloc( 1, sizeof(MyNode) );
scope_plugin->list->next = scope_plugin->list;
return &post_plugin->xine_post;
}
MyNode*
scope_plugin_list( void *post )
{
return ((scope_plugin_t*)post)->list;
}
int
scope_plugin_channels( void *post )
{
return ((scope_plugin_t*)post)->channels;
}
metronom_t*
scope_plugin_metronom( void *post )
{
return &((scope_plugin_t*)post)->metronom;
}

52
src/engine/xinescope.h Normal file
View File

@@ -0,0 +1,52 @@
/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004
Copyright: See COPYING file that comes with this distribution
This has to be a c file or for some reason it won't link! (GCC 3.4.1)
*/
#ifndef XINESCOPE_H
#define XINESCOPE_H
/* need access to some stuff for scope time stamping */
#define METRONOM_INTERNAL
#include "config.h"
#include <sys/types.h>
#include <xine/metronom.h>
typedef struct my_node_s MyNode;
struct my_node_s
{
MyNode *next;
int16_t *mem;
int num_frames;
int64_t vpts;
int64_t vpts_end;
};
#ifdef __cplusplus
extern "C"
{
#endif
xine_post_t*
scope_plugin_new( xine_t*, xine_audio_port_t* );
/* we sacrifice type-safety here because some GCCs appear broken
* and choke on redefining the xine_post_t typedef
*/
MyNode*
scope_plugin_list( void* );
int
scope_plugin_channels( void* );
metronom_t*
scope_plugin_metronom( void* );
#ifdef __cplusplus
}
#endif
#endif