1 /* Octopus Media Engine
2 * Copyright 2008 Movial Creative Technologies Inc
4 * Authors: Mikko Rasa, <mikko.rasa@movial.fi>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
22 #include <gst/interfaces/xoverlay.h>
23 #include "octopus-backend-gst.h"
24 #include "octopus-module.h"
25 #include "octopus-module-manager.h"
26 #include "octopus-route.h"
28 typedef struct _RouteDataGst RouteDataGst;
39 validate_component(OctopusBackend *backend,
40 OctopusComponent *comp);
42 can_link_components(OctopusBackend *backend,
43 OctopusComponent *comp1,
44 OctopusComponent *comp2);
47 init_route (OctopusBackend *backend,
50 destroy_route(OctopusBackend *backend,
53 play_route(OctopusBackend *backend,
56 pause_route(OctopusBackend *backend,
59 stop_route(OctopusBackend *backend,
63 route_mapping_changed(OctopusRoute *route,
66 route_endpoint_changed(OctopusRoute *route,
67 OctopusEndpoint *endpoint,
70 have_type(GstTypeFind *typefind,
75 pad_added(GstElement *elem,
79 pad_removed(GstElement *elem,
83 no_more_pads(GstElement *elem,
86 bus_watch(GstBus *bus,
90 position_timer(gpointer);
92 G_DEFINE_TYPE(OctopusBackendGst, octopus_backend_gst, OCTOPUS_TYPE_BACKEND)
95 octopus_backend_gst_class_init(OctopusBackendGstClass *klass)
97 OctopusBackendClass *backend;
99 backend = OCTOPUS_BACKEND_CLASS(klass);
101 backend->validate_component = validate_component;
102 backend->can_link_components = can_link_components;
103 backend->init_route = init_route;
104 backend->destroy_route = destroy_route;
105 backend->play_route = play_route;
106 backend->pause_route = pause_route;
107 backend->stop_route = stop_route;
111 octopus_backend_gst_init(OctopusBackendGst *backend)
113 OctopusBackend *base;
115 base = OCTOPUS_BACKEND(backend);
117 base->name = "gstreamer";
125 can_maybe_sink_caps(GstElementFactory *factory,
129 gboolean result = FALSE;
131 pads = gst_element_factory_get_static_pad_templates(factory);
132 for(; pads; pads = pads->next) {
133 GstStaticPadTemplate *tmpl = pads->data;
134 if(tmpl->direction == GST_PAD_SINK && tmpl->presence == GST_PAD_ALWAYS) {
135 caps = gst_caps_intersect(caps, gst_static_pad_template_get_caps(tmpl));
136 result = !gst_caps_is_empty(caps);
137 gst_caps_unref(caps);
146 * OctopusBackend implementation
150 validate_component(OctopusBackend *backend,
151 OctopusComponent *comp)
155 for(iter = comp->elements; iter; iter = iter->next) {
156 GstElementFactory *factory;
158 factory = gst_element_factory_find(((OctopusElement *)iter->data)->name);
161 gst_object_unref(factory);
168 can_link_components(OctopusBackend *backend,
169 OctopusComponent *comp1,
170 OctopusComponent *comp2)
172 OctopusElement *elem;
173 GstElementFactory *factory;
175 GstCaps *caps = NULL;
176 gboolean result = FALSE;
178 elem = (OctopusElement *)g_slist_last(comp1->elements)->data;
179 factory = gst_element_factory_find(elem->name);
181 pads = gst_element_factory_get_static_pad_templates(factory);
182 for(; pads; pads = pads->next) {
183 GstStaticPadTemplate *tmpl = pads->data;
184 if(tmpl->direction == GST_PAD_SRC && tmpl->presence == GST_PAD_ALWAYS) {
185 caps = gst_static_pad_template_get_caps(tmpl);
189 gst_object_unref(factory);
192 if(gst_caps_is_any(caps))
195 elem = (OctopusElement *)comp2->elements->data;
196 factory = gst_element_factory_find(elem->name);
197 result = can_maybe_sink_caps(factory, caps);
198 gst_object_unref(factory);
205 init_route(OctopusBackend *backend,
211 data = g_new0(RouteDataGst, 1);
212 data->pipeline = gst_element_factory_make("pipeline", NULL);
213 g_object_set_data(G_OBJECT(route), "data", data);
215 g_signal_connect(route, "mapping-changed", G_CALLBACK(route_mapping_changed), 0);
216 g_signal_connect(route, "endpoint-changed", G_CALLBACK(route_endpoint_changed), 0);
218 bus = gst_pipeline_get_bus(GST_PIPELINE(data->pipeline));
219 gst_bus_add_watch(bus, bus_watch, route);
225 destroy_route(OctopusBackend *backend,
230 stop_route(backend, route);
231 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
232 gst_object_unref(GST_OBJECT(data->pipeline));
236 play_route(OctopusBackend *backend,
240 GstStateChangeReturn ret;
242 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
243 if(!data->ready && !data->fakesink) {
244 /* Keep the pipeline in an async state change while building it */
245 data->fakesink = gst_element_factory_make("fakesink", NULL);
246 gst_bin_add(GST_BIN(data->pipeline), data->fakesink);
249 ret = gst_element_set_state(GST_ELEMENT(data->pipeline), GST_STATE_PLAYING);
250 if(ret == GST_STATE_CHANGE_FAILURE) {
251 g_debug("Pipeline failed to start playback");
253 } else if(ret != GST_STATE_CHANGE_ASYNC) {
254 octopus_route_state_changed(route, OCTOPUS_ROUTE_PLAYING);
257 if(!data->timeout_tag)
258 data->timeout_tag = g_timeout_add(1000, position_timer, route);
264 pause_route(OctopusBackend *backend,
268 GstStateChangeReturn ret;
270 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
272 if(data->timeout_tag) {
273 g_source_remove(data->timeout_tag);
274 data->timeout_tag = 0;
277 ret = gst_element_set_state(GST_ELEMENT(data->pipeline), GST_STATE_PAUSED);
278 if(ret == GST_STATE_CHANGE_FAILURE) {
279 g_debug("Pipeline failed to pause playback");
281 } else if(ret != GST_STATE_CHANGE_ASYNC) {
282 octopus_route_state_changed(route, OCTOPUS_ROUTE_PAUSED);
289 stop_route(OctopusBackend *backend,
293 GstStateChangeReturn ret;
295 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
297 if(data->timeout_tag) {
298 g_source_remove(data->timeout_tag);
299 data->timeout_tag = 0;
302 ret = gst_element_set_state(GST_ELEMENT(data->pipeline), GST_STATE_NULL);
303 if(ret == GST_STATE_CHANGE_FAILURE) {
304 g_debug("Pipeline failed to stop playback");
306 } else if(ret != GST_STATE_CHANGE_ASYNC) {
307 octopus_route_state_changed(route, OCTOPUS_ROUTE_STOPPED);
318 configure_endpoint(GstElement *elem,
319 const OctopusEndpoint *endpoint)
322 g_debug("Setting URI '%s'", endpoint->uri);
323 if(GST_IS_URI_HANDLER(elem)) {
324 GstURIHandler *uri_handler;
326 uri_handler = GST_URI_HANDLER(elem);
327 gst_uri_handler_set_uri(uri_handler, endpoint->uri);
331 loc=gst_uri_get_location(endpoint->uri);
333 if(g_object_class_find_property(G_OBJECT_GET_CLASS(elem), "location"))
334 g_object_set(G_OBJECT(elem), "location", loc, NULL);
335 else if(g_object_class_find_property(G_OBJECT_GET_CLASS(elem), "file-name"))
336 g_object_set(G_OBJECT(elem), "file-name", loc, NULL);
338 g_warning("Don't know how to set URI");
342 if(endpoint->window) {
343 g_debug("Setting X11 display ('%s', %lx)", endpoint->display, endpoint->window);
344 g_object_set(G_OBJECT(elem), "display", endpoint->display, NULL);
345 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(elem), endpoint->window);
350 realize_element(OctopusRoute *route,
351 OctopusElement *oct_elem)
353 GstElement *gst_elem;
357 g_debug("Realizing element '%s'", oct_elem->name);
358 gst_elem = gst_element_factory_make(oct_elem->name, oct_elem->endpoint);
360 if(oct_elem->endpoint) {
361 const OctopusEndpoint *endpoint;
362 if((endpoint = octopus_route_get_endpoint(route, oct_elem->endpoint)))
363 configure_endpoint(gst_elem, endpoint);
366 for(iter2 = oct_elem->parameters; iter2; iter2 = iter2->next) {
367 OctopusParameter *param = (OctopusParameter *)iter2->data;
370 pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(gst_elem), param->name);
372 if(pspec->value_type == G_TYPE_INT) {
373 g_object_set(G_OBJECT(gst_elem), param->name, strtol(param->value, NULL, 0), NULL);
374 } else if(pspec->value_type == G_TYPE_UINT) {
375 g_object_set(G_OBJECT(gst_elem), param->name, strtoul(param->value, NULL, 0), NULL);
376 } else if(pspec->value_type == G_TYPE_INT64) {
377 g_object_set(G_OBJECT(gst_elem), param->name, strtoll(param->value, NULL, 0), NULL);
378 } else if(pspec->value_type == G_TYPE_UINT64) {
379 g_object_set(G_OBJECT(gst_elem), param->name, strtoull(param->value, NULL, 0), NULL);
380 } else if(pspec->value_type == G_TYPE_BOOLEAN) {
381 if(!strcmp(param->value, "true"))
382 g_object_set(G_OBJECT(gst_elem), param->name, TRUE, NULL);
383 else if(!strcmp(param->value, "false"))
384 g_object_set(G_OBJECT(gst_elem), param->name, FALSE, NULL);
385 } else if(pspec->value_type == G_TYPE_STRING) {
386 g_object_set(G_OBJECT(gst_elem), param->name, param->value, NULL);
389 g_debug("Unknown parameter '%s' specified for element '%s'", param->name, oct_elem->name);
393 /* XXX better way to figure out if it's a typefind? */
394 if(g_str_has_prefix(oct_elem->name, "typefind")) {
395 g_debug("Connecting to 'have-type' signal");
396 g_signal_connect(G_OBJECT(gst_elem), "have-type", G_CALLBACK(have_type), route);
399 iter = gst_element_class_get_pad_template_list(GST_ELEMENT_GET_CLASS(gst_elem));
400 for(; iter; iter = iter->next) {
401 GstPadTemplate *tmpl = (GstPadTemplate *)iter->data;
402 if(GST_PAD_TEMPLATE_DIRECTION(tmpl) == GST_PAD_SRC
403 && GST_PAD_TEMPLATE_PRESENCE(tmpl) == GST_PAD_SOMETIMES)
405 g_debug("Connecting to dynamic pad signals");
406 g_signal_connect(G_OBJECT(gst_elem), "pad-added", G_CALLBACK(pad_added), route);
407 g_signal_connect(G_OBJECT(gst_elem), "pad-removed", G_CALLBACK(pad_removed), route);
408 g_signal_connect(G_OBJECT(gst_elem), "no-more-pads", G_CALLBACK(no_more_pads), route);
417 realize_component(OctopusBackend *backend,
419 OctopusComponent *component,
426 g_debug("Realizing component '%s'", component->name);
428 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
430 state = GST_STATE(data->pipeline);
431 if(state != GST_STATE_NULL)
432 state = GST_STATE_PAUSED;
434 for(iter = component->elements; iter; iter = iter->next) {
435 OctopusElement *oct_elem = (OctopusElement *)iter->data;
436 GstElement *gst_elem;
438 gst_elem = realize_element(route, oct_elem);
439 gst_bin_add(GST_BIN(data->pipeline), gst_elem);
444 if((sinkpad = gst_element_get_static_pad(gst_elem, "sink"))) {
445 if(GST_PAD_LINK_FAILED(gst_pad_link(srcpad, sinkpad)))
446 g_debug("Linking pads failed - component '%s' may be broken", component->name);
447 gst_object_unref(sinkpad);
450 gst_object_unref(srcpad);
452 gst_element_set_state(gst_elem, state);
454 srcpad = gst_element_get_static_pad(gst_elem, "src");
461 realize_component_chain(OctopusBackend *backend,
469 gst_object_ref(srcpad);
470 for(iter = chain; iter; iter = iter->next)
471 srcpad = realize_component(backend, route, (OctopusComponent *)iter->data, srcpad);
473 gst_object_unref(srcpad);
477 continue_route_from_pad(OctopusBackend *backend,
484 GSList *chain = NULL;
486 caps = gst_pad_get_caps(srcpad);
487 g_object_get(G_OBJECT(route), "sinks", &sinks, NULL);
489 for(iter = backend->components; (!chain && iter); iter = iter->next) {
490 OctopusComponent *comp = (OctopusComponent *)iter->data;
491 OctopusElement *elem = (OctopusElement *)comp->elements->data;
492 GstElementFactory *factory;
494 factory = gst_element_factory_find(elem->name);
495 if(can_maybe_sink_caps(factory, caps)) {
498 g_debug("%s[%s] can sink this", comp->name, elem->name);
500 for(j = 0; (!chain && sinks[j]); ++j)
501 chain = octopus_backend_build_component_chain(backend, comp, sinks[j]);
503 gst_object_unref(factory);
505 gst_caps_unref(caps);
508 realize_component_chain(backend, route, chain, srcpad);
511 g_debug("Unable to continue route");
512 octopus_route_playback_error(route, "Unable to build route: unsupported media type");
517 route_mapping_changed(OctopusRoute *route,
520 OctopusBackend *backend;
521 OctopusComponent *component;
526 g_debug("Route mapping changed");
528 g_object_get(G_OBJECT(route), "backend", &backend,
530 "sinks", &sinks, NULL);
531 g_assert(OCTOPUS_IS_BACKEND_GST(backend));
533 for(i = 0; sources[i]; ++i) {
534 GSList *chain = NULL;
537 component = octopus_backend_get_component_by_endpoint(backend, sources[i], OCTOPUS_ENDPOINT_SOURCE);
541 for(j = 0; sinks[j]; ++j) {
542 if((chain = octopus_backend_build_component_chain(backend, component, sinks[j]))) {
543 realize_component_chain(backend, route, chain, NULL);
554 route_endpoint_changed(OctopusRoute *route,
555 OctopusEndpoint *endpoint,
561 gboolean found = FALSE;
563 g_debug("Route endpoint '%s' changed", endpoint->name);
565 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
567 iter = gst_bin_iterate_recurse(GST_BIN(data->pipeline));
568 while(!found && gst_iterator_next(iter, &element) == GST_ITERATOR_OK) {
571 g_object_get(G_OBJECT(element), "name", &elem_name, NULL);
572 if(g_str_has_prefix(elem_name, endpoint->name)) {
573 configure_endpoint(element, endpoint);
578 gst_object_unref(element);
580 gst_iterator_free(iter);
584 have_type(GstTypeFind *typefind,
590 OctopusBackend *backend;
594 route = OCTOPUS_ROUTE(user_data);
595 g_object_get(G_OBJECT(route), "backend", &backend, NULL);
596 g_assert(OCTOPUS_IS_BACKEND_GST(backend));
598 caps_str = gst_caps_to_string(caps);
599 g_debug("Have type: %s", caps_str);
602 pad = gst_element_get_static_pad(GST_ELEMENT(typefind), "src");
603 continue_route_from_pad(backend, route, pad);
608 pad_added(GstElement *elem,
613 OctopusBackend *backend;
622 g_object_get(G_OBJECT(elem), "name", &elem_name, NULL);
623 g_object_get(G_OBJECT(pad), "name", &pad_name, NULL);
624 caps = gst_pad_get_caps(pad);
625 caps_str = gst_caps_to_string(caps);
626 g_debug("Pad '%s' added to '%s', caps: %s", pad_name, elem_name, caps_str);
627 gst_caps_unref(caps);
632 route = OCTOPUS_ROUTE(user_data);
633 g_object_get(G_OBJECT(route), "backend", &backend, NULL);
634 g_assert(OCTOPUS_IS_BACKEND_GST(backend));
636 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
638 queue = gst_element_factory_make("queue", NULL);
639 gst_bin_add(GST_BIN(data->pipeline), queue);
640 gst_element_set_state(queue, GST_STATE_PAUSED);
641 qpad = gst_element_get_static_pad(queue, "sink");
642 gst_pad_link(pad, qpad);
643 gst_object_unref(qpad);
645 qpad = gst_element_get_static_pad(queue, "src");
646 continue_route_from_pad(backend, route, qpad);
647 gst_object_unref(qpad);
651 pad_removed(GstElement *elem,
660 if(gst_pad_get_direction(pad) != GST_PAD_SRC)
663 g_object_get(G_OBJECT(elem), "name", &elem_name, NULL);
664 g_object_get(G_OBJECT(pad), "name", &pad_name, NULL);
665 g_debug("Pad '%s' removed from '%s'", pad_name, elem_name);
669 route = OCTOPUS_ROUTE(user_data);
670 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
676 GSList *stale = NULL;
679 /* Find any elements that have a sink pad with nothing connected */
680 iter = gst_bin_iterate_elements(GST_BIN(data->pipeline));
681 while(gst_iterator_next(iter, &elem) != GST_ITERATOR_DONE) {
682 GstPad *pad = gst_element_get_static_pad(GST_ELEMENT(elem), "sink");
684 GstPad *peer = gst_pad_get_peer(pad);
686 gst_object_unref(peer);
688 stale = g_slist_prepend(stale, elem);
689 gst_object_unref(pad);
691 gst_object_unref(GST_OBJECT(elem));
693 gst_iterator_free(iter);
695 /* No stale elements found - we're done */
699 for(ptr = stale; ptr; ptr = ptr->next) {
703 elem = GST_ELEMENT(ptr->data);
704 g_object_get(G_OBJECT(elem), "name", &name, NULL);
705 g_debug("Removing element '%s'", name);
706 gst_element_set_state(elem, GST_STATE_NULL);
707 gst_bin_remove(GST_BIN(data->pipeline), elem);
713 no_more_pads(GstElement *elem,
720 g_object_get(G_OBJECT(elem), "name", &elem_name, NULL);
721 g_debug("No more pads from '%s'", elem_name);
724 route = OCTOPUS_ROUTE(user_data);
725 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
727 gst_element_set_state(data->fakesink, GST_STATE_NULL);
728 gst_bin_remove(GST_BIN(data->pipeline), data->fakesink);
729 data->fakesink = NULL;
735 bus_watch(GstBus *bus,
741 OctopusBackend *backend;
743 route = OCTOPUS_ROUTE(user_data);
744 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
745 g_object_get(G_OBJECT(route), "backend", &backend, NULL);
747 switch(GST_MESSAGE_TYPE(msg)) {
748 case GST_MESSAGE_ERROR: {
749 GError *error = NULL;
751 gst_message_parse_error(msg, &error, NULL);
752 g_debug("GST ERROR: %s", error->message);
753 octopus_route_playback_error(route, error->message);
755 stop_route(backend, route);
758 case GST_MESSAGE_EOS:
760 stop_route(backend, route);
763 case GST_MESSAGE_STATE_CHANGED: {
767 GstState previous, state, pending;
768 gst_message_parse_state_changed (msg, &previous, &state, &pending);
769 elem = GST_ELEMENT(GST_MESSAGE_SRC(msg));
770 name = gst_element_get_name(elem);
771 g_debug("GST STATE CHANGED: %s %s->%s, pending %s", name, gst_element_state_get_name(previous), gst_element_state_get_name(state), gst_element_state_get_name(pending));
774 if(elem == data->pipeline && pending == GST_STATE_VOID_PENDING) {
775 if(state == GST_STATE_NULL)
776 octopus_route_state_changed(route, OCTOPUS_ROUTE_STOPPED);
777 else if(state == GST_STATE_PAUSED)
778 octopus_route_state_changed(route, OCTOPUS_ROUTE_PAUSED);
779 else if(state == GST_STATE_PLAYING)
780 octopus_route_state_changed(route, OCTOPUS_ROUTE_PLAYING);
785 g_debug("Unhandled GST message of type %d", GST_MESSAGE_TYPE(msg));
792 position_timer(gpointer user_data)
794 OctopusRoute *route = (OctopusRoute *)user_data;
796 GstFormat fmt = GST_FORMAT_TIME;
800 data = (RouteDataGst *)g_object_get_data(G_OBJECT(route), "data");
801 gst_element_query_position(data->pipeline, &fmt, &pos);
802 gst_element_query_duration(data->pipeline, &fmt, &dur);
804 octopus_route_stream_position(route, (guint)(pos/1000000000), (guint)(dur/1000000000));
809 // vim: filetype=c:expandtab:shiftwidth=2:tabstop=2:softtabstop=2