2
// banshee-player-pipeline.c
5
// Aaron Bockover <abockover@novell.com>
6
// Julien Moutte <julien@fluendo.com>
8
// Copyright (C) 2005-2008 Novell, Inc.
9
// Copyright (C) 2010 Fluendo S.A.
11
// Permission is hereby granted, free of charge, to any person obtaining
12
// a copy of this software and associated documentation files (the
13
// "Software"), to deal in the Software without restriction, including
14
// without limitation the rights to use, copy, modify, merge, publish,
15
// distribute, sublicense, and/or sell copies of the Software, and to
16
// permit persons to whom the Software is furnished to do so, subject to
17
// the following conditions:
19
// The above copyright notice and this permission notice shall be
20
// included in all copies or substantial portions of the Software.
22
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
#include "banshee-player-pipeline.h"
32
#include "banshee-player-cdda.h"
33
#include "banshee-player-dvd.h"
34
#include "banshee-player-video.h"
35
#include "banshee-player-equalizer.h"
36
#include "banshee-player-missing-elements.h"
37
#include "banshee-player-replaygain.h"
38
#include "banshee-player-vis.h"
40
// ---------------------------------------------------------------------------
42
// ---------------------------------------------------------------------------
45
bp_stream_has_video (GstElement *playbin)
48
g_object_get (G_OBJECT (playbin), "n-video", &n_video, NULL);
54
bp_pipeline_process_tag (const GstTagList *tag_list, const gchar *tag_name, BansheePlayer *player)
59
g_return_if_fail (IS_BANSHEE_PLAYER (player));
61
value_count = gst_tag_list_get_tag_size (tag_list, tag_name);
62
if (value_count < 1) {
66
value = gst_tag_list_get_value_index (tag_list, tag_name, 0);
68
if (value != NULL && player->tag_found_cb != NULL) {
69
player->tag_found_cb (player, tag_name, value);
74
playbin_stream_changed_cb (GstElement * element, BansheePlayer *player)
78
// We're being called from the streaming thread, so don't do anything here
79
msg = gst_message_new_application (GST_OBJECT (player->playbin), gst_structure_new ("stream-changed", NULL));
80
gst_element_post_message (player->playbin, msg);
84
bp_next_track_starting (BansheePlayer *player)
88
g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
89
g_return_val_if_fail (GST_IS_ELEMENT (player->playbin), FALSE);
91
// FIXME: Work around BGO #602437 - gapless transition between tracks with
92
// video streams results in broken behaviour - most obviously, huge A/V
94
// Will be in GStreamer 0.10.31
95
has_video = bp_stream_has_video (player->playbin);
96
if (player->in_gapless_transition && has_video) {
99
bp_debug ("[Gapless]: Aborting gapless transition to stream with video. Triggering normal track change");
100
g_object_get (G_OBJECT (player->playbin), "uri", &uri, NULL);
101
gst_element_set_state (player->playbin, GST_STATE_READY);
103
g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
104
gst_element_set_state (player->playbin, GST_STATE_PLAYING);
106
player->in_gapless_transition = FALSE;
107
// The transition to playing will happen asynchronously, and will trigger
108
// a second track-starting message. Stop processing this one.
111
player->in_gapless_transition = FALSE;
113
if (player->next_track_starting_cb != NULL) {
114
bp_debug ("[gapless] Triggering track-change signal");
115
player->next_track_starting_cb (player);
121
bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
123
BansheePlayer *player = (BansheePlayer *)userdata;
125
g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
126
g_return_val_if_fail (message != NULL, FALSE);
128
switch (GST_MESSAGE_TYPE (message)) {
129
case GST_MESSAGE_EOS: {
130
if (player->eos_cb != NULL) {
131
player->eos_cb (player);
136
case GST_MESSAGE_STATE_CHANGED: {
137
GstState old, new, pending;
138
gst_message_parse_state_changed (message, &old, &new, &pending);
140
_bp_missing_elements_handle_state_changed (player, old, new);
142
if (player->state_changed_cb != NULL && GST_MESSAGE_SRC (message) == GST_OBJECT (player->playbin)) {
143
player->state_changed_cb (player, old, new, pending);
148
case GST_MESSAGE_BUFFERING: {
149
const GstStructure *buffering_struct;
150
gint buffering_progress = 0;
152
buffering_struct = gst_message_get_structure (message);
153
if (!gst_structure_get_int (buffering_struct, "buffer-percent", &buffering_progress)) {
154
g_warning ("Could not get completion percentage from BUFFERING message");
158
if (buffering_progress >= 100) {
159
player->buffering = FALSE;
160
if (player->target_state == GST_STATE_PLAYING) {
161
gst_element_set_state (player->playbin, GST_STATE_PLAYING);
163
} else if (!player->buffering && player->target_state == GST_STATE_PLAYING) {
164
GstState current_state;
165
gst_element_get_state (player->playbin, ¤t_state, NULL, 0);
166
if (current_state == GST_STATE_PLAYING) {
167
gst_element_set_state (player->playbin, GST_STATE_PAUSED);
169
player->buffering = TRUE;
172
if (player->buffering_cb != NULL) {
173
player->buffering_cb (player, buffering_progress);
178
case GST_MESSAGE_TAG: {
181
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_TAG) {
185
gst_message_parse_tag (message, &tags);
187
if (GST_IS_TAG_LIST (tags)) {
188
gst_tag_list_foreach (tags, (GstTagForeachFunc)bp_pipeline_process_tag, player);
189
gst_tag_list_free (tags);
194
case GST_MESSAGE_ERROR: {
198
_bp_pipeline_destroy (player);
200
if (player->error_cb != NULL) {
201
gst_message_parse_error (message, &error, &debug);
202
player->error_cb (player, error->domain, error->code, error->message, debug);
203
g_error_free (error);
210
case GST_MESSAGE_ELEMENT: {
211
const GstStructure *messageStruct;
212
messageStruct = gst_message_get_structure (message);
213
if (GST_MESSAGE_SRC (message) == GST_OBJECT (player->playbin) && gst_structure_has_name (messageStruct, "playbin2-stream-changed")) {
214
bp_next_track_starting (player);
216
_bp_missing_elements_process_message (player, message);
217
_bp_dvd_elements_process_message (player, message);
221
case GST_MESSAGE_APPLICATION: {
223
const GstStructure * s = gst_message_get_structure (message);
224
name = gst_structure_get_name (s);
225
if (name && !strcmp (name, "stream-changed")) {
226
_bp_parse_stream_info (player);
237
#ifdef ENABLE_GAPLESS
238
static void bp_about_to_finish_callback (GstElement *playbin, BansheePlayer *player)
240
g_return_if_fail (IS_BANSHEE_PLAYER (player));
241
g_return_if_fail (GST_IS_ELEMENT (playbin));
243
if (bp_stream_has_video (playbin)) {
244
bp_debug ("[Gapless]: Not attempting gapless transition from stream with video");
248
if (player->about_to_finish_cb != NULL) {
249
player->in_gapless_transition = TRUE;
251
bp_debug ("[Gapless] Requesting next track");
252
player->about_to_finish_cb (player);
255
#endif //ENABLE_GAPLESS
257
static void bp_volume_changed_callback (GstElement *playbin, GParamSpec *spec, BansheePlayer *player)
261
g_return_if_fail (IS_BANSHEE_PLAYER (player));
262
g_return_if_fail (GST_IS_ELEMENT (playbin));
264
g_object_get (G_OBJECT (playbin), "volume", &volume, NULL);
266
player->current_volume = volume;
268
if (player->volume_changed_cb != NULL) {
269
player->volume_changed_cb (player, volume);
273
// ---------------------------------------------------------------------------
274
// Internal Functions
275
// ---------------------------------------------------------------------------
278
_bp_pipeline_construct (BansheePlayer *player)
283
GstElement *audiosink;
284
GstElement *audiosinkqueue;
285
GstElement *eq_audioconvert = NULL;
286
GstElement *eq_audioconvert2 = NULL;
288
g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
290
// Playbin is the core element that handles autoplugging (finding the right
291
// source and decoder elements) based on source URI and stream content
292
player->playbin = gst_element_factory_make ("playbin2", "playbin");
294
#ifdef ENABLE_GAPLESS
295
// FIXME: Connect a proxy about-to-finish callback that will generate a next-track-starting callback.
296
// This can be removed once playbin2 generates its own next-track signal.
297
// bgo#584987 - this is included in >= 0.10.26
298
g_signal_connect (player->playbin, "about-to-finish", G_CALLBACK (bp_about_to_finish_callback), player);
299
#endif //ENABLE_GAPLESS
301
g_return_val_if_fail (player->playbin != NULL, FALSE);
303
g_signal_connect (player->playbin, "notify::volume", G_CALLBACK (bp_volume_changed_callback), player);
304
g_signal_connect (player->playbin, "video-changed", G_CALLBACK (playbin_stream_changed_cb), player);
305
g_signal_connect (player->playbin, "audio-changed", G_CALLBACK (playbin_stream_changed_cb), player);
306
g_signal_connect (player->playbin, "text-changed", G_CALLBACK (playbin_stream_changed_cb), player);
308
// Try to find an audio sink, prefer gconf, which typically is set to auto these days,
309
// fall back on auto, which should work on windows, and as a last ditch, try alsa
310
audiosink = gst_element_factory_make ("gconfaudiosink", "audiosink");
311
if (audiosink == NULL) {
312
audiosink = gst_element_factory_make ("directsoundsink", "audiosink");
313
if (audiosink != NULL) {
314
g_object_set (G_OBJECT (audiosink), "volume", 1.0, NULL);
316
audiosink = gst_element_factory_make ("autoaudiosink", "audiosink");
317
if (audiosink == NULL) {
318
audiosink = gst_element_factory_make ("alsasink", "audiosink");
323
g_return_val_if_fail (audiosink != NULL, FALSE);
325
// Set the profile to "music and movies" (gst-plugins-good 0.10.3)
326
if (g_object_class_find_property (G_OBJECT_GET_CLASS (audiosink), "profile")) {
327
g_object_set (G_OBJECT (audiosink), "profile", 1, NULL);
330
/* Set the audio sink to READY so it can autodetect the right sink element
331
* if needed, as this allows us to correctly determine whether it has a
333
gst_element_set_state (audiosink, GST_STATE_READY);
335
// See if the audiosink has a 'volume' property. If it does, we assume it saves and restores
336
// its volume information - and that we shouldn't
337
player->audiosink_has_volume = FALSE;
338
if (!GST_IS_BIN (audiosink)) {
339
player->audiosink_has_volume = g_object_class_find_property (G_OBJECT_GET_CLASS (audiosink), "volume") != NULL;
341
GstIterator *elem_iter = gst_bin_iterate_recurse (GST_BIN (audiosink));
342
BANSHEE_GST_ITERATOR_ITERATE (elem_iter, GstElement *, element, TRUE, {
343
player->audiosink_has_volume |= g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume") != NULL;
344
gst_object_unref (element);
347
bp_debug ("Audiosink has volume: %s",
348
player->audiosink_has_volume ? "YES" : "NO");
351
// Create a custom audio sink bin that will hold the real primary sink
352
player->audiobin = gst_bin_new ("audiobin");
353
g_return_val_if_fail (player->audiobin != NULL, FALSE);
355
// Our audio sink is a tee, so plugins can attach their own pipelines
356
player->audiotee = gst_element_factory_make ("tee", "audiotee");
357
g_return_val_if_fail (player->audiotee != NULL, FALSE);
359
// Create a volume control with low latency
360
player->volume = gst_element_factory_make ("volume", NULL);
361
g_return_val_if_fail (player->volume != NULL, FALSE);
363
// call the volume changed callback once so the volume from the pipeline is
364
// set in the player object
365
bp_volume_changed_callback (player->playbin, NULL, player);
367
audiosinkqueue = gst_element_factory_make ("queue", "audiosinkqueue");
368
g_return_val_if_fail (audiosinkqueue != NULL, FALSE);
370
player->equalizer = _bp_equalizer_new (player);
371
player->preamp = NULL;
372
if (player->equalizer != NULL) {
373
eq_audioconvert = gst_element_factory_make ("audioconvert", "audioconvert");
374
eq_audioconvert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
375
player->preamp = gst_element_factory_make ("volume", "preamp");
378
// Add elements to custom audio sink
379
gst_bin_add_many (GST_BIN (player->audiobin), player->audiotee, player->volume, audiosinkqueue, audiosink, NULL);
381
if (player->equalizer != NULL) {
382
gst_bin_add_many (GST_BIN (player->audiobin), eq_audioconvert, eq_audioconvert2, player->equalizer, player->preamp, NULL);
385
// Ghost pad the audio bin so audio is passed from the bin into the tee
386
teepad = gst_element_get_pad (player->audiotee, "sink");
387
gst_element_add_pad (player->audiobin, gst_ghost_pad_new ("sink", teepad));
388
gst_object_unref (teepad);
390
// Link the queue and the actual audio sink
391
if (player->equalizer != NULL) {
392
// link in equalizer, preamp and audioconvert.
393
gst_element_link_many (audiosinkqueue, eq_audioconvert, player->preamp,
394
player->equalizer, eq_audioconvert2, player->volume, audiosink, NULL);
396
// link the queue with the real audio sink
397
gst_element_link_many (audiosinkqueue, player->volume, audiosink, NULL);
399
player->before_rgvolume = player->volume;
400
player->after_rgvolume = player->audiosink = audiosink;
401
player->rgvolume_in_pipeline = FALSE;
402
_bp_replaygain_pipeline_rebuild (player);
404
_bp_vis_pipeline_setup (player);
406
// Now that our internal audio sink is constructed, tell playbin to use it
407
g_object_set (G_OBJECT (player->playbin), "audio-sink", player->audiobin, NULL);
409
// Connect to the bus to get messages
410
bus = gst_pipeline_get_bus (GST_PIPELINE (player->playbin));
411
gst_bus_add_watch (bus, bp_pipeline_bus_callback, player);
413
// Link the first tee pad to the primary audio sink queue
414
GstPad *sinkpad = gst_element_get_pad (audiosinkqueue, "sink");
415
pad = gst_element_get_request_pad (player->audiotee, "src%d");
416
g_object_set(player->audiotee, "alloc-pad", pad, NULL);
417
gst_pad_link (pad, sinkpad);
418
gst_object_unref (GST_OBJECT (pad));
420
// Now allow specialized pipeline setups
421
_bp_cdda_pipeline_setup (player);
422
_bp_dvd_pipeline_setup (player);
423
_bp_video_pipeline_setup (player, bus);
424
_bp_dvd_find_navigation (player);
430
_bp_pipeline_destroy (BansheePlayer *player)
432
g_return_if_fail (IS_BANSHEE_PLAYER (player));
434
if (player->playbin == NULL) {
438
if (GST_IS_ELEMENT (player->playbin)) {
439
player->target_state = GST_STATE_NULL;
440
gst_element_set_state (player->playbin, GST_STATE_NULL);
442
// The audiosink was set READY early to detect sink volume control in
443
// case it is out of sync with the playbin state ensure it's in NULL now
444
if (player->audiosink != NULL && GST_STATE (player->audiosink) != GST_STATE_NULL)
445
gst_element_set_state (player->audiosink, GST_STATE_NULL);
447
gst_object_unref (GST_OBJECT (player->playbin));
450
_bp_vis_pipeline_destroy (player);
452
player->playbin = NULL;