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_empty ("stream-changed"));
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_STREAM_START: {
222
bp_next_track_starting (player);
226
case GST_MESSAGE_APPLICATION: {
228
const GstStructure * s = gst_message_get_structure (message);
229
name = gst_structure_get_name (s);
230
if (name && !strcmp (name, "stream-changed")) {
231
_bp_parse_stream_info (player);
242
#ifdef ENABLE_GAPLESS
243
static void bp_about_to_finish_callback (GstElement *playbin, BansheePlayer *player)
245
g_return_if_fail (IS_BANSHEE_PLAYER (player));
246
g_return_if_fail (GST_IS_ELEMENT (playbin));
248
if (bp_stream_has_video (playbin)) {
249
bp_debug ("[Gapless]: Not attempting gapless transition from stream with video");
253
if (player->about_to_finish_cb != NULL) {
254
player->in_gapless_transition = TRUE;
256
bp_debug ("[Gapless] Requesting next track");
257
player->about_to_finish_cb (player);
260
#endif //ENABLE_GAPLESS
262
static void bp_volume_changed_callback (GstElement *playbin, GParamSpec *spec, BansheePlayer *player)
266
g_return_if_fail (IS_BANSHEE_PLAYER (player));
267
g_return_if_fail (GST_IS_ELEMENT (playbin));
269
g_object_get (G_OBJECT (playbin), "volume", &volume, NULL);
271
player->current_volume = volume;
273
if (player->volume_changed_cb != NULL) {
274
player->volume_changed_cb (player, volume);
278
// ---------------------------------------------------------------------------
279
// Internal Functions
280
// ---------------------------------------------------------------------------
283
_bp_pipeline_construct (BansheePlayer *player)
288
GstElement *audiosink;
289
GstElement *audiosinkqueue;
290
GstElement *eq_audioconvert = NULL;
291
GstElement *eq_audioconvert2 = NULL;
293
g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
295
// Playbin is the core element that handles autoplugging (finding the right
296
// source and decoder elements) based on source URI and stream content
297
player->playbin = gst_element_factory_make ("playbin", "playbin");
299
#ifdef ENABLE_GAPLESS
300
// FIXME: Connect a proxy about-to-finish callback that will generate a next-track-starting callback.
301
// This can be removed once playbin generates its own next-track signal.
302
// bgo#584987 - this is included in >= 0.10.26
303
g_signal_connect (player->playbin, "about-to-finish", G_CALLBACK (bp_about_to_finish_callback), player);
304
#endif //ENABLE_GAPLESS
306
g_return_val_if_fail (player->playbin != NULL, FALSE);
308
g_signal_connect (player->playbin, "notify::volume", G_CALLBACK (bp_volume_changed_callback), player);
309
g_signal_connect (player->playbin, "video-changed", G_CALLBACK (playbin_stream_changed_cb), player);
310
g_signal_connect (player->playbin, "audio-changed", G_CALLBACK (playbin_stream_changed_cb), player);
311
g_signal_connect (player->playbin, "text-changed", G_CALLBACK (playbin_stream_changed_cb), player);
313
audiosink = gst_element_factory_make ("directsoundsink", "audiosink");
314
if (audiosink != NULL) {
315
g_object_set (G_OBJECT (audiosink), "volume", 1.0, NULL);
317
audiosink = gst_element_factory_make ("autoaudiosink", "audiosink");
318
if (audiosink == NULL) {
319
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;
346
bp_debug ("Audiosink has volume: %s",
347
player->audiosink_has_volume ? "YES" : "NO");
350
// Create a custom audio sink bin that will hold the real primary sink
351
player->audiobin = gst_bin_new ("audiobin");
352
g_return_val_if_fail (player->audiobin != NULL, FALSE);
354
// Our audio sink is a tee, so plugins can attach their own pipelines
355
player->audiotee = gst_element_factory_make ("tee", "audiotee");
356
g_return_val_if_fail (player->audiotee != NULL, FALSE);
358
// Create a volume control with low latency
359
player->volume = gst_element_factory_make ("volume", NULL);
360
g_return_val_if_fail (player->volume != NULL, FALSE);
362
// call the volume changed callback once so the volume from the pipeline is
363
// set in the player object
364
bp_volume_changed_callback (player->playbin, NULL, player);
366
audiosinkqueue = gst_element_factory_make ("queue", "audiosinkqueue");
367
g_return_val_if_fail (audiosinkqueue != NULL, FALSE);
369
player->equalizer = _bp_equalizer_new (player);
370
player->preamp = NULL;
371
if (player->equalizer != NULL) {
372
eq_audioconvert = gst_element_factory_make ("audioconvert", "audioconvert");
373
eq_audioconvert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
374
player->preamp = gst_element_factory_make ("volume", "preamp");
377
// Add elements to custom audio sink
378
gst_bin_add_many (GST_BIN (player->audiobin), player->audiotee, player->volume, audiosinkqueue, audiosink, NULL);
380
if (player->equalizer != NULL) {
381
gst_bin_add_many (GST_BIN (player->audiobin), eq_audioconvert, eq_audioconvert2, player->equalizer, player->preamp, NULL);
384
// Ghost pad the audio bin so audio is passed from the bin into the tee
385
teepad = gst_element_get_static_pad (player->audiotee, "sink");
386
gst_element_add_pad (player->audiobin, gst_ghost_pad_new ("sink", teepad));
387
gst_object_unref (teepad);
389
// Link the queue and the actual audio sink
390
if (player->equalizer != NULL) {
391
// link in equalizer, preamp and audioconvert.
392
gst_element_link_many (audiosinkqueue, eq_audioconvert, player->preamp,
393
player->equalizer, eq_audioconvert2, player->volume, audiosink, NULL);
395
// link the queue with the real audio sink
396
gst_element_link_many (audiosinkqueue, player->volume, audiosink, NULL);
398
player->before_rgvolume = player->volume;
399
player->after_rgvolume = player->audiosink = audiosink;
400
player->rgvolume_in_pipeline = FALSE;
401
_bp_replaygain_pipeline_rebuild (player);
403
_bp_vis_pipeline_setup (player);
405
// Now that our internal audio sink is constructed, tell playbin to use it
406
g_object_set (G_OBJECT (player->playbin), "audio-sink", player->audiobin, NULL);
408
// Connect to the bus to get messages
409
bus = gst_pipeline_get_bus (GST_PIPELINE (player->playbin));
410
gst_bus_add_watch (bus, bp_pipeline_bus_callback, player);
412
// Link the first tee pad to the primary audio sink queue
413
GstPad *sinkpad = gst_element_get_static_pad (audiosinkqueue, "sink");
414
pad = gst_element_get_request_pad (player->audiotee, "src_%u");
415
g_object_set(player->audiotee, "alloc-pad", pad, NULL);
416
gst_pad_link (pad, sinkpad);
417
gst_object_unref (GST_OBJECT (pad));
419
// Now allow specialized pipeline setups
420
_bp_cdda_pipeline_setup (player);
421
_bp_dvd_pipeline_setup (player);
422
_bp_video_pipeline_setup (player, bus);
423
_bp_dvd_find_navigation (player);
429
_bp_pipeline_destroy (BansheePlayer *player)
431
g_return_if_fail (IS_BANSHEE_PLAYER (player));
433
if (player->playbin == NULL) {
437
if (GST_IS_ELEMENT (player->playbin)) {
438
player->target_state = GST_STATE_NULL;
439
gst_element_set_state (player->playbin, GST_STATE_NULL);
441
// The audiosink was set READY early to detect sink volume control in
442
// case it is out of sync with the playbin state ensure it's in NULL now
443
if (player->audiosink != NULL && GST_STATE (player->audiosink) != GST_STATE_NULL)
444
gst_element_set_state (player->audiosink, GST_STATE_NULL);
446
gst_object_unref (GST_OBJECT (player->playbin));
449
_bp_vis_pipeline_destroy (player);
451
player->playbin = NULL;