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