2018-02-27 18:06:05 +01: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 > *
2021-03-20 21:14:47 +01:00
* Copyright ( C ) 2017 - 2021 Jonas Kvinge < jonas @ jkvinge . net > *
2018-02-27 18:06:05 +01:00
* *
* This program is free software ; you can redistribute it and / or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation ; either version 2 of the License , or *
* ( at your option ) any later version . *
* *
* This program is distributed in the hope that it will be useful , *
* but WITHOUT ANY WARRANTY ; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the *
* GNU General Public License for more details . *
* *
* You should have received a copy of the GNU General Public License *
* along with this program ; if not , write to the *
* Free Software Foundation , Inc . , *
* 51 Franklin Steet , Fifth Floor , Boston , MA 02111 - 1307 , USA . *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "config.h"
2018-05-01 00:41:33 +02:00
# include <glib.h>
# include <glib-object.h>
# include <memory>
2021-06-21 19:52:37 +02:00
# include <algorithm>
2018-05-01 00:41:33 +02:00
# include <vector>
2020-06-14 23:54:18 +02:00
# include <cmath>
2018-02-27 18:06:05 +01:00
# include <gst/gst.h>
2020-10-21 00:07:58 +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>
# include <QFuture>
2021-01-30 21:53:53 +01:00
# include <QFutureWatcher>
2018-04-06 22:13:11 +02:00
# include <QTimer>
2018-05-01 00:41:33 +02:00
# include <QList>
# include <QByteArray>
# include <QChar>
# include <QString>
# include <QStringList>
# include <QUrl>
2018-04-06 22:13:11 +02:00
# include <QTimeLine>
2020-07-18 17:53:14 +02:00
# include <QEasingCurve>
2018-05-01 00:41:33 +02:00
# include <QMetaObject>
2020-02-09 02:29:35 +01:00
# include <QTimerEvent>
2018-02-27 18:06:05 +01:00
# include "core/logging.h"
# include "core/taskmanager.h"
2020-10-21 00:07:58 +02:00
# include "core/signalchecker.h"
2022-12-28 03:12:00 +01:00
# include "utilities/timeconstants.h"
2018-05-01 00:41:33 +02:00
# include "enginebase.h"
# include "enginetype.h"
# include "gstengine.h"
# include "gstenginepipeline.h"
# include "gstbufferconsumer.h"
2019-04-18 15:03:01 +02:00
2018-02-27 18:06:05 +01:00
const char * GstEngine : : kAutoSink = " autoaudiosink " ;
const char * GstEngine : : kALSASink = " alsasink " ;
2018-06-07 19:38:40 +02:00
const char * GstEngine : : kOpenALSASink = " openalsink " ;
2018-02-27 18:06:05 +01:00
const char * GstEngine : : kOSSSink = " osssink " ;
const char * GstEngine : : kOSS4Sink = " oss4sink " ;
const char * GstEngine : : kJackAudioSink = " jackaudiosink " ;
const char * GstEngine : : kPulseSink = " pulsesink " ;
const char * GstEngine : : kA2DPSink = " a2dpsink " ;
const char * GstEngine : : kAVDTPSink = " avdtpsink " ;
2018-06-07 19:38:40 +02:00
const char * GstEngine : : InterAudiosink = " interaudiosink " ;
const char * GstEngine : : kDirectSoundSink = " directsoundsink " ;
const char * GstEngine : : kOSXAudioSink = " osxaudiosink " ;
2020-10-21 00:07:58 +02:00
const int GstEngine : : kDiscoveryTimeoutS = 10 ;
2018-02-27 18:06:05 +01:00
2021-06-20 19:04:08 +02:00
GstEngine : : GstEngine ( TaskManager * task_manager , QObject * parent )
: Engine : : Base ( Engine : : GStreamer , parent ) ,
task_manager_ ( task_manager ) ,
2020-10-21 00:07:58 +02:00
gst_startup_ ( nullptr ) ,
discoverer_ ( nullptr ) ,
2018-02-27 18:06:05 +01:00
buffering_task_id_ ( - 1 ) ,
latest_buffer_ ( nullptr ) ,
2019-10-27 23:48:54 +01:00
stereo_balancer_enabled_ ( false ) ,
2021-06-20 23:53:28 +02:00
stereo_balance_ ( 0.0F ) ,
2019-11-08 23:07:21 +01:00
equalizer_enabled_ ( false ) ,
equalizer_preamp_ ( 0 ) ,
2018-02-27 18:06:05 +01:00
seek_timer_ ( new QTimer ( this ) ) ,
2021-06-20 23:53:28 +02:00
waiting_to_seek_ ( false ) ,
seek_pos_ ( 0 ) ,
2018-02-27 18:06:05 +01:00
timer_id_ ( - 1 ) ,
is_fading_out_to_pause_ ( false ) ,
has_faded_out_ ( false ) ,
scope_chunk_ ( 0 ) ,
2020-10-21 00:07:58 +02:00
have_new_buffer_ ( false ) ,
2021-06-20 23:53:28 +02:00
scope_chunks_ ( 0 ) ,
2020-10-21 00:07:58 +02:00
discovery_finished_cb_id_ ( - 1 ) ,
2021-07-11 07:40:57 +02:00
discovery_discovered_cb_id_ ( - 1 ) {
2018-02-27 18:06:05 +01:00
seek_timer_ - > setSingleShot ( true ) ;
seek_timer_ - > setInterval ( kSeekDelayNanosec / kNsecPerMsec ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( seek_timer_ , & QTimer : : timeout , this , & GstEngine : : SeekNow ) ;
2018-02-27 18:06:05 +01:00
2021-03-21 04:47:11 +01:00
GstEngine : : ReloadSettings ( ) ;
2018-02-27 18:06:05 +01:00
}
GstEngine : : ~ GstEngine ( ) {
2019-11-08 23:07:21 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
current_pipeline_ . reset ( ) ;
2019-11-08 23:07:21 +01:00
2019-10-20 18:52:58 +02:00
if ( latest_buffer_ ) {
gst_buffer_unref ( latest_buffer_ ) ;
latest_buffer_ = nullptr ;
}
2019-11-08 23:07:21 +01:00
2020-10-21 00:07:58 +02:00
if ( discoverer_ ) {
2021-08-23 21:21:08 +02:00
if ( discovery_discovered_cb_id_ ! = - 1 ) {
2020-10-21 00:07:58 +02:00
g_signal_handler_disconnect ( G_OBJECT ( discoverer_ ) , discovery_discovered_cb_id_ ) ;
2021-08-23 21:21:08 +02:00
}
if ( discovery_finished_cb_id_ ! = - 1 ) {
2020-10-21 00:07:58 +02:00
g_signal_handler_disconnect ( G_OBJECT ( discoverer_ ) , discovery_finished_cb_id_ ) ;
2021-08-23 21:21:08 +02:00
}
2020-10-21 00:07:58 +02:00
gst_discoverer_stop ( discoverer_ ) ;
g_object_unref ( discoverer_ ) ;
discoverer_ = nullptr ;
}
2018-02-27 18:06:05 +01:00
}
bool GstEngine : : Init ( ) {
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
return true ;
}
Engine : : State GstEngine : : state ( ) const {
2019-09-07 23:34:13 +02:00
if ( ! current_pipeline_ ) return stream_url_ . isEmpty ( ) ? Engine : : Empty : Engine : : Idle ;
2018-02-27 18:06:05 +01:00
switch ( current_pipeline_ - > state ( ) ) {
case GST_STATE_NULL :
return Engine : : Empty ;
case GST_STATE_READY :
return Engine : : Idle ;
case GST_STATE_PLAYING :
return Engine : : Playing ;
case GST_STATE_PAUSED :
return Engine : : Paused ;
default :
return Engine : : Empty ;
}
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : StartPreloading ( const QUrl & stream_url , const QUrl & original_url , const bool force_stop_at_end , const qint64 beginning_nanosec , const qint64 end_nanosec ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2019-09-07 23:34:13 +02:00
QByteArray gst_url = FixupUrl ( stream_url ) ;
2018-02-27 18:06:05 +01:00
2018-04-02 03:43:56 +02:00
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
2020-10-21 00:07:58 +02:00
if ( current_pipeline_ ) {
2018-09-22 23:13:56 +02:00
current_pipeline_ - > SetNextUrl ( gst_url , original_url , beginning_nanosec , force_stop_at_end ? end_nanosec : 0 ) ;
2020-10-21 00:07:58 +02:00
// Add request to discover the stream
if ( discoverer_ ) {
2021-04-13 22:11:44 +02:00
if ( ! gst_discoverer_discover_uri_async ( discoverer_ , gst_url . constData ( ) ) ) {
2020-10-21 00:07:58 +02:00
qLog ( Error ) < < " Failed to start stream discovery for " < < gst_url ;
}
}
}
2018-03-10 13:02:56 +01:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
bool GstEngine : : Load ( const QUrl & stream_url , const QUrl & original_url , Engine : : TrackChangeFlags change , const bool force_stop_at_end , const quint64 beginning_nanosec , const qint64 end_nanosec ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2019-09-07 23:34:13 +02:00
Engine : : Base : : Load ( stream_url , original_url , change , force_stop_at_end , beginning_nanosec , end_nanosec ) ;
2018-02-27 18:06:05 +01:00
2019-09-07 23:34:13 +02:00
QByteArray gst_url = FixupUrl ( stream_url ) ;
2018-02-27 18:06:05 +01:00
bool crossfade = current_pipeline_ & & ( ( crossfade_enabled_ & & change & Engine : : Manual ) | | ( autocrossfade_enabled_ & & change & Engine : : Auto ) | | ( ( crossfade_enabled_ | | autocrossfade_enabled_ ) & & change & Engine : : Intro ) ) ;
if ( change & Engine : : Auto & & change & Engine : : SameAlbum & & ! crossfade_same_album_ )
crossfade = false ;
2019-09-07 23:34:13 +02:00
if ( ! crossfade & & current_pipeline_ & & current_pipeline_ - > stream_url ( ) = = gst_url & & change & Engine : : Auto ) {
2018-02-27 18:06:05 +01:00
// We're not crossfading, and the pipeline is already playing the URI we want, so just do nothing.
return true ;
}
2018-03-10 13:02:56 +01:00
2020-04-20 17:49:06 +02:00
std : : shared_ptr < GstEnginePipeline > pipeline = CreatePipeline ( gst_url , original_url , force_stop_at_end ? end_nanosec : 0 ) ;
2018-02-27 18:06:05 +01:00
if ( ! pipeline ) return false ;
if ( crossfade ) StartFadeout ( ) ;
BufferingFinished ( ) ;
current_pipeline_ = pipeline ;
SetVolume ( volume_ ) ;
2019-11-08 23:07:21 +01:00
SetStereoBalance ( stereo_balance_ ) ;
2019-04-20 22:23:22 +02:00
SetEqualizerParameters ( equalizer_preamp_ , equalizer_gains_ ) ;
2018-02-27 18:06:05 +01:00
// Maybe fade in this track
2021-08-23 21:21:08 +02:00
if ( crossfade ) {
2018-02-27 18:06:05 +01:00
current_pipeline_ - > StartFader ( fadeout_duration_nanosec_ , QTimeLine : : Forward ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2020-10-21 00:07:58 +02:00
// Setting up stream discoverer
if ( ! discoverer_ ) {
discoverer_ = gst_discoverer_new ( kDiscoveryTimeoutS * GST_SECOND , nullptr ) ;
if ( discoverer_ ) {
discovery_discovered_cb_id_ = CHECKED_GCONNECT ( G_OBJECT ( discoverer_ ) , " discovered " , & StreamDiscovered , this ) ;
discovery_finished_cb_id_ = CHECKED_GCONNECT ( G_OBJECT ( discoverer_ ) , " finished " , & StreamDiscoveryFinished , this ) ;
gst_discoverer_start ( discoverer_ ) ;
}
}
// Add request to discover the stream
if ( discoverer_ ) {
2021-04-13 22:11:44 +02:00
if ( ! gst_discoverer_discover_uri_async ( discoverer_ , gst_url . constData ( ) ) ) {
2020-10-21 00:07:58 +02:00
qLog ( Error ) < < " Failed to start stream discovery for " < < gst_url ;
}
}
2018-02-27 18:06:05 +01:00
return true ;
2018-10-30 23:39:08 +01:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
bool GstEngine : : Play ( const quint64 offset_nanosec ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
if ( ! current_pipeline_ | | current_pipeline_ - > is_buffering ( ) ) return false ;
2021-01-30 21:53:53 +01:00
QFutureWatcher < GstStateChangeReturn > * watcher = new QFutureWatcher < GstStateChangeReturn > ( ) ;
2022-10-29 18:45:09 +02:00
const int pipeline_id = current_pipeline_ - > id ( ) ;
QObject : : connect ( watcher , & QFutureWatcher < GstStateChangeReturn > : : finished , this , [ this , watcher , pipeline_id , offset_nanosec ] ( ) {
const GstStateChangeReturn ret = watcher - > result ( ) ;
2021-01-30 21:53:53 +01:00
watcher - > deleteLater ( ) ;
2022-10-29 18:45:09 +02:00
PlayDone ( ret , offset_nanosec , pipeline_id ) ;
2021-01-30 21:53:53 +01:00
} ) ;
2022-10-29 18:45:09 +02:00
QFuture < GstStateChangeReturn > future = current_pipeline_ - > SetState ( GST_STATE_PLAYING ) ;
2021-06-16 00:30:21 +02:00
watcher - > setFuture ( future ) ;
2018-06-28 01:15:32 +02:00
if ( is_fading_out_to_pause_ ) {
current_pipeline_ - > SetState ( GST_STATE_PAUSED ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
return true ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : Stop ( const bool stop_after ) {
2018-02-27 18:06:05 +01:00
StopTimers ( ) ;
2022-10-29 18:43:49 +02:00
stream_url_ . clear ( ) ; // To ensure we return Empty from state()
original_url_ . clear ( ) ;
2018-02-27 18:06:05 +01:00
beginning_nanosec_ = end_nanosec_ = 0 ;
2018-04-02 03:43:56 +02:00
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
2018-02-27 18:06:05 +01:00
if ( is_fading_out_to_pause_ ) {
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( current_pipeline_ . get ( ) , & GstEnginePipeline : : FaderFinished , nullptr , nullptr ) ;
2018-02-27 18:06:05 +01:00
is_fading_out_to_pause_ = false ;
has_faded_out_ = true ;
fadeout_pause_pipeline_ . reset ( ) ;
fadeout_pipeline_ . reset ( ) ;
}
if ( fadeout_enabled_ & & current_pipeline_ & & ! stop_after ) StartFadeout ( ) ;
current_pipeline_ . reset ( ) ;
BufferingFinished ( ) ;
emit StateChanged ( Engine : : Empty ) ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
void GstEngine : : Pause ( ) {
if ( ! current_pipeline_ | | current_pipeline_ - > is_buffering ( ) ) return ;
2018-04-02 03:43:56 +02:00
// Check if we started a fade out. If it isn't finished yet and the user pressed play, we inverse the fader and resume the playback.
2018-02-27 18:06:05 +01:00
if ( is_fading_out_to_pause_ ) {
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( current_pipeline_ . get ( ) , & GstEnginePipeline : : FaderFinished , nullptr , nullptr ) ;
2020-07-18 17:53:14 +02:00
current_pipeline_ - > StartFader ( fadeout_pause_duration_nanosec_ , QTimeLine : : Forward , QEasingCurve : : InOutQuad , false ) ;
2018-02-27 18:06:05 +01:00
is_fading_out_to_pause_ = false ;
has_faded_out_ = false ;
emit StateChanged ( Engine : : Playing ) ;
return ;
}
if ( current_pipeline_ - > state ( ) = = GST_STATE_PLAYING ) {
if ( fadeout_pause_enabled_ ) {
StartFadeoutPause ( ) ;
}
else {
current_pipeline_ - > SetState ( GST_STATE_PAUSED ) ;
emit StateChanged ( Engine : : Paused ) ;
StopTimers ( ) ;
}
}
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
void GstEngine : : Unpause ( ) {
if ( ! current_pipeline_ | | current_pipeline_ - > is_buffering ( ) ) return ;
if ( current_pipeline_ - > state ( ) = = GST_STATE_PAUSED ) {
current_pipeline_ - > SetState ( GST_STATE_PLAYING ) ;
2018-03-10 13:02:56 +01:00
// Check if we faded out last time. If yes, fade in no matter what the settings say.
// If we pause with fadeout, deactivate fadeout and resume playback, the player would be muted if not faded in.
2018-02-27 18:06:05 +01:00
if ( has_faded_out_ ) {
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( current_pipeline_ . get ( ) , & GstEnginePipeline : : FaderFinished , nullptr , nullptr ) ;
2020-07-18 17:53:14 +02:00
current_pipeline_ - > StartFader ( fadeout_pause_duration_nanosec_ , QTimeLine : : Forward , QEasingCurve : : InOutQuad , false ) ;
2018-02-27 18:06:05 +01:00
has_faded_out_ = false ;
}
emit StateChanged ( Engine : : Playing ) ;
StartTimers ( ) ;
}
}
2019-09-15 20:27:32 +02:00
void GstEngine : : Seek ( const quint64 offset_nanosec ) {
2018-02-27 18:06:05 +01:00
if ( ! current_pipeline_ ) return ;
seek_pos_ = beginning_nanosec_ + offset_nanosec ;
waiting_to_seek_ = true ;
if ( ! seek_timer_ - > isActive ( ) ) {
SeekNow ( ) ;
seek_timer_ - > start ( ) ; // Stop us from seeking again for a little while
}
}
2022-12-03 03:46:59 +01:00
void GstEngine : : SetVolumeSW ( const uint volume ) {
if ( current_pipeline_ ) current_pipeline_ - > SetVolume ( volume ) ;
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
qint64 GstEngine : : position_nanosec ( ) const {
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ ) return 0 ;
2018-02-27 18:06:05 +01:00
2021-10-30 02:21:29 +02:00
const qint64 result = current_pipeline_ - > position ( ) - static_cast < qint64 > ( beginning_nanosec_ ) ;
2022-06-13 00:23:42 +02:00
return std : : max ( 0LL , result ) ;
2018-06-28 01:15:32 +02:00
}
qint64 GstEngine : : length_nanosec ( ) const {
if ( ! current_pipeline_ ) return 0 ;
2021-10-30 02:21:29 +02:00
const qint64 result = end_nanosec_ - static_cast < qint64 > ( beginning_nanosec_ ) ;
2018-06-28 01:15:32 +02:00
if ( result > 0 ) {
return result ;
}
else {
// Get the length from the pipeline if we don't know.
return current_pipeline_ - > length ( ) ;
}
}
2019-09-15 20:27:32 +02:00
const Engine : : Scope & GstEngine : : scope ( const int chunk_length ) {
2018-06-28 01:15:32 +02:00
// The new buffer could have a different size
if ( have_new_buffer_ ) {
if ( latest_buffer_ ) {
2020-06-15 17:59:02 +02:00
scope_chunks_ = ceil ( ( static_cast < double > ( GST_BUFFER_DURATION ( latest_buffer_ ) / static_cast < double > ( chunk_length * kNsecPerMsec ) ) ) ) ;
2018-06-28 01:15:32 +02:00
}
// if the buffer is shorter than the chunk length
if ( scope_chunks_ < = 0 ) {
scope_chunks_ = 1 ;
}
scope_chunk_ = 0 ;
have_new_buffer_ = false ;
}
if ( latest_buffer_ ) {
UpdateScope ( chunk_length ) ;
}
return scope_ ;
}
EngineBase : : OutputDetailsList GstEngine : : GetOutputsList ( ) const {
2020-10-17 17:29:09 +02:00
const_cast < GstEngine * > ( this ) - > EnsureInitialized ( ) ;
2018-10-22 20:40:02 +02:00
2018-06-28 01:15:32 +02:00
PluginDetailsList plugins = GetPluginList ( " Sink/Audio " ) ;
2021-06-20 19:04:08 +02:00
EngineBase : : OutputDetailsList ret ;
ret . reserve ( plugins . count ( ) ) ;
2018-06-28 01:15:32 +02:00
for ( const PluginDetails & plugin : plugins ) {
OutputDetails output ;
output . name = plugin . name ;
output . description = plugin . description ;
if ( plugin . name = = kAutoSink ) output . iconname = " soundcard " ;
2019-04-08 18:46:11 +02:00
else if ( plugin . name = = kALSASink | | plugin . name = = kOSS4Sink ) output . iconname = " alsa " ;
else if ( plugin . name = = kJackAudioSink ) output . iconname = " jack " ;
2018-06-28 01:15:32 +02:00
else if ( plugin . name = = kPulseSink ) output . iconname = " pulseaudio " ;
2019-04-08 18:46:11 +02:00
else if ( plugin . name = = kA2DPSink | | plugin . name = = kAVDTPSink ) output . iconname = " bluetooth " ;
2018-06-28 01:15:32 +02:00
else output . iconname = " soundcard " ;
ret . append ( output ) ;
}
return ret ;
}
2018-07-01 01:29:52 +02:00
bool GstEngine : : ValidOutput ( const QString & output ) {
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-10-22 20:40:02 +02:00
2018-07-01 01:29:52 +02:00
PluginDetailsList plugins = GetPluginList ( " Sink/Audio " ) ;
2021-06-21 19:52:37 +02:00
return std : : any_of ( plugins . begin ( ) , plugins . end ( ) , [ output ] ( const PluginDetails & plugin ) { return plugin . name = = output ; } ) ;
2018-07-01 01:29:52 +02:00
}
bool GstEngine : : CustomDeviceSupport ( const QString & output ) {
2021-11-11 00:57:21 +01:00
return ( output = = kALSASink | | output = = kOpenALSASink | | output = = kOSSSink | | output = = kOSS4Sink | | output = = kPulseSink | | output = = kA2DPSink | | output = = kAVDTPSink | | output = = kJackAudioSink ) ;
2018-06-28 01:15:32 +02:00
}
2018-09-21 23:29:00 +02:00
bool GstEngine : : ALSADeviceSupport ( const QString & output ) {
return ( output = = kALSASink ) ;
}
2018-06-28 01:15:32 +02:00
void GstEngine : : ReloadSettings ( ) {
Engine : : Base : : ReloadSettings ( ) ;
if ( output_ . isEmpty ( ) ) output_ = kAutoSink ;
}
2019-10-19 01:45:24 +02:00
void GstEngine : : ConsumeBuffer ( GstBuffer * buffer , const int pipeline_id , const QString & format ) {
2018-06-28 01:15:32 +02:00
// Schedule this to run in the GUI thread. The buffer gets added to the queue and unreffed by UpdateScope.
2019-10-19 01:45:24 +02:00
if ( ! QMetaObject : : invokeMethod ( this , " AddBufferToScope " , Q_ARG ( GstBuffer * , buffer ) , Q_ARG ( int , pipeline_id ) , Q_ARG ( QString , format ) ) ) {
2018-06-28 01:15:32 +02:00
qLog ( Warning ) < < " Failed to invoke AddBufferToScope on GstEngine " ;
2020-05-24 23:21:26 +02:00
gst_buffer_unref ( buffer ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
2018-02-27 18:06:05 +01:00
}
2019-11-08 23:07:21 +01:00
void GstEngine : : SetStereoBalancerEnabled ( const bool enabled ) {
2018-03-04 20:13:05 +01:00
2019-11-08 23:07:21 +01:00
stereo_balancer_enabled_ = enabled ;
if ( current_pipeline_ ) current_pipeline_ - > set_stereo_balancer_enabled ( enabled ) ;
2018-02-27 18:06:05 +01:00
}
2019-11-08 23:07:21 +01:00
void GstEngine : : SetStereoBalance ( const float value ) {
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
stereo_balance_ = value ;
if ( current_pipeline_ ) current_pipeline_ - > SetStereoBalance ( value ) ;
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
}
void GstEngine : : SetEqualizerEnabled ( const bool enabled ) {
equalizer_enabled_ = enabled ;
if ( current_pipeline_ ) current_pipeline_ - > set_equalizer_enabled ( enabled ) ;
2019-04-20 22:23:22 +02:00
2018-02-27 18:06:05 +01:00
}
2019-11-08 23:07:21 +01:00
void GstEngine : : SetEqualizerParameters ( const int preamp , const QList < int > & band_gains ) {
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
equalizer_preamp_ = preamp ;
equalizer_gains_ = band_gains ;
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
if ( current_pipeline_ ) current_pipeline_ - > SetEqualizerParams ( preamp , band_gains ) ;
2019-04-20 22:23:22 +02:00
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : AddBufferConsumer ( GstBufferConsumer * consumer ) {
2019-11-08 23:07:21 +01:00
2018-06-28 01:15:32 +02:00
buffer_consumers_ < < consumer ;
if ( current_pipeline_ ) current_pipeline_ - > AddBufferConsumer ( consumer ) ;
2019-11-08 23:07:21 +01:00
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : RemoveBufferConsumer ( GstBufferConsumer * consumer ) {
2019-11-08 23:07:21 +01:00
2018-06-28 01:15:32 +02:00
buffer_consumers_ . removeAll ( consumer ) ;
if ( current_pipeline_ ) current_pipeline_ - > RemoveBufferConsumer ( consumer ) ;
2019-11-08 23:07:21 +01:00
2018-02-27 18:06:05 +01:00
}
void GstEngine : : timerEvent ( QTimerEvent * e ) {
if ( e - > timerId ( ) ! = timer_id_ ) return ;
if ( current_pipeline_ ) {
const qint64 current_position = position_nanosec ( ) ;
const qint64 current_length = length_nanosec ( ) ;
const qint64 remaining = current_length - current_position ;
const qint64 fudge = kTimerIntervalNanosec + 100 * kNsecPerMsec ; // Mmm fudge
2021-10-30 02:21:29 +02:00
const qint64 gap = static_cast < qint64 > ( buffer_duration_nanosec_ ) + ( autocrossfade_enabled_ ? fadeout_duration_nanosec_ : kPreloadGapNanosec ) ;
2018-02-27 18:06:05 +01:00
// only if we know the length of the current stream...
if ( current_length > 0 ) {
// emit TrackAboutToEnd when we're a few seconds away from finishing
if ( remaining < gap + fudge ) {
EmitAboutToEnd ( ) ;
}
}
}
2018-03-04 20:13:05 +01:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : EndOfStreamReached ( const int pipeline_id , const bool has_next_track ) {
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ . get ( ) | | current_pipeline_ - > id ( ) ! = pipeline_id )
return ;
if ( ! has_next_track ) {
current_pipeline_ . reset ( ) ;
BufferingFinished ( ) ;
}
emit TrackEnded ( ) ;
2018-10-30 23:39:08 +01:00
2018-06-28 01:15:32 +02:00
}
2022-04-14 20:56:57 +02:00
void GstEngine : : HandlePipelineError ( const int pipeline_id , const int domain , const int error_code , const QString & message , const QString & debugstr ) {
2018-06-28 01:15:32 +02:00
2018-10-30 23:39:08 +01:00
if ( ! current_pipeline_ . get ( ) | | current_pipeline_ - > id ( ) ! = pipeline_id ) return ;
2018-06-28 01:15:32 +02:00
2020-10-13 00:49:34 +02:00
qLog ( Error ) < < " GStreamer error: " < < domain < < error_code < < message ;
2018-06-28 01:15:32 +02:00
current_pipeline_ . reset ( ) ;
BufferingFinished ( ) ;
emit StateChanged ( Engine : : Error ) ;
2018-10-30 23:39:08 +01:00
2021-02-11 22:23:54 +01:00
if (
( domain = = static_cast < int > ( GST_RESOURCE_ERROR ) & & (
error_code = = static_cast < int > ( GST_RESOURCE_ERROR_NOT_FOUND ) | |
error_code = = static_cast < int > ( GST_RESOURCE_ERROR_OPEN_READ ) | |
error_code = = static_cast < int > ( GST_RESOURCE_ERROR_NOT_AUTHORIZED )
) )
2021-02-11 22:33:54 +01:00
| | ( domain = = static_cast < int > ( GST_STREAM_ERROR ) & & error_code = = static_cast < int > ( GST_STREAM_ERROR_TYPE_NOT_FOUND ) )
2021-02-11 22:23:54 +01:00
) {
2019-09-07 23:34:13 +02:00
emit InvalidSongRequested ( stream_url_ ) ;
2018-10-30 23:39:08 +01:00
}
else {
emit FatalError ( ) ;
}
2018-06-28 01:15:32 +02:00
emit Error ( message ) ;
2022-04-14 20:56:57 +02:00
emit Error ( debugstr ) ;
2018-06-28 01:15:32 +02:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : NewMetaData ( const int pipeline_id , const Engine : : SimpleMetaBundle & bundle ) {
2018-06-28 01:15:32 +02:00
2019-09-07 23:34:13 +02:00
if ( ! current_pipeline_ . get ( ) | | current_pipeline_ - > id ( ) ! = pipeline_id ) return ;
2018-06-28 01:15:32 +02:00
emit MetaData ( bundle ) ;
2019-09-07 23:34:13 +02:00
2018-06-28 01:15:32 +02:00
}
2019-10-19 01:45:24 +02:00
void GstEngine : : AddBufferToScope ( GstBuffer * buf , const int pipeline_id , const QString & format ) {
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ | | current_pipeline_ - > id ( ) ! = pipeline_id ) {
gst_buffer_unref ( buf ) ;
return ;
}
if ( latest_buffer_ ) {
gst_buffer_unref ( latest_buffer_ ) ;
}
2019-10-19 01:45:24 +02:00
buffer_format_ = format ;
2018-06-28 01:15:32 +02:00
latest_buffer_ = buf ;
have_new_buffer_ = true ;
}
void GstEngine : : FadeoutFinished ( ) {
fadeout_pipeline_ . reset ( ) ;
emit FadeoutFinishedSignal ( ) ;
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
void GstEngine : : FadeoutPauseFinished ( ) {
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
fadeout_pause_pipeline_ - > SetState ( GST_STATE_PAUSED ) ;
current_pipeline_ - > SetState ( GST_STATE_PAUSED ) ;
emit StateChanged ( Engine : : Paused ) ;
StopTimers ( ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
is_fading_out_to_pause_ = false ;
has_faded_out_ = true ;
fadeout_pause_pipeline_ . reset ( ) ;
fadeout_pipeline_ . reset ( ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
emit FadeoutFinishedSignal ( ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
}
void GstEngine : : SeekNow ( ) {
if ( ! waiting_to_seek_ ) return ;
waiting_to_seek_ = false ;
2018-04-07 12:20:31 +02:00
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ ) return ;
2021-10-30 02:21:29 +02:00
if ( ! current_pipeline_ - > Seek ( static_cast < qint64 > ( seek_pos_ ) ) ) {
2018-06-28 01:15:32 +02:00
qLog ( Warning ) < < " Seek failed " ;
}
2021-08-23 21:21:08 +02:00
2018-02-27 18:06:05 +01:00
}
2021-01-30 21:53:53 +01:00
void GstEngine : : PlayDone ( const GstStateChangeReturn ret , const quint64 offset_nanosec , const int pipeline_id ) {
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ | | pipeline_id ! = current_pipeline_ - > id ( ) ) {
2018-02-27 18:06:05 +01:00
return ;
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
if ( ret = = GST_STATE_CHANGE_FAILURE ) {
// Failure, but we got a redirection URL - try loading that instead
QByteArray redirect_url = current_pipeline_ - > redirect_url ( ) ;
2019-09-07 23:34:13 +02:00
if ( ! redirect_url . isEmpty ( ) & & redirect_url ! = current_pipeline_ - > stream_url ( ) ) {
2018-06-28 01:15:32 +02:00
qLog ( Info ) < < " Redirecting to " < < redirect_url ;
2018-09-22 23:13:56 +02:00
current_pipeline_ = CreatePipeline ( redirect_url , current_pipeline_ - > original_url ( ) , end_nanosec_ ) ;
2018-06-28 01:15:32 +02:00
Play ( offset_nanosec ) ;
return ;
}
// Failure - give up
qLog ( Warning ) < < " Could not set thread to PLAYING. " ;
2018-02-27 18:06:05 +01:00
current_pipeline_ . reset ( ) ;
BufferingFinished ( ) ;
2018-06-28 01:15:32 +02:00
return ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
StartTimers ( ) ;
2018-02-27 18:06:05 +01:00
2018-09-22 23:13:56 +02:00
// Initial offset
2018-06-28 01:15:32 +02:00
if ( offset_nanosec ! = 0 | | beginning_nanosec_ ! = 0 ) {
Seek ( offset_nanosec ) ;
}
emit StateChanged ( Engine : : Playing ) ;
2018-09-22 23:13:56 +02:00
// We've successfully started playing a media stream with this url
2019-09-07 23:34:13 +02:00
emit ValidSongRequested ( stream_url_ ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : BufferingStarted ( ) {
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
if ( buffering_task_id_ ! = - 1 ) {
task_manager_ - > SetTaskFinished ( buffering_task_id_ ) ;
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
buffering_task_id_ = task_manager_ - > StartTask ( tr ( " Buffering " ) ) ;
task_manager_ - > SetTaskProgress ( buffering_task_id_ , 0 , 100 ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2019-09-15 20:27:32 +02:00
void GstEngine : : BufferingProgress ( const int percent ) {
2018-06-28 01:15:32 +02:00
task_manager_ - > SetTaskProgress ( buffering_task_id_ , percent , 100 ) ;
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
void GstEngine : : BufferingFinished ( ) {
if ( buffering_task_id_ ! = - 1 ) {
task_manager_ - > SetTaskFinished ( buffering_task_id_ ) ;
buffering_task_id_ = - 1 ;
}
2018-02-27 18:06:05 +01:00
}
GstEngine : : PluginDetailsList GstEngine : : GetPluginList ( const QString & classname ) const {
2020-10-17 17:29:09 +02:00
const_cast < GstEngine * > ( this ) - > EnsureInitialized ( ) ;
2018-10-22 20:40:02 +02:00
2018-02-27 18:06:05 +01:00
PluginDetailsList ret ;
GstRegistry * registry = gst_registry_get ( ) ;
GList * const features = gst_registry_get_feature_list ( registry , GST_TYPE_ELEMENT_FACTORY ) ;
GList * p = features ;
while ( p ) {
GstElementFactory * factory = GST_ELEMENT_FACTORY ( p - > data ) ;
2021-07-11 09:49:38 +02:00
if ( QString ( gst_element_factory_get_klass ( factory ) ) . contains ( classname ) ) {
2018-02-27 18:06:05 +01:00
PluginDetails details ;
details . name = QString : : fromUtf8 ( gst_plugin_feature_get_name ( p - > data ) ) ;
details . description = QString : : fromUtf8 ( gst_element_factory_get_metadata ( factory , GST_ELEMENT_METADATA_DESCRIPTION ) ) ;
2022-04-02 01:37:43 +02:00
if ( details . name = = " wasapi2sink " & & details . description = = " Stream audio to an audio capture device through WASAPI " ) {
details . description + = " 2 " ;
}
2018-02-27 18:06:05 +01:00
ret < < details ;
//qLog(Debug) << details.name << details.description;
}
p = g_list_next ( p ) ;
}
gst_plugin_feature_list_free ( features ) ;
return ret ;
}
2018-06-28 01:15:32 +02:00
QByteArray GstEngine : : FixupUrl ( const QUrl & url ) {
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-06-28 01:15:32 +02:00
QByteArray uri ;
// 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.
2023-01-10 18:26:42 +01:00
if ( url . isLocalFile ( ) & & ! url . host ( ) . isEmpty ( ) ) {
2019-02-25 16:35:29 +01:00
QString str = " file://// " + url . host ( ) + url . path ( ) ;
2020-04-30 17:32:31 +02:00
uri = str . toUtf8 ( ) ;
2018-06-28 01:15:32 +02:00
}
else if ( url . scheme ( ) = = " cdda " ) {
QString str ;
if ( url . path ( ) . isEmpty ( ) ) {
str = url . toString ( ) ;
str . remove ( str . lastIndexOf ( QChar ( ' a ' ) ) , 1 ) ;
}
else {
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
2022-08-28 02:44:37 +02:00
// So we handle them ourselves: we extract the track number and re-create a URL with only cdda:// + the track number (which can be handled by Gstreamer).
2018-06-28 01:15:32 +02:00
// We keep the device in mind, and we will set it later using SourceSetupCallback
QStringList path = url . path ( ) . split ( ' / ' ) ;
2019-02-25 16:35:29 +01:00
str = QString ( " cdda://%1 " ) . arg ( path . takeLast ( ) ) ;
2018-06-28 01:15:32 +02:00
QString device = path . join ( " / " ) ;
2018-10-22 20:40:02 +02:00
if ( current_pipeline_ ) current_pipeline_ - > SetSourceDevice ( device ) ;
2018-06-28 01:15:32 +02:00
}
2020-04-30 17:32:31 +02:00
uri = str . toUtf8 ( ) ;
2018-06-28 01:15:32 +02:00
}
else {
uri = url . toEncoded ( ) ;
}
return uri ;
}
void GstEngine : : StartFadeout ( ) {
if ( is_fading_out_to_pause_ ) return ;
fadeout_pipeline_ = current_pipeline_ ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( fadeout_pipeline_ . get ( ) , nullptr , nullptr , nullptr ) ;
2018-06-28 01:15:32 +02:00
fadeout_pipeline_ - > RemoveAllBufferConsumers ( ) ;
fadeout_pipeline_ - > StartFader ( fadeout_duration_nanosec_ , QTimeLine : : Backward ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( fadeout_pipeline_ . get ( ) , & GstEnginePipeline : : FaderFinished , this , & GstEngine : : FadeoutFinished ) ;
2018-06-28 01:15:32 +02:00
}
void GstEngine : : StartFadeoutPause ( ) {
fadeout_pause_pipeline_ = current_pipeline_ ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( fadeout_pause_pipeline_ . get ( ) , & GstEnginePipeline : : FaderFinished , nullptr , nullptr ) ;
2018-06-28 01:15:32 +02:00
2020-07-18 17:53:14 +02:00
fadeout_pause_pipeline_ - > StartFader ( fadeout_pause_duration_nanosec_ , QTimeLine : : Backward , QEasingCurve : : InOutQuad , false ) ;
2018-06-28 01:15:32 +02:00
if ( fadeout_pipeline_ & & fadeout_pipeline_ - > state ( ) = = GST_STATE_PLAYING ) {
2020-07-18 17:53:14 +02:00
fadeout_pipeline_ - > StartFader ( fadeout_pause_duration_nanosec_ , QTimeLine : : Backward , QEasingCurve : : Linear , false ) ;
2018-06-28 01:15:32 +02:00
}
2021-01-26 16:48:04 +01:00
QObject : : connect ( fadeout_pause_pipeline_ . get ( ) , & GstEnginePipeline : : FaderFinished , this , & GstEngine : : FadeoutPauseFinished ) ;
2018-06-28 01:15:32 +02:00
is_fading_out_to_pause_ = true ;
}
void GstEngine : : StartTimers ( ) {
StopTimers ( ) ;
timer_id_ = startTimer ( kTimerIntervalNanosec / kNsecPerMsec ) ;
}
void GstEngine : : StopTimers ( ) {
if ( timer_id_ ! = - 1 ) {
killTimer ( timer_id_ ) ;
timer_id_ = - 1 ;
}
}
2020-04-20 17:49:06 +02:00
std : : shared_ptr < GstEnginePipeline > GstEngine : : CreatePipeline ( ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2021-10-16 21:28:56 +02:00
std : : shared_ptr < GstEnginePipeline > ret = std : : make_shared < GstEnginePipeline > ( ) ;
2018-06-28 01:15:32 +02:00
ret - > set_output_device ( output_ , device_ ) ;
2019-11-08 23:07:21 +01:00
ret - > set_volume_enabled ( volume_control_ ) ;
ret - > set_stereo_balancer_enabled ( stereo_balancer_enabled_ ) ;
ret - > set_equalizer_enabled ( equalizer_enabled_ ) ;
2021-04-22 21:55:26 +02:00
ret - > set_replaygain ( rg_enabled_ , rg_mode_ , rg_preamp_ , rg_fallbackgain_ , rg_compression_ ) ;
2018-02-27 18:06:05 +01:00
ret - > set_buffer_duration_nanosec ( buffer_duration_nanosec_ ) ;
2020-10-07 20:29:26 +02:00
ret - > set_buffer_low_watermark ( buffer_low_watermark_ ) ;
ret - > set_buffer_high_watermark ( buffer_high_watermark_ ) ;
2020-11-04 22:16:20 +01:00
ret - > set_proxy_settings ( proxy_address_ , proxy_authentication_ , proxy_user_ , proxy_pass_ ) ;
2021-05-11 19:14:00 +02:00
ret - > set_channels ( channels_enabled_ , channels_ ) ;
2022-03-05 01:30:49 +01:00
ret - > set_bs2b_enabled ( bs2b_enabled_ ) ;
2022-12-03 03:46:59 +01:00
ret - > set_fading_enabled ( fadeout_enabled_ | | autocrossfade_enabled_ | | fadeout_pause_enabled_ ) ;
2018-02-27 18:06:05 +01:00
ret - > AddBufferConsumer ( this ) ;
2018-05-01 00:41:33 +02:00
for ( GstBufferConsumer * consumer : buffer_consumers_ ) {
2018-02-27 18:06:05 +01:00
ret - > AddBufferConsumer ( consumer ) ;
}
2021-01-26 16:48:04 +01:00
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : EndOfStreamReached , this , & GstEngine : : EndOfStreamReached ) ;
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : Error , this , & GstEngine : : HandlePipelineError ) ;
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : MetadataFound , this , & GstEngine : : NewMetaData ) ;
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : BufferingStarted , this , & GstEngine : : BufferingStarted ) ;
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : BufferingProgress , this , & GstEngine : : BufferingProgress ) ;
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : BufferingFinished , this , & GstEngine : : BufferingFinished ) ;
2022-12-03 03:46:59 +01:00
QObject : : connect ( ret . get ( ) , & GstEnginePipeline : : VolumeChanged , this , & EngineBase : : UpdateVolume ) ;
2018-02-27 18:06:05 +01:00
2018-04-02 03:43:56 +02:00
return ret ;
}
2020-04-20 17:49:06 +02:00
std : : shared_ptr < GstEnginePipeline > GstEngine : : CreatePipeline ( const QByteArray & gst_url , const QUrl & original_url , const qint64 end_nanosec ) {
2018-04-02 03:43:56 +02:00
2020-04-20 17:49:06 +02:00
std : : shared_ptr < GstEnginePipeline > ret = CreatePipeline ( ) ;
2021-10-16 21:28:56 +02:00
QString error ;
if ( ! ret - > InitFromUrl ( gst_url , original_url , end_nanosec , error ) ) {
ret . reset ( ) ;
emit Error ( error ) ;
emit StateChanged ( Engine : : Error ) ;
emit FatalError ( ) ;
}
2018-02-27 18:06:05 +01:00
return ret ;
2018-03-10 13:02:56 +01:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : UpdateScope ( const int chunk_length ) {
2018-02-27 18:06:05 +01:00
2022-10-13 22:39:31 +02:00
using sample_type = Engine : : Scope : : value_type ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
// Prevent dbz or invalid chunk size
if ( ! GST_CLOCK_TIME_IS_VALID ( GST_BUFFER_DURATION ( latest_buffer_ ) ) ) return ;
if ( GST_BUFFER_DURATION ( latest_buffer_ ) = = 0 ) return ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
GstMapInfo map ;
gst_buffer_map ( latest_buffer_ , & map , GST_MAP_READ ) ;
2018-03-10 13:02:56 +01:00
2018-06-28 01:15:32 +02:00
// Determine where to split the buffer
2021-03-21 18:53:02 +01:00
int chunk_density = static_cast < int > ( ( map . size * kNsecPerMsec ) / GST_BUFFER_DURATION ( latest_buffer_ ) ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
int chunk_size = chunk_length * chunk_density ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
// In case a buffer doesn't arrive in time
if ( scope_chunk_ > = scope_chunks_ ) {
scope_chunk_ = 0 ;
2020-05-24 23:21:26 +02:00
gst_buffer_unmap ( latest_buffer_ , & map ) ;
2018-06-28 01:15:32 +02:00
return ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
const sample_type * source = reinterpret_cast < sample_type * > ( map . data ) ;
sample_type * dest = scope_ . data ( ) ;
source + = ( chunk_size / sizeof ( sample_type ) ) * scope_chunk_ ;
2018-02-27 18:06:05 +01:00
2021-08-24 17:52:08 +02:00
size_t bytes = 0 ;
2018-04-07 12:19:01 +02:00
2018-06-28 01:15:32 +02:00
// Make sure we don't go beyond the end of the buffer
if ( scope_chunk_ = = scope_chunks_ - 1 ) {
2022-03-22 21:09:05 +01:00
bytes = qMin ( static_cast < Engine : : Scope : : size_type > ( map . size - ( chunk_size * scope_chunk_ ) ) , scope_ . size ( ) * sizeof ( sample_type ) ) ;
2018-06-28 01:15:32 +02:00
}
else {
bytes = qMin ( static_cast < Engine : : Scope : : size_type > ( chunk_size ) , scope_ . size ( ) * sizeof ( sample_type ) ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
scope_chunk_ + + ;
2019-10-19 01:45:24 +02:00
2020-07-04 00:29:11 +02:00
if ( buffer_format_ . startsWith ( " S16LE " ) | |
buffer_format_ . startsWith ( " U16LE " ) | |
buffer_format_ . startsWith ( " S24LE " ) | |
2021-10-12 18:46:12 +02:00
buffer_format_ . startsWith ( " S24_32LE " ) | |
2020-07-04 00:29:11 +02:00
buffer_format_ . startsWith ( " S32LE " ) | |
buffer_format_ . startsWith ( " F32LE " )
) {
2019-10-19 01:45:24 +02:00
memcpy ( dest , source , bytes ) ;
2019-10-20 18:52:58 +02:00
}
else {
2019-10-19 01:45:24 +02:00
memset ( dest , 0 , bytes ) ;
2019-10-20 18:52:58 +02:00
}
2018-06-07 19:38:40 +02:00
2018-06-28 01:15:32 +02:00
gst_buffer_unmap ( latest_buffer_ , & map ) ;
2018-06-07 19:38:40 +02:00
2018-06-28 01:15:32 +02:00
if ( scope_chunk_ = = scope_chunks_ ) {
gst_buffer_unref ( latest_buffer_ ) ;
latest_buffer_ = nullptr ;
2019-10-19 01:45:24 +02:00
buffer_format_ . clear ( ) ;
2018-06-28 01:15:32 +02:00
}
2018-06-07 19:38:40 +02:00
}
2020-10-21 00:07:58 +02:00
void GstEngine : : StreamDiscovered ( GstDiscoverer * , GstDiscovererInfo * info , GError * , gpointer self ) {
GstEngine * instance = reinterpret_cast < GstEngine * > ( self ) ;
if ( ! instance - > current_pipeline_ ) return ;
QString discovered_url ( gst_discoverer_info_get_uri ( info ) ) ;
GstDiscovererResult result = gst_discoverer_info_get_result ( info ) ;
if ( result ! = GST_DISCOVERER_OK ) {
QString error_message = GSTdiscovererErrorMessage ( result ) ;
2021-03-21 04:47:11 +01:00
qLog ( Error ) < < QString ( " Stream discovery for %1 failed: %2 " ) . arg ( discovered_url , error_message ) ;
2020-10-21 00:07:58 +02:00
return ;
}
GList * audio_streams = gst_discoverer_info_get_audio_streams ( info ) ;
if ( audio_streams ) {
GstDiscovererStreamInfo * stream_info = reinterpret_cast < GstDiscovererStreamInfo * > ( g_list_first ( audio_streams ) - > data ) ;
Engine : : SimpleMetaBundle bundle ;
if ( discovered_url = = instance - > current_pipeline_ - > stream_url ( ) ) {
2021-03-13 03:14:30 +01:00
bundle . type = Engine : : SimpleMetaBundle : : Type_Current ;
2020-10-21 00:07:58 +02:00
bundle . url = instance - > current_pipeline_ - > original_url ( ) ;
}
else if ( discovered_url = = instance - > current_pipeline_ - > next_stream_url ( ) ) {
2021-03-13 03:14:30 +01:00
bundle . type = Engine : : SimpleMetaBundle : : Type_Next ;
2020-10-21 00:07:58 +02:00
bundle . url = instance - > current_pipeline_ - > next_original_url ( ) ;
}
bundle . stream_url = QUrl ( discovered_url ) ;
2021-10-30 02:21:29 +02:00
bundle . samplerate = static_cast < int > ( gst_discoverer_audio_info_get_sample_rate ( GST_DISCOVERER_AUDIO_INFO ( stream_info ) ) ) ;
bundle . bitdepth = static_cast < int > ( gst_discoverer_audio_info_get_depth ( GST_DISCOVERER_AUDIO_INFO ( stream_info ) ) ) ;
bundle . bitrate = static_cast < int > ( gst_discoverer_audio_info_get_bitrate ( GST_DISCOVERER_AUDIO_INFO ( stream_info ) ) / 1000 ) ;
2020-10-21 00:07:58 +02:00
GstCaps * caps = gst_discoverer_stream_info_get_caps ( stream_info ) ;
2021-01-08 22:38:37 +01:00
const guint caps_size = gst_caps_get_size ( caps ) ;
for ( guint i = 0 ; i < caps_size ; + + i ) {
GstStructure * gst_structure = gst_caps_get_structure ( caps , i ) ;
if ( ! gst_structure ) continue ;
QString mimetype = gst_structure_get_name ( gst_structure ) ;
if ( ! mimetype . isEmpty ( ) & & mimetype ! = " audio/mpeg " ) {
bundle . filetype = Song : : FiletypeByMimetype ( mimetype ) ;
if ( bundle . filetype = = Song : : FileType_Unknown ) {
qLog ( Error ) < < " Unknown mimetype " < < mimetype ;
}
}
}
if ( bundle . filetype = = Song : : FileType_Unknown ) {
gchar * codec_description = gst_pb_utils_get_codec_description ( caps ) ;
QString filetype_description = ( codec_description ? QString ( codec_description ) : QString ( ) ) ;
g_free ( codec_description ) ;
if ( ! filetype_description . isEmpty ( ) ) {
bundle . filetype = Song : : FiletypeByDescription ( filetype_description ) ;
if ( bundle . filetype = = Song : : FileType_Unknown ) {
qLog ( Error ) < < " Unknown filetype " < < filetype_description ;
}
}
}
2020-10-21 00:07:58 +02:00
gst_caps_unref ( caps ) ;
gst_discoverer_stream_info_list_free ( audio_streams ) ;
2021-03-04 01:14:06 +01:00
qLog ( Debug ) < < " Got stream info for " < < discovered_url + " : " < < Song : : TextForFiletype ( bundle . filetype ) ;
2020-10-21 00:07:58 +02:00
emit instance - > MetaData ( bundle ) ;
}
else {
qLog ( Error ) < < " Could not detect an audio stream in " < < discovered_url ;
}
}
void GstEngine : : StreamDiscoveryFinished ( GstDiscoverer * , gpointer ) { }
QString GstEngine : : GSTdiscovererErrorMessage ( GstDiscovererResult result ) {
switch ( result ) {
case GST_DISCOVERER_URI_INVALID : return " The URI is invalid " ;
case GST_DISCOVERER_TIMEOUT : return " The discovery timed-out " ;
case GST_DISCOVERER_BUSY : return " The discoverer was already discovering a file " ;
case GST_DISCOVERER_MISSING_PLUGINS : return " Some plugins are missing for full discovery " ;
case GST_DISCOVERER_ERROR :
default : return " An error happened and the GError is set " ;
}
}