Initial commit.
This commit is contained in:
118
src/engine/alsadevicefinder.cpp
Normal file
118
src/engine/alsadevicefinder.cpp
Normal 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;
|
||||
}
|
||||
35
src/engine/alsadevicefinder.h
Normal file
35
src/engine/alsadevicefinder.h
Normal 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
|
||||
40
src/engine/bufferconsumer.h
Normal file
40
src/engine/bufferconsumer.h
Normal 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
|
||||
57
src/engine/devicefinder.cpp
Normal file
57
src/engine/devicefinder.cpp
Normal 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
64
src/engine/devicefinder.h
Normal 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
|
||||
|
||||
58
src/engine/directsounddevicefinder.cpp
Normal file
58
src/engine/directsounddevicefinder.cpp
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
49
src/engine/directsounddevicefinder.h
Normal file
49
src/engine/directsounddevicefinder.h
Normal 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
42
src/engine/engine_fwd.h
Normal 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
105
src/engine/enginebase.cpp
Normal 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
186
src/engine/enginebase.h
Normal 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
|
||||
93
src/engine/enginedevice.cpp
Normal file
93
src/engine/enginedevice.cpp
Normal 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
54
src/engine/enginedevice.h
Normal 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
53
src/engine/enginetype.cpp
Normal 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
44
src/engine/enginetype.h
Normal 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
|
||||
35
src/engine/gstelementdeleter.cpp
Normal file
35
src/engine/gstelementdeleter.cpp
Normal 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);
|
||||
}
|
||||
48
src/engine/gstelementdeleter.h
Normal file
48
src/engine/gstelementdeleter.h
Normal 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
983
src/engine/gstengine.cpp
Normal 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
220
src/engine/gstengine.h
Normal 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 */
|
||||
1199
src/engine/gstenginepipeline.cpp
Normal file
1199
src/engine/gstenginepipeline.cpp
Normal file
File diff suppressed because it is too large
Load Diff
300
src/engine/gstenginepipeline.h
Normal file
300
src/engine/gstenginepipeline.h
Normal 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
|
||||
110
src/engine/osxdevicefinder.cpp
Normal file
110
src/engine/osxdevicefinder.cpp
Normal 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;
|
||||
}
|
||||
|
||||
37
src/engine/osxdevicefinder.h
Normal file
37
src/engine/osxdevicefinder.h
Normal 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
159
src/engine/phononengine.cpp
Normal 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
73
src/engine/phononengine.h
Normal 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
|
||||
142
src/engine/pulsedevicefinder.cpp
Normal file
142
src/engine/pulsedevicefinder.cpp
Normal 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_);
|
||||
}
|
||||
}
|
||||
|
||||
60
src/engine/pulsedevicefinder.h
Normal file
60
src/engine/pulsedevicefinder.h
Normal 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
335
src/engine/vlcengine.cpp
Normal 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
85
src/engine/vlcengine.h
Normal 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
67
src/engine/vlcscopedref.h
Normal 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
1437
src/engine/xineengine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
211
src/engine/xineengine.h
Normal file
211
src/engine/xineengine.h
Normal 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
188
src/engine/xinescope.c
Normal 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
52
src/engine/xinescope.h
Normal 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
|
||||
Reference in New Issue
Block a user