2010-04-06 18:57:02 +02:00
|
|
|
/***************************************************************************
|
|
|
|
* 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. *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#define DEBUG_PREFIX "Gst-Engine"
|
|
|
|
|
2010-08-01 20:09:38 +02:00
|
|
|
#include "config.h"
|
2010-04-06 18:57:02 +02:00
|
|
|
#include "gstengine.h"
|
2010-04-11 21:47:21 +02:00
|
|
|
#include "gstenginepipeline.h"
|
2010-08-04 14:13:43 +02:00
|
|
|
#include "core/boundfuturewatcher.h"
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-08-01 20:09:38 +02:00
|
|
|
#ifdef HAVE_IMOBILEDEVICE
|
|
|
|
# include "gstafcsrc/gstafcsrc.h"
|
|
|
|
#endif
|
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
#include <math.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <vector>
|
2010-04-21 19:11:50 +02:00
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#include <boost/bind.hpp>
|
2010-04-06 18:57:02 +02:00
|
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QRegExp>
|
|
|
|
#include <QFile>
|
2010-04-07 15:51:14 +02:00
|
|
|
#include <QSettings>
|
2010-04-06 18:57:02 +02:00
|
|
|
#include <QtDebug>
|
2010-04-11 16:26:30 +02:00
|
|
|
#include <QCoreApplication>
|
2010-04-11 23:40:26 +02:00
|
|
|
#include <QTimeLine>
|
2010-06-11 14:22:21 +02:00
|
|
|
#include <QDir>
|
2010-08-27 15:57:39 +02:00
|
|
|
#include <QtConcurrentRun>
|
2010-04-06 18:57:02 +02:00
|
|
|
|
|
|
|
#include <gst/gst.h>
|
|
|
|
|
|
|
|
|
|
|
|
using std::vector;
|
2010-04-11 21:47:21 +02:00
|
|
|
using boost::shared_ptr;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
const char* GstEngine::kSettingsGroup = "GstEngine";
|
|
|
|
const char* GstEngine::kAutoSink = "autoaudiosink";
|
2010-07-14 13:16:56 +02:00
|
|
|
const char* GstEngine::kHypnotoadPipeline =
|
|
|
|
"audiotestsrc wave=6 ! "
|
|
|
|
"audioecho intensity=1 delay=50000000 ! "
|
|
|
|
"audioecho intensity=1 delay=25000000 ! "
|
|
|
|
"equalizer-10bands "
|
|
|
|
"band0=-24 band1=-3 band2=7.5 band3=12 band4=8 "
|
|
|
|
"band5=6 band6=5 band7=6 band8=0 band9=-24";
|
2010-04-06 18:57:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
GstEngine::GstEngine()
|
2010-04-07 00:58:41 +02:00
|
|
|
: Engine::Base(),
|
|
|
|
delayq_(g_queue_new()),
|
|
|
|
current_sample_(0),
|
2010-04-22 18:54:09 +02:00
|
|
|
equalizer_enabled_(false),
|
2010-05-23 15:07:15 +02:00
|
|
|
rg_enabled_(false),
|
|
|
|
rg_mode_(0),
|
|
|
|
rg_preamp_(0.0),
|
|
|
|
rg_compression_(true),
|
2010-10-11 18:46:06 +02:00
|
|
|
buffer_duration_ms_(1000),
|
2010-05-03 16:55:00 +02:00
|
|
|
seek_timer_(new QTimer(this)),
|
2010-08-02 20:13:40 +02:00
|
|
|
timer_id_(-1),
|
2010-08-28 20:48:16 +02:00
|
|
|
next_element_id_(0)
|
2010-04-06 18:57:02 +02:00
|
|
|
{
|
2010-04-22 18:54:09 +02:00
|
|
|
seek_timer_->setSingleShot(true);
|
|
|
|
seek_timer_->setInterval(kSeekDelay);
|
|
|
|
connect(seek_timer_, SIGNAL(timeout()), SLOT(SeekNow()));
|
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
ReloadSettings();
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
GstEngine::~GstEngine() {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// Destroy scope delay queue
|
2010-04-12 02:20:52 +02:00
|
|
|
ClearScopeBuffers();
|
2010-04-07 00:58:41 +02:00
|
|
|
g_queue_free(delayq_);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// Save configuration
|
|
|
|
gst_deinit();
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-22 15:49:16 +02:00
|
|
|
void GstEngine::SetEnv(const char *key, const QString &value) {
|
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
putenv(QString("%1=%2").arg(key, value).toLocal8Bit().constData());
|
|
|
|
#else
|
|
|
|
setenv(key, value.toLocal8Bit().constData(), 1);
|
|
|
|
#endif
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
bool GstEngine::Init() {
|
2010-04-21 19:11:50 +02:00
|
|
|
QString scanner_path;
|
|
|
|
QString plugin_path;
|
2010-06-11 14:22:21 +02:00
|
|
|
QString registry_filename;
|
2010-04-21 19:11:50 +02:00
|
|
|
|
2010-04-22 15:49:16 +02:00
|
|
|
// On windows and mac we bundle the gstreamer plugins with clementine
|
2010-04-21 19:11:50 +02:00
|
|
|
#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
|
|
|
|
|
2010-06-11 14:22:21 +02:00
|
|
|
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
|
|
|
registry_filename = QString("%1/.config/%2/gst-registry-%3.bin").arg(
|
|
|
|
QDir::homePath(), QCoreApplication::organizationName(),
|
|
|
|
QCoreApplication::applicationVersion());
|
|
|
|
#endif
|
|
|
|
|
2010-04-21 19:11:50 +02:00
|
|
|
if (!scanner_path.isEmpty())
|
2010-04-22 15:49:16 +02:00
|
|
|
SetEnv("GST_PLUGIN_SCANNER", scanner_path);
|
2010-06-11 14:22:21 +02:00
|
|
|
|
2010-04-21 19:11:50 +02:00
|
|
|
if (!plugin_path.isEmpty()) {
|
2010-04-22 15:49:16 +02:00
|
|
|
SetEnv("GST_PLUGIN_PATH", plugin_path);
|
2010-04-21 19:11:50 +02:00
|
|
|
// Never load plugins from anywhere else.
|
2010-04-22 15:49:16 +02:00
|
|
|
SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
|
2010-04-21 19:11:50 +02:00
|
|
|
}
|
|
|
|
|
2010-06-11 14:22:21 +02:00
|
|
|
if (!registry_filename.isEmpty()) {
|
|
|
|
SetEnv("GST_REGISTRY", registry_filename);
|
|
|
|
}
|
|
|
|
|
2010-08-27 15:57:39 +02:00
|
|
|
initialising_ = QtConcurrent::run(this, &GstEngine::InitialiseGstreamer);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::InitialiseGstreamer() {
|
|
|
|
gst_init(NULL, NULL);
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-08-01 20:09:38 +02:00
|
|
|
#ifdef HAVE_IMOBILEDEVICE
|
|
|
|
afcsrc_register_static();
|
|
|
|
#endif
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
void GstEngine::ReloadSettings() {
|
2010-04-12 01:03:39 +02:00
|
|
|
Engine::Base::ReloadSettings();
|
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
|
|
|
sink_ = s.value("sink", kAutoSink).toString();
|
2010-04-08 22:14:11 +02:00
|
|
|
device_ = s.value("device").toString();
|
2010-05-04 17:51:23 +02:00
|
|
|
|
2010-08-27 18:41:35 +02:00
|
|
|
if (sink_.isEmpty())
|
|
|
|
sink_ = kAutoSink;
|
2010-05-23 15:07:15 +02:00
|
|
|
|
|
|
|
rg_enabled_ = s.value("rgenabled", false).toBool();
|
|
|
|
rg_mode_ = s.value("rgmode", 0).toInt();
|
2010-05-25 23:33:16 +02:00
|
|
|
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
|
2010-05-23 15:07:15 +02:00
|
|
|
rg_compression_ = s.value("rgcompression", true).toBool();
|
2010-10-11 17:58:05 +02:00
|
|
|
|
|
|
|
buffer_duration_ms_ = s.value("bufferduration", 1000).toInt();
|
2010-04-07 15:51:14 +02:00
|
|
|
}
|
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
bool GstEngine::CanDecode(const QUrl &url) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// We had some bug reports claiming that video files cause crashes in canDecode(),
|
|
|
|
// so don't try to decode them
|
|
|
|
if ( url.path().toLower().endsWith( ".mov" ) ||
|
|
|
|
url.path().toLower().endsWith( ".avi" ) ||
|
|
|
|
url.path().toLower().endsWith( ".wmv" ) )
|
|
|
|
return false;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
can_decode_success_ = false;
|
|
|
|
can_decode_last_ = false;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 14:56:05 +02:00
|
|
|
// Create the pipeline
|
2010-04-21 19:11:50 +02:00
|
|
|
shared_ptr<GstElement> pipeline(gst_pipeline_new("pipeline"),
|
|
|
|
boost::bind(gst_object_unref, _1));
|
|
|
|
if (!pipeline) return false;
|
|
|
|
GstElement* src = CreateElement("giosrc", pipeline.get()); if (!src) return false;
|
2010-05-26 00:25:04 +02:00
|
|
|
GstElement* bin = CreateElement("decodebin2", pipeline.get()); if (!bin) return false;
|
2010-04-21 00:00:02 +02:00
|
|
|
|
|
|
|
gst_element_link(src, bin);
|
|
|
|
g_signal_connect(G_OBJECT(bin), "new-decoded-pad", G_CALLBACK(CanDecodeNewPadCallback), this);
|
|
|
|
g_signal_connect(G_OBJECT(bin), "no-more-pads", G_CALLBACK(CanDecodeLastCallback), this);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-21 19:11:50 +02:00
|
|
|
// These handlers just print out errors to stderr
|
|
|
|
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline.get())), CanDecodeBusCallbackSync, 0);
|
|
|
|
gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline.get())), CanDecodeBusCallback, 0);
|
|
|
|
|
2010-04-07 14:56:05 +02:00
|
|
|
// Set the file we're testing
|
2010-04-21 00:00:02 +02:00
|
|
|
g_object_set(G_OBJECT(src), "location", url.toEncoded().constData(), NULL);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 14:56:05 +02:00
|
|
|
// Start the pipeline playing
|
2010-04-21 19:11:50 +02:00
|
|
|
gst_element_set_state(pipeline.get(), GST_STATE_PLAYING);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// Wait until found audio stream
|
2010-04-07 14:56:05 +02:00
|
|
|
int count = 0;
|
|
|
|
while (!can_decode_success_ && !can_decode_last_ && count < 100) {
|
2010-04-07 00:58:41 +02:00
|
|
|
count++;
|
|
|
|
usleep(1000);
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 14:56:05 +02:00
|
|
|
// Stop playing
|
2010-04-21 19:11:50 +02:00
|
|
|
gst_element_set_state(pipeline.get(), GST_STATE_NULL);
|
2010-04-07 00:58:41 +02:00
|
|
|
|
|
|
|
return can_decode_success_;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-21 19:11:50 +02:00
|
|
|
|
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
void GstEngine::CanDecodeNewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer self) {
|
2010-04-12 02:20:52 +02:00
|
|
|
GstEngine* instance = reinterpret_cast<GstEngine*>(self);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
GstCaps* caps = gst_pad_get_caps(pad);
|
|
|
|
if (gst_caps_get_size(caps) > 0) {
|
|
|
|
GstStructure* str = gst_caps_get_structure(caps, 0);
|
|
|
|
if (g_strrstr(gst_structure_get_name( str ), "audio" ))
|
|
|
|
instance->can_decode_success_ = true;
|
|
|
|
}
|
|
|
|
gst_caps_unref(caps);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
void GstEngine::CanDecodeLastCallback(GstElement*, gpointer self) {
|
2010-04-12 02:20:52 +02:00
|
|
|
GstEngine* instance = reinterpret_cast<GstEngine*>(self);
|
2010-04-11 21:47:21 +02:00
|
|
|
instance->can_decode_last_ = true;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-21 19:11:50 +02:00
|
|
|
void GstEngine::PrintGstError(GstMessage *msg) {
|
|
|
|
GError* error;
|
|
|
|
gchar* debugs;
|
|
|
|
|
|
|
|
gst_message_parse_error(msg, &error, &debugs);
|
|
|
|
qDebug() << error->message;
|
|
|
|
qDebug() << debugs;
|
|
|
|
|
|
|
|
g_error_free(error);
|
|
|
|
free(debugs);
|
|
|
|
}
|
|
|
|
|
|
|
|
GstBusSyncReply GstEngine::CanDecodeBusCallbackSync(GstBus*, GstMessage* msg, gpointer) {
|
|
|
|
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR)
|
|
|
|
PrintGstError(msg);
|
|
|
|
return GST_BUS_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean GstEngine::CanDecodeBusCallback(GstBus*, GstMessage* msg, gpointer) {
|
|
|
|
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR)
|
|
|
|
PrintGstError(msg);
|
|
|
|
return GST_BUS_DROP;
|
|
|
|
}
|
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
uint GstEngine::position() const {
|
|
|
|
if (!current_pipeline_)
|
|
|
|
return 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
return uint(current_pipeline_->position() / GST_MSECOND);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
uint GstEngine::length() const {
|
|
|
|
if (!current_pipeline_)
|
|
|
|
return 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
return uint(current_pipeline_->length() / GST_MSECOND);
|
|
|
|
}
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
Engine::State GstEngine::state() const {
|
|
|
|
if (!current_pipeline_)
|
2010-04-12 01:24:03 +02:00
|
|
|
return url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
switch (current_pipeline_->state()) {
|
2010-04-07 00:58:41 +02:00
|
|
|
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;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-06-06 16:06:23 +02:00
|
|
|
void GstEngine::ConsumeBuffer(GstBuffer *buffer, GstEnginePipeline* pipeline) {
|
|
|
|
// 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(GstEnginePipeline*, pipeline))) {
|
|
|
|
qWarning() << "Failed to invoke AddBufferToScope on GstEngine";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::AddBufferToScope(GstBuffer* buf, GstEnginePipeline* pipeline) {
|
|
|
|
if (current_pipeline_.get() != pipeline) {
|
2010-04-12 18:41:44 +02:00
|
|
|
gst_buffer_unref(buf);
|
2010-04-12 18:39:48 +02:00
|
|
|
return;
|
2010-04-12 18:41:44 +02:00
|
|
|
}
|
2010-04-12 18:39:48 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
g_queue_push_tail(delayq_, buf);
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
const Engine::Scope& GstEngine::scope() {
|
|
|
|
UpdateScope();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
if (current_sample_ >= kScopeSize) {
|
2010-04-07 00:58:41 +02:00
|
|
|
// ok, we have a full buffer now, so give it to the scope
|
2010-04-12 01:24:03 +02:00
|
|
|
for (int i=0; i< kScopeSize; i++)
|
|
|
|
scope_[i] = current_scope_[i];
|
2010-04-07 00:58:41 +02:00
|
|
|
current_sample_ = 0;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
return scope_;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
void GstEngine::UpdateScope() {
|
2010-04-07 02:18:55 +02:00
|
|
|
typedef int16_t sampletype;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// prune the scope and get the current pos of the audio device
|
2010-07-11 15:31:03 +02:00
|
|
|
const quint64 pos = PruneScope();
|
|
|
|
const quint64 segment_start = current_pipeline_->segment_start();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// head of the delay queue is the most delayed, so we work with that one
|
|
|
|
GstBuffer *buf = reinterpret_cast<GstBuffer *>( g_queue_peek_head(delayq_) );
|
|
|
|
if (!buf)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// start time for this buffer
|
2010-07-11 15:31:03 +02:00
|
|
|
quint64 stime = GST_BUFFER_TIMESTAMP(buf) - segment_start;
|
2010-04-07 00:58:41 +02:00
|
|
|
// duration of the buffer...
|
|
|
|
quint64 dur = GST_BUFFER_DURATION(buf);
|
|
|
|
// therefore we can calculate the end time for the buffer
|
|
|
|
quint64 etime = stime + dur;
|
|
|
|
|
|
|
|
// determine the number of channels
|
|
|
|
GstStructure* structure = gst_caps_get_structure ( GST_BUFFER_CAPS( buf ), 0);
|
|
|
|
int channels = 2;
|
|
|
|
gst_structure_get_int (structure, "channels", &channels);
|
|
|
|
|
|
|
|
// scope does not support >2 channels
|
|
|
|
if (channels > 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// if the audio device is playing this buffer now
|
|
|
|
if (pos <= stime || pos >= etime)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// calculate the number of samples in the buffer
|
|
|
|
int sz = GST_BUFFER_SIZE(buf) / sizeof(sampletype);
|
|
|
|
// number of frames is the number of samples in each channel (frames like in the alsa sense)
|
|
|
|
int frames = sz / channels;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// find the offset into the buffer to the sample closest to where the audio device is playing
|
|
|
|
// it is the (time into the buffer cooresponding to the audio device pos) / (the sample rate)
|
|
|
|
// sample rate = duration of the buffer / number of frames in the buffer
|
|
|
|
// then we multiply by the number of channels to find the offset of the left channel sample
|
|
|
|
// of the frame in the buffer
|
|
|
|
int off = channels * (pos - stime) / (dur / frames);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// note that we are assuming 32 bit samples, but this should probably be generalized...
|
|
|
|
sampletype* data = reinterpret_cast<sampletype *>(GST_BUFFER_DATA(buf));
|
|
|
|
if (off >= sz) // better be...
|
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
int i = off; // starting at offset
|
|
|
|
|
|
|
|
// loop while we fill the current buffer. If we need another buffer and one is available,
|
|
|
|
// get it and keep filling. If there are no more buffers available (not too likely)
|
|
|
|
// then leave everything in this state and wait until the next time the scope updates
|
2010-04-12 01:24:03 +02:00
|
|
|
while (buf && current_sample_ < kScopeSize && i < sz) {
|
|
|
|
for (int j = 0; j < channels && current_sample_ < kScopeSize; j++) {
|
2010-04-07 00:58:41 +02:00
|
|
|
current_scope_[current_sample_ ++] = data[i + j];
|
|
|
|
}
|
|
|
|
i+=channels; // advance to the next frame
|
|
|
|
|
2010-04-09 15:01:20 +02:00
|
|
|
if (i >= sz - 1) {
|
2010-04-07 00:58:41 +02:00
|
|
|
// here we are out of samples in the current buffer, so we get another one
|
|
|
|
buf = reinterpret_cast<GstBuffer *>( g_queue_pop_head(delayq_) );
|
|
|
|
gst_buffer_unref(buf);
|
|
|
|
buf = reinterpret_cast<GstBuffer *>( g_queue_peek_head(delayq_) );
|
|
|
|
if (buf) {
|
|
|
|
stime = GST_BUFFER_TIMESTAMP(buf);
|
|
|
|
dur = GST_BUFFER_DURATION(buf);
|
|
|
|
etime = stime + dur;
|
|
|
|
i = 0;
|
|
|
|
sz = GST_BUFFER_SIZE(buf) / sizeof(sampletype);
|
|
|
|
data = reinterpret_cast<sampletype *>(GST_BUFFER_DATA(buf));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-21 15:55:30 +02:00
|
|
|
void GstEngine::StartPreloading(const QUrl& url) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-05-08 19:39:12 +02:00
|
|
|
if (autocrossfade_enabled_) {
|
|
|
|
// Have to create a new pipeline so we can crossfade between the two
|
|
|
|
|
|
|
|
preload_pipeline_ = CreatePipeline(url);
|
|
|
|
if (!preload_pipeline_)
|
|
|
|
return;
|
2010-04-21 15:55:30 +02:00
|
|
|
|
2010-05-08 19:39:12 +02:00
|
|
|
// We don't want to get metadata messages before the track starts playing -
|
|
|
|
// we reconnect this in GstEngine::Load
|
|
|
|
disconnect(preload_pipeline_.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)), this, 0);
|
2010-04-21 16:04:40 +02:00
|
|
|
|
2010-05-08 19:39:12 +02:00
|
|
|
preloaded_url_ = url;
|
|
|
|
preload_pipeline_->SetState(GST_STATE_PAUSED);
|
|
|
|
} else {
|
|
|
|
// 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(url);
|
|
|
|
}
|
2010-04-21 15:55:30 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 03:59:21 +02:00
|
|
|
bool GstEngine::Load(const QUrl& url, Engine::TrackChangeType change) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-12 03:59:21 +02:00
|
|
|
Engine::Base::Load(url, change);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-27 19:53:52 +02:00
|
|
|
// Clementine just crashes when asked to load a file that doesn't exist on
|
|
|
|
// Windows, so check for that here. This is definitely the wrong place for
|
|
|
|
// this "fix"...
|
2010-05-01 16:01:01 +02:00
|
|
|
if (url.scheme() == "file" && !QFile::exists(url.toLocalFile()))
|
2010-04-27 19:53:52 +02:00
|
|
|
return false;
|
|
|
|
|
2010-06-03 20:39:42 +02:00
|
|
|
QUrl gst_url = 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()) {
|
|
|
|
gst_url.setPath("//" + gst_url.host() + gst_url.path());
|
|
|
|
gst_url.setHost(QString());
|
|
|
|
}
|
|
|
|
|
2010-04-12 03:59:21 +02:00
|
|
|
const bool crossfade = current_pipeline_ &&
|
|
|
|
((crossfade_enabled_ && change == Engine::Manual) ||
|
|
|
|
(autocrossfade_enabled_ && change == Engine::Auto));
|
2010-04-12 01:03:39 +02:00
|
|
|
|
2010-06-03 20:39:42 +02:00
|
|
|
if (!crossfade && current_pipeline_ && current_pipeline_->url() == gst_url &&
|
2010-05-22 19:19:27 +02:00
|
|
|
change == Engine::Auto) {
|
2010-05-08 19:39:12 +02:00
|
|
|
// We're not crossfading, and the pipeline is already playing the URI we
|
|
|
|
// want, so just do nothing.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-04-21 15:55:30 +02:00
|
|
|
shared_ptr<GstEnginePipeline> pipeline;
|
2010-06-03 20:39:42 +02:00
|
|
|
if (preload_pipeline_ && preloaded_url_ == gst_url) {
|
2010-04-21 15:55:30 +02:00
|
|
|
pipeline = preload_pipeline_;
|
2010-04-21 16:04:40 +02:00
|
|
|
connect(preload_pipeline_.get(),
|
|
|
|
SIGNAL(MetadataFound(Engine::SimpleMetaBundle)),
|
|
|
|
SLOT(NewMetaData(Engine::SimpleMetaBundle)));
|
2010-04-21 15:55:30 +02:00
|
|
|
} else {
|
2010-06-03 20:39:42 +02:00
|
|
|
pipeline = CreatePipeline(gst_url);
|
2010-04-21 15:55:30 +02:00
|
|
|
if (!pipeline)
|
|
|
|
return false;
|
|
|
|
}
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-04-12 03:59:21 +02:00
|
|
|
if (crossfade)
|
2010-04-12 01:03:39 +02:00
|
|
|
StartFadeout();
|
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_ = pipeline;
|
2010-04-21 15:55:30 +02:00
|
|
|
preload_pipeline_.reset();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
SetVolume(volume_);
|
|
|
|
SetEqualizerEnabled(equalizer_enabled_);
|
|
|
|
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
|
2010-04-12 01:03:39 +02:00
|
|
|
|
|
|
|
// Maybe fade in this track
|
2010-04-12 03:59:21 +02:00
|
|
|
if (crossfade)
|
2010-04-12 01:03:39 +02:00
|
|
|
current_pipeline_->StartFader(fadeout_duration_, QTimeLine::Forward);
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
return true;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:03:39 +02:00
|
|
|
void GstEngine::StartFadeout() {
|
|
|
|
fadeout_pipeline_ = current_pipeline_;
|
|
|
|
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
|
2010-06-06 16:06:23 +02:00
|
|
|
fadeout_pipeline_->RemoveAllBufferConsumers();
|
2010-04-12 02:20:52 +02:00
|
|
|
ClearScopeBuffers();
|
2010-04-12 01:03:39 +02:00
|
|
|
|
|
|
|
fadeout_pipeline_->StartFader(fadeout_duration_, QTimeLine::Backward);
|
|
|
|
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
|
|
|
|
}
|
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
bool GstEngine::Play( uint offset ) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-09-26 22:59:41 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return false;
|
|
|
|
|
2010-08-04 14:13:43 +02:00
|
|
|
QFuture<GstStateChangeReturn> future = current_pipeline_->SetState(GST_STATE_PLAYING);
|
|
|
|
BoundFutureWatcher<GstStateChangeReturn, uint>* watcher =
|
|
|
|
new BoundFutureWatcher<GstStateChangeReturn, uint>(offset, this);
|
|
|
|
watcher->setFuture(future);
|
|
|
|
connect(watcher, SIGNAL(finished()), SLOT(PlayDone()));
|
2010-06-23 13:47:54 +02:00
|
|
|
|
2010-08-04 14:13:43 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::PlayDone() {
|
|
|
|
BoundFutureWatcher<GstStateChangeReturn, uint>* watcher =
|
|
|
|
static_cast<BoundFutureWatcher<GstStateChangeReturn, uint>*>(sender());
|
|
|
|
watcher->deleteLater();
|
|
|
|
GstStateChangeReturn ret = watcher->result();
|
|
|
|
|
2010-08-04 22:32:53 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
|
|
|
|
2010-08-04 14:13:43 +02:00
|
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
2010-06-23 13:47:54 +02:00
|
|
|
// 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()) {
|
|
|
|
qDebug() << "Redirecting to" << redirect_url;
|
|
|
|
current_pipeline_ = CreatePipeline(redirect_url);
|
2010-08-04 14:13:43 +02:00
|
|
|
Play(watcher->data());
|
|
|
|
return;
|
2010-06-23 13:47:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Failure - give up
|
2010-04-07 00:58:41 +02:00
|
|
|
qWarning() << "Could not set thread to PLAYING.";
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2010-08-04 14:13:43 +02:00
|
|
|
return;
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
StartTimers();
|
2010-05-28 19:14:00 +02:00
|
|
|
|
|
|
|
current_sample_ = 0;
|
2010-08-04 14:13:43 +02:00
|
|
|
|
|
|
|
if (watcher->data()) {
|
|
|
|
Seek(watcher->data());
|
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Playing);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Stop() {
|
2010-06-30 22:36:16 +02:00
|
|
|
StopTimers();
|
2010-05-03 16:15:42 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
url_ = QUrl(); // To ensure we return Empty from state()
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 02:21:41 +02:00
|
|
|
if (fadeout_enabled_ && current_pipeline_)
|
2010-04-12 01:03:39 +02:00
|
|
|
StartFadeout();
|
2010-04-11 23:40:26 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Empty);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-11 23:40:26 +02:00
|
|
|
void GstEngine::FadeoutFinished() {
|
|
|
|
fadeout_pipeline_.reset();
|
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Pause() {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if ( current_pipeline_->state() == GST_STATE_PLAYING ) {
|
|
|
|
current_pipeline_->SetState(GST_STATE_PAUSED);
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Paused);
|
2010-06-25 10:48:19 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
StopTimers();
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Unpause() {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if ( current_pipeline_->state() == GST_STATE_PAUSED ) {
|
|
|
|
current_pipeline_->SetState(GST_STATE_PLAYING);
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Playing);
|
2010-06-25 10:48:19 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
StartTimers();
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Seek(uint ms) {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-22 18:54:09 +02:00
|
|
|
seek_pos_ = ms;
|
|
|
|
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;
|
2010-04-22 19:02:06 +02:00
|
|
|
waiting_to_seek_ = false;
|
|
|
|
|
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
2010-04-22 18:54:09 +02:00
|
|
|
|
|
|
|
if (current_pipeline_->Seek(seek_pos_ * GST_MSECOND))
|
2010-04-12 02:20:52 +02:00
|
|
|
ClearScopeBuffers();
|
2010-04-11 21:47:21 +02:00
|
|
|
else
|
|
|
|
qDebug() << "Seek failed";
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::SetEqualizerEnabled(bool enabled) {
|
2010-04-07 00:58:41 +02:00
|
|
|
equalizer_enabled_= enabled;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetEqualizerEnabled(enabled);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-12 22:33:59 +02:00
|
|
|
void GstEngine::SetEqualizerParameters(int preamp, const QList<int>& band_gains) {
|
2010-04-07 00:58:41 +02:00
|
|
|
equalizer_preamp_ = preamp;
|
2010-04-07 18:26:04 +02:00
|
|
|
equalizer_gains_ = band_gains;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetEqualizerParams(preamp, band_gains);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::SetVolumeSW( uint percent ) {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetVolume(percent);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
void GstEngine::StartTimers() {
|
|
|
|
StopTimers();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-07-02 21:23:08 +02:00
|
|
|
timer_id_ = startTimer(kTimerInterval);
|
2010-06-30 22:36:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::StopTimers() {
|
2010-07-02 21:23:08 +02:00
|
|
|
if (timer_id_ != -1) {
|
|
|
|
killTimer(timer_id_);
|
|
|
|
timer_id_ = -1;
|
2010-06-30 22:36:16 +02:00
|
|
|
}
|
|
|
|
}
|
2010-04-12 01:52:16 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
void GstEngine::timerEvent(QTimerEvent* e) {
|
2010-07-02 21:23:08 +02:00
|
|
|
if (e->timerId() != timer_id_)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// keep the scope from building while we are not visible
|
|
|
|
// this is why the timer must run as long as we are playing, and not just when
|
|
|
|
// we are fading
|
|
|
|
PruneScope();
|
|
|
|
|
|
|
|
// Emit TrackAboutToEnd when we're a few seconds away from finishing
|
|
|
|
if (current_pipeline_) {
|
|
|
|
const qint64 nanosec_position = current_pipeline_->position();
|
|
|
|
const qint64 nanosec_length = current_pipeline_->length();
|
|
|
|
const qint64 remaining = (nanosec_length - nanosec_position) / 1000000;
|
|
|
|
const qint64 fudge = kTimerInterval + 100; // Mmm fudge
|
|
|
|
const qint64 gap = autocrossfade_enabled_ ? fadeout_duration_ : kPreloadGap;
|
|
|
|
|
|
|
|
if (nanosec_length > 0 && remaining < gap + fudge)
|
|
|
|
EmitAboutToEnd();
|
2010-04-12 01:52:16 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
void GstEngine::HandlePipelineError(const QString& message) {
|
2010-04-12 02:20:52 +02:00
|
|
|
qWarning() << "Gstreamer error:" << message;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2010-04-19 14:04:35 +02:00
|
|
|
emit Error(message);
|
|
|
|
emit StateChanged(Engine::Empty);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-05-08 19:39:12 +02:00
|
|
|
void GstEngine::EndOfStreamReached(bool has_next_track) {
|
|
|
|
if (!has_next_track)
|
|
|
|
current_pipeline_.reset();
|
|
|
|
ClearScopeBuffers();
|
2010-04-12 01:24:03 +02:00
|
|
|
emit TrackEnded();
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
void GstEngine::NewMetaData(const Engine::SimpleMetaBundle& bundle) {
|
2010-04-12 01:24:03 +02:00
|
|
|
emit MetaData(bundle);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-08-02 20:13:40 +02:00
|
|
|
GstElement* GstEngine::CreateElement(const QString& factoryName, GstElement* bin) {
|
|
|
|
// Make a unique name
|
|
|
|
QString name = factoryName + "-" + QString::number(next_element_id_ ++);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-08-02 20:13:40 +02:00
|
|
|
GstElement* element = gst_element_factory_make(
|
|
|
|
factoryName.toAscii().constData(), name.toAscii().constData());
|
|
|
|
|
|
|
|
if (!element) {
|
2010-04-21 13:14:12 +02:00
|
|
|
emit Error(QString("GStreamer could not create the element: %1. "
|
|
|
|
"Please make sure that you have installed all necessary GStreamer plugins (e.g. OGG and MP3)").arg( factoryName ) );
|
2010-08-02 20:13:40 +02:00
|
|
|
gst_object_unref(GST_OBJECT(bin));
|
|
|
|
return NULL;
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-08-02 20:13:40 +02:00
|
|
|
if (bin)
|
|
|
|
gst_bin_add(GST_BIN(bin), element);
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
return element;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
GstEngine::PluginDetailsList
|
|
|
|
GstEngine::GetPluginList(const QString& classname) const {
|
2010-08-28 21:22:58 +02:00
|
|
|
const_cast<GstEngine*>(this)->EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
PluginDetailsList ret;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
GstRegistry* registry = gst_registry_get_default();
|
2010-05-28 21:51:51 +02:00
|
|
|
GList* const features =
|
2010-04-07 15:51:14 +02:00
|
|
|
gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY);
|
|
|
|
|
2010-05-28 21:51:51 +02:00
|
|
|
GList* p = features;
|
|
|
|
while (p) {
|
|
|
|
GstElementFactory* factory = GST_ELEMENT_FACTORY(p->data);
|
2010-04-07 15:51:14 +02:00
|
|
|
if (QString(factory->details.klass).contains(classname)) {
|
|
|
|
PluginDetails details;
|
2010-05-28 21:51:51 +02:00
|
|
|
details.name = QString::fromUtf8(GST_PLUGIN_FEATURE_NAME(p->data));
|
2010-04-07 15:51:14 +02:00
|
|
|
details.long_name = QString::fromUtf8(factory->details.longname);
|
|
|
|
details.description = QString::fromUtf8(factory->details.description);
|
|
|
|
details.author = QString::fromUtf8(factory->details.author);
|
|
|
|
ret << details;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
2010-05-28 21:51:51 +02:00
|
|
|
p = g_list_next(p);
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-07 15:51:14 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
gst_plugin_feature_list_free(features);
|
2010-04-07 15:51:14 +02:00
|
|
|
return ret;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-07-14 13:16:56 +02:00
|
|
|
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-21 13:14:12 +02:00
|
|
|
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
|
2010-04-11 21:47:21 +02:00
|
|
|
ret->set_output_device(sink_, device_);
|
2010-05-23 15:07:15 +02:00
|
|
|
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
|
2010-10-11 17:58:05 +02:00
|
|
|
ret->set_buffer_duration_ms(buffer_duration_ms_);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-06-06 16:06:23 +02:00
|
|
|
ret->AddBufferConsumer(this);
|
|
|
|
foreach (BufferConsumer* consumer, buffer_consumers_)
|
|
|
|
ret->AddBufferConsumer(consumer);
|
|
|
|
|
2010-05-08 19:39:12 +02:00
|
|
|
connect(ret.get(), SIGNAL(EndOfStreamReached(bool)), SLOT(EndOfStreamReached(bool)));
|
2010-04-11 21:47:21 +02:00
|
|
|
connect(ret.get(), SIGNAL(Error(QString)), SLOT(HandlePipelineError(QString)));
|
|
|
|
connect(ret.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)),
|
|
|
|
SLOT(NewMetaData(Engine::SimpleMetaBundle)));
|
2010-04-12 02:20:52 +02:00
|
|
|
connect(ret.get(), SIGNAL(destroyed()), SLOT(ClearScopeBuffers()));
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-07-14 13:16:56 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl& url) {
|
|
|
|
shared_ptr<GstEnginePipeline> ret = CreatePipeline();
|
|
|
|
if (!ret->InitFromUrl(url))
|
2010-04-11 21:47:21 +02:00
|
|
|
ret.reset();
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
return ret;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
qint64 GstEngine::PruneScope() {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// get the position playing in the audio device
|
2010-07-11 15:31:03 +02:00
|
|
|
const qint64 pos = current_pipeline_->position();
|
|
|
|
const qint64 segment_start = current_pipeline_->segment_start();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
GstBuffer *buf = 0;
|
2010-05-27 22:30:15 +02:00
|
|
|
quint64 etime = 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// free up the buffers that the audio device has advanced past already
|
|
|
|
do {
|
|
|
|
// most delayed buffers are at the head of the queue
|
|
|
|
buf = reinterpret_cast<GstBuffer *>( g_queue_peek_head(delayq_) );
|
|
|
|
if (buf) {
|
|
|
|
// the start time of the buffer
|
2010-07-11 15:31:03 +02:00
|
|
|
quint64 stime = GST_BUFFER_TIMESTAMP(buf) - segment_start;
|
2010-04-07 00:58:41 +02:00
|
|
|
// the duration of the buffer
|
|
|
|
quint64 dur = GST_BUFFER_DURATION(buf);
|
|
|
|
// therefore we can calculate the end time of the buffer
|
|
|
|
etime = stime + dur;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// purge this buffer if the pos is past the end time of the buffer
|
|
|
|
if (pos > qint64(etime)) {
|
|
|
|
g_queue_pop_head(delayq_);
|
|
|
|
gst_buffer_unref(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (buf && pos > qint64(etime));
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
return pos;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 02:20:52 +02:00
|
|
|
void GstEngine::ClearScopeBuffers() {
|
2010-04-07 00:58:41 +02:00
|
|
|
// just free them all
|
|
|
|
while (g_queue_get_length(delayq_)) {
|
|
|
|
GstBuffer* buf = reinterpret_cast<GstBuffer *>( g_queue_pop_head(delayq_) );
|
|
|
|
gst_buffer_unref(buf);
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
2010-04-08 22:14:11 +02:00
|
|
|
|
2010-04-08 22:17:57 +02:00
|
|
|
bool GstEngine::DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(const QString &name) {
|
2010-04-08 22:14:11 +02:00
|
|
|
return (name == "alsasink" || name == "osssink" || name == "pulsesink");
|
|
|
|
}
|
2010-06-06 16:06:23 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2010-06-14 21:15:10 +02:00
|
|
|
|
2010-07-14 13:43:23 +02:00
|
|
|
int GstEngine::AddBackgroundStream(shared_ptr<GstEnginePipeline> pipeline) {
|
2010-06-14 21:15:10 +02:00
|
|
|
// We don't want to get metadata messages or end notifications.
|
|
|
|
disconnect(pipeline.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)), this, 0);
|
|
|
|
disconnect(pipeline.get(), SIGNAL(EndOfStreamReached(bool)), this, 0);
|
|
|
|
connect(pipeline.get(), SIGNAL(EndOfStreamReached(bool)), SLOT(BackgroundStreamFinished()));
|
2010-10-02 14:51:09 +02:00
|
|
|
|
2010-07-14 13:43:23 +02:00
|
|
|
const int stream_id = next_background_stream_id_++;
|
2010-06-14 21:15:10 +02:00
|
|
|
background_streams_[stream_id] = pipeline;
|
2010-10-02 14:51:09 +02:00
|
|
|
|
|
|
|
QFuture<GstStateChangeReturn> future = pipeline->SetState(GST_STATE_PLAYING);
|
|
|
|
BoundFutureWatcher<GstStateChangeReturn, int>* watcher =
|
|
|
|
new BoundFutureWatcher<GstStateChangeReturn, int>(stream_id, this);
|
|
|
|
watcher->setFuture(future);
|
|
|
|
connect(watcher, SIGNAL(finished()), SLOT(BackgroundStreamPlayDone()));
|
|
|
|
|
2010-06-14 21:15:10 +02:00
|
|
|
return stream_id;
|
|
|
|
}
|
|
|
|
|
2010-10-02 14:51:09 +02:00
|
|
|
void GstEngine::BackgroundStreamPlayDone() {
|
|
|
|
BoundFutureWatcher<GstStateChangeReturn, int>* watcher =
|
|
|
|
static_cast<BoundFutureWatcher<GstStateChangeReturn, int>*>(sender());
|
|
|
|
watcher->deleteLater();
|
|
|
|
|
|
|
|
const int stream_id = watcher->data();
|
|
|
|
GstStateChangeReturn ret = watcher->result();
|
|
|
|
|
|
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
|
|
qWarning() << "Could not set thread to PLAYING.";
|
|
|
|
background_streams_.remove(stream_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-14 13:43:23 +02:00
|
|
|
int GstEngine::AddBackgroundStream(const QUrl& url) {
|
|
|
|
shared_ptr<GstEnginePipeline> pipeline = CreatePipeline(url);
|
|
|
|
if (!pipeline) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pipeline->SetVolume(30);
|
|
|
|
pipeline->SetNextUrl(url);
|
|
|
|
return AddBackgroundStream(pipeline);
|
|
|
|
}
|
|
|
|
|
2010-06-14 21:15:10 +02:00
|
|
|
void GstEngine::StopBackgroundStream(int id) {
|
|
|
|
background_streams_.remove(id); // Removes last shared_ptr reference.
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::BackgroundStreamFinished() {
|
|
|
|
GstEnginePipeline* pipeline = qobject_cast<GstEnginePipeline*>(sender());
|
|
|
|
pipeline->SetNextUrl(pipeline->url());
|
|
|
|
}
|
2010-07-14 13:16:56 +02:00
|
|
|
|
|
|
|
int GstEngine::AllGloryToTheHypnotoad() {
|
|
|
|
shared_ptr<GstEnginePipeline> pipeline = CreatePipeline();
|
|
|
|
pipeline->InitFromString(kHypnotoadPipeline);
|
|
|
|
if (!pipeline) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pipeline->SetVolume(5); // Hypnotoad is *loud*.
|
2010-07-14 13:43:23 +02:00
|
|
|
return AddBackgroundStream(pipeline);
|
2010-07-14 13:16:56 +02:00
|
|
|
}
|