GLVideoSinkGStreamer.cpp [plain text]
#include "config.h"
#include "GLVideoSinkGStreamer.h"
#if ENABLE(VIDEO) && USE(GSTREAMER_GL)
#include "GStreamerCommon.h"
#include "MediaPlayerPrivateGStreamer.h"
#include "PlatformDisplay.h"
#include <gst/app/gstappsink.h>
#include <wtf/glib/WTFGType.h>
#if PLATFORM(X11) && GST_GL_HAVE_PLATFORM_EGL
#undef None
#endif // PLATFORM(X11) && GST_GL_HAVE_PLATFORM_EGL
using namespace WebCore;
enum {
PROP_0,
PROP_STATS,
PROP_LAST
};
struct _WebKitGLVideoSinkPrivate {
GRefPtr<GstElement> appSink;
MediaPlayerPrivateGStreamer* mediaPlayerPrivate;
};
GST_DEBUG_CATEGORY_STATIC(webkit_gl_video_sink_debug);
#define GST_CAT_DEFAULT webkit_gl_video_sink_debug
#define GST_GL_CAPS_FORMAT "{ RGBx, RGBA, I420, Y444, YV12, Y41B, Y42B, NV12, NV21, VUYA }"
static GstStaticPadTemplate sinkTemplate = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
#define webkit_gl_video_sink_parent_class parent_class
WEBKIT_DEFINE_TYPE_WITH_CODE(WebKitGLVideoSink, webkit_gl_video_sink, GST_TYPE_BIN,
GST_DEBUG_CATEGORY_INIT(webkit_gl_video_sink_debug, "webkitglvideosink", 0, "GL video sink element"))
static void webKitGLVideoSinkConstructed(GObject* object)
{
GST_CALL_PARENT(G_OBJECT_CLASS, constructed, (object));
WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(object);
sink->priv->appSink = gst_element_factory_make("appsink", "webkit-gl-video-appsink");
ASSERT(sink->priv->appSink);
g_object_set(sink->priv->appSink.get(), "enable-last-sample", FALSE, "emit-signals", TRUE, "max-buffers", 1, nullptr);
GstElement* upload = gst_element_factory_make("glupload", nullptr);
GstElement* colorconvert = gst_element_factory_make("glcolorconvert", nullptr);
ASSERT(upload);
ASSERT(colorconvert);
gst_bin_add_many(GST_BIN_CAST(sink), upload, colorconvert, sink->priv->appSink.get(), nullptr);
GRefPtr<GstCaps> caps;
if (webkitGstCheckVersion(1, 16, 2) || getenv("WEBKIT_GST_NO_RGBA_CONVERSION"))
caps = adoptGRef(gst_caps_from_string("video/x-raw, format = (string) " GST_GL_CAPS_FORMAT));
else {
GST_INFO_OBJECT(sink, "Forcing RGBA as GStreamer is not new enough.");
caps = adoptGRef(gst_caps_from_string("video/x-raw, format = (string) RGBA"));
}
gst_caps_set_features(caps.get(), 0, gst_caps_features_new(GST_CAPS_FEATURE_MEMORY_GL_MEMORY, nullptr));
g_object_set(sink->priv->appSink.get(), "caps", caps.get(), nullptr);
gst_element_link_many(upload, colorconvert, sink->priv->appSink.get(), nullptr);
GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(upload, "sink"));
gst_element_add_pad(GST_ELEMENT_CAST(sink), gst_ghost_pad_new("sink", pad.get()));
}
void webKitGLVideoSinkFinalize(GObject* object)
{
ASSERT(isMainThread());
WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(object);
WebKitGLVideoSinkPrivate* priv = sink->priv;
if (priv->mediaPlayerPrivate)
g_signal_handlers_disconnect_by_data(priv->appSink.get(), priv->mediaPlayerPrivate);
GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
}
Optional<GRefPtr<GstContext>> requestGLContext(const char* contextType)
{
auto& sharedDisplay = PlatformDisplay::sharedDisplayForCompositing();
auto* gstGLDisplay = sharedDisplay.gstGLDisplay();
auto* gstGLContext = sharedDisplay.gstGLContext();
if (!(gstGLDisplay && gstGLContext))
return WTF::nullopt;
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
GstContext* displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
gst_context_set_gl_display(displayContext, gstGLDisplay);
return adoptGRef(displayContext);
}
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
GstContext* appContext = gst_context_new("gst.gl.app_context", TRUE);
GstStructure* structure = gst_context_writable_structure(appContext);
#if GST_CHECK_VERSION(1, 12, 0)
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, gstGLContext, nullptr);
#else
gst_structure_set(structure, "context", GST_GL_TYPE_CONTEXT, gstGLContext, nullptr);
#endif
return adoptGRef(appContext);
}
return WTF::nullopt;
}
static bool setGLContext(GstElement* elementSink, const char* contextType)
{
GRefPtr<GstContext> oldContext = gst_element_get_context(elementSink, contextType);
if (!oldContext) {
auto newContext = requestGLContext(contextType);
if (!newContext.hasValue())
return false;
gst_element_set_context(elementSink, newContext->get());
}
return true;
}
static GstStateChangeReturn webKitGLVideoSinkChangeState(GstElement* element, GstStateChange transition)
{
#if GST_CHECK_VERSION(1, 14, 0)
GST_DEBUG_OBJECT(element, "%s", gst_state_change_get_name(transition));
#endif
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
#if GST_CHECK_VERSION(1, 14, 0)
case GST_STATE_CHANGE_READY_TO_READY:
#endif
case GST_STATE_CHANGE_READY_TO_PAUSED: {
if (!setGLContext(element, GST_GL_DISPLAY_CONTEXT_TYPE))
return GST_STATE_CHANGE_FAILURE;
if (!setGLContext(element, "gst.gl.app_context"))
return GST_STATE_CHANGE_FAILURE;
break;
}
default:
break;
}
return GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
}
static void webKitGLVideoSinkGetProperty(GObject* object, guint propertyId, GValue* value, GParamSpec* paramSpec)
{
WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(object);
switch (propertyId) {
case PROP_STATS:
if (webkitGstCheckVersion(1, 17, 0)) {
GUniqueOutPtr<GstStructure> stats;
g_object_get(sink->priv->appSink.get(), "stats", &stats.outPtr(), nullptr);
gst_value_set_structure(value, stats.get());
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, paramSpec);
RELEASE_ASSERT_NOT_REACHED();
break;
}
}
static void webkit_gl_video_sink_class_init(WebKitGLVideoSinkClass* klass)
{
GObjectClass* objectClass = G_OBJECT_CLASS(klass);
GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
objectClass->constructed = webKitGLVideoSinkConstructed;
objectClass->finalize = webKitGLVideoSinkFinalize;
objectClass->get_property = webKitGLVideoSinkGetProperty;
gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate));
gst_element_class_set_static_metadata(elementClass, "WebKit GL video sink", "Sink/Video", "Renders video", "Philippe Normand <philn@igalia.com>");
g_object_class_install_property(objectClass, PROP_STATS, g_param_spec_boxed("stats", "Statistics",
"Sink Statistics", GST_TYPE_STRUCTURE, static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
elementClass->change_state = GST_DEBUG_FUNCPTR(webKitGLVideoSinkChangeState);
}
void webKitGLVideoSinkSetMediaPlayerPrivate(WebKitGLVideoSink* sink, MediaPlayerPrivateGStreamer* player)
{
WebKitGLVideoSinkPrivate* priv = sink->priv;
priv->mediaPlayerPrivate = player;
g_signal_connect(priv->appSink.get(), "new-sample", G_CALLBACK(+[](GstElement* sink, MediaPlayerPrivateGStreamer* player) -> GstFlowReturn {
GRefPtr<GstSample> sample = adoptGRef(gst_app_sink_pull_sample(GST_APP_SINK(sink)));
player->triggerRepaint(sample.get());
return GST_FLOW_OK;
}), player);
g_signal_connect(priv->appSink.get(), "new-preroll", G_CALLBACK(+[](GstElement* sink, MediaPlayerPrivateGStreamer* player) -> GstFlowReturn {
GRefPtr<GstSample> sample = adoptGRef(gst_app_sink_pull_preroll(GST_APP_SINK(sink)));
player->triggerRepaint(sample.get());
return GST_FLOW_OK;
}), player);
GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(priv->appSink.get(), "sink"));
gst_pad_add_probe(pad.get(), static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_PUSH | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), [] (GstPad*, GstPadProbeInfo* info, gpointer userData) -> GstPadProbeReturn {
if (info->type & GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM) {
if (GST_QUERY_TYPE(GST_PAD_PROBE_INFO_QUERY(info)) != GST_QUERY_DRAIN)
return GST_PAD_PROBE_OK;
GST_DEBUG("Acting upon DRAIN query");
}
if (info->type & GST_PAD_PROBE_TYPE_EVENT_FLUSH) {
if (GST_EVENT_TYPE(GST_PAD_PROBE_INFO_EVENT(info)) != GST_EVENT_FLUSH_START)
return GST_PAD_PROBE_OK;
GST_DEBUG("Acting upon flush-start event");
}
auto* player = static_cast<MediaPlayerPrivateGStreamer*>(userData);
player->flushCurrentBuffer();
return GST_PAD_PROBE_OK;
}, player, nullptr);
}
bool webKitGLVideoSinkProbePlatform()
{
if (!PlatformDisplay::sharedDisplayForCompositing().gstGLContext()) {
GST_WARNING("WebKit shared GL context is not available.");
return false;
}
return isGStreamerPluginAvailable("app") && isGStreamerPluginAvailable("opengl");
}
#endif