1 /*
2  *  Copyright (C) 2010 Igalia S.L
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Library General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Library General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Library General Public License
15  *  along with this library; see the file COPYING.LIB.  If not, write to
16  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  *  Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 #include "GStreamerGWorld.h"
22 #if USE(GSTREAMER)
23 
24 #include "GOwnPtrGStreamer.h"
25 #include <gst/gst.h>
26 #include <gst/interfaces/xoverlay.h>
27 #include <gst/pbutils/pbutils.h>
28 
29 #if PLATFORM(GTK)
30 #include <gtk/gtk.h>
31 #ifdef GDK_WINDOWING_X11
32 #include <gdk/gdkx.h> // for GDK_WINDOW_XID
33 #endif
34 #endif
35 
36 using namespace std;
37 
38 namespace WebCore {
39 
gstGWorldSyncMessageCallback(GstBus * bus,GstMessage * message,gpointer data)40 gboolean gstGWorldSyncMessageCallback(GstBus* bus, GstMessage* message, gpointer data)
41 {
42     ASSERT(GST_MESSAGE_TYPE(message) == GST_MESSAGE_ELEMENT);
43 
44     GStreamerGWorld* gstGWorld = static_cast<GStreamerGWorld*>(data);
45 
46     if (gst_structure_has_name(message->structure, "prepare-xwindow-id")
47         || gst_structure_has_name(message->structure, "have-ns-view"))
48         gstGWorld->setWindowOverlay(message);
49     return TRUE;
50 }
51 
createGWorld(GstElement * pipeline)52 PassRefPtr<GStreamerGWorld> GStreamerGWorld::createGWorld(GstElement* pipeline)
53 {
54     return adoptRef(new GStreamerGWorld(pipeline));
55 }
56 
GStreamerGWorld(GstElement * pipeline)57 GStreamerGWorld::GStreamerGWorld(GstElement* pipeline)
58     : m_pipeline(pipeline)
59     , m_dynamicPadName(0)
60 {
61     // XOverlay messages need to be handled synchronously.
62     GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline));
63     gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, this);
64     g_signal_connect(bus, "sync-message::element", G_CALLBACK(gstGWorldSyncMessageCallback), this);
65     gst_object_unref(bus);
66 }
67 
~GStreamerGWorld()68 GStreamerGWorld::~GStreamerGWorld()
69 {
70     exitFullscreen();
71 
72     m_pipeline = 0;
73 }
74 
enterFullscreen()75 bool GStreamerGWorld::enterFullscreen()
76 {
77     if (m_dynamicPadName)
78         return false;
79 
80     if (!m_videoWindow)
81         m_videoWindow = PlatformVideoWindow::createWindow();
82 
83     GstElement* platformVideoSink = gst_element_factory_make("autovideosink", "platformVideoSink");
84     GstElement* colorspace = gst_element_factory_make("ffmpegcolorspace", "colorspace");
85     GstElement* queue = gst_element_factory_make("queue", "queue");
86     GstElement* videoScale = gst_element_factory_make("videoscale", "videoScale");
87 
88     // Get video sink bin and the tee inside.
89     GOwnPtr<GstElement> videoSink;
90     g_object_get(m_pipeline, "video-sink", &videoSink.outPtr(), NULL);
91     GstElement* tee = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoTee");
92     GstElement* valve = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoValve");
93 
94     g_object_set(valve, "drop-probability", 1.0, NULL);
95 
96     // Add and link a queue, ffmpegcolorspace, videoscale and sink in the bin.
97     gst_bin_add_many(GST_BIN(videoSink.get()), platformVideoSink, videoScale, colorspace, queue, NULL);
98 #if GST_CHECK_VERSION(0, 10, 30)
99     // Faster elements linking, if possible.
100     gst_element_link_pads_full(queue, "src", colorspace, "sink", GST_PAD_LINK_CHECK_NOTHING);
101     gst_element_link_pads_full(colorspace, "src", videoScale, "sink", GST_PAD_LINK_CHECK_NOTHING);
102     gst_element_link_pads_full(videoScale, "src", platformVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING);
103 #else
104     gst_element_link_many(queue, colorspace, videoScale, platformVideoSink, NULL);
105 #endif
106 
107     // Link a new src pad from tee to queue.
108     GstPad* srcPad = gst_element_get_request_pad(tee, "src%d");
109     GstPad* sinkPad = gst_element_get_static_pad(queue, "sink");
110     gst_pad_link(srcPad, sinkPad);
111     gst_object_unref(GST_OBJECT(sinkPad));
112 
113     m_dynamicPadName = gst_pad_get_name(srcPad);
114 
115     // Roll new elements to pipeline state.
116     gst_element_sync_state_with_parent(queue);
117     gst_element_sync_state_with_parent(colorspace);
118     gst_element_sync_state_with_parent(videoScale);
119     gst_element_sync_state_with_parent(platformVideoSink);
120 
121     gst_object_unref(tee);
122 
123     // Query the current media segment informations and send them towards
124     // the new tee branch downstream.
125 
126     GstQuery* query = gst_query_new_segment(GST_FORMAT_TIME);
127     gboolean queryResult = gst_element_query(m_pipeline, query);
128 
129 #if GST_CHECK_VERSION(0, 10, 30)
130     if (!queryResult) {
131         gst_query_unref(query);
132         gst_object_unref(GST_OBJECT(srcPad));
133         return true;
134     }
135 #else
136     // GStreamer < 0.10.30 doesn't set the query result correctly, so
137     // just ignore it to avoid a compilation warning.
138     // See https://bugzilla.gnome.org/show_bug.cgi?id=620490.
139     (void) queryResult;
140 #endif
141 
142     GstFormat format;
143     gint64 position;
144     if (!gst_element_query_position(m_pipeline, &format, &position))
145         position = 0;
146 
147     gdouble rate;
148     gint64 startValue, stopValue;
149     gst_query_parse_segment(query, &rate, &format, &startValue, &stopValue);
150 
151     GstEvent* event = gst_event_new_new_segment(FALSE, rate, format, startValue, stopValue, position);
152     gst_pad_push_event(srcPad, event);
153 
154     gst_query_unref(query);
155     gst_object_unref(GST_OBJECT(srcPad));
156     return true;
157 }
158 
exitFullscreen()159 void GStreamerGWorld::exitFullscreen()
160 {
161     if (!m_dynamicPadName)
162         return;
163 
164     // Get video sink bin and the elements to remove.
165     GOwnPtr<GstElement> videoSink;
166     g_object_get(m_pipeline, "video-sink", &videoSink.outPtr(), NULL);
167     GstElement* tee = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoTee");
168     GstElement* platformVideoSink = gst_bin_get_by_name(GST_BIN(videoSink.get()), "platformVideoSink");
169     GstElement* queue = gst_bin_get_by_name(GST_BIN(videoSink.get()), "queue");
170     GstElement* colorspace = gst_bin_get_by_name(GST_BIN(videoSink.get()), "colorspace");
171     GstElement* videoScale = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoScale");
172 
173     GstElement* valve = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoValve");
174 
175     g_object_set(valve, "drop-probability", 0.0, NULL);
176 
177     // Get pads to unlink and remove.
178     GstPad* srcPad = gst_element_get_static_pad(tee, m_dynamicPadName);
179     GstPad* sinkPad = gst_element_get_static_pad(queue, "sink");
180 
181     // Block data flow towards the pipeline branch to remove.
182     gst_pad_set_blocked(srcPad, true);
183 
184     // Unlink and release request pad.
185     gst_pad_unlink(srcPad, sinkPad);
186     gst_element_release_request_pad(tee, srcPad);
187     gst_object_unref(GST_OBJECT(srcPad));
188     gst_object_unref(GST_OBJECT(sinkPad));
189 
190     // Unlink, remove and cleanup queue, ffmpegcolorspace, videoScale and sink.
191     gst_element_unlink_many(queue, colorspace, videoScale, platformVideoSink, NULL);
192     gst_bin_remove_many(GST_BIN(videoSink.get()), queue, colorspace, videoScale, platformVideoSink, NULL);
193     gst_element_set_state(queue, GST_STATE_NULL);
194     gst_element_set_state(colorspace, GST_STATE_NULL);
195     gst_element_set_state(videoScale, GST_STATE_NULL);
196     gst_element_set_state(platformVideoSink, GST_STATE_NULL);
197     gst_object_unref(queue);
198     gst_object_unref(colorspace);
199     gst_object_unref(videoScale);
200     gst_object_unref(platformVideoSink);
201 
202     gst_object_unref(tee);
203     m_dynamicPadName = 0;
204 }
205 
setWindowOverlay(GstMessage * message)206 void GStreamerGWorld::setWindowOverlay(GstMessage* message)
207 {
208     GstObject* sink = GST_MESSAGE_SRC(message);
209 
210     if (!GST_IS_X_OVERLAY(sink))
211         return;
212 
213     if (g_object_class_find_property(G_OBJECT_GET_CLASS(sink), "force-aspect-ratio"))
214         g_object_set(sink, "force-aspect-ratio", TRUE, NULL);
215 
216     if (m_videoWindow) {
217         m_videoWindow->prepareForOverlay(message);
218 
219 // gst_x_overlay_set_window_handle was introduced in -plugins-base
220 // 0.10.31, just like the macro for checking the version.
221 #ifdef GST_CHECK_PLUGINS_BASE_VERSION
222         gst_x_overlay_set_window_handle(GST_X_OVERLAY(sink), m_videoWindow->videoWindowId());
223 #else
224         gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), m_videoWindow->videoWindowId());
225 #endif
226     }
227 }
228 
229 }
230 #endif // USE(GSTREAMER)
231