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 | |
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 | gst_pad_set_event_function(GST_PAD(pad), webkitTextCombinerPadEvent); |
98 | } |
99 | |
100 | static 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 | |
108 | static 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 | |
124 | static 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 | |
215 | static 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 | |
245 | static 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 | |
259 | static 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 | |
276 | static 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 | |
288 | GstElement* 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 | |