Move stream discoverer from pipeline to engine

Fixes #491
This commit is contained in:
Jonas Kvinge 2020-10-21 00:07:58 +02:00
parent ca8877ad47
commit 95ac85f642
4 changed files with 144 additions and 143 deletions

View File

@ -31,6 +31,7 @@
#include <string>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <QtGlobal>
#include <QFuture>
@ -53,6 +54,7 @@
#include "core/logging.h"
#include "core/taskmanager.h"
#include "core/timeconstants.h"
#include "core/signalchecker.h"
#include "enginebase.h"
#include "enginetype.h"
#include "gstengine.h"
@ -71,9 +73,12 @@ const char *GstEngine::kAVDTPSink = "avdtpsink";
const char *GstEngine::InterAudiosink = "interaudiosink";
const char *GstEngine::kDirectSoundSink = "directsoundsink";
const char *GstEngine::kOSXAudioSink = "osxaudiosink";
const int GstEngine::kDiscoveryTimeoutS = 10;
GstEngine::GstEngine(TaskManager *task_manager)
: task_manager_(task_manager),
gst_startup_(nullptr),
discoverer_(nullptr),
buffering_task_id_(-1),
latest_buffer_(nullptr),
stereo_balancer_enabled_(false),
@ -86,7 +91,10 @@ GstEngine::GstEngine(TaskManager *task_manager)
is_fading_out_to_pause_(false),
has_faded_out_(false),
scope_chunk_(0),
have_new_buffer_(false) {
have_new_buffer_(false),
discovery_finished_cb_id_(-1),
discovery_discovered_cb_id_(-1)
{
type_ = Engine::GStreamer;
seek_timer_->setSingleShot(true);
@ -107,6 +115,19 @@ GstEngine::~GstEngine() {
latest_buffer_ = nullptr;
}
if (discoverer_) {
if (discovery_discovered_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_discovered_cb_id_);
if (discovery_finished_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_finished_cb_id_);
gst_discoverer_stop(discoverer_);
g_object_unref(discoverer_);
discoverer_ = nullptr;
}
}
bool GstEngine::Init() {
@ -141,8 +162,17 @@ void GstEngine::StartPreloading(const QUrl &stream_url, const QUrl &original_url
QByteArray gst_url = FixupUrl(stream_url);
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
if (current_pipeline_)
if (current_pipeline_) {
current_pipeline_->SetNextUrl(gst_url, original_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0);
// Add request to discover the stream
#ifdef Q_OS_LINUX
if (discoverer_) {
if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.toStdString().c_str())) {
qLog(Error) << "Failed to start stream discovery for" << gst_url;
}
}
#endif
}
}
@ -180,6 +210,27 @@ bool GstEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::T
if (crossfade)
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward);
// Setting up stream discoverer
#ifdef Q_OS_LINUX
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_);
}
}
#endif
// Add request to discover the stream
#ifdef Q_OS_LINUX
if (discoverer_) {
if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.toStdString().c_str())) {
qLog(Error) << "Failed to start stream discovery for" << gst_url;
}
}
#endif
return true;
}
@ -845,3 +896,69 @@ void GstEngine::UpdateScope(const int chunk_length) {
}
}
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);
qLog(Error) << QString("Stream discovery for %1 failed: %2").arg(discovered_url).arg(error_message);
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()) {
bundle.url = instance->current_pipeline_->original_url();
}
else if (discovered_url == instance->current_pipeline_->next_stream_url()) {
bundle.url = instance->current_pipeline_->next_original_url();
}
bundle.stream_url = QUrl(discovered_url);
bundle.samplerate = gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info));
bundle.bitdepth = gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info));
bundle.bitrate = gst_discoverer_audio_info_get_bitrate(GST_DISCOVERER_AUDIO_INFO(stream_info)) / 1000;
GstCaps *caps = gst_discoverer_stream_info_get_caps(stream_info);
gchar *codec_description = gst_pb_utils_get_codec_description(caps);
QString filetype_description = (codec_description ? QString(codec_description) : QString("Unknown"));
g_free(codec_description);
gst_caps_unref(caps);
gst_discoverer_stream_info_list_free(audio_streams);
bundle.filetype = Song::FiletypeByDescription(filetype_description);
qLog(Info) << "Got stream info for" << discovered_url + ":" << filetype_description;
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";
}
}

View File

@ -28,6 +28,7 @@
#include <memory>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <QtGlobal>
#include <QObject>
@ -126,19 +127,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
void BufferingFinished();
private:
static const char *kAutoSink;
static const char *kALSASink;
static const char *kOpenALSASink;
static const char *kOSSSink;
static const char *kOSS4Sink;
static const char *kJackAudioSink;
static const char *kPulseSink;
static const char *kA2DPSink;
static const char *kAVDTPSink;
static const char *InterAudiosink;
static const char *kDirectSoundSink;
static const char *kOSXAudioSink;
PluginDetailsList GetPluginList(const QString &classname) const;
QByteArray FixupUrl(const QUrl &url);
@ -153,13 +141,32 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
void UpdateScope(int chunk_length);
static void StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self);
static void StreamDiscoveryFinished(GstDiscoverer*, gpointer);
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
private:
static const char *kAutoSink;
static const char *kALSASink;
static const char *kOpenALSASink;
static const char *kOSSSink;
static const char *kOSS4Sink;
static const char *kJackAudioSink;
static const char *kPulseSink;
static const char *kA2DPSink;
static const char *kAVDTPSink;
static const char *InterAudiosink;
static const char *kDirectSoundSink;
static const char *kOSXAudioSink;
static const int kDiscoveryTimeoutS;
static const qint64 kTimerIntervalNanosec = 1000 * kNsecPerMsec; // 1s
static const qint64 kPreloadGapNanosec = 5000 * kNsecPerMsec; // 5s
static const qint64 kSeekDelayNanosec = 100 * kNsecPerMsec; // 100msec
TaskManager *task_manager_;
GstStartup *gst_startup_;
GstDiscoverer *discoverer_;
int buffering_task_id_;
std::shared_ptr<GstEnginePipeline> current_pipeline_;
@ -197,6 +204,9 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
int scope_chunks_;
QString buffer_format_;
int discovery_finished_cb_id_;
int discovery_discovered_cb_id_;
};
#endif /* GSTENGINE_H */

View File

@ -28,7 +28,6 @@
#include <glib-object.h>
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <gst/pbutils/pbutils.h>
#include <QtGlobal>
#include <QObject>
@ -59,7 +58,6 @@
const int GstEnginePipeline::kGstStateTimeoutNanosecs = 10000000;
const int GstEnginePipeline::kFaderFudgeMsec = 2000;
const int GstEnginePipeline::kDiscoveryTimeoutS = 10;
const int GstEnginePipeline::kEqBandCount = 10;
const int GstEnginePipeline::kEqBandFrequencies[] = { 60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000 };
@ -107,13 +105,10 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
audiopanorama_(nullptr),
equalizer_(nullptr),
equalizer_preamp_(nullptr),
discoverer_(nullptr),
pad_added_cb_id_(-1),
notify_source_cb_id_(-1),
about_to_finish_cb_id_(-1),
bus_cb_id_(-1),
discovery_finished_cb_id_(-1),
discovery_discovered_cb_id_(-1),
unsupported_analyzer_(false)
{
@ -127,18 +122,6 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
GstEnginePipeline::~GstEnginePipeline() {
if (discoverer_) {
if (discovery_discovered_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_discovered_cb_id_);
if (discovery_finished_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_finished_cb_id_);
g_object_unref(discoverer_);
discoverer_ = nullptr;
}
if (pipeline_) {
if (pad_added_cb_id_ != -1)
@ -234,16 +217,6 @@ bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl ori
notify_source_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this);
about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this);
#ifdef Q_OS_LINUX
// Setting up a 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_);
}
#endif
if (!InitAudioBin()) return false;
// Set playbin's sink to be our custom audio-sink.
@ -442,15 +415,6 @@ bool GstEnginePipeline::InitAudioBin() {
bus_cb_id_ = gst_bus_add_watch(bus, BusCallback, this);
gst_object_unref(bus);
// Add request to discover the stream
#ifdef Q_OS_LINUX
if (discoverer_) {
if (!gst_discoverer_discover_uri_async(discoverer_, stream_url_.toStdString().c_str())) {
qLog(Error) << "Failed to start stream discovery for" << stream_url_;
}
}
#endif
unsupported_analyzer_ = false;
return true;
@ -1005,16 +969,6 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
next_uri_set_ = false;
g_object_set(G_OBJECT(pipeline_), "uri", stream_url_.constData(), nullptr);
SetState(GST_STATE_PLAYING);
// Add request to discover the stream
#ifdef Q_OS_LINUX
if (discoverer_) {
if (!gst_discoverer_discover_uri_async(discoverer_, stream_url_.toStdString().c_str())) {
qLog(Error) << "Failed to start stream discovery for" << stream_url_;
}
}
#endif
}
}
@ -1263,78 +1217,4 @@ void GstEnginePipeline::SetNextUrl(const QByteArray &stream_url, const QUrl &ori
next_beginning_offset_nanosec_ = beginning_nanosec;
next_end_offset_nanosec_ = end_nanosec;
#ifdef Q_OS_LINUX
// Add request to discover the stream
if (discoverer_) {
if (!gst_discoverer_discover_uri_async(discoverer_, next_stream_url_.toStdString().c_str())) {
qLog(Error) << "Failed to start stream discovery for" << next_stream_url_;
}
}
#endif
}
void GstEnginePipeline::StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
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);
qLog(Error) << QString("Stream discovery for %1 failed: %2").arg(discovered_url).arg(error_message);
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->stream_url_) {
bundle.url = instance->original_url_;
}
else if (discovered_url == instance->next_stream_url_) {
bundle.url = instance->next_original_url_;
}
bundle.stream_url = QUrl(discovered_url);
bundle.samplerate = gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info));
bundle.bitdepth = gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info));
bundle.bitrate = gst_discoverer_audio_info_get_bitrate(GST_DISCOVERER_AUDIO_INFO(stream_info)) / 1000;
GstCaps *caps = gst_discoverer_stream_info_get_caps(stream_info);
gchar *codec_description = gst_pb_utils_get_codec_description(caps);
QString filetype_description = (codec_description ? QString(codec_description) : QString("Unknown"));
g_free(codec_description);
gst_caps_unref(caps);
gst_discoverer_stream_info_list_free(audio_streams);
bundle.filetype = Song::FiletypeByDescription(filetype_description);
qLog(Info) << "Got stream info for" << discovered_url + ":" << filetype_description;
emit instance->MetadataFound(instance->id(), bundle);
}
else {
qLog(Error) << "Could not detect an audio stream in" << discovered_url;
}
}
void GstEnginePipeline::StreamDiscoveryFinished(GstDiscoverer*, gpointer) {}
QString GstEnginePipeline::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";
}
}

View File

@ -29,7 +29,6 @@
#include <glib-object.h>
#include <glib/gtypes.h>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <QtGlobal>
#include <QObject>
@ -100,7 +99,9 @@ class GstEnginePipeline : public QObject {
// Get information about the music playback
QByteArray stream_url() const { return stream_url_; }
QByteArray next_stream_url() const { return next_stream_url_; }
QUrl original_url() const { return original_url_; }
QUrl next_original_url() const { return next_original_url_; }
bool is_valid() const { return valid_; }
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
@ -149,9 +150,6 @@ class GstEnginePipeline : public QObject {
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
static void TaskEnterCallback(GstTask*, GThread*, gpointer);
static void StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self);
static void StreamDiscoveryFinished(GstDiscoverer*, gpointer);
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
void TagMessageReceived(GstMessage*);
void ErrorMessageReceived(GstMessage*);
@ -174,7 +172,6 @@ class GstEnginePipeline : public QObject {
private:
static const int kGstStateTimeoutNanosecs;
static const int kFaderFudgeMsec;
static const int kDiscoveryTimeoutS;
static const int kEqBandCount;
static const int kEqBandFrequencies[];
@ -275,14 +272,11 @@ class GstEnginePipeline : public QObject {
GstElement *audiopanorama_;
GstElement *equalizer_;
GstElement *equalizer_preamp_;
GstDiscoverer *discoverer_;
int pad_added_cb_id_;
int notify_source_cb_id_;
int about_to_finish_cb_id_;
int bus_cb_id_;
int discovery_finished_cb_id_;
int discovery_discovered_cb_id_;
QThreadPool set_state_threadpool_;