Factor out the gstreamer pipeline bits into another class.
This commit is contained in:
parent
1293865fea
commit
14d0b00f46
|
@ -14,6 +14,7 @@ set(CLEMENTINE-SOURCES
|
||||||
engines/enginebase.cpp
|
engines/enginebase.cpp
|
||||||
engines/gstengine.cpp
|
engines/gstengine.cpp
|
||||||
engines/gstequalizer.cpp
|
engines/gstequalizer.cpp
|
||||||
|
engines/gstenginepipeline.cpp
|
||||||
analyzers/baranalyzer.cpp
|
analyzers/baranalyzer.cpp
|
||||||
analyzers/analyzerbase.cpp
|
analyzers/analyzerbase.cpp
|
||||||
fht.cpp
|
fht.cpp
|
||||||
|
@ -84,6 +85,7 @@ set(CLEMENTINE-MOC-HEADERS
|
||||||
playlist.h
|
playlist.h
|
||||||
engines/enginebase.h
|
engines/enginebase.h
|
||||||
engines/gstengine.h
|
engines/gstengine.h
|
||||||
|
engines/gstenginepipeline.h
|
||||||
sliderwidget.h
|
sliderwidget.h
|
||||||
playlistview.h
|
playlistview.h
|
||||||
backgroundthread.h
|
backgroundthread.h
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
#include "gstengine.h"
|
#include "gstengine.h"
|
||||||
#include "gstequalizer.h"
|
#include "gstequalizer.h"
|
||||||
|
#include "gstenginepipeline.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -39,157 +40,18 @@
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#define RETURN_IF_PIPELINE_EMPTY if ( !pipeline_filled_ ) return
|
|
||||||
|
|
||||||
|
|
||||||
using std::vector;
|
using std::vector;
|
||||||
|
using boost::shared_ptr;
|
||||||
|
|
||||||
GstEngine* GstEngine::sInstance = NULL;
|
|
||||||
const char* GstEngine::kSettingsGroup = "GstEngine";
|
const char* GstEngine::kSettingsGroup = "GstEngine";
|
||||||
const char* GstEngine::kAutoSink = "autoaudiosink";
|
const char* GstEngine::kAutoSink = "autoaudiosink";
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// CALLBACKS
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//GstBusSyncReply
|
|
||||||
gboolean GstEngine::BusCallback(GstBus*, GstMessage* msg, gpointer) {
|
|
||||||
switch ( GST_MESSAGE_TYPE(msg)) {
|
|
||||||
case GST_MESSAGE_ERROR: {
|
|
||||||
GError* error;
|
|
||||||
gchar* debugs;
|
|
||||||
|
|
||||||
gst_message_parse_error(msg,&error,&debugs);
|
|
||||||
qDebug() << "ERROR RECEIVED IN BUS_CB <" << error->message << ">" ;;
|
|
||||||
|
|
||||||
instance()->gst_error_ = QString::fromAscii( error->message );
|
|
||||||
instance()->gst_debug_ = QString::fromAscii( debugs );
|
|
||||||
QMetaObject::invokeMethod(instance(), "HandlePipelineError", Qt::QueuedConnection);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case GST_MESSAGE_TAG: {
|
|
||||||
gchar* string=NULL;
|
|
||||||
Engine::SimpleMetaBundle bundle;
|
|
||||||
GstTagList* taglist;
|
|
||||||
gst_message_parse_tag(msg,&taglist);
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
if ( gst_tag_list_get_string( taglist, GST_TAG_TITLE, &string ) && string ) {
|
|
||||||
qDebug() << "received tag 'Title': " << QString( string ) ;
|
|
||||||
bundle.title = string;
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
if ( gst_tag_list_get_string( taglist, GST_TAG_ARTIST, &string ) && string ) {
|
|
||||||
qDebug() << "received tag 'Artist': " << QString( string ) ;
|
|
||||||
bundle.artist = string;
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
if ( gst_tag_list_get_string( taglist, GST_TAG_COMMENT, &string ) && string ) {
|
|
||||||
qDebug() << "received tag 'Comment': " << QString( string ) ;
|
|
||||||
bundle.comment = string;
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
if ( gst_tag_list_get_string( taglist, GST_TAG_ALBUM, &string ) && string ) {
|
|
||||||
qDebug() << "received tag 'Album': " << QString( string ) ;
|
|
||||||
bundle.album = string;
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
g_free(string);
|
|
||||||
gst_tag_list_free(taglist);
|
|
||||||
if (success) {
|
|
||||||
instance()->meta_bundle_ = bundle;
|
|
||||||
QMetaObject::invokeMethod(instance(), "NewMetaData", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return GST_BUS_DROP;
|
|
||||||
}
|
|
||||||
|
|
||||||
GstBusSyncReply GstEngine::BusCallbackSync(GstBus*, GstMessage* msg, gpointer) {
|
|
||||||
switch (GST_MESSAGE_TYPE(msg)) {
|
|
||||||
case GST_MESSAGE_EOS:
|
|
||||||
QMetaObject::invokeMethod(instance(), "EndOfStreamReached",
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GST_BUS_PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void GstEngine::NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer) {
|
|
||||||
GstPad* const audiopad = gst_element_get_pad( instance()->gst_audiobin_, "sink" );
|
|
||||||
|
|
||||||
if ( GST_PAD_IS_LINKED( audiopad ) ) {
|
|
||||||
qDebug() << "audiopad is already linked. Unlinking old pad." ;
|
|
||||||
gst_pad_unlink( audiopad, GST_PAD_PEER( audiopad ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
gst_pad_link( pad, audiopad );
|
|
||||||
|
|
||||||
gst_object_unref( audiopad );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void GstEngine::HandoffCallback(GstPad*, GstBuffer* buf, gpointer arg) {
|
|
||||||
GstEngine *thisObj = static_cast<GstEngine *>( arg );
|
|
||||||
|
|
||||||
// push the buffer onto the delay queue
|
|
||||||
gst_buffer_ref(buf);
|
|
||||||
g_queue_push_tail(thisObj->delayq_, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEngine::EventCallback(GstPad*, GstEvent* event, gpointer) {
|
|
||||||
|
|
||||||
switch ( static_cast<int>(event->type) )
|
|
||||||
{
|
|
||||||
case GST_EVENT_EOS:
|
|
||||||
qDebug() << "EOS reached";
|
|
||||||
QMetaObject::invokeMethod(instance(), "EndOfStreamReached",
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GST_EVENT_TAG:
|
|
||||||
qDebug() << "GOT NEW TAG";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void GstEngine::CanDecodeNewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEngine::CanDecodeLastCallback(GstElement*, gpointer) {
|
|
||||||
instance()->can_decode_last_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
GstEngine::GstEngine()
|
GstEngine::GstEngine()
|
||||||
: Engine::Base(),
|
: Engine::Base(),
|
||||||
event_cb_id_(0),
|
|
||||||
delayq_(g_queue_new()),
|
delayq_(g_queue_new()),
|
||||||
current_sample_(0),
|
current_sample_(0),
|
||||||
pipeline_filled_(false),
|
|
||||||
fade_value_(0.0),
|
|
||||||
equalizer_enabled_(false),
|
equalizer_enabled_(false),
|
||||||
shutdown_(false),
|
shutdown_(false),
|
||||||
can_decode_pipeline_(NULL),
|
can_decode_pipeline_(NULL),
|
||||||
|
@ -200,16 +62,13 @@ GstEngine::GstEngine()
|
||||||
}
|
}
|
||||||
|
|
||||||
GstEngine::~GstEngine() {
|
GstEngine::~GstEngine() {
|
||||||
DestroyPipeline();
|
current_pipeline_.reset();
|
||||||
|
|
||||||
if (can_decode_pipeline_)
|
if (can_decode_pipeline_)
|
||||||
gst_object_unref(GST_OBJECT(can_decode_pipeline_));
|
gst_object_unref(GST_OBJECT(can_decode_pipeline_));
|
||||||
|
|
||||||
#ifdef GST_KIOSTREAMS
|
|
||||||
delete[] m_streamBuf;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Destroy scope delay queue
|
// Destroy scope delay queue
|
||||||
|
ClearScopeQ();
|
||||||
g_queue_free(delayq_);
|
g_queue_free(delayq_);
|
||||||
|
|
||||||
// Save configuration
|
// Save configuration
|
||||||
|
@ -218,8 +77,6 @@ GstEngine::~GstEngine() {
|
||||||
|
|
||||||
|
|
||||||
bool GstEngine::init() {
|
bool GstEngine::init() {
|
||||||
sInstance = this;
|
|
||||||
|
|
||||||
// GStreamer initialization
|
// GStreamer initialization
|
||||||
GError *err;
|
GError *err;
|
||||||
if ( !gst_init_check( NULL, NULL, &err ) ) {
|
if ( !gst_init_check( NULL, NULL, &err ) ) {
|
||||||
|
@ -264,8 +121,8 @@ bool GstEngine::canDecode(const QUrl &url) {
|
||||||
can_decode_bin_ = CreateElement("decodebin", can_decode_pipeline_);
|
can_decode_bin_ = CreateElement("decodebin", can_decode_pipeline_);
|
||||||
|
|
||||||
gst_element_link(can_decode_src_, can_decode_bin_);
|
gst_element_link(can_decode_src_, can_decode_bin_);
|
||||||
g_signal_connect(G_OBJECT(can_decode_bin_), "new-decoded-pad", G_CALLBACK(CanDecodeNewPadCallback), NULL);
|
g_signal_connect(G_OBJECT(can_decode_bin_), "new-decoded-pad", G_CALLBACK(CanDecodeNewPadCallback), this);
|
||||||
g_signal_connect(G_OBJECT(can_decode_bin_), "no-more-pads", G_CALLBACK(CanDecodeLastCallback), NULL);
|
g_signal_connect(G_OBJECT(can_decode_bin_), "no-more-pads", G_CALLBACK(CanDecodeLastCallback), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the file we're testing
|
// Set the file we're testing
|
||||||
|
@ -287,43 +144,44 @@ bool GstEngine::canDecode(const QUrl &url) {
|
||||||
return can_decode_success_;
|
return can_decode_success_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GstEngine::CanDecodeNewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer self) {
|
||||||
|
GstEngine* instance = static_cast<GstEngine*>(self);
|
||||||
|
|
||||||
uint GstEngine::position() const {
|
GstCaps* caps = gst_pad_get_caps(pad);
|
||||||
RETURN_IF_PIPELINE_EMPTY 0;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
GstFormat fmt = GST_FORMAT_TIME;
|
void GstEngine::CanDecodeLastCallback(GstElement*, gpointer self) {
|
||||||
// Value will hold the current time position in nanoseconds. Must be initialized!
|
GstEngine* instance = static_cast<GstEngine*>(self);
|
||||||
gint64 value = 0;
|
instance->can_decode_last_ = true;
|
||||||
gst_element_query_position( gst_pipeline_, &fmt, &value );
|
|
||||||
|
|
||||||
return static_cast<uint>( ( value / GST_MSECOND ) ); // nanosec -> msec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint GstEngine::position() const {
|
||||||
|
if (!current_pipeline_)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return uint(current_pipeline_->position() / GST_MSECOND);
|
||||||
|
}
|
||||||
|
|
||||||
uint GstEngine::length() const {
|
uint GstEngine::length() const {
|
||||||
RETURN_IF_PIPELINE_EMPTY 0;
|
if (!current_pipeline_)
|
||||||
|
return 0;
|
||||||
|
|
||||||
GstFormat fmt = GST_FORMAT_TIME;
|
return uint(current_pipeline_->length() / GST_MSECOND);
|
||||||
// Value will hold the track length in nanoseconds. Must be initialized!
|
|
||||||
gint64 value = 0;
|
|
||||||
gst_element_query_duration(gst_pipeline_, &fmt, &value);
|
|
||||||
|
|
||||||
return uint( value / GST_MSECOND ); // nanosec -> msec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Engine::State GstEngine::state() const {
|
Engine::State GstEngine::state() const {
|
||||||
RETURN_IF_PIPELINE_EMPTY m_url.isEmpty() ? Engine::Empty : Engine::Idle;
|
if (!current_pipeline_)
|
||||||
|
return m_url.isEmpty() ? Engine::Empty : Engine::Idle;
|
||||||
|
|
||||||
GstState s, sp;
|
switch (current_pipeline_->state()) {
|
||||||
GstStateChangeReturn sret = gst_element_get_state( gst_pipeline_, &s, &sp, kGstStateTimeout);
|
|
||||||
|
|
||||||
if (sret == GST_STATE_CHANGE_FAILURE) {
|
|
||||||
qDebug() << "Gst get state fails";
|
|
||||||
return Engine::Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (s) {
|
|
||||||
case GST_STATE_NULL: return Engine::Empty;
|
case GST_STATE_NULL: return Engine::Empty;
|
||||||
case GST_STATE_READY: return Engine::Idle;
|
case GST_STATE_READY: return Engine::Idle;
|
||||||
case GST_STATE_PLAYING: return Engine::Playing;
|
case GST_STATE_PLAYING: return Engine::Playing;
|
||||||
|
@ -332,6 +190,9 @@ Engine::State GstEngine::state() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GstEngine::NewBuffer(GstBuffer* buf) {
|
||||||
|
g_queue_push_tail(delayq_, buf);
|
||||||
|
}
|
||||||
|
|
||||||
const Engine::Scope& GstEngine::scope() {
|
const Engine::Scope& GstEngine::scope() {
|
||||||
UpdateScope();
|
UpdateScope();
|
||||||
|
@ -422,129 +283,14 @@ void GstEngine::UpdateScope() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool GstEngine::metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle& b) {
|
|
||||||
qDebug() << "GstEngine::metaDataForUrl " << url ;
|
|
||||||
// TODO
|
|
||||||
/*if ( url.scheme() == "cdda" )
|
|
||||||
{
|
|
||||||
// TODO: gstreamer doesn't support cddb natively, but could perhaps use libkcddb?
|
|
||||||
b.title = QString( "Track %1" ).arg( url.host() );
|
|
||||||
b.album = "AudioCD";
|
|
||||||
|
|
||||||
if ( setupAudioCD( url.encodedQuery().remove( QRegExp( "^\\?" ) ), url.host().toUInt(), true ) )
|
|
||||||
{
|
|
||||||
GstPad *pad;
|
|
||||||
if ( ( pad = gst_element_get_pad( gst_src_, "src" ) ) )
|
|
||||||
{
|
|
||||||
GstCaps *caps;
|
|
||||||
if ( ( caps = gst_pad_get_caps( pad ) ) )
|
|
||||||
{
|
|
||||||
GstStructure *structure;
|
|
||||||
if ( ( structure = gst_caps_get_structure( GST_CAPS(caps), 0 ) ) )
|
|
||||||
{
|
|
||||||
gint channels, rate, width;
|
|
||||||
gst_structure_get_int( structure, "channels", &channels );
|
|
||||||
gst_structure_get_int( structure, "rate", &rate );
|
|
||||||
gst_structure_get_int( structure, "width", &width );
|
|
||||||
b.bitrate = ( rate * width * channels ) / 1000;
|
|
||||||
b.samplerate = rate;
|
|
||||||
}
|
|
||||||
gst_caps_unref( caps );
|
|
||||||
}
|
|
||||||
|
|
||||||
GstQuery *query;
|
|
||||||
if ( ( query = gst_query_new_duration( GST_FORMAT_TIME ) ) )
|
|
||||||
{
|
|
||||||
if ( gst_pad_query( pad, query )) {
|
|
||||||
gint64 time;
|
|
||||||
|
|
||||||
gst_query_parse_duration( query, NULL, &time );
|
|
||||||
b.length = QString::number( time / GST_SECOND );
|
|
||||||
}
|
|
||||||
gst_query_unref( query );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gst_object_unref( GST_OBJECT( pad ) );
|
|
||||||
if ( !gst_element_set_state( gst_pipeline_, GST_STATE_NULL ) ) {
|
|
||||||
qWarning() << "Could not set thread to NULL.";
|
|
||||||
DestroyPipeline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}*/
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool GstEngine::getAudioCDContents(const QString &device, QList<QUrl> &urls) {
|
|
||||||
// TODO
|
|
||||||
/*qDebug() << "GstEngine::getAudioCDContents " << device ;
|
|
||||||
|
|
||||||
bool result = false;
|
|
||||||
if ( setupAudioCD( device, 0, true ) )
|
|
||||||
{
|
|
||||||
GstFormat format;
|
|
||||||
if ( ( format = gst_format_get_by_nick("track") ) != GST_FORMAT_UNDEFINED )
|
|
||||||
{
|
|
||||||
gint64 tracks = 0;
|
|
||||||
if ( gst_element_query_duration( gst_pipeline_, &format, &tracks ) )
|
|
||||||
{
|
|
||||||
qDebug() << "Found " << tracks << " cdda tracks" ;
|
|
||||||
for ( int i = 1; i <= tracks; ++i )
|
|
||||||
{
|
|
||||||
QUrl temp( QString( "cdda://%1" ).arg( i ) );
|
|
||||||
if ( !device.isNull() )
|
|
||||||
temp.setQuery( device );
|
|
||||||
urls << temp;
|
|
||||||
}
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( !gst_element_set_state( gst_pipeline_, GST_STATE_NULL ) ) {
|
|
||||||
qWarning() << "Could not set thread to NULL.";
|
|
||||||
DestroyPipeline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;*/
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GstEngine::load(const QUrl& url, bool stream) {
|
bool GstEngine::load(const QUrl& url, bool stream) {
|
||||||
Engine::Base::load( url, stream );
|
Engine::Base::load( url, stream );
|
||||||
|
|
||||||
qDebug() << "Loading url: " << url.toEncoded() ;
|
shared_ptr<GstEnginePipeline> pipeline(CreatePipeline(url));
|
||||||
|
if (!pipeline)
|
||||||
if ( url.scheme() == "cdda" ) {
|
|
||||||
/*if ( !setupAudioCD( url.encodedQuery().remove( QRegExp( "^\\?" ) ), url.host().toUInt(), false ) )*/
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
if ( !CreatePipeline() )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
gst_src_ = CreateElement("giosrc");
|
current_pipeline_ = pipeline;
|
||||||
if (!gst_src_) {
|
|
||||||
qDebug() << "******* cannot get stream src " ;
|
|
||||||
|
|
||||||
DestroyPipeline();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_object_set (G_OBJECT (gst_src_), "location", url.toEncoded().constData(), NULL);
|
|
||||||
gst_bin_add( GST_BIN( gst_pipeline_ ), gst_src_ );
|
|
||||||
|
|
||||||
if ( !( gst_decodebin_ = CreateElement( "decodebin", gst_pipeline_ ) ) ) { DestroyPipeline(); return false; }
|
|
||||||
g_signal_connect( G_OBJECT( gst_decodebin_ ), "new-decoded-pad", G_CALLBACK( NewPadCallback ), NULL );
|
|
||||||
|
|
||||||
GstPad* p = gst_element_get_pad (gst_decodebin_, "sink");
|
|
||||||
if (p) {
|
|
||||||
event_cb_id_ = gst_pad_add_event_probe (p, G_CALLBACK(EventCallback), this);
|
|
||||||
gst_object_unref (p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link elements. The link from decodebin to audioconvert will be made in the newPad-callback
|
|
||||||
gst_element_link( gst_src_, gst_decodebin_ );
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolume(m_volume);
|
setVolume(m_volume);
|
||||||
setEqualizerEnabled(equalizer_enabled_);
|
setEqualizerEnabled(equalizer_enabled_);
|
||||||
|
@ -555,10 +301,9 @@ bool GstEngine::load(const QUrl& url, bool stream) {
|
||||||
|
|
||||||
bool GstEngine::play( uint offset ) {
|
bool GstEngine::play( uint offset ) {
|
||||||
// Try to play input pipeline; if fails, destroy input bin
|
// Try to play input pipeline; if fails, destroy input bin
|
||||||
GstStateChangeReturn sret;
|
if (!current_pipeline_->SetState(GST_STATE_PLAYING)) {
|
||||||
if ( !(sret = gst_element_set_state( gst_pipeline_, GST_STATE_PLAYING )) ) {
|
|
||||||
qWarning() << "Could not set thread to PLAYING.";
|
qWarning() << "Could not set thread to PLAYING.";
|
||||||
DestroyPipeline();
|
current_pipeline_.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,50 +320,48 @@ bool GstEngine::play( uint offset ) {
|
||||||
void GstEngine::stop() {
|
void GstEngine::stop() {
|
||||||
m_url = QUrl(); // To ensure we return Empty from state()
|
m_url = QUrl(); // To ensure we return Empty from state()
|
||||||
|
|
||||||
if (pipeline_filled_) {
|
current_pipeline_.reset();
|
||||||
// Is a fade running?
|
|
||||||
if ( fade_value_ == 0.0 )
|
|
||||||
fade_value_ = 1.0;
|
|
||||||
else
|
|
||||||
DestroyPipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit stateChanged(Engine::Empty);
|
emit stateChanged(Engine::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::pause() {
|
void GstEngine::pause() {
|
||||||
RETURN_IF_PIPELINE_EMPTY;
|
if (!current_pipeline_)
|
||||||
|
return;
|
||||||
|
|
||||||
if ( GST_STATE( gst_pipeline_ ) == GST_STATE_PLAYING ) {
|
if ( current_pipeline_->state() == GST_STATE_PLAYING ) {
|
||||||
gst_element_set_state( gst_pipeline_, GST_STATE_PAUSED );
|
current_pipeline_->SetState(GST_STATE_PAUSED);
|
||||||
emit stateChanged( Engine::Paused );
|
emit stateChanged(Engine::Paused);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::unpause() {
|
void GstEngine::unpause() {
|
||||||
RETURN_IF_PIPELINE_EMPTY;
|
if (!current_pipeline_)
|
||||||
|
return;
|
||||||
|
|
||||||
if ( GST_STATE( gst_pipeline_ ) == GST_STATE_PAUSED ) {
|
if ( current_pipeline_->state() == GST_STATE_PAUSED ) {
|
||||||
gst_element_set_state( gst_pipeline_, GST_STATE_PLAYING );
|
current_pipeline_->SetState(GST_STATE_PLAYING);
|
||||||
emit stateChanged( Engine::Playing );
|
emit stateChanged(Engine::Playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::seek( uint ms ) {
|
void GstEngine::seek( uint ms ) {
|
||||||
RETURN_IF_PIPELINE_EMPTY;
|
if (!current_pipeline_)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!gst_element_seek(gst_pipeline_, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, ms*GST_MSECOND,
|
if (current_pipeline_->Seek(ms * GST_MSECOND))
|
||||||
GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) qDebug() << "Seek failed" ;
|
ClearScopeQ();
|
||||||
else ClearScopeQ();
|
else
|
||||||
gst_element_get_state(gst_pipeline_, NULL, NULL, 100*GST_MSECOND);
|
qDebug() << "Seek failed";
|
||||||
|
|
||||||
|
// ??
|
||||||
|
//gst_element_get_state(gst_pipeline_, NULL, NULL, 100*GST_MSECOND);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::setEqualizerEnabled(bool enabled) {
|
void GstEngine::setEqualizerEnabled(bool enabled) {
|
||||||
equalizer_enabled_= enabled;
|
equalizer_enabled_= enabled;
|
||||||
|
|
||||||
RETURN_IF_PIPELINE_EMPTY;
|
if (current_pipeline_)
|
||||||
|
current_pipeline_->SetEqualizerEnabled(enabled);
|
||||||
g_object_set(G_OBJECT(gst_equalizer_), "active", enabled, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -626,30 +369,13 @@ void GstEngine::setEqualizerParameters( int preamp, const QList<int>& band_gains
|
||||||
equalizer_preamp_ = preamp;
|
equalizer_preamp_ = preamp;
|
||||||
equalizer_gains_ = band_gains;
|
equalizer_gains_ = band_gains;
|
||||||
|
|
||||||
RETURN_IF_PIPELINE_EMPTY;
|
if (current_pipeline_)
|
||||||
|
current_pipeline_->SetEqualizerParams(preamp, band_gains);
|
||||||
// Preamp
|
|
||||||
g_object_set(G_OBJECT(gst_equalizer_), "preamp", ( preamp + 100 ) / 2, NULL);
|
|
||||||
|
|
||||||
// Gains
|
|
||||||
vector<int> gains_temp;
|
|
||||||
gains_temp.resize( band_gains.count() );
|
|
||||||
for ( int i = 0; i < band_gains.count(); i++ )
|
|
||||||
gains_temp[i] = band_gains.at( i ) + 100;
|
|
||||||
|
|
||||||
g_object_set(G_OBJECT(gst_equalizer_), "gain", &gains_temp, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::setVolumeSW( uint percent ) {
|
void GstEngine::setVolumeSW( uint percent ) {
|
||||||
RETURN_IF_PIPELINE_EMPTY;
|
if (current_pipeline_)
|
||||||
|
current_pipeline_->SetVolume(percent);
|
||||||
double fade;
|
|
||||||
if ( fade_value_ > 0.0 )
|
|
||||||
fade = 1.0 - log10( ( 1.0 - fade_value_ ) * 9.0 + 1.0 );
|
|
||||||
else
|
|
||||||
fade = 1.0;
|
|
||||||
|
|
||||||
g_object_set( G_OBJECT(gst_volume_), "volume", (double) percent * fade * 0.01, NULL );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -662,7 +388,7 @@ void GstEngine::timerEvent( QTimerEvent* ) {
|
||||||
// *** Volume fading ***
|
// *** Volume fading ***
|
||||||
|
|
||||||
// Are we currently fading?
|
// Are we currently fading?
|
||||||
if ( fade_value_ > 0.0 ) {
|
/*if ( fade_value_ > 0.0 ) {
|
||||||
// TODO
|
// TODO
|
||||||
//m_fadeValue -= ( AmarokConfig::fadeoutLength() ) ? 1.0 / AmarokConfig::fadeoutLength() * TIMER_INTERVAL : 1.0;
|
//m_fadeValue -= ( AmarokConfig::fadeoutLength() ) ? 1.0 / AmarokConfig::fadeoutLength() * TIMER_INTERVAL : 1.0;
|
||||||
fade_value_ -= 1.0;
|
fade_value_ -= 1.0;
|
||||||
|
@ -675,7 +401,7 @@ void GstEngine::timerEvent( QTimerEvent* ) {
|
||||||
//killTimers();
|
//killTimers();
|
||||||
}
|
}
|
||||||
setVolume( volume() );
|
setVolume( volume() );
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -683,34 +409,20 @@ void GstEngine::timerEvent( QTimerEvent* ) {
|
||||||
// PRIVATE SLOTS
|
// PRIVATE SLOTS
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void GstEngine::HandlePipelineError() {
|
void GstEngine::HandlePipelineError(const QString& message) {
|
||||||
QString text = "[GStreamer Error] ";
|
qDebug() << "Gstreamer error:" << message;
|
||||||
text += gst_error_;
|
|
||||||
|
|
||||||
if ( !gst_debug_.isEmpty() ) {
|
current_pipeline_.reset();
|
||||||
text += " ** ";
|
|
||||||
text += gst_debug_;
|
|
||||||
}
|
|
||||||
|
|
||||||
gst_error_ = QString();
|
|
||||||
emit statusText( text );
|
|
||||||
qWarning() << text ;
|
|
||||||
|
|
||||||
DestroyPipeline();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GstEngine::EndOfStreamReached() {
|
void GstEngine::EndOfStreamReached() {
|
||||||
DestroyPipeline();
|
current_pipeline_.reset();
|
||||||
emit trackEnded();
|
emit trackEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::NewMetaData() {
|
void GstEngine::NewMetaData(const Engine::SimpleMetaBundle& bundle) {
|
||||||
emit metaData( meta_bundle_ );
|
emit metaData(bundle);
|
||||||
}
|
|
||||||
|
|
||||||
void GstEngine::ErrorNoOutput() {
|
|
||||||
QMessageBox::information( 0, "Error", "<p>Please select a GStreamer <u>output plugin</u> in the engine settings dialog.</p>" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GstElement* GstEngine::CreateElement(
|
GstElement* GstEngine::CreateElement(
|
||||||
|
@ -759,116 +471,30 @@ GstEngine::PluginDetailsList
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl& url) {
|
||||||
|
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline);
|
||||||
|
ret->set_forwards_buffers(true);
|
||||||
|
ret->set_output_device(sink_, device_);
|
||||||
|
|
||||||
bool GstEngine::CreatePipeline() {
|
connect(ret.get(), SIGNAL(EndOfStreamReached()), SLOT(EndOfStreamReached()));
|
||||||
DestroyPipeline();
|
connect(ret.get(), SIGNAL(BufferFound(GstBuffer*)), SLOT(NewBuffer(GstBuffer*)));
|
||||||
|
connect(ret.get(), SIGNAL(Error(QString)), SLOT(HandlePipelineError(QString)));
|
||||||
|
connect(ret.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)),
|
||||||
|
SLOT(NewMetaData(Engine::SimpleMetaBundle)));
|
||||||
|
connect(ret.get(), SIGNAL(destroyed()), SLOT(ClearScopeQ()));
|
||||||
|
|
||||||
gst_pipeline_ = gst_pipeline_new( "pipeline" );
|
if (!ret->Init(url))
|
||||||
gst_audiobin_ = gst_bin_new( "audiobin" );
|
ret.reset();
|
||||||
|
|
||||||
if ( !( gst_audiosink_ = CreateElement( sink_, gst_audiobin_ ) ) ) {
|
return ret;
|
||||||
QMetaObject::invokeMethod(this, "ErrorNoOutput", Qt::QueuedConnection);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(sink_) && !device_.isEmpty()) {
|
|
||||||
g_object_set(G_OBJECT(gst_audiosink_), "device", device_.toUtf8().constData(), NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
gst_equalizer_ = GST_ELEMENT(gst_equalizer_new());
|
|
||||||
gst_bin_add(GST_BIN(gst_audiobin_), gst_equalizer_);
|
|
||||||
|
|
||||||
if ( !( gst_audioconvert_ = CreateElement( "audioconvert", gst_audiobin_ ) ) ) { return false; }
|
|
||||||
if ( !( gst_identity_ = CreateElement( "identity", gst_audiobin_ ) ) ) { return false; }
|
|
||||||
if ( !( gst_volume_ = CreateElement( "volume", gst_audiobin_ ) ) ) { return false; }
|
|
||||||
if ( !( gst_audioscale_ = CreateElement( "audioresample", gst_audiobin_ ) ) ) { return false; }
|
|
||||||
|
|
||||||
GstPad* p;
|
|
||||||
p = gst_element_get_pad(gst_audioconvert_, "sink");
|
|
||||||
gst_element_add_pad(gst_audiobin_,gst_ghost_pad_new("sink",p));
|
|
||||||
gst_object_unref(p);
|
|
||||||
|
|
||||||
// add a data probe on the src pad if the audioconvert element for our scope
|
|
||||||
// we do it here because we want pre-equalized and pre-volume samples
|
|
||||||
// so that our visualization are not affected by them
|
|
||||||
p = gst_element_get_pad (gst_audioconvert_, "src");
|
|
||||||
gst_pad_add_buffer_probe (p, G_CALLBACK(HandoffCallback), this);
|
|
||||||
gst_object_unref (p);
|
|
||||||
|
|
||||||
// Ensure we get the right type out of audioconvert for our scope
|
|
||||||
GstCaps* caps = gst_caps_new_simple ("audio/x-raw-int",
|
|
||||||
"width", G_TYPE_INT, 16,
|
|
||||||
"signed", G_TYPE_BOOLEAN, true,
|
|
||||||
NULL);
|
|
||||||
gst_element_link_filtered(gst_audioconvert_, gst_equalizer_, caps);
|
|
||||||
gst_caps_unref(caps);
|
|
||||||
|
|
||||||
// Add an extra audioconvert at the end as osxaudiosink supports only one format.
|
|
||||||
GstElement* convert = CreateElement( "audioconvert", gst_audiobin_, "FFFUUUU" );
|
|
||||||
if (!convert) { return false; }
|
|
||||||
gst_element_link_many( gst_equalizer_, gst_identity_, gst_volume_,
|
|
||||||
gst_audioscale_, convert, gst_audiosink_, NULL );
|
|
||||||
|
|
||||||
|
|
||||||
gst_bin_add( GST_BIN(gst_pipeline_), gst_audiobin_);
|
|
||||||
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(gst_pipeline_)), BusCallbackSync, NULL);
|
|
||||||
gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(gst_pipeline_)), BusCallback, NULL);
|
|
||||||
|
|
||||||
pipeline_filled_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GstEngine::DestroyPipeline() {
|
|
||||||
fade_value_ = 0.0;
|
|
||||||
|
|
||||||
ClearScopeQ();
|
|
||||||
|
|
||||||
if ( pipeline_filled_ ) {
|
|
||||||
// Remove the event handler while we destroy the pipeline
|
|
||||||
GstPad *p = gst_element_get_pad (gst_decodebin_, "sink");
|
|
||||||
if (p)
|
|
||||||
gst_pad_remove_event_probe(p, event_cb_id_);
|
|
||||||
|
|
||||||
gst_element_set_state( gst_pipeline_, GST_STATE_NULL );
|
|
||||||
gst_object_unref( GST_OBJECT( gst_pipeline_ ) );
|
|
||||||
|
|
||||||
pipeline_filled_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool GstEngine::SetupAudioCD( const QString& device, unsigned track, bool pause) {
|
|
||||||
qDebug() << "setupAudioCD: device = " << device << ", track = " << track << ", pause = " << pause ;
|
|
||||||
bool filled = pipeline_filled_ && gst_src_ && strcmp( gst_element_get_name( gst_src_ ), "cdiocddasrc" ) == 0;
|
|
||||||
if ( filled || CreatePipeline() ) {
|
|
||||||
if ( filled || ( gst_src_ = CreateElement( "cdiocddasrc", gst_pipeline_, "cdiocddasrc" ) ) ) {
|
|
||||||
// TODO: allow user to configure default device rather than falling back to gstreamer default when no device passed in
|
|
||||||
if ( !device.isNull() )
|
|
||||||
g_object_set( G_OBJECT(gst_src_), "device", device.toAscii().constData(), NULL );
|
|
||||||
if ( track )
|
|
||||||
g_object_set (G_OBJECT (gst_src_), "track", track, NULL);
|
|
||||||
if ( filled || gst_element_link( gst_src_, gst_audiobin_ ) ) {
|
|
||||||
// the doco says we should only have to go to READY to read metadata, but that doesn't actually work
|
|
||||||
if ( gst_element_set_state( gst_pipeline_, pause ? GST_STATE_PAUSED : GST_STATE_READY ) != GST_STATE_CHANGE_FAILURE && gst_element_get_state( gst_pipeline_, NULL, NULL, GST_CLOCK_TIME_NONE ) == GST_STATE_CHANGE_SUCCESS )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DestroyPipeline();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
qint64 GstEngine::PruneScope() {
|
qint64 GstEngine::PruneScope() {
|
||||||
if ( !pipeline_filled_ ) return 0; // don't prune if we aren't playing
|
if (!current_pipeline_)
|
||||||
|
return 0;
|
||||||
|
|
||||||
// get the position playing in the audio device
|
// get the position playing in the audio device
|
||||||
GstFormat fmt = GST_FORMAT_TIME;
|
gint64 pos = current_pipeline_->position();
|
||||||
gint64 pos = 0;
|
|
||||||
gst_element_query_position( gst_pipeline_, &fmt, &pos );
|
|
||||||
|
|
||||||
GstBuffer *buf = 0;
|
GstBuffer *buf = 0;
|
||||||
quint64 etime;
|
quint64 etime;
|
||||||
|
|
|
@ -31,17 +31,16 @@
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
|
#include <boost/shared_ptr.hpp>
|
||||||
|
|
||||||
class QTimerEvent;
|
class QTimerEvent;
|
||||||
|
|
||||||
|
class GstEnginePipeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class GstEngine
|
* @class GstEngine
|
||||||
* @short GStreamer engine plugin
|
* @short GStreamer engine plugin
|
||||||
* @author Mark Kretschmann <markey@web.de>
|
* @author Mark Kretschmann <markey@web.de>
|
||||||
*
|
|
||||||
* GstEngine uses following pipeline for playing (syntax used by gst-launch):
|
|
||||||
* { filesrc location=file.ext ! decodebin ! audioconvert ! audioscale ! volume
|
|
||||||
* ! adder } ! { queue ! equalizer ! identity ! volume ! audiosink }
|
|
||||||
*/
|
*/
|
||||||
class GstEngine : public Engine::Base {
|
class GstEngine : public Engine::Base {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -69,15 +68,15 @@ class GstEngine : public Engine::Base {
|
||||||
Engine::State state() const;
|
Engine::State state() const;
|
||||||
const Engine::Scope& scope();
|
const Engine::Scope& scope();
|
||||||
|
|
||||||
virtual bool metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
|
|
||||||
virtual bool getAudioCDContents(const QString &device, QList<QUrl> &urls);
|
|
||||||
|
|
||||||
void gstStatusText(const QString& str) { emit statusText( str ); }
|
void gstStatusText(const QString& str) { emit statusText( str ); }
|
||||||
void gstMetaData(Engine::SimpleMetaBundle &bundle) { emit metaData( bundle ); }
|
void gstMetaData(Engine::SimpleMetaBundle &bundle) { emit metaData( bundle ); }
|
||||||
|
|
||||||
PluginDetailsList GetOutputsList() const { return GetPluginList( "Sink/Audio" ); }
|
PluginDetailsList GetOutputsList() const { return GetPluginList( "Sink/Audio" ); }
|
||||||
static bool DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(const QString& name);
|
static bool DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(const QString& name);
|
||||||
|
|
||||||
|
static GstElement* CreateElement(
|
||||||
|
const QString& factoryName, GstElement* bin = 0, const QString& name = 0);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool load(const QUrl&, bool stream);
|
bool load(const QUrl&, bool stream);
|
||||||
bool play(uint offset);
|
bool play(uint offset);
|
||||||
|
@ -99,54 +98,22 @@ class GstEngine : public Engine::Base {
|
||||||
void timerEvent(QTimerEvent*);
|
void timerEvent(QTimerEvent*);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void HandlePipelineError();
|
|
||||||
void EndOfStreamReached();
|
void EndOfStreamReached();
|
||||||
|
void HandlePipelineError(const QString& message);
|
||||||
/** Called when no output sink was selected. Shows the GStreamer engine settings dialog. */
|
void NewMetaData(const Engine::SimpleMetaBundle& bundle);
|
||||||
void ErrorNoOutput();
|
void NewBuffer(GstBuffer* buf);
|
||||||
|
void ClearScopeQ();
|
||||||
/** Transmits new decoded metadata to the application */
|
|
||||||
void NewMetaData();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static GstEngine* instance() { return sInstance; }
|
// Callbacks
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a GStreamer element and puts it into pipeline.
|
|
||||||
* @param factoryName Name of the element class to create.
|
|
||||||
* @param bin Container into which the element is put.
|
|
||||||
* @param name Identifier for the element.
|
|
||||||
* @return Pointer to the created element, or NULL for failure.
|
|
||||||
*/
|
|
||||||
static GstElement* CreateElement(
|
|
||||||
const QString& factoryName, GstElement* bin = 0, const QString& name = 0);
|
|
||||||
|
|
||||||
// CALLBACKS:
|
|
||||||
/** Bus message */
|
|
||||||
static GstBusSyncReply BusCallbackSync( GstBus*, GstMessage*, gpointer );
|
|
||||||
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
|
|
||||||
/** Called when decodebin has generated a new pad */
|
|
||||||
static void NewPadCallback(GstElement*, GstPad*, gboolean, gpointer);
|
|
||||||
/** Used by canDecode(). When called, the format probably can be decoded */
|
|
||||||
static void CanDecodeNewPadCallback(GstElement*, GstPad*, gboolean, gpointer);
|
static void CanDecodeNewPadCallback(GstElement*, GstPad*, gboolean, gpointer);
|
||||||
/** Used by canDecode(). Called after last pad so it makes no sense to wait anymore */
|
|
||||||
static void CanDecodeLastCallback(GstElement*, gpointer);
|
static void CanDecodeLastCallback(GstElement*, gpointer);
|
||||||
/** Called when new metadata tags have been found */
|
|
||||||
static void EventCallback( GstPad*, GstEvent* event, gpointer arg);
|
|
||||||
/** Duplicates audio data for application side processing */
|
|
||||||
static void HandoffCallback( GstPad*, GstBuffer*, gpointer );
|
|
||||||
|
|
||||||
/** Get a list of available plugins from a specified Class */
|
/** Get a list of available plugins from a specified Class */
|
||||||
PluginDetailsList GetPluginList(const QString& classname) const;
|
PluginDetailsList GetPluginList(const QString& classname) const;
|
||||||
|
|
||||||
/** Construct the output pipeline */
|
/** Construct the output pipeline */
|
||||||
bool CreatePipeline();
|
boost::shared_ptr<GstEnginePipeline> CreatePipeline(const QUrl& url);
|
||||||
|
|
||||||
/** Stops playback, destroys all input pipelines, destroys output pipeline, and frees resources */
|
|
||||||
void DestroyPipeline();
|
|
||||||
|
|
||||||
/* Constructs the pipeline for audio CDs, optionally selecting a device and/or track and/or setting the state to paused */
|
|
||||||
bool SetupAudioCD( const QString& device, unsigned track, bool pause );
|
|
||||||
|
|
||||||
/** Beams the streaming buffer status to Amarok */
|
/** Beams the streaming buffer status to Amarok */
|
||||||
void SendBufferStatus();
|
void SendBufferStatus();
|
||||||
|
@ -156,33 +123,11 @@ class GstEngine : public Engine::Base {
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Interval of main timer, handles the volume fading
|
// Interval of main timer, handles the volume fading
|
||||||
static const int kTimerInterval = 40; //msec
|
static const int kTimerInterval = 40; //msec
|
||||||
static const int kGstStateTimeout = 10000000;
|
|
||||||
|
|
||||||
QString sink_;
|
QString sink_;
|
||||||
QString device_;
|
QString device_;
|
||||||
|
|
||||||
static GstEngine* sInstance;
|
boost::shared_ptr<GstEnginePipeline> current_pipeline_;
|
||||||
|
|
||||||
GstElement* gst_pipeline_;
|
|
||||||
|
|
||||||
GstElement* gst_src_;
|
|
||||||
GstElement* gst_decodebin_;
|
|
||||||
|
|
||||||
GstElement* gst_audiobin_;
|
|
||||||
|
|
||||||
GstElement* gst_audioconvert_;
|
|
||||||
GstElement* gst_equalizer_;
|
|
||||||
GstElement* gst_identity_;
|
|
||||||
GstElement* gst_volume_;
|
|
||||||
GstElement* gst_audioscale_;
|
|
||||||
GstElement* gst_audiosink_;
|
|
||||||
|
|
||||||
QString gst_error_;
|
|
||||||
QString gst_debug_;
|
|
||||||
|
|
||||||
int metacount_;
|
|
||||||
|
|
||||||
uint event_cb_id_;
|
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// scope
|
// scope
|
||||||
|
@ -197,19 +142,15 @@ class GstEngine : public Engine::Base {
|
||||||
|
|
||||||
void UpdateScope();
|
void UpdateScope();
|
||||||
qint64 PruneScope();
|
qint64 PruneScope();
|
||||||
void ClearScopeQ();
|
|
||||||
|
|
||||||
QMutex scope_mutex_;
|
QMutex scope_mutex_;
|
||||||
|
|
||||||
bool pipeline_filled_;
|
|
||||||
float fade_value_;
|
float fade_value_;
|
||||||
|
|
||||||
bool equalizer_enabled_;
|
bool equalizer_enabled_;
|
||||||
int equalizer_preamp_;
|
int equalizer_preamp_;
|
||||||
QList<int> equalizer_gains_;
|
QList<int> equalizer_gains_;
|
||||||
|
|
||||||
Engine::SimpleMetaBundle meta_bundle_;
|
|
||||||
|
|
||||||
bool shutdown_;
|
bool shutdown_;
|
||||||
mutable bool can_decode_success_;
|
mutable bool can_decode_success_;
|
||||||
mutable bool can_decode_last_;
|
mutable bool can_decode_last_;
|
||||||
|
|
|
@ -0,0 +1,299 @@
|
||||||
|
/* 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 "gstenginepipeline.h"
|
||||||
|
#include "gstequalizer.h"
|
||||||
|
#include "gstengine.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
GstEnginePipeline::GstEnginePipeline()
|
||||||
|
: QObject(NULL),
|
||||||
|
valid_(false),
|
||||||
|
sink_(GstEngine::kAutoSink),
|
||||||
|
pipeline_(NULL),
|
||||||
|
src_(NULL),
|
||||||
|
decodebin_(NULL),
|
||||||
|
audiobin_(NULL),
|
||||||
|
audioconvert_(NULL),
|
||||||
|
equalizer_(NULL),
|
||||||
|
identity_(NULL),
|
||||||
|
volume_(NULL),
|
||||||
|
audioscale_(NULL),
|
||||||
|
audiosink_(NULL),
|
||||||
|
event_cb_id_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::set_output_device(const QString &sink, const QString &device) {
|
||||||
|
sink_ = sink;
|
||||||
|
device_ = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GstEnginePipeline::Init(const QUrl &url) {
|
||||||
|
pipeline_ = gst_pipeline_new("pipeline");
|
||||||
|
|
||||||
|
// Source
|
||||||
|
src_ = GstEngine::CreateElement("giosrc");
|
||||||
|
if (!src_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(src_), "location", url.toEncoded().constData(), NULL);
|
||||||
|
gst_bin_add(GST_BIN(pipeline_), src_);
|
||||||
|
|
||||||
|
// Decode bin
|
||||||
|
if (!(decodebin_ = GstEngine::CreateElement("decodebin", pipeline_))) { return false; }
|
||||||
|
g_signal_connect(G_OBJECT(decodebin_), "new-decoded-pad", G_CALLBACK(NewPadCallback), this);
|
||||||
|
|
||||||
|
GstPad* pad = gst_element_get_pad(decodebin_, "sink");
|
||||||
|
if (pad) {
|
||||||
|
event_cb_id_ = gst_pad_add_event_probe (pad, G_CALLBACK(EventCallback), this);
|
||||||
|
gst_object_unref(pad);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The link from decodebin to audioconvert will be made in the newPad-callback
|
||||||
|
gst_element_link(src_, decodebin_);
|
||||||
|
|
||||||
|
// Audio bin
|
||||||
|
audiobin_ = gst_bin_new("audiobin");
|
||||||
|
gst_bin_add(GST_BIN(pipeline_), audiobin_);
|
||||||
|
|
||||||
|
if (!(audiosink_ = GstEngine::CreateElement(sink_, audiobin_)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (GstEngine::DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(sink_) && !device_.isEmpty())
|
||||||
|
g_object_set(G_OBJECT(audiosink_), "device", device_.toUtf8().constData(), NULL);
|
||||||
|
|
||||||
|
equalizer_ = GST_ELEMENT(gst_equalizer_new());
|
||||||
|
gst_bin_add(GST_BIN(audiobin_), equalizer_);
|
||||||
|
|
||||||
|
if (!(audioconvert_ = GstEngine::CreateElement("audioconvert", audiobin_))) { return false; }
|
||||||
|
if (!(identity_ = GstEngine::CreateElement("identity", audiobin_))) { return false; }
|
||||||
|
if (!(volume_ = GstEngine::CreateElement("volume", audiobin_))) { return false; }
|
||||||
|
if (!(audioscale_ = GstEngine::CreateElement("audioresample", audiobin_))) { return false; }
|
||||||
|
|
||||||
|
pad = gst_element_get_pad(audioconvert_, "sink");
|
||||||
|
gst_element_add_pad(audiobin_, gst_ghost_pad_new("sink", pad));
|
||||||
|
gst_object_unref(pad);
|
||||||
|
|
||||||
|
// add a data probe on the src pad if the audioconvert element for our scope
|
||||||
|
// we do it here because we want pre-equalized and pre-volume samples
|
||||||
|
// so that our visualization are not affected by them
|
||||||
|
pad = gst_element_get_pad(audioconvert_, "src");
|
||||||
|
gst_pad_add_buffer_probe(pad, G_CALLBACK(HandoffCallback), this);
|
||||||
|
gst_object_unref (pad);
|
||||||
|
|
||||||
|
// Ensure we get the right type out of audioconvert for our scope
|
||||||
|
GstCaps* caps = gst_caps_new_simple ("audio/x-raw-int",
|
||||||
|
"width", G_TYPE_INT, 16,
|
||||||
|
"signed", G_TYPE_BOOLEAN, true,
|
||||||
|
NULL);
|
||||||
|
gst_element_link_filtered(audioconvert_, equalizer_, caps);
|
||||||
|
gst_caps_unref(caps);
|
||||||
|
|
||||||
|
// Add an extra audioconvert at the end as osxaudiosink supports only one format.
|
||||||
|
GstElement* convert = GstEngine::CreateElement("audioconvert", audiobin_, "FFFUUUU");
|
||||||
|
if (!convert) { return false; }
|
||||||
|
gst_element_link_many(equalizer_, identity_, volume_,
|
||||||
|
audioscale_, convert, audiosink_, NULL);
|
||||||
|
|
||||||
|
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallbackSync, this);
|
||||||
|
gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallback, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GstEnginePipeline::~GstEnginePipeline() {
|
||||||
|
// We don't want an EOS signal from the decodebin
|
||||||
|
if (decodebin_) {
|
||||||
|
GstPad *p = gst_element_get_pad(decodebin_, "sink");
|
||||||
|
if (p)
|
||||||
|
gst_pad_remove_event_probe(p, event_cb_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipeline_) {
|
||||||
|
gst_element_set_state(pipeline_, GST_STATE_NULL);
|
||||||
|
gst_object_unref(GST_OBJECT(pipeline_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gboolean GstEnginePipeline::BusCallback(GstBus*, GstMessage* msg, gpointer self) {
|
||||||
|
GstEnginePipeline* instance = static_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
switch ( GST_MESSAGE_TYPE(msg)) {
|
||||||
|
case GST_MESSAGE_ERROR: {
|
||||||
|
GError* error;
|
||||||
|
gchar* debugs;
|
||||||
|
|
||||||
|
gst_message_parse_error(msg, &error, &debugs);
|
||||||
|
qDebug() << "ERROR RECEIVED IN BUS_CB <" << error->message << ">" ;
|
||||||
|
|
||||||
|
emit instance->Error(QString::fromAscii(error->message));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GST_MESSAGE_TAG: {
|
||||||
|
gchar* string=NULL;
|
||||||
|
Engine::SimpleMetaBundle bundle;
|
||||||
|
GstTagList* taglist;
|
||||||
|
gst_message_parse_tag(msg,&taglist);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if ( gst_tag_list_get_string( taglist, GST_TAG_TITLE, &string ) && string ) {
|
||||||
|
qDebug() << "received tag 'Title': " << QString( string ) ;
|
||||||
|
bundle.title = string;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
if ( gst_tag_list_get_string( taglist, GST_TAG_ARTIST, &string ) && string ) {
|
||||||
|
qDebug() << "received tag 'Artist': " << QString( string ) ;
|
||||||
|
bundle.artist = string;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
if ( gst_tag_list_get_string( taglist, GST_TAG_COMMENT, &string ) && string ) {
|
||||||
|
qDebug() << "received tag 'Comment': " << QString( string ) ;
|
||||||
|
bundle.comment = string;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
if ( gst_tag_list_get_string( taglist, GST_TAG_ALBUM, &string ) && string ) {
|
||||||
|
qDebug() << "received tag 'Album': " << QString( string ) ;
|
||||||
|
bundle.album = string;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
g_free(string);
|
||||||
|
gst_tag_list_free(taglist);
|
||||||
|
if (success)
|
||||||
|
emit instance->MetadataFound(bundle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return GST_BUS_DROP;
|
||||||
|
}
|
||||||
|
|
||||||
|
GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg, gpointer self) {
|
||||||
|
GstEnginePipeline* instance = static_cast<GstEnginePipeline*>(self);
|
||||||
|
switch (GST_MESSAGE_TYPE(msg)) {
|
||||||
|
case GST_MESSAGE_EOS:
|
||||||
|
emit instance->EndOfStreamReached();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GST_BUS_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer self) {
|
||||||
|
GstEnginePipeline* instance = static_cast<GstEnginePipeline*>(self);
|
||||||
|
GstPad* const audiopad = gst_element_get_pad(instance->audiobin_, "sink");
|
||||||
|
|
||||||
|
if (GST_PAD_IS_LINKED(audiopad)) {
|
||||||
|
qDebug() << "audiopad is already linked. Unlinking old pad." ;
|
||||||
|
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_pad_link(pad, audiopad);
|
||||||
|
|
||||||
|
gst_object_unref(audiopad);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GstEnginePipeline::HandoffCallback(GstPad*, GstBuffer* buf, gpointer self) {
|
||||||
|
GstEnginePipeline* instance = static_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
if (instance->forwards_buffers_) {
|
||||||
|
gst_buffer_ref(buf);
|
||||||
|
emit instance->BufferFound(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::EventCallback(GstPad*, GstEvent* event, gpointer self) {
|
||||||
|
GstEnginePipeline* instance = static_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
switch(static_cast<int>(event->type)) {
|
||||||
|
case GST_EVENT_EOS:
|
||||||
|
emit instance->EndOfStreamReached();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 GstEnginePipeline::position() const {
|
||||||
|
GstFormat fmt = GST_FORMAT_TIME;
|
||||||
|
gint64 value = 0;
|
||||||
|
gst_element_query_position(pipeline_, &fmt, &value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
qint64 GstEnginePipeline::length() const {
|
||||||
|
GstFormat fmt = GST_FORMAT_TIME;
|
||||||
|
gint64 value = 0;
|
||||||
|
gst_element_query_duration(pipeline_, &fmt, &value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GstState GstEnginePipeline::state() const {
|
||||||
|
GstState s, sp;
|
||||||
|
if (gst_element_get_state(pipeline_, &s, &sp, kGstStateTimeout) ==
|
||||||
|
GST_STATE_CHANGE_FAILURE)
|
||||||
|
return GST_STATE_NULL;
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GstEnginePipeline::SetState(GstState state) {
|
||||||
|
return gst_element_set_state(pipeline_, state) != GST_STATE_CHANGE_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GstEnginePipeline::Seek(qint64 nanosec) {
|
||||||
|
return gst_element_seek(pipeline_, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
|
||||||
|
GST_SEEK_TYPE_SET, nanosec, GST_SEEK_TYPE_NONE,
|
||||||
|
GST_CLOCK_TIME_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetEqualizerEnabled(bool enabled) {
|
||||||
|
g_object_set(G_OBJECT(equalizer_), "active", enabled, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetEqualizerParams(int preamp, const QList<int>& band_gains) {
|
||||||
|
// Preamp
|
||||||
|
g_object_set(G_OBJECT(equalizer_), "preamp", ( preamp + 100 ) / 2, NULL);
|
||||||
|
|
||||||
|
// Gains
|
||||||
|
std::vector<int> gains_temp;
|
||||||
|
gains_temp.resize( band_gains.count() );
|
||||||
|
for ( int i = 0; i < band_gains.count(); i++ )
|
||||||
|
gains_temp[i] = band_gains.at( i ) + 100;
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(equalizer_), "gain", &gains_temp, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetVolume(int percent) {
|
||||||
|
g_object_set(G_OBJECT(volume_), "volume", double(percent) * 0.01, NULL);
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/* 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 GSTENGINEPIPELINE_H
|
||||||
|
#define GSTENGINEPIPELINE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
#include "engine_fwd.h"
|
||||||
|
|
||||||
|
class GstEngine;
|
||||||
|
|
||||||
|
class GstEnginePipeline : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
GstEnginePipeline();
|
||||||
|
~GstEnginePipeline();
|
||||||
|
|
||||||
|
// Call these setters before Init
|
||||||
|
void set_output_device(const QString& sink, const QString& device);
|
||||||
|
void set_forwards_buffers(bool v) { forwards_buffers_ = v; }
|
||||||
|
|
||||||
|
// Creates the pipeline, returns false on error
|
||||||
|
bool Init(const QUrl& url);
|
||||||
|
|
||||||
|
// Control the music playback
|
||||||
|
bool SetState(GstState state);
|
||||||
|
bool Seek(qint64 nanosec);
|
||||||
|
void SetEqualizerEnabled(bool enabled);
|
||||||
|
void SetEqualizerParams(int preamp, const QList<int>& band_gains);
|
||||||
|
void SetVolume(int percent);
|
||||||
|
|
||||||
|
// Get information about the music playback
|
||||||
|
bool is_valid() const { return valid_; }
|
||||||
|
qint64 position() const;
|
||||||
|
qint64 length() const;
|
||||||
|
GstState state() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void EndOfStreamReached();
|
||||||
|
void MetadataFound(const Engine::SimpleMetaBundle& bundle);
|
||||||
|
void BufferFound(GstBuffer* buf);
|
||||||
|
void Error(const QString& message);
|
||||||
|
|
||||||
|
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*, gboolean, gpointer);
|
||||||
|
static void HandoffCallback(GstPad*, GstBuffer*, gpointer);
|
||||||
|
static void EventCallback(GstPad*, GstEvent*, gpointer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const int kGstStateTimeout = 10000000;
|
||||||
|
|
||||||
|
bool valid_;
|
||||||
|
QString sink_;
|
||||||
|
QString device_;
|
||||||
|
bool forwards_buffers_;
|
||||||
|
|
||||||
|
GstElement* pipeline_;
|
||||||
|
|
||||||
|
// Bins
|
||||||
|
// src ! decodebin ! audiobin
|
||||||
|
GstElement* src_;
|
||||||
|
GstElement* decodebin_;
|
||||||
|
GstElement* audiobin_;
|
||||||
|
|
||||||
|
// Elements in the audiobin
|
||||||
|
// audioconvert ! equalizer ! identity ! volume ! audioscale ! audioconvert ! audiosink
|
||||||
|
GstElement* audioconvert_;
|
||||||
|
GstElement* equalizer_;
|
||||||
|
GstElement* identity_;
|
||||||
|
GstElement* volume_;
|
||||||
|
GstElement* audioscale_;
|
||||||
|
GstElement* audiosink_;
|
||||||
|
|
||||||
|
uint event_cb_id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GSTENGINEPIPELINE_H
|
Loading…
Reference in New Issue