2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
2018-09-21 01:12:21 +02:00
* Copyright 2018 , Jonas Kvinge < jonas @ jkvinge . net >
2018-02-27 18:06:05 +01:00
*
* Strawberry is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Strawberry is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
# ifndef GSTENGINEPIPELINE_H
# define GSTENGINEPIPELINE_H
# include "config.h"
# include <memory>
2018-05-01 00:41:33 +02:00
# include <glib.h>
# include <glib-object.h>
# include <glib/gtypes.h>
2018-02-27 18:06:05 +01:00
# include <gst/gst.h>
2019-09-07 23:34:13 +02:00
# include <gst/pbutils/pbutils.h>
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
# include <QtGlobal>
2018-02-27 18:06:05 +01:00
# include <QObject>
2018-05-01 00:41:33 +02:00
# include <QMutex>
2018-02-27 18:06:05 +01:00
# include <QThreadPool>
2018-05-01 00:41:33 +02:00
# include <QFuture>
2018-02-27 18:06:05 +01:00
# include <QTimeLine>
2018-05-01 00:41:33 +02:00
# include <QBasicTimer>
2018-04-02 03:43:56 +02:00
# include <QByteArray>
2018-05-01 00:41:33 +02:00
# include <QList>
2018-04-02 03:43:56 +02:00
# include <QVariant>
2018-05-01 00:41:33 +02:00
# include <QString>
2018-09-22 23:30:19 +02:00
# include <QUrl>
2018-05-01 00:41:33 +02:00
# include <QTimerEvent>
2018-02-27 18:06:05 +01:00
2018-10-19 20:18:46 +02:00
using std : : unique_ptr ;
2018-02-27 18:06:05 +01:00
class GstEngine ;
2018-05-01 00:41:33 +02:00
class GstBufferConsumer ;
class GstElementDeleter ;
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
namespace Engine {
struct SimpleMetaBundle ;
} // namespace Engine
2018-04-02 03:43:56 +02:00
struct GstPlayBin ;
2018-02-27 18:06:05 +01:00
class GstEnginePipeline : public QObject {
Q_OBJECT
public :
GstEnginePipeline ( GstEngine * engine ) ;
~ GstEnginePipeline ( ) ;
// Globally unique across all pipelines.
int id ( ) const { return id_ ; }
// Call these setters before Init
void set_output_device ( const QString & sink , const QVariant & device ) ;
2019-11-08 23:07:21 +01:00
void set_volume_enabled ( const bool enabled ) ;
void set_stereo_balancer_enabled ( const bool enabled ) ;
void set_equalizer_enabled ( const bool enabled ) ;
2019-09-15 20:27:32 +02:00
void set_replaygain ( const bool enabled , const int mode , const float preamp , const bool compression ) ;
2018-02-27 18:06:05 +01:00
void set_buffer_duration_nanosec ( qint64 duration_nanosec ) ;
void set_buffer_min_fill ( int percent ) ;
// Creates the pipeline, returns false on error
2019-09-15 20:27:32 +02:00
bool InitFromUrl ( const QByteArray & stream_url , const QUrl original_url , const qint64 end_nanosec ) ;
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
// GstBufferConsumers get fed audio data. Thread-safe.
void AddBufferConsumer ( GstBufferConsumer * consumer ) ;
void RemoveBufferConsumer ( GstBufferConsumer * consumer ) ;
2018-02-27 18:06:05 +01:00
void RemoveAllBufferConsumers ( ) ;
// Control the music playback
2019-09-15 20:27:32 +02:00
QFuture < GstStateChangeReturn > SetState ( const GstState state ) ;
Q_INVOKABLE bool Seek ( const qint64 nanosec ) ;
void SetVolume ( const int percent ) ;
2019-11-08 23:07:21 +01:00
void SetStereoBalance ( const float value ) ;
void SetEqualizerParams ( const int preamp , const QList < int > & band_gains ) ;
2019-09-15 20:27:32 +02:00
void StartFader ( const qint64 duration_nanosec , const QTimeLine : : Direction direction = QTimeLine : : Forward , const QTimeLine : : CurveShape shape = QTimeLine : : LinearCurve , const bool use_fudge_timer = true ) ;
2018-02-27 18:06:05 +01:00
2018-04-02 03:43:56 +02:00
// If this is set then it will be loaded automatically when playback finishes for gapless playback
2019-09-07 23:34:13 +02:00
void SetNextUrl ( const QByteArray & stream_url , const QUrl & original_url , qint64 beginning_nanosec , qint64 end_nanosec ) ;
2019-09-22 22:47:07 +02:00
bool has_next_valid_url ( ) const { return ! next_stream_url_ . isEmpty ( ) ; }
2018-04-02 03:43:56 +02:00
void SetSourceDevice ( QString device ) { source_device_ = device ; }
2018-02-27 18:06:05 +01:00
// Get information about the music playback
2019-09-07 23:34:13 +02:00
QByteArray stream_url ( ) const { return stream_url_ ; }
2018-09-22 23:13:56 +02:00
QUrl original_url ( ) const { return original_url_ ; }
2018-02-27 18:06:05 +01:00
bool is_valid ( ) const { return valid_ ; }
2019-11-08 23:07:21 +01:00
2018-04-02 03:43:56 +02:00
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
2018-02-27 18:06:05 +01:00
qint64 position ( ) const ;
2018-04-02 03:43:56 +02:00
// Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware.
2018-02-27 18:06:05 +01:00
qint64 length ( ) const ;
2018-04-02 03:43:56 +02:00
// Returns this pipeline's state. May return GST_STATE_NULL if the state check timed out. The timeout value is a reasonable default.
2018-02-27 18:06:05 +01:00
GstState state ( ) const ;
qint64 segment_start ( ) const { return segment_start_ ; }
2018-04-02 03:43:56 +02:00
// Don't allow the user to change the playback state (playing/paused) while the pipeline is buffering.
2018-02-27 18:06:05 +01:00
bool is_buffering ( ) const { return buffering_ ; }
2018-04-02 03:43:56 +02:00
QByteArray redirect_url ( ) const { return redirect_url_ ; }
2018-02-27 18:06:05 +01:00
QString source_device ( ) const { return source_device_ ; }
public slots :
void SetVolumeModifier ( qreal mod ) ;
2019-11-08 23:07:21 +01:00
signals :
2019-09-15 20:27:32 +02:00
void EndOfStreamReached ( const int pipeline_id , const bool has_next_track ) ;
void MetadataFound ( const int pipeline_id , const Engine : : SimpleMetaBundle & bundle ) ;
2018-02-27 18:06:05 +01:00
// This indicates an error, delegated from GStreamer, in the pipeline.
// The message, domain and error_code are related to GStreamer's GError.
2019-09-15 20:27:32 +02:00
void Error ( const int pipeline_id , const QString & message , const int domain , const int error_code ) ;
2018-02-27 18:06:05 +01:00
void FaderFinished ( ) ;
void BufferingStarted ( ) ;
void BufferingProgress ( int percent ) ;
void BufferingFinished ( ) ;
protected :
void timerEvent ( QTimerEvent * ) ;
private :
2019-11-08 23:07:21 +01:00
bool InitAudioBin ( ) ;
2018-04-02 03:43:56 +02:00
// Static callbacks. The GstEnginePipeline instance is passed in the last argument.
2019-11-08 23:07:21 +01:00
static GstPadProbeReturn EventHandoffCallback ( GstPad * , GstPadProbeInfo * , gpointer ) ;
static void SourceSetupCallback ( GstPlayBin * , GParamSpec * pspec , gpointer ) ;
2018-02-27 18:06:05 +01:00
static void NewPadCallback ( GstElement * , GstPad * , gpointer ) ;
2019-11-08 23:07:21 +01:00
static GstPadProbeReturn PlaybinProbe ( GstPad * , GstPadProbeInfo * , gpointer ) ;
2018-02-27 18:06:05 +01:00
static GstPadProbeReturn HandoffCallback ( GstPad * , GstPadProbeInfo * , gpointer ) ;
2018-04-02 03:43:56 +02:00
static void AboutToFinishCallback ( GstPlayBin * , gpointer ) ;
2019-11-08 23:07:21 +01:00
static GstBusSyncReply BusCallbackSync ( GstBus * , GstMessage * , gpointer ) ;
static gboolean BusCallback ( GstBus * , GstMessage * , gpointer ) ;
2018-02-27 18:06:05 +01:00
static void TaskEnterCallback ( GstTask * , GThread * , gpointer ) ;
2019-11-08 23:07:21 +01:00
static void StreamDiscovered ( GstDiscoverer * discoverer , GstDiscovererInfo * info , GError * err , gpointer instance ) ;
static void StreamDiscoveryFinished ( GstDiscoverer * discoverer , gpointer instance ) ;
static QString GSTdiscovererErrorMessage ( GstDiscovererResult result ) ;
2018-02-27 18:06:05 +01:00
void TagMessageReceived ( GstMessage * ) ;
void ErrorMessageReceived ( GstMessage * ) ;
void ElementMessageReceived ( GstMessage * ) ;
void StateChangedMessageReceived ( GstMessage * ) ;
void BufferingMessageReceived ( GstMessage * ) ;
void StreamStatusMessageReceived ( GstMessage * ) ;
2018-04-02 03:43:56 +02:00
void StreamStartMessageReceived ( ) ;
2018-02-27 18:06:05 +01:00
2018-09-22 23:13:56 +02:00
QString ParseStrTag ( GstTagList * list , const char * tag ) const ;
guint ParseUIntTag ( GstTagList * list , const char * tag ) const ;
2018-02-27 18:06:05 +01:00
void UpdateVolume ( ) ;
void UpdateStereoBalance ( ) ;
2019-11-08 23:07:21 +01:00
void UpdateEqualizer ( ) ;
2019-09-07 23:34:13 +02:00
2018-02-27 18:06:05 +01:00
private slots :
void FaderTimelineFinished ( ) ;
private :
static const int kGstStateTimeoutNanosecs ;
static const int kFaderFudgeMsec ;
2019-09-07 23:34:13 +02:00
static const int kDiscoveryTimeoutS ;
2018-02-27 18:06:05 +01:00
static const int kEqBandCount ;
static const int kEqBandFrequencies [ ] ;
static GstElementDeleter * sElementDeleter ;
GstEngine * engine_ ;
2018-04-02 03:43:56 +02:00
// Using == to compare two pipelines is a bad idea, because new ones often get created in the same address as old ones. This ID will be unique for each pipeline.
// Threading warning: access to the static ID field isn't protected by a mutex because all pipeline creation is currently done in the main thread.
2018-02-27 18:06:05 +01:00
static int sId ;
int id_ ;
// General settings for the pipeline
bool valid_ ;
2018-06-28 01:15:32 +02:00
QString output_ ;
2018-02-27 18:06:05 +01:00
QVariant device_ ;
2019-11-08 23:07:21 +01:00
bool volume_enabled_ ;
bool stereo_balancer_enabled_ ;
bool eq_enabled_ ;
bool rg_enabled_ ;
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
// Stereo balance:
2018-02-27 18:06:05 +01:00
// From -1.0 - 1.0
// -1.0 is left, 1.0 is right.
float stereo_balance_ ;
2019-10-27 23:48:54 +01:00
// Equalizer
int eq_preamp_ ;
QList < int > eq_band_gains_ ;
2018-02-27 18:06:05 +01:00
// ReplayGain
int rg_mode_ ;
float rg_preamp_ ;
bool rg_compression_ ;
// Buffering
quint64 buffer_duration_nanosec_ ;
int buffer_min_fill_ ;
bool buffering_ ;
2018-06-28 01:15:32 +02:00
// These get called when there is a new audio buffer available
QList < GstBufferConsumer * > buffer_consumers_ ;
QMutex buffer_consumers_mutex_ ;
qint64 segment_start_ ;
bool segment_start_received_ ;
2018-02-27 18:06:05 +01:00
2018-03-10 13:02:56 +01:00
// The URL that is currently playing, and the URL that is to be preloaded when the current track is close to finishing.
2019-09-07 23:34:13 +02:00
QByteArray stream_url_ ;
2018-09-22 23:13:56 +02:00
QUrl original_url_ ;
2019-09-07 23:34:13 +02:00
QByteArray next_stream_url_ ;
2018-09-22 23:13:56 +02:00
QUrl next_original_url_ ;
2018-02-27 18:06:05 +01:00
2018-03-10 13:02:56 +01:00
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
2018-02-27 18:06:05 +01:00
qint64 end_offset_nanosec_ ;
2018-03-10 13:02:56 +01:00
// We store the beginning and end for the preloading song too, so we can just carry on without reloading the file if the sections carry on from each other.
2018-02-27 18:06:05 +01:00
qint64 next_beginning_offset_nanosec_ ;
qint64 next_end_offset_nanosec_ ;
2018-03-10 13:02:56 +01:00
// Set temporarily when moving to the next contiguous section in a multi-part file.
2018-02-27 18:06:05 +01:00
bool ignore_next_seek_ ;
2018-03-10 13:02:56 +01:00
// Set temporarily when switching out the decode bin, so metadata doesn't get sent while the Player still thinks it's playing the last song
2018-02-27 18:06:05 +01:00
bool ignore_tags_ ;
2018-03-10 13:02:56 +01:00
// When the gstreamer source requests a redirect we store the URL here and callers can pick it up after the state change to PLAYING fails.
2018-04-02 03:43:56 +02:00
QByteArray redirect_url_ ;
2018-02-27 18:06:05 +01:00
// When we need to specify the device to use as source (for CD device)
QString source_device_ ;
2019-10-20 02:56:47 +02:00
// Seeking while the pipeline is in the READY state doesn't work, so we have to wait until it goes to PAUSED or PLAYING.
// Also we have to wait for the playbin to be connected.
2018-02-27 18:06:05 +01:00
bool pipeline_is_initialised_ ;
bool pipeline_is_connected_ ;
qint64 pending_seek_nanosec_ ;
// We can only use gst_element_query_position() when the pipeline is in
2018-04-02 03:43:56 +02:00
// PAUSED nor PLAYING state. Whenever we get a new position (e.g. after a correct call to gst_element_query_position() or after a seek), we store
// it here so that we can use it when using gst_element_query_position() is not possible.
2018-02-27 18:06:05 +01:00
mutable gint64 last_known_position_ns_ ;
2018-04-02 03:43:56 +02:00
// Complete the transition to the next song when it starts playing
bool next_uri_set_ ;
2018-02-27 18:06:05 +01:00
int volume_percent_ ;
qreal volume_modifier_ ;
std : : unique_ptr < QTimeLine > fader_ ;
QBasicTimer fader_fudge_timer_ ;
bool use_fudge_timer_ ;
GstElement * pipeline_ ;
GstElement * audiobin_ ;
2019-10-27 23:48:54 +01:00
GstElement * audioqueue_ ;
2019-03-27 00:27:49 +01:00
GstElement * volume_ ;
2019-10-27 23:48:54 +01:00
GstElement * audiopanorama_ ;
2019-03-27 00:27:49 +01:00
GstElement * equalizer_ ;
2019-10-27 23:48:54 +01:00
GstElement * equalizer_preamp_ ;
2019-09-07 23:34:13 +02:00
GstDiscoverer * discoverer_ ;
2018-02-27 18:06:05 +01:00
2019-09-17 22:42:51 +02:00
int pad_added_cb_id_ ;
int notify_source_cb_id_ ;
2019-11-08 23:07:21 +01:00
int about_to_finish_cb_id_ ;
2019-09-17 22:42:51 +02:00
int bus_cb_id_ ;
int discovery_finished_cb_id_ ;
int discovery_discovered_cb_id_ ;
2018-02-27 18:06:05 +01:00
QThreadPool set_state_threadpool_ ;
2019-10-20 02:56:47 +02:00
GstSegment last_playbin_segment_ ;
2019-09-17 22:42:51 +02:00
2018-02-27 18:06:05 +01:00
} ;
# endif // GSTENGINEPIPELINE_H