Esempio n. 1
0
/**
 * gst_format_register:
 * @nick: The nick of the new format
 * @description: The description of the new format
 *
 * Create a new GstFormat based on the nick or return an
 * already registered format with that nick.
 *
 * Returns: A new GstFormat or an already registered format
 * with the same nick.
 *
 * MT safe.
 */
GstFormat
gst_format_register (const gchar * nick, const gchar * description)
{
  GstFormatDefinition *format;
  GstFormat query;

  g_return_val_if_fail (nick != NULL, GST_FORMAT_UNDEFINED);
  g_return_val_if_fail (description != NULL, GST_FORMAT_UNDEFINED);

  query = gst_format_get_by_nick (nick);
  if (query != GST_FORMAT_UNDEFINED)
    return query;

  g_mutex_lock (&mutex);
  format = g_slice_new (GstFormatDefinition);
  format->value = (GstFormat) _n_values;
  format->nick = g_strdup (nick);
  format->description = g_strdup (description);
  format->quark = g_quark_from_static_string (format->nick);

  g_hash_table_insert (_nick_to_format, (gpointer) format->nick, format);
  g_hash_table_insert (_format_to_nick, GINT_TO_POINTER (format->value),
      format);
  _gst_formats = g_list_append (_gst_formats, format);
  _n_values++;
  g_mutex_unlock (&mutex);

  return format->value;
}
Esempio n. 2
0
// set the position of the media file
void gst_binding_set_track (gstPlay *play, gint64 track_number, float speed) {
	if (!isValid (play)) return;
	
	gst_element_seek (play->element, speed, gst_format_get_by_nick ("track"),
	      GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, track_number - 1,
	      GST_SEEK_TYPE_NONE, -1);
}
static void
rb_audiocd_plugin_reuse_stream_cb (RBPlayer *player,
				   const char *new_uri,
				   const char *stream_uri,
				   GstElement *element,
				   RBAudioCdPlugin *plugin)
{
	GstFormat track_format = gst_format_get_by_nick ("track");
	char *track_str;
	char *new_device;
	guint track;
	guint cdda_len;

	/* get the new track number */
	cdda_len = strlen ("cdda://");
	new_device = g_utf8_strrchr (new_uri, -1, '#');
	track_str = g_strndup (new_uri + cdda_len, new_device - (new_uri + cdda_len));
	track = atoi (track_str);
	g_free (track_str);

	rb_debug ("seeking to track %d on CD device %s", track, new_device+1);

	gst_element_seek (element,
			  1.0, track_format, GST_SEEK_FLAG_FLUSH,
			  GST_SEEK_TYPE_SET, track-1,
			  GST_SEEK_TYPE_NONE, -1);
}
Esempio n. 4
0
/*
 * Class method: find(nick)
 * nick: the nick of an existing format.
 *
 * Returns: a reference to the Gst::Format object registered with the
 * given nick, or nil if this query was not registered.
 */
static VALUE
rg_s_find (VALUE self, VALUE nick)
{
    GstFormat format = gst_format_get_by_nick (RVAL2CSTR (nick));
    return format != GST_FORMAT_UNDEFINED
        ? RGST_FORMAT_NEW (&format)
        : Qnil;
}
P_INVOKE void
bp_dvd_go_to_previous_chapter (BansheePlayer *player)
{
    gint64 index;
    GstFormat format = gst_format_get_by_nick ("chapter");
    gst_element_query_position (player->playbin, format, &index);
    gst_element_seek (player->playbin, 1.0, format, GST_SEEK_FLAG_FLUSH,
        GST_SEEK_TYPE_SET, index - 1, GST_SEEK_TYPE_NONE, 0);
}
static void
gst_progress_report_report (GstProgressReport * filter, GTimeVal cur_time,
    GstBuffer * buf)
{
  GstFormat try_formats[] = { GST_FORMAT_TIME, GST_FORMAT_BYTES,
    GST_FORMAT_PERCENT, GST_FORMAT_BUFFERS,
    GST_FORMAT_DEFAULT
  };
  GstMessage *msg;
  GstFormat format = GST_FORMAT_UNDEFINED;
  gboolean done = FALSE;
  glong run_time;
  gint hh, mm, ss;

  run_time = cur_time.tv_sec - filter->start_time.tv_sec;

  hh = (run_time / 3600) % 100;
  mm = (run_time / 60) % 60;
  ss = (run_time % 60);

  GST_OBJECT_LOCK (filter);

  if (filter->format != NULL && strcmp (filter->format, "auto") != 0) {
    format = gst_format_get_by_nick (filter->format);
  }

  if (format != GST_FORMAT_UNDEFINED) {
    done = gst_progress_report_do_query (filter, format, hh, mm, ss, buf);
  } else {
    gint i;

    for (i = 0; i < G_N_ELEMENTS (try_formats); ++i) {
      done = gst_progress_report_do_query (filter, try_formats[i], hh, mm, ss,
          buf);
      if (done)
        break;
    }
  }

  if (!done && !filter->silent) {
    g_print ("%s (%2d:%2d:%2d): Could not query position and/or duration\n",
        GST_OBJECT_NAME (filter), hh, mm, ss);
  }

  msg = filter->pending_msg;
  filter->pending_msg = NULL;
  GST_OBJECT_UNLOCK (filter);

  if (msg) {
    gst_element_post_message (GST_ELEMENT_CAST (filter), msg);
  }
}
static gboolean
rb_audiocd_get_cd_info (RBAudioCdSource *source,
			gint64 *num_tracks)
{
	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
	GstFormat fmt = gst_format_get_by_nick ("track");
	GstFormat out_fmt = fmt;
	if (!gst_element_query_duration (priv->cdda, &out_fmt, num_tracks) || out_fmt != fmt) {
		return FALSE;
	}

	return TRUE;
}
Esempio n. 8
0
SongLoader::Result SongLoader::LoadAudioCD() {
#ifdef HAVE_AUDIOCD
  // Create gstreamer cdda element
  GstElement* cdda = gst_element_make_from_uri (GST_URI_SRC, "cdda://", NULL);
  if (cdda == NULL) {
    qLog(Error) << "Error while creating CDDA GstElement";
    return Error;
  }

  // Change the element's state to ready and paused, to be able to query it
  if (gst_element_set_state(cdda, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE ||
      gst_element_set_state(cdda, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
    qLog(Error) << "Error while changing CDDA GstElement's state";
    gst_element_set_state(cdda, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(cdda));
    return Error;
  }

  // Get number of tracks
  GstFormat fmt = gst_format_get_by_nick ("track");
  GstFormat out_fmt = fmt;
  gint64 num_tracks = 0;
  if (!gst_element_query_duration (cdda, &out_fmt, &num_tracks) || out_fmt != fmt) {
    qLog(Error) << "Error while querying cdda GstElement";
    gst_object_unref(GST_OBJECT(cdda));
    return Error;
  }

  for (int track_number = 1; track_number <= num_tracks; track_number++) {
    // Init song
    Song song;
    guint64 duration = 0;
    // quint64 == ulonglong and guint64 == ulong, therefore we must cast
    if (gst_tag_list_get_uint64 (GST_CDDA_BASE_SRC(cdda)->tracks[track_number-1].tags,
                                 GST_TAG_DURATION, &duration)) {
      song.set_length_nanosec((quint64)duration);
    }
    song.set_valid(true);
    song.set_filetype(Song::Type_Cdda);
    song.set_url(QUrl(QString("cdda://%1").arg(track_number)));
    song.set_title(QString("Track %1").arg(track_number));
    song.set_track(track_number);
    songs_ << song;
  }

  // Generate MusicBrainz DiscId
  gst_tag_register_musicbrainz_tags();
  GstElement *pipe = gst_pipeline_new ("pipeline");
  gst_bin_add (GST_BIN (pipe), cdda);
  gst_element_set_state (pipe, GST_STATE_READY);
  gst_element_set_state (pipe, GST_STATE_PAUSED);
  GstMessage *msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe),
                    GST_CLOCK_TIME_NONE,
                    GST_MESSAGE_TAG);
  GstTagList *tags = NULL;
  gst_message_parse_tag (msg, &tags);
  char *string_mb = NULL;
  if (gst_tag_list_get_string (tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &string_mb)) {
    QString musicbrainz_discid(string_mb);
    qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid;

    MusicBrainzClient *musicbrainz_client = new MusicBrainzClient(this);
    connect(musicbrainz_client,
            SIGNAL(Finished(const QString&, const QString&, MusicBrainzClient::ResultList)),
            SLOT(AudioCDTagsLoaded(const QString&, const QString&, MusicBrainzClient::ResultList)));
    musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
    g_free(string_mb);
  }
  
  // Clean all the Gstreamer objects we have used: we don't need them anymore
  gst_object_unref(GST_OBJECT(cdda));
  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref(GST_OBJECT(pipe));
  gst_object_unref(GST_OBJECT(msg));
  gst_object_unref(GST_OBJECT(tags));

  return Success;
#else // HAVE_AUDIOCD
  return Error;
#endif
}
static void
gst_file_index_load (GstFileIndex * index)
{
  xmlDocPtr doc;
  xmlNodePtr root, part;
  xmlChar *val;

  g_assert (index->location);
  g_return_if_fail (!index->is_loaded);

  {
    gchar *path = g_strdup_printf ("%s/gstindex.xml", index->location);
    GError *err = NULL;
    gchar *buf;
    gsize len;

    g_file_get_contents (path, &buf, &len, &err);
    g_free (path);
    if (err) {
      GST_ERROR_OBJECT (index, "%s", err->message);
      return;
    }

    doc = xmlParseMemory (buf, len);
    g_free (buf);
  }

  //xmlDocFormatDump (stderr, doc, TRUE);

  root = doc->xmlRootNode;
  if (strcmp ((char *) root->name, "gstfileindex") != 0) {
    GST_ERROR_OBJECT (index, "root node isn't a gstfileindex");
    return;
  }

  val = xmlGetProp (root, (xmlChar *) "version");
  if (!val || atoi ((char *) val) != 1) {
    GST_ERROR_OBJECT (index, "version != 1");
    return;
  }
  free (val);

  for (part = root->children; part; part = part->next) {
    if (strcmp ((char *) part->name, "writers") == 0) {
      xmlNodePtr writer;

      for (writer = part->children; writer; writer = writer->next) {
        xmlChar *datafile = xmlGetProp (writer, (xmlChar *) "datafile");
        gchar *path = g_strdup_printf ("%s/%s", index->location, datafile);
        int fd;
        GstFileIndexId *id_index;
        xmlNodePtr wpart;
        xmlChar *entries_str;
        gpointer array_data;

        free (datafile);

        fd = open (path, O_RDONLY);
        g_free (path);
        if (fd < 0) {
          GST_ERROR_OBJECT (index,
              "Can't open '%s': %s", path, g_strerror (errno));
          continue;
        }

        id_index = g_slice_new0 (GstFileIndexId);
        id_index->id_desc = (char *) xmlGetProp (writer, (xmlChar *) "id");

        for (wpart = writer->children; wpart; wpart = wpart->next) {
          if (strcmp ((char *) wpart->name, "formats") == 0) {
            xmlChar *count_str = xmlGetProp (wpart, (xmlChar *) "count");
            gint fx = 0;
            xmlNodePtr format;

            id_index->nformats = atoi ((char *) count_str);
            free (count_str);

            id_index->format = g_new (GstFormat, id_index->nformats);

            for (format = wpart->children; format; format = format->next) {
              xmlChar *nick = xmlGetProp (format, (xmlChar *) "nick");
              GstFormat fmt = gst_format_get_by_nick ((gchar *) nick);

              if (fmt == GST_FORMAT_UNDEFINED)
                GST_ERROR_OBJECT (index, "format '%s' undefined", nick);
              g_assert (fx < id_index->nformats);
              id_index->format[fx++] = fmt;
              free (nick);
            }
          } else
            GST_INFO_OBJECT (index, "unknown wpart '%s'", wpart->name);
        }

        g_assert (id_index->nformats > 0);
        _fc_alloc_array (id_index);
        g_assert (id_index->array->data == NULL);       /* little bit risky */

        entries_str = xmlGetProp (writer, (xmlChar *) "entries");
        id_index->array->len = atoi ((char *) entries_str);
        free (entries_str);

        array_data =
            mmap (NULL, ARRAY_TOTAL_SIZE (id_index), PROT_READ, MAP_SHARED, fd,
            0);
        close (fd);
        if (array_data == MAP_FAILED) {
          GST_ERROR_OBJECT (index,
              "mmap %s failed: %s", path, g_strerror (errno));
          continue;
        }

        id_index->array->data = array_data;

        index->unresolved = g_slist_prepend (index->unresolved, id_index);
      }
    } else
      GST_INFO_OBJECT (index, "unknown part '%s'", part->name);
  }

  xmlFreeDoc (doc);

  GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
  index->is_loaded = TRUE;
  GST_LOG_OBJECT (index, "index %s loaded OK", index->location);
}
Esempio n. 10
0
static void
build_pipeline (SjExtractor *extractor)
{
  SjExtractorPrivate *priv;
  GstBus *bus;

  g_return_if_fail (SJ_IS_EXTRACTOR (extractor));

  priv = extractor->priv;

  if (priv->pipeline != NULL) {
    gst_object_unref (GST_OBJECT (priv->pipeline));
  }
  priv->pipeline = gst_pipeline_new ("pipeline");
  bus = gst_element_get_bus (priv->pipeline);
  gst_bus_add_signal_watch (bus);

  g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb), extractor);

  /* Read from CD */
  priv->cdsrc = gst_element_make_from_uri (GST_URI_SRC, "cdda://1", "cd_src", NULL);
  if (priv->cdsrc == NULL) {
    g_set_error (&priv->construct_error,
                 SJ_ERROR, SJ_ERROR_INTERNAL_ERROR,
                 _("Could not create GStreamer CD reader"));
    return;
  }

  g_object_set (G_OBJECT (priv->cdsrc), "device", priv->device_path, NULL);
  if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->cdsrc), "paranoia-mode")) {
	  g_object_set (G_OBJECT (priv->cdsrc), "paranoia-mode", priv->paranoia_mode, NULL);
  }

  /* Get the track format for seeking later */
  priv->track_format = gst_format_get_by_nick ("track");
  g_assert (priv->track_format != 0);

  /* Encode */
  priv->encodebin = build_encoder (extractor);
  if (priv->encodebin == NULL) {
    g_set_error (&priv->construct_error,
                 SJ_ERROR, SJ_ERROR_INTERNAL_ERROR,
                 _("Could not create GStreamer encoders for %s"),
                 gst_encoding_profile_get_name (priv->profile));
    return;
  }
  /* Connect to the eos so we know when its finished */
  g_signal_connect (bus, "message::eos", G_CALLBACK (eos_cb), extractor);

  /* Write to disk */
  priv->filesink = gst_element_factory_make (FILE_SINK, "file_sink");
  if (priv->filesink == NULL) {
    g_set_error (&priv->construct_error,
                 SJ_ERROR, SJ_ERROR_INTERNAL_ERROR,
                 _("Could not create GStreamer file output"));
    return;
  }
#if 0
  g_signal_connect (G_OBJECT (priv->filesink), "allow-overwrite", G_CALLBACK (just_say_yes), extractor);
#endif

  /* Add the elements to the pipeline */
  gst_bin_add_many (GST_BIN (priv->pipeline), priv->cdsrc, priv->encodebin, priv->filesink, NULL);

  /* Link it all together */
  if (!gst_element_link_many (priv->cdsrc, priv->encodebin, priv->filesink, NULL)) {
    g_set_error (&priv->construct_error,
                 SJ_ERROR, SJ_ERROR_INTERNAL_ERROR,
                 _("Could not link pipeline"));
    return;
  }

  priv->rebuild_pipeline = FALSE;
}
Esempio n. 11
0
void CddaDevice::Init() {
  QMutexLocker locker(&mutex_init_);
  song_count_ = 0; // Reset song count, in case it was already set
  cdio_ = cdio_open (url_.path().toLocal8Bit().constData(), DRIVER_DEVICE);
  if (cdio_ == NULL) {
    return;
  }
  // Create gstreamer cdda element
  cdda_ = gst_element_make_from_uri (GST_URI_SRC, "cdda://", NULL);
  if (cdda_ == NULL) {
    model_->Reset();
    return;
  }

  GST_CDDA_BASE_SRC(cdda_)->device = g_strdup (url_.path().toLocal8Bit().constData());

  // Change the element's state to ready and paused, to be able to query it
  if (gst_element_set_state(cdda_, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE ||
      gst_element_set_state(cdda_, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
    model_->Reset();
    gst_element_set_state(cdda_, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(cdda_));
    return;
  }

  // Get number of tracks
  GstFormat fmt = gst_format_get_by_nick ("track");
  GstFormat out_fmt = fmt;
  gint64 num_tracks = 0;
  if (!gst_element_query_duration (cdda_, &out_fmt, &num_tracks) || out_fmt != fmt) {
    qLog(Error) << "Error while querying cdda GstElement";
    model_->Reset();
    gst_object_unref(GST_OBJECT(cdda_));
    return;
  }

  SongList songs;
  for (int track_number = 1; track_number <= num_tracks; track_number++) {
    // Init song
    Song song;
    guint64 duration = 0;
    // quint64 == ulonglong and guint64 == ulong, therefore we must cast
    if (gst_tag_list_get_uint64 (GST_CDDA_BASE_SRC(cdda_)->tracks[track_number-1].tags,
                                 GST_TAG_DURATION, &duration)) {
      song.set_length_nanosec((quint64)duration);
    }
    song.set_id(track_number);
    song.set_valid(true);
    song.set_filetype(Song::Type_Cdda);
    song.set_url(QUrl(QString("cdda://%1/%2").arg(url_.path()).arg(track_number)));
    song.set_title(QString("Track %1").arg(track_number));
    song.set_track(track_number);
    songs << song;
  }
  song_count_ = num_tracks;
  connect(this, SIGNAL(SongsDiscovered(const SongList&)), model_, SLOT(SongsDiscovered(const SongList&)));
  emit SongsDiscovered(songs);

  // Generate MusicBrainz DiscId
  gst_tag_register_musicbrainz_tags();
  GstElement *pipe = gst_pipeline_new ("pipeline");
  gst_bin_add (GST_BIN (pipe), cdda_);
  gst_element_set_state (pipe, GST_STATE_READY);
  gst_element_set_state (pipe, GST_STATE_PAUSED);
  GstMessage *msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe),
                    GST_CLOCK_TIME_NONE,
                    GST_MESSAGE_TAG);
  GstTagList *tags = NULL;
  gst_message_parse_tag (msg, &tags);
  char *string_mb = NULL;
  if (gst_tag_list_get_string (tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &string_mb)) {
    QString musicbrainz_discid(string_mb);
    qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid;

    MusicBrainzClient *musicbrainz_client = new MusicBrainzClient(this);
    connect(musicbrainz_client,
            SIGNAL(Finished(const QString&, const QString&, MusicBrainzClient::ResultList)),
            SLOT(AudioCDTagsLoaded(const QString&, const QString&, MusicBrainzClient::ResultList)));
    musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
    g_free(string_mb);
  }

  // Clean all the Gstreamer objects we have used: we don't need them anymore
  gst_element_set_state (pipe, GST_STATE_NULL);
  // This will also cause cdda_ to be unref'd.
  gst_object_unref(GST_OBJECT(pipe));
  gst_object_unref(GST_OBJECT(msg));
  gst_tag_list_free(tags);
}
Esempio n. 12
0
void CddaSongLoader::LoadSongsFromCdda() {
  QMutexLocker locker(&mutex_load_);
  cdio_ = cdio_open(url_.path().toLocal8Bit().constData(), DRIVER_DEVICE);
  if (cdio_ == nullptr) {
    return;
  }
  // Create gstreamer cdda element
  GError* error = nullptr;
  cdda_ = gst_element_make_from_uri(GST_URI_SRC, "cdda://", nullptr, &error);
  if (error) {
    qLog(Error) << error->code << QString::fromLocal8Bit(error->message);
  }
  if (cdda_ == nullptr) {
    return;
  }

  if (!url_.isEmpty()) {
    g_object_set(cdda_, "device", g_strdup(url_.path().toLocal8Bit().constData()),
                 nullptr);
  }
  if (g_object_class_find_property (G_OBJECT_GET_CLASS (cdda_), "paranoia-mode")) {
    g_object_set (cdda_, "paranoia-mode", 0, NULL);
  }

  // Change the element's state to ready and paused, to be able to query it
  if (gst_element_set_state(cdda_, GST_STATE_READY) ==
          GST_STATE_CHANGE_FAILURE ||
      gst_element_set_state(cdda_, GST_STATE_PAUSED) ==
          GST_STATE_CHANGE_FAILURE) {
    gst_element_set_state(cdda_, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(cdda_));
    return;
  }

  // Get number of tracks
  GstFormat fmt = gst_format_get_by_nick("track");
  GstFormat out_fmt = fmt;
  gint64 num_tracks = 0;
  if (!gst_element_query_duration(cdda_, out_fmt, &num_tracks) ||
      out_fmt != fmt) {
    qLog(Error) << "Error while querying cdda GstElement";
    gst_object_unref(GST_OBJECT(cdda_));
    return;
  }

  SongList songs;
  for (int track_number = 1; track_number <= num_tracks; track_number++) {
    // Init song
    Song song;
    song.set_id(track_number);
    song.set_valid(true);
    song.set_filetype(Song::Type_Cdda);
    song.set_url(
        GetUrlFromTrack(track_number));
    song.set_title(QString("Track %1").arg(track_number));
    song.set_track(track_number);
    songs << song;
  }
  emit SongsLoaded(songs);

  gst_tag_register_musicbrainz_tags();

  GstElement* pipeline = gst_pipeline_new("pipeline");
  GstElement* sink = gst_element_factory_make ("fakesink", NULL);
  gst_bin_add_many (GST_BIN (pipeline), cdda_, sink, NULL);
  gst_element_link (cdda_, sink);
  gst_element_set_state(pipeline, GST_STATE_READY);
  gst_element_set_state(pipeline, GST_STATE_PAUSED);

  // Get TOC and TAG messages
  GstMessage* msg = nullptr;
  GstMessage* msg_toc = nullptr;
  GstMessage* msg_tag = nullptr;
  while ((msg = gst_bus_timed_pop_filtered(
              GST_ELEMENT_BUS(pipeline), 2 * GST_SECOND,
              (GstMessageType)(GST_MESSAGE_TOC | GST_MESSAGE_TAG)))) {
    if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) {
      if (msg_toc) gst_message_unref(msg_toc); // Shouldn't happen, but just in case
      msg_toc = msg;
    } else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) {
      if (msg_tag) gst_message_unref(msg_tag);
      msg_tag = msg;
    }
  }

  // Handle TOC message: get tracks duration
  if (msg_toc) {
    GstToc* toc;
    gst_message_parse_toc (msg_toc, &toc, nullptr);
    if (toc) {
      GList* entries = gst_toc_get_entries(toc);
      if (entries && songs.size() <= g_list_length (entries)) {
        int i = 0;
        for (GList* node = entries; node != nullptr; node = node->next) {
          GstTocEntry *entry = static_cast<GstTocEntry*>(node->data);
          quint64 duration = 0;
          gint64 start, stop;
          if (gst_toc_entry_get_start_stop_times (entry, &start, &stop))
            duration = stop - start;
          songs[i++].set_length_nanosec(duration);
        }
      }
    }
    gst_message_unref(msg_toc);
  }
  emit SongsDurationLoaded(songs);

  // Handle TAG message: generate MusicBrainz DiscId
  if (msg_tag) {
    GstTagList* tags = nullptr;
    gst_message_parse_tag(msg_tag, &tags);
    char* string_mb = nullptr;
    if (gst_tag_list_get_string(tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID,
                                &string_mb)) {
      QString musicbrainz_discid(string_mb);
      qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid;
      emit MusicBrainzDiscIdLoaded(musicbrainz_discid);

      g_free(string_mb);
      gst_message_unref(msg_tag);
      gst_tag_list_free(tags);
    }
  }

  gst_element_set_state(pipeline, GST_STATE_NULL);
  // This will also cause cdda_ to be unref'd.
  gst_object_unref(pipeline);
}