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 "GStreamerCommon.h"
32
33static GstStaticPadTemplate sinkTemplate =
34 GST_STATIC_PAD_TEMPLATE("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST,
35 GST_STATIC_CAPS_ANY);
36
37static GstStaticPadTemplate srcTemplate =
38 GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
39 GST_STATIC_CAPS_ANY);
40
41GST_DEBUG_CATEGORY_STATIC(webkitTextCombinerDebug);
42#define GST_CAT_DEFAULT webkitTextCombinerDebug
43
44#define webkit_text_combiner_parent_class parent_class
45G_DEFINE_TYPE_WITH_CODE(WebKitTextCombiner, webkit_text_combiner, GST_TYPE_BIN,
46 GST_DEBUG_CATEGORY_INIT(webkitTextCombinerDebug, "webkittextcombiner", 0,
47 "webkit text combiner"));
48
49enum {
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
62typedef struct _WebKitTextCombinerPad WebKitTextCombinerPad;
63typedef struct _WebKitTextCombinerPadClass WebKitTextCombinerPadClass;
64
65struct _WebKitTextCombinerPad {
66 GstGhostPad parent;
67
68 GstTagList* tags;
69};
70
71struct _WebKitTextCombinerPadClass {
72 GstGhostPadClass parent;
73};
74
75G_DEFINE_TYPE(WebKitTextCombinerPad, webkit_text_combiner_pad, GST_TYPE_GHOST_PAD);
76
77static gboolean webkitTextCombinerPadEvent(GstPad*, GstObject* parent, GstEvent*);
78
79static 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
95static void webkit_text_combiner_pad_init(WebKitTextCombinerPad* pad)
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 GRefPtr<GstPad> target = adoptGRef(gst_ghost_pad_get_target(GST_GHOST_PAD(pad)));
139 ASSERT(target);
140
141 GRefPtr<GstElement> targetParent = adoptGRef(gst_pad_get_parent_element(target.get()));
142 ASSERT(targetParent);
143
144 GRefPtr<GstCaps> textCaps = adoptGRef(gst_caps_new_empty_simple("text/x-raw"));
145 if (gst_caps_can_intersect(textCaps.get(), caps)) {
146 /* Caps are plain text, put a WebVTT encoder between the ghostpad and
147 * the funnel */
148 if (targetParent.get() == combiner->funnel) {
149 /* Setup a WebVTT encoder */
150 GstElement* encoder = gst_element_factory_make("webvttenc", nullptr);
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 GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(encoder, "sink"));
161 ASSERT(sinkPad);
162
163 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(pad), sinkPad.get());
164 ASSERT(ret);
165
166 /* Connect the WebVTT encoder to the funnel */
167 GRefPtr<GstPad> srcPad = adoptGRef(gst_element_get_static_pad(encoder, "src"));
168 ASSERT(srcPad);
169
170 ret = GST_PAD_LINK_SUCCESSFUL(gst_pad_link(srcPad.get(), target.get()));
171 ASSERT(ret);
172 } /* else: pipeline is already correct */
173 } else {
174 /* Caps are not plain text, remove the WebVTT encoder */
175 if (targetParent.get() != combiner->funnel) {
176 /* Get the funnel sink pad */
177 GRefPtr<GstPad> srcPad = adoptGRef(gst_element_get_static_pad(targetParent.get(), "src"));
178 ASSERT(srcPad);
179
180 GRefPtr<GstPad> sinkPad = adoptGRef(gst_pad_get_peer(srcPad.get()));
181 ASSERT(sinkPad);
182
183 /* Switch the ghostpad to target the funnel */
184 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(pad), sinkPad.get());
185 ASSERT(ret);
186
187 /* Remove the WebVTT encoder */
188 ret = gst_bin_remove(GST_BIN(combiner), targetParent.get());
189 ASSERT(ret);
190 } /* else: pipeline is already correct */
191 }
192 break;
193 }
194 case GST_EVENT_TAG: {
195 GstTagList* tags;
196 gst_event_parse_tag(event, &tags);
197 ASSERT(tags);
198
199 GST_OBJECT_LOCK(pad);
200 if (!combinerPad->tags)
201 combinerPad->tags = gst_tag_list_copy(tags);
202 else
203 gst_tag_list_insert(combinerPad->tags, tags, GST_TAG_MERGE_REPLACE);
204 GST_OBJECT_UNLOCK(pad);
205
206 g_object_notify(G_OBJECT(pad), "tags");
207 break;
208 }
209 default:
210 break;
211 }
212 return gst_pad_event_default(pad, parent, event);
213}
214
215static GstPad* webkitTextCombinerRequestNewPad(GstElement * element,
216 GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
217{
218 gboolean ret;
219 UNUSED_PARAM(ret);
220 ASSERT(templ);
221
222 WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(element);
223 ASSERT(combiner);
224
225 GstPad* pad = gst_element_request_pad(combiner->funnel, templ, name, caps);
226 ASSERT(pad);
227
228 GstPad* ghostPad = GST_PAD(g_object_new(WEBKIT_TYPE_TEXT_COMBINER_PAD, "direction", gst_pad_get_direction(pad), nullptr));
229 ASSERT(ghostPad);
230
231 ret = gst_ghost_pad_construct(GST_GHOST_PAD(ghostPad));
232 ASSERT(ret);
233
234 ret = gst_ghost_pad_set_target(GST_GHOST_PAD(ghostPad), pad);
235 ASSERT(ret);
236
237 ret = gst_pad_set_active(ghostPad, true);
238 ASSERT(ret);
239
240 ret = gst_element_add_pad(GST_ELEMENT(combiner), ghostPad);
241 ASSERT(ret);
242 return ghostPad;
243}
244
245static void webkitTextCombinerReleasePad(GstElement *element, GstPad *pad)
246{
247 WebKitTextCombiner* combiner = WEBKIT_TEXT_COMBINER(element);
248 if (GRefPtr<GstPad> peer = adoptGRef(gst_pad_get_peer(pad))) {
249 GRefPtr<GstElement> parent = adoptGRef(gst_pad_get_parent_element(peer.get()));
250 ASSERT(parent);
251 gst_element_release_request_pad(parent.get(), peer.get());
252 if (parent.get() != combiner->funnel)
253 gst_bin_remove(GST_BIN(combiner), parent.get());
254 }
255
256 gst_element_remove_pad(element, pad);
257}
258
259static void webkit_text_combiner_class_init(WebKitTextCombinerClass* klass)
260{
261 GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
262
263 gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate));
264 gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&srcTemplate));
265
266 gst_element_class_set_metadata(elementClass, "WebKit text combiner", "Generic",
267 "A funnel that accepts any caps, but converts plain text to WebVTT",
268 "Brendan Long <b.long@cablelabs.com>");
269
270 elementClass->request_new_pad =
271 GST_DEBUG_FUNCPTR(webkitTextCombinerRequestNewPad);
272 elementClass->release_pad =
273 GST_DEBUG_FUNCPTR(webkitTextCombinerReleasePad);
274}
275
276static void webkit_text_combiner_pad_class_init(WebKitTextCombinerPadClass* klass)
277{
278 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
279
280 gobjectClass->finalize = GST_DEBUG_FUNCPTR(webkitTextCombinerPadFinalize);
281 gobjectClass->get_property = GST_DEBUG_FUNCPTR(webkitTextCombinerPadGetProperty);
282
283 g_object_class_install_property(gobjectClass, PROP_PAD_TAGS,
284 g_param_spec_boxed("tags", "Tags", "The currently active tags on the pad", GST_TYPE_TAG_LIST,
285 static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
286}
287
288GstElement* webkitTextCombinerNew()
289{
290 return GST_ELEMENT(g_object_new(WEBKIT_TYPE_TEXT_COMBINER, nullptr));
291}
292
293#endif // ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(VIDEO_TRACK)
294