4d33329d131fa2104cb79429e7e452d99d5f8de4
[WebKit-https.git] / Source / WebCore / platform / graphics / gstreamer / TextCombinerGStreamer.cpp
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 #include "TextCombinerGStreamer.h"
28
29 #if ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(VIDEO_TRACK)
30
31 #include "GRefPtrGStreamer.h"
32
33 static GstStaticPadTemplate sinkTemplate =
34     GST_STATIC_PAD_TEMPLATE("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST,
35         GST_STATIC_CAPS_ANY);
36
37 static GstStaticPadTemplate srcTemplate =
38     GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
39         GST_STATIC_CAPS_ANY);
40
41 GST_DEBUG_CATEGORY_STATIC(webkitTextCombinerDebug);
42 #define GST_CAT_DEFAULT webkitTextCombinerDebug
43
44 #define webkit_text_combiner_parent_class parent_class
45 G_DEFINE_TYPE_WITH_CODE(WebKitTextCombiner, webkit_text_combiner, GST_TYPE_BIN,
46     GST_DEBUG_CATEGORY_INIT(webkitTextCombinerDebug, "webkittextcombiner", 0,
47         "webkit text combiner"));
48
49 enum {
50     PROP_PAD_0,
51     PROP_PAD_TAGS
52 };
53
54 #define WEBKIT_TYPE_TEXT_COMBINER_PAD webkit_text_combiner_pad_get_type()
55
56 #define WEBKIT_TEXT_COMBINER_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_TEXT_COMBINER_PAD, WebKitTextCombinerPad))
57 #define WEBKIT_TEXT_COMBINER_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WEBKIT_TYPE_TEXT_COMBINER_PAD, WebKitTextCombinerPadClass))
58 #define WEBKIT_IS_TEXT_COMBINER_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_TYPE_TEXT_COMBINER_PAD))
59 #define WEBKIT_IS_TEXT_COMBINER_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WEBKIT_TYPE_TEXT_COMBINER_PAD))
60 #define WEBKIT_TEXT_COMBINER_PAD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), WEBKIT_TYPE_TEXT_COMBINER_PAD, WebKitTextCombinerPadClass))
61
62 typedef struct _WebKitTextCombinerPad WebKitTextCombinerPad;
63 typedef struct _WebKitTextCombinerPadClass WebKitTextCombinerPadClass;
64
65 struct _WebKitTextCombinerPad {
66     GstGhostPad parent;
67
68     GstTagList* tags;
69 };
70
71 struct _WebKitTextCombinerPadClass {
72     GstGhostPadClass parent;
73 };
74
75 G_DEFINE_TYPE(WebKitTextCombinerPad, webkit_text_combiner_pad, GST_TYPE_GHOST_PAD);
76
77 static gboolean webkitTextCombinerPadEvent(GstPad*, GstObject* parent, GstEvent*);
78
79 static void webkit_text_combiner_init(WebKitTextCombiner* combiner)
80 {
81     combiner->funnel = gst_element_factory_make("funnel", nullptr);
82     ASSERT(combiner->funnel);
83
84     gboolean ret = gst_bin_add(GST_BIN(combiner), combiner->funnel);
85     UNUSED_PARAM(ret);
86     ASSERT(ret);
87
88     GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(combiner->funnel, "src"));
89     ASSERT(pad);
90
91     ret = gst_element_add_pad(GST_ELEMENT(combiner), gst_ghost_pad_new("src", pad.get()));
92     ASSERT(ret);
93 }
94
95 static void webkit_text_combiner_pad_init(WebKitTextCombinerPad* pad)
96 {
97     pad->tags = 0;
98
99     gst_pad_set_event_function(GST_PAD(pad), webkitTextCombinerPadEvent);
100 }
101
102 static void webkitTextCombinerPadFinalize(GObject* object)
103 {
104     WebKitTextCombinerPad* pad = WEBKIT_TEXT_COMBINER_PAD(object);
105     if (pad->tags)
106         gst_tag_list_unref(pad->tags);
107     G_OBJECT_CLASS(webkit_text_combiner_pad_parent_class)->finalize(object);
108 }
109
110 static void webkitTextCombinerPadGetProperty(GObject* object, guint propertyId, GValue* value, GParamSpec* pspec)
111 {
112     WebKitTextCombinerPad* pad = WEBKIT_TEXT_COMBINER_PAD(object);
113     switch (propertyId) {
114     case PROP_PAD_TAGS:
115         GST_OBJECT_LOCK(object);
116         if (pad->tags)
117             g_value_take_boxed(value, gst_tag_list_copy(pad->tags));
118         GST_OBJECT_UNLOCK(object);
119         break;
120     default:
121         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec);
122         break;
123     }
124 }
125
126 static gboolean webkitTextCombinerPadEvent(GstPad* pad, GstObject* parent, GstEvent* event)
127 {
128     gboolean ret;
129     UNUSED_PARAM(ret);
130     WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(parent);
131     WebKitTextCombinerPad* combinerPad = WEBKIT_TEXT_COMBINER_PAD(pad);
132     ASSERT(combiner);
133
134     switch (GST_EVENT_TYPE(event)) {
135     case GST_EVENT_CAPS: {
136         GstCaps* caps;
137         gst_event_parse_caps(event, &caps);
138         ASSERT(caps);
139
140         GstPad* target = gst_ghost_pad_get_target(GST_GHOST_PAD(pad));
141         ASSERT(target);
142
143         GstElement* targetParent = gst_pad_get_parent_element(target);
144         ASSERT(targetParent);
145
146         GstCaps* textCaps = gst_caps_new_empty_simple("text/x-raw");
147         if (gst_caps_can_intersect(textCaps, caps)) {
148             /* Caps are plain text, put a WebVTT encoder between the ghostpad and
149              * the funnel */
150             if (targetParent == combiner->funnel) {
151                 /* Setup a WebVTT encoder */
152                 GstElement* encoder = gst_element_factory_make("webvttenc", nullptr);
153                 ASSERT(encoder);
154
155                 ret = gst_bin_add(GST_BIN(combiner), encoder);
156                 ASSERT(ret);
157
158                 ret = gst_element_sync_state_with_parent(encoder);
159                 ASSERT(ret);
160
161                 /* Switch the ghostpad to target the WebVTT encoder */
162                 GstPad* sinkPad = gst_element_get_static_pad(encoder, "sink");
163                 ASSERT(sinkPad);
164
165                 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(pad), sinkPad);
166                 ASSERT(ret);
167                 gst_object_unref(sinkPad);
168
169                 /* Connect the WebVTT encoder to the funnel */
170                 GstPad* srcPad = gst_element_get_static_pad(encoder, "src");
171                 ASSERT(srcPad);
172
173                 ret = GST_PAD_LINK_SUCCESSFUL(gst_pad_link(srcPad, target));
174                 ASSERT(ret);
175                 gst_object_unref(srcPad);
176             } /* else: pipeline is already correct */
177         } else {
178             /* Caps are not plain text, remove the WebVTT encoder */
179             if (targetParent != combiner->funnel) {
180                 /* Get the funnel sink pad */
181                 GstPad* srcPad = gst_element_get_static_pad(targetParent, "src");
182                 ASSERT(srcPad);
183
184                 GstPad* sinkPad = gst_pad_get_peer(srcPad);
185                 ASSERT(sinkPad);
186                 gst_object_unref(srcPad);
187
188                 /* Switch the ghostpad to target the funnel */
189                 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(pad), sinkPad);
190                 ASSERT(ret);
191                 gst_object_unref(sinkPad);
192
193                 /* Remove the WebVTT encoder */
194                 ret = gst_bin_remove(GST_BIN(combiner), targetParent);
195                 ASSERT(ret);
196             } /* else: pipeline is already correct */
197         }
198         gst_caps_unref(textCaps);
199         gst_object_unref(targetParent);
200         gst_object_unref(target);
201         break;
202     }
203     case GST_EVENT_TAG: {
204         GstTagList* tags;
205         gst_event_parse_tag(event, &tags);
206         ASSERT(tags);
207
208         GST_OBJECT_LOCK(pad);
209         if (!combinerPad->tags)
210             combinerPad->tags = gst_tag_list_copy(tags);
211         else
212             gst_tag_list_insert(combinerPad->tags, tags, GST_TAG_MERGE_REPLACE);
213         GST_OBJECT_UNLOCK(pad);
214
215         g_object_notify(G_OBJECT(pad), "tags");
216         break;
217     }
218     default:
219         break;
220     }
221     return gst_pad_event_default(pad, parent, event);
222 }
223
224 static GstPad* webkitTextCombinerRequestNewPad(GstElement * element,
225     GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
226 {
227     gboolean ret;
228     UNUSED_PARAM(ret);
229     ASSERT(templ);
230
231     WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(element);
232     ASSERT(combiner);
233
234     GstPad* pad = gst_element_request_pad(combiner->funnel, templ, name, caps);
235     ASSERT(pad);
236
237     GstPad* ghostPad = GST_PAD(g_object_new(WEBKIT_TYPE_TEXT_COMBINER_PAD, "direction", gst_pad_get_direction(pad), nullptr));
238     ASSERT(ghostPad);
239
240     ret = gst_ghost_pad_construct(GST_GHOST_PAD(ghostPad));
241     ASSERT(ret);
242
243     ret = gst_ghost_pad_set_target(GST_GHOST_PAD(ghostPad), pad);
244     ASSERT(ret);
245
246     ret = gst_pad_set_active(ghostPad, true);
247     ASSERT(ret);
248
249     ret = gst_element_add_pad(GST_ELEMENT(combiner), ghostPad);
250     ASSERT(ret);
251     return ghostPad;
252 }
253
254 static void webkitTextCombinerReleasePad(GstElement *element, GstPad *pad)
255 {
256     WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(element);
257     GstPad* peer = gst_pad_get_peer(pad);
258     if (peer) {
259         GstElement* parent = gst_pad_get_parent_element(peer);
260         ASSERT(parent);
261         gst_element_release_request_pad(parent, peer);
262         if (parent != combiner->funnel)
263             gst_bin_remove(GST_BIN(combiner), parent);
264     }
265
266     gst_element_remove_pad(element, pad);
267 }
268
269 static void webkit_text_combiner_class_init(WebKitTextCombinerClass* klass)
270 {
271     GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
272
273     gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate));
274     gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&srcTemplate));
275
276     gst_element_class_set_metadata(elementClass, "WebKit text combiner", "Generic",
277         "A funnel that accepts any caps, but converts plain text to WebVTT",
278         "Brendan Long <b.long@cablelabs.com>");
279
280     elementClass->request_new_pad =
281         GST_DEBUG_FUNCPTR(webkitTextCombinerRequestNewPad);
282     elementClass->release_pad =
283         GST_DEBUG_FUNCPTR(webkitTextCombinerReleasePad);
284 }
285
286 static void webkit_text_combiner_pad_class_init(WebKitTextCombinerPadClass* klass)
287 {
288     GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
289
290     gobjectClass->finalize = GST_DEBUG_FUNCPTR(webkitTextCombinerPadFinalize);
291     gobjectClass->get_property = GST_DEBUG_FUNCPTR(webkitTextCombinerPadGetProperty);
292
293     g_object_class_install_property(gobjectClass, PROP_PAD_TAGS,
294         g_param_spec_boxed("tags", "Tags", "The currently active tags on the pad", GST_TYPE_TAG_LIST,
295             static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
296 }
297
298 GstElement* webkitTextCombinerNew()
299 {
300     return GST_ELEMENT(g_object_new(WEBKIT_TYPE_TEXT_COMBINER, nullptr));
301 }
302
303 #endif // ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(VIDEO_TRACK)