1/* 2 * Copyright (C) 2013 Cable Television Laboratories, Inc. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY 18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#if ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(VIDEO_TRACK) 28 29#include "TextCombinerGStreamer.h" 30 31static GstStaticPadTemplate sinkTemplate = 32 GST_STATIC_PAD_TEMPLATE("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST, 33 GST_STATIC_CAPS_ANY); 34 35static GstStaticPadTemplate srcTemplate = 36 GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, 37 GST_STATIC_CAPS_ANY); 38 39GST_DEBUG_CATEGORY_STATIC(webkitTextCombinerDebug); 40#define GST_CAT_DEFAULT webkitTextCombinerDebug 41 42#define webkit_text_combiner_parent_class parent_class 43G_DEFINE_TYPE_WITH_CODE(WebKitTextCombiner, webkit_text_combiner, GST_TYPE_BIN, 44 GST_DEBUG_CATEGORY_INIT(webkitTextCombinerDebug, "webkittextcombiner", 0, 45 "webkit text combiner")); 46 47enum { 48 PROP_PAD_0, 49 PROP_PAD_TAGS 50}; 51 52#define WEBKIT_TYPE_TEXT_COMBINER_PAD webkit_text_combiner_pad_get_type() 53 54#define WEBKIT_TEXT_COMBINER_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_TEXT_COMBINER_PAD, WebKitTextCombinerPad)) 55#define WEBKIT_TEXT_COMBINER_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WEBKIT_TYPE_TEXT_COMBINER_PAD, WebKitTextCombinerPadClass)) 56#define WEBKIT_IS_TEXT_COMBINER_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_TYPE_TEXT_COMBINER_PAD)) 57#define WEBKIT_IS_TEXT_COMBINER_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WEBKIT_TYPE_TEXT_COMBINER_PAD)) 58#define WEBKIT_TEXT_COMBINER_PAD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), WEBKIT_TYPE_TEXT_COMBINER_PAD, WebKitTextCombinerPadClass)) 59 60typedef struct _WebKitTextCombinerPad WebKitTextCombinerPad; 61typedef struct _WebKitTextCombinerPadClass WebKitTextCombinerPadClass; 62 63struct _WebKitTextCombinerPad { 64 GstGhostPad parent; 65 66 GstTagList* tags; 67}; 68 69struct _WebKitTextCombinerPadClass { 70 GstGhostPadClass parent; 71}; 72 73G_DEFINE_TYPE(WebKitTextCombinerPad, webkit_text_combiner_pad, GST_TYPE_GHOST_PAD); 74 75static gboolean webkitTextCombinerPadEvent(GstPad*, GstObject* parent, GstEvent*); 76 77static void webkit_text_combiner_init(WebKitTextCombiner* combiner) 78{ 79 combiner->funnel = gst_element_factory_make("funnel", NULL); 80 ASSERT(combiner->funnel); 81 82 gboolean ret = gst_bin_add(GST_BIN(combiner), combiner->funnel); 83 UNUSED_PARAM(ret); 84 ASSERT(ret); 85 86 GstPad* pad = gst_element_get_static_pad(combiner->funnel, "src"); 87 ASSERT(pad); 88 89 ret = gst_element_add_pad(GST_ELEMENT(combiner), gst_ghost_pad_new("src", pad)); 90 ASSERT(ret); 91} 92 93static void webkit_text_combiner_pad_init(WebKitTextCombinerPad* pad) 94{ 95 pad->tags = 0; 96 97 gst_pad_set_event_function(GST_PAD(pad), webkitTextCombinerPadEvent); 98} 99 100static void webkitTextCombinerPadFinalize(GObject* object) 101{ 102 WebKitTextCombinerPad* pad = WEBKIT_TEXT_COMBINER_PAD(object); 103 if (pad->tags) 104 gst_tag_list_unref(pad->tags); 105 G_OBJECT_CLASS(webkit_text_combiner_pad_parent_class)->finalize(object); 106} 107 108static void webkitTextCombinerPadGetProperty(GObject* object, guint propertyId, GValue* value, GParamSpec* pspec) 109{ 110 WebKitTextCombinerPad* pad = WEBKIT_TEXT_COMBINER_PAD(object); 111 switch (propertyId) { 112 case PROP_PAD_TAGS: 113 GST_OBJECT_LOCK(object); 114 if (pad->tags) 115 g_value_take_boxed(value, gst_tag_list_copy(pad->tags)); 116 GST_OBJECT_UNLOCK(object); 117 break; 118 default: 119 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec); 120 break; 121 } 122} 123 124static gboolean webkitTextCombinerPadEvent(GstPad* pad, GstObject* parent, GstEvent* event) 125{ 126 gboolean ret; 127 UNUSED_PARAM(ret); 128 WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(parent); 129 WebKitTextCombinerPad* combinerPad = WEBKIT_TEXT_COMBINER_PAD(pad); 130 ASSERT(combiner); 131 132 switch (GST_EVENT_TYPE(event)) { 133 case GST_EVENT_CAPS: { 134 GstCaps* caps; 135 gst_event_parse_caps(event, &caps); 136 ASSERT(caps); 137 138 GstPad* target = gst_ghost_pad_get_target(GST_GHOST_PAD(pad)); 139 ASSERT(target); 140 141 GstElement* targetParent = gst_pad_get_parent_element(target); 142 ASSERT(targetParent); 143 144 GstCaps* textCaps = gst_caps_new_empty_simple("text/x-raw"); 145 if (gst_caps_can_intersect(textCaps, caps)) { 146 /* Caps are plain text, put a WebVTT encoder between the ghostpad and 147 * the funnel */ 148 if (targetParent == combiner->funnel) { 149 /* Setup a WebVTT encoder */ 150 GstElement* encoder = gst_element_factory_make("webvttenc", NULL); 151 ASSERT(encoder); 152 153 ret = gst_bin_add(GST_BIN(combiner), encoder); 154 ASSERT(ret); 155 156 ret = gst_element_sync_state_with_parent(encoder); 157 ASSERT(ret); 158 159 /* Switch the ghostpad to target the WebVTT encoder */ 160 GstPad* sinkPad = gst_element_get_static_pad(encoder, "sink"); 161 ASSERT(sinkPad); 162 163 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(pad), sinkPad); 164 ASSERT(ret); 165 gst_object_unref(sinkPad); 166 167 /* Connect the WebVTT encoder to the funnel */ 168 GstPad* srcPad = gst_element_get_static_pad(encoder, "src"); 169 ASSERT(srcPad); 170 171 ret = GST_PAD_LINK_SUCCESSFUL(gst_pad_link(srcPad, target)); 172 ASSERT(ret); 173 gst_object_unref(srcPad); 174 } /* else: pipeline is already correct */ 175 } else { 176 /* Caps are not plain text, remove the WebVTT encoder */ 177 if (targetParent != combiner->funnel) { 178 /* Get the funnel sink pad */ 179 GstPad* srcPad = gst_element_get_static_pad(targetParent, "src"); 180 ASSERT(srcPad); 181 182 GstPad* sinkPad = gst_pad_get_peer(srcPad); 183 ASSERT(sinkPad); 184 gst_object_unref(srcPad); 185 186 /* Switch the ghostpad to target the funnel */ 187 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(pad), sinkPad); 188 ASSERT(ret); 189 gst_object_unref(sinkPad); 190 191 /* Remove the WebVTT encoder */ 192 ret = gst_bin_remove(GST_BIN(combiner), targetParent); 193 ASSERT(ret); 194 } /* else: pipeline is already correct */ 195 } 196 gst_caps_unref(textCaps); 197 gst_object_unref(targetParent); 198 gst_object_unref(target); 199 break; 200 } 201 case GST_EVENT_TAG: { 202 GstTagList* tags; 203 gst_event_parse_tag(event, &tags); 204 ASSERT(tags); 205 206 GST_OBJECT_LOCK(pad); 207 if (!combinerPad->tags) 208 combinerPad->tags = gst_tag_list_copy(tags); 209 else 210 gst_tag_list_insert(combinerPad->tags, tags, GST_TAG_MERGE_REPLACE); 211 GST_OBJECT_UNLOCK(pad); 212 213 g_object_notify(G_OBJECT(pad), "tags"); 214 break; 215 } 216 default: 217 break; 218 } 219 return gst_pad_event_default(pad, parent, event); 220} 221 222static GstPad* webkitTextCombinerRequestNewPad(GstElement * element, 223 GstPadTemplate * templ, const gchar * name, const GstCaps * caps) 224{ 225 gboolean ret; 226 UNUSED_PARAM(ret); 227 ASSERT(templ); 228 229 WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(element); 230 ASSERT(combiner); 231 232 GstPad* pad = gst_element_request_pad(combiner->funnel, templ, name, caps); 233 ASSERT(pad); 234 235 GstPad* ghostPad = GST_PAD(g_object_new(WEBKIT_TYPE_TEXT_COMBINER_PAD, "direction", gst_pad_get_direction(pad), NULL)); 236 ASSERT(ghostPad); 237 238 ret = gst_ghost_pad_construct(GST_GHOST_PAD(ghostPad)); 239 ASSERT(ret); 240 241 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(ghostPad), pad); 242 ASSERT(ret); 243 244 ret = gst_pad_set_active(ghostPad, true); 245 ASSERT(ret); 246 247 ret = gst_element_add_pad(GST_ELEMENT(combiner), ghostPad); 248 ASSERT(ret); 249 return ghostPad; 250} 251 252static void webkitTextCombinerReleasePad(GstElement *element, GstPad *pad) 253{ 254 WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(element); 255 GstPad* peer = gst_pad_get_peer(pad); 256 if (peer) { 257 GstElement* parent = gst_pad_get_parent_element(peer); 258 ASSERT(parent); 259 gst_element_release_request_pad(parent, peer); 260 if (parent != combiner->funnel) 261 gst_bin_remove(GST_BIN(combiner), parent); 262 } 263 264 gst_element_remove_pad(element, pad); 265} 266 267static void webkit_text_combiner_class_init(WebKitTextCombinerClass* klass) 268{ 269 GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); 270 271 gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate)); 272 gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&srcTemplate)); 273 274 gst_element_class_set_metadata(elementClass, "WebKit text combiner", "Generic", 275 "A funnel that accepts any caps, but converts plain text to WebVTT", 276 "Brendan Long <b.long@cablelabs.com>"); 277 278 elementClass->request_new_pad = 279 GST_DEBUG_FUNCPTR(webkitTextCombinerRequestNewPad); 280 elementClass->release_pad = 281 GST_DEBUG_FUNCPTR(webkitTextCombinerReleasePad); 282} 283 284static void webkit_text_combiner_pad_class_init(WebKitTextCombinerPadClass* klass) 285{ 286 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); 287 288 gobjectClass->finalize = GST_DEBUG_FUNCPTR(webkitTextCombinerPadFinalize); 289 gobjectClass->get_property = GST_DEBUG_FUNCPTR(webkitTextCombinerPadGetProperty); 290 291 g_object_class_install_property(gobjectClass, PROP_PAD_TAGS, 292 g_param_spec_boxed("tags", "Tags", "The currently active tags on the pad", GST_TYPE_TAG_LIST, 293 static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); 294} 295 296GstElement* webkitTextCombinerNew() 297{ 298 return GST_ELEMENT(g_object_new(WEBKIT_TYPE_TEXT_COMBINER, 0)); 299} 300 301#endif // ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(VIDEO_TRACK) 302