blob: 6663bfbe0f34aed9bc29cbeb412e6137ccb29a65 [file] [log] [blame]
/*
* Copyright (C) 2007 OpenedHand
* Copyright (C) 2007 Alp Toker <alp@atoker.com>
* Copyright (C) 2009, 2010, 2011, 2012, 2015, 2016 Igalia S.L
* Copyright (C) 2015, 2016 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
*
* WebKitVideoSink is a GStreamer sink element that triggers
* repaints in the WebKit GStreamer media player for the
* current video buffer.
*/
#include "config.h"
#include "VideoSinkGStreamer.h"
#if ENABLE(VIDEO) && USE(GSTREAMER)
#include "GRefPtrGStreamer.h"
#include "GStreamerUtilities.h"
#include "IntSize.h"
#include <glib.h>
#include <gst/gst.h>
#include <gst/video/gstvideometa.h>
#include <wtf/Condition.h>
#include <wtf/RunLoop.h>
using namespace WebCore;
// CAIRO_FORMAT_RGB24 used to render the video buffers is little/big endian dependant.
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GST_CAPS_FORMAT "{ BGRx, BGRA }"
#else
#define GST_CAPS_FORMAT "{ xRGB, ARGB }"
#endif
#define WEBKIT_VIDEO_SINK_PAD_CAPS GST_VIDEO_CAPS_MAKE_WITH_FEATURES(GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META, GST_CAPS_FORMAT) ";" GST_VIDEO_CAPS_MAKE(GST_CAPS_FORMAT)
static GstStaticPadTemplate s_sinkTemplate = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(WEBKIT_VIDEO_SINK_PAD_CAPS));
GST_DEBUG_CATEGORY_STATIC(webkitVideoSinkDebug);
#define GST_CAT_DEFAULT webkitVideoSinkDebug
enum {
REPAINT_REQUESTED,
REPAINT_CANCELLED,
LAST_SIGNAL
};
static guint webkitVideoSinkSignals[LAST_SIGNAL] = { 0, };
static void webkitVideoSinkRepaintRequested(WebKitVideoSink*, GstSample*);
static GRefPtr<GstSample> webkitVideoSinkRequestRender(WebKitVideoSink*, GstBuffer*);
class VideoRenderRequestScheduler {
public:
void start()
{
LockHolder locker(m_sampleMutex);
m_unlocked = false;
}
void stop()
{
LockHolder locker(m_sampleMutex);
m_sample = nullptr;
m_unlocked = true;
}
void drain()
{
LockHolder locker(m_sampleMutex);
m_sample = nullptr;
}
bool requestRender(WebKitVideoSink* sink, GstBuffer* buffer)
{
LockHolder locker(m_sampleMutex);
if (m_unlocked)
return true;
m_sample = webkitVideoSinkRequestRender(sink, buffer);
if (!m_sample)
return false;
auto sample = WTFMove(m_sample);
locker.unlockEarly();
if (LIKELY(GST_IS_SAMPLE(sample.get())))
webkitVideoSinkRepaintRequested(sink, sample.get());
return true;
}
private:
Lock m_sampleMutex;
GRefPtr<GstSample> m_sample;
// If this is true all processing should finish ASAP
// This is necessary because there could be a race between
// unlock() and render(), where unlock() wins, signals the
// Condition, then render() tries to render a frame although
// everything else isn't running anymore. This will lead
// to deadlocks because render() holds the stream lock.
//
// Protected by the sample mutex
bool m_unlocked { false };
};
struct _WebKitVideoSinkPrivate {
_WebKitVideoSinkPrivate()
{
gst_video_info_init(&info);
}
~_WebKitVideoSinkPrivate()
{
if (currentCaps)
gst_caps_unref(currentCaps);
}
VideoRenderRequestScheduler scheduler;
GstVideoInfo info;
GstCaps* currentCaps;
};
#define webkit_video_sink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE(WebKitVideoSink, webkit_video_sink, GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT(webkitVideoSinkDebug, "webkitsink", 0, "webkit video sink"));
static void webkit_video_sink_init(WebKitVideoSink* sink)
{
sink->priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate);
g_object_set(GST_BASE_SINK(sink), "enable-last-sample", FALSE, nullptr);
new (sink->priv) WebKitVideoSinkPrivate();
}
static void webkitVideoSinkRepaintRequested(WebKitVideoSink* sink, GstSample* sample)
{
g_signal_emit(sink, webkitVideoSinkSignals[REPAINT_REQUESTED], 0, sample);
}
static void webkitVideoSinkRepaintCancelled(WebKitVideoSink* sink)
{
g_signal_emit(sink, webkitVideoSinkSignals[REPAINT_CANCELLED], 0);
}
static GRefPtr<GstSample> webkitVideoSinkRequestRender(WebKitVideoSink* sink, GstBuffer* buffer)
{
WebKitVideoSinkPrivate* priv = sink->priv;
GRefPtr<GstSample> sample = adoptGRef(gst_sample_new(buffer, priv->currentCaps, nullptr, nullptr));
// The video info structure is valid only if the sink handled an allocation query.
GstVideoFormat format = GST_VIDEO_INFO_FORMAT(&priv->info);
if (format == GST_VIDEO_FORMAT_UNKNOWN)
return nullptr;
return sample;
}
static GstFlowReturn webkitVideoSinkRender(GstBaseSink* baseSink, GstBuffer* buffer)
{
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
return sink->priv->scheduler.requestRender(sink, buffer) ? GST_FLOW_OK : GST_FLOW_ERROR;
}
static void webkitVideoSinkFinalize(GObject* object)
{
WEBKIT_VIDEO_SINK(object)->priv->~WebKitVideoSinkPrivate();
G_OBJECT_CLASS(parent_class)->finalize(object);
}
static gboolean webkitVideoSinkUnlock(GstBaseSink* baseSink)
{
WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(baseSink)->priv;
priv->scheduler.stop();
webkitVideoSinkRepaintCancelled(WEBKIT_VIDEO_SINK(baseSink));
return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, unlock, (baseSink), TRUE);
}
static gboolean webkitVideoSinkUnlockStop(GstBaseSink* baseSink)
{
WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(baseSink)->priv;
priv->scheduler.start();
return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, unlock_stop, (baseSink), TRUE);
}
static gboolean webkitVideoSinkStop(GstBaseSink* baseSink)
{
WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(baseSink)->priv;
priv->scheduler.stop();
webkitVideoSinkRepaintCancelled(WEBKIT_VIDEO_SINK(baseSink));
if (priv->currentCaps) {
gst_caps_unref(priv->currentCaps);
priv->currentCaps = nullptr;
}
return TRUE;
}
static gboolean webkitVideoSinkStart(GstBaseSink* baseSink)
{
WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(baseSink)->priv;
priv->scheduler.start();
return TRUE;
}
static gboolean webkitVideoSinkSetCaps(GstBaseSink* baseSink, GstCaps* caps)
{
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
WebKitVideoSinkPrivate* priv = sink->priv;
GST_DEBUG_OBJECT(sink, "Current caps %" GST_PTR_FORMAT ", setting caps %" GST_PTR_FORMAT, priv->currentCaps, caps);
GstVideoInfo videoInfo;
gst_video_info_init(&videoInfo);
if (!gst_video_info_from_caps(&videoInfo, caps)) {
GST_ERROR_OBJECT(sink, "Invalid caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
priv->info = videoInfo;
gst_caps_replace(&priv->currentCaps, caps);
return TRUE;
}
static gboolean webkitVideoSinkProposeAllocation(GstBaseSink* baseSink, GstQuery* query)
{
GstCaps* caps;
gst_query_parse_allocation(query, &caps, nullptr);
if (!caps)
return FALSE;
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
if (!gst_video_info_from_caps(&sink->priv->info, caps))
return FALSE;
gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, nullptr);
gst_query_add_allocation_meta(query, GST_VIDEO_CROP_META_API_TYPE, nullptr);
gst_query_add_allocation_meta(query, GST_VIDEO_GL_TEXTURE_UPLOAD_META_API_TYPE, nullptr);
return TRUE;
}
static gboolean webkitVideoSinkEvent(GstBaseSink* baseSink, GstEvent* event)
{
switch (GST_EVENT_TYPE(event)) {
case GST_EVENT_FLUSH_START: {
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
sink->priv->scheduler.drain();
GST_DEBUG_OBJECT(sink, "Flush-start, releasing m_sample");
}
FALLTHROUGH;
default:
return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, event, (baseSink, event), TRUE);
}
}
static void webkit_video_sink_class_init(WebKitVideoSinkClass* klass)
{
GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
GstBaseSinkClass* baseSinkClass = GST_BASE_SINK_CLASS(klass);
GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&s_sinkTemplate));
gst_element_class_set_metadata(elementClass, "WebKit video sink", "Sink/Video", "Sends video data from a GStreamer pipeline to WebKit", "Igalia, Alp Toker <alp@atoker.com>");
g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate));
gobjectClass->finalize = webkitVideoSinkFinalize;
baseSinkClass->unlock = webkitVideoSinkUnlock;
baseSinkClass->unlock_stop = webkitVideoSinkUnlockStop;
baseSinkClass->render = webkitVideoSinkRender;
baseSinkClass->preroll = webkitVideoSinkRender;
baseSinkClass->stop = webkitVideoSinkStop;
baseSinkClass->start = webkitVideoSinkStart;
baseSinkClass->set_caps = webkitVideoSinkSetCaps;
baseSinkClass->propose_allocation = webkitVideoSinkProposeAllocation;
baseSinkClass->event = webkitVideoSinkEvent;
webkitVideoSinkSignals[REPAINT_REQUESTED] = g_signal_new("repaint-requested",
G_TYPE_FROM_CLASS(klass),
static_cast<GSignalFlags>(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
0, // Class offset
0, // Accumulator
0, // Accumulator data
g_cclosure_marshal_generic,
G_TYPE_NONE, // Return type
1, // Only one parameter
GST_TYPE_SAMPLE);
webkitVideoSinkSignals[REPAINT_CANCELLED] = g_signal_new("repaint-cancelled",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST,
0, // Class offset
nullptr, // Accumulator
nullptr, // Accumulator data
g_cclosure_marshal_generic,
G_TYPE_NONE, // Return type
0, // No parameters
G_TYPE_NONE);
}
GstElement* webkitVideoSinkNew()
{
return GST_ELEMENT(g_object_new(WEBKIT_TYPE_VIDEO_SINK, nullptr));
}
#endif // ENABLE(VIDEO) && USE(GSTREAMER)