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
36using namespace std;
37
38namespace WebCore {
39
40gboolean 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
52PassRefPtr<GStreamerGWorld> GStreamerGWorld::createGWorld(GstElement* pipeline)
53{
54    return adoptRef(new GStreamerGWorld(pipeline));
55}
56
57GStreamerGWorld::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
68GStreamerGWorld::~GStreamerGWorld()
69{
70    exitFullscreen();
71
72    m_pipeline = 0;
73}
74
75bool 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
159void 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    // Unlink and release request pad.
182    gst_pad_unlink(srcPad, sinkPad);
183    gst_element_release_request_pad(tee, srcPad);
184    gst_object_unref(GST_OBJECT(srcPad));
185    gst_object_unref(GST_OBJECT(sinkPad));
186
187    // Unlink, remove and cleanup queue, ffmpegcolorspace, videoScale and sink.
188    gst_element_unlink_many(queue, colorspace, videoScale, platformVideoSink, NULL);
189    gst_bin_remove_many(GST_BIN(videoSink.get()), queue, colorspace, videoScale, platformVideoSink, NULL);
190    gst_element_set_state(queue, GST_STATE_NULL);
191    gst_element_set_state(colorspace, GST_STATE_NULL);
192    gst_element_set_state(videoScale, GST_STATE_NULL);
193    gst_element_set_state(platformVideoSink, GST_STATE_NULL);
194    gst_object_unref(queue);
195    gst_object_unref(colorspace);
196    gst_object_unref(videoScale);
197    gst_object_unref(platformVideoSink);
198
199    gst_object_unref(tee);
200    m_dynamicPadName = 0;
201}
202
203void GStreamerGWorld::setWindowOverlay(GstMessage* message)
204{
205    GstObject* sink = GST_MESSAGE_SRC(message);
206
207    if (!GST_IS_X_OVERLAY(sink))
208        return;
209
210    if (g_object_class_find_property(G_OBJECT_GET_CLASS(sink), "force-aspect-ratio"))
211        g_object_set(sink, "force-aspect-ratio", TRUE, NULL);
212
213    if (m_videoWindow) {
214        m_videoWindow->prepareForOverlay(message);
215
216// gst_x_overlay_set_window_handle was introduced in -plugins-base
217// 0.10.31, just like the macro for checking the version.
218#ifdef GST_CHECK_PLUGINS_BASE_VERSION
219        gst_x_overlay_set_window_handle(GST_X_OVERLAY(sink), m_videoWindow->videoWindowId());
220#else
221        gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), m_videoWindow->videoWindowId());
222#endif
223    }
224}
225
226}
227#endif // USE(GSTREAMER)
228