VideoSinkGStreamer.cpp [plain text]
#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/OwnPtr.h>
#include <wtf/gobject/GMainLoopSource.h>
#include <wtf/gobject/GMutexLocker.h>
using namespace WebCore;
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GST_CAPS_FORMAT "{ BGRx, BGRA }"
#else
#define GST_CAPS_FORMAT "{ xRGB, ARGB }"
#endif
#if GST_CHECK_VERSION(1, 1, 0)
#define GST_FEATURED_CAPS GST_VIDEO_CAPS_MAKE_WITH_FEATURES(GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META, GST_CAPS_FORMAT) ";"
#else
#define GST_FEATURED_CAPS
#endif
#define WEBKIT_VIDEO_SINK_PAD_CAPS GST_FEATURED_CAPS 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,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_CAPS
};
static guint webkitVideoSinkSignals[LAST_SIGNAL] = { 0, };
struct _WebKitVideoSinkPrivate {
GstBuffer* buffer;
GMainLoopSource timeoutSource;
GMutex* bufferMutex;
GCond* dataCondition;
GstVideoInfo info;
GstCaps* currentCaps;
bool unlocked;
};
#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);
new (sink->priv) WebKitVideoSinkPrivate();
sink->priv->dataCondition = new GCond;
g_cond_init(sink->priv->dataCondition);
sink->priv->bufferMutex = new GMutex;
g_mutex_init(sink->priv->bufferMutex);
gst_video_info_init(&sink->priv->info);
}
static void webkitVideoSinkTimeoutCallback(WebKitVideoSink* sink)
{
WebKitVideoSinkPrivate* priv = sink->priv;
GMutexLocker lock(priv->bufferMutex);
GstBuffer* buffer = priv->buffer;
priv->buffer = 0;
if (!buffer || priv->unlocked || UNLIKELY(!GST_IS_BUFFER(buffer))) {
g_cond_signal(priv->dataCondition);
return;
}
g_signal_emit(sink, webkitVideoSinkSignals[REPAINT_REQUESTED], 0, buffer);
gst_buffer_unref(buffer);
g_cond_signal(priv->dataCondition);
}
static GstFlowReturn webkitVideoSinkRender(GstBaseSink* baseSink, GstBuffer* buffer)
{
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
WebKitVideoSinkPrivate* priv = sink->priv;
GMutexLocker lock(priv->bufferMutex);
if (priv->unlocked)
return GST_FLOW_OK;
priv->buffer = gst_buffer_ref(buffer);
GstVideoFormat format = GST_VIDEO_INFO_FORMAT(&priv->info);
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
gst_buffer_unref(buffer);
return GST_FLOW_ERROR;
}
#if !(USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS))
if (format == GST_VIDEO_FORMAT_ARGB || format == GST_VIDEO_FORMAT_BGRA) {
GstBuffer* newBuffer = WebCore::createGstBuffer(buffer);
if (UNLIKELY(!newBuffer)) {
gst_buffer_unref(buffer);
return GST_FLOW_ERROR;
}
GstVideoFrame sourceFrame;
GstVideoFrame destinationFrame;
if (!gst_video_frame_map(&sourceFrame, &priv->info, buffer, GST_MAP_READ)) {
gst_buffer_unref(buffer);
gst_buffer_unref(newBuffer);
return GST_FLOW_ERROR;
}
if (!gst_video_frame_map(&destinationFrame, &priv->info, newBuffer, GST_MAP_WRITE)) {
gst_video_frame_unmap(&sourceFrame);
gst_buffer_unref(buffer);
gst_buffer_unref(newBuffer);
return GST_FLOW_ERROR;
}
const guint8* source = static_cast<guint8*>(GST_VIDEO_FRAME_PLANE_DATA(&sourceFrame, 0));
guint8* destination = static_cast<guint8*>(GST_VIDEO_FRAME_PLANE_DATA(&destinationFrame, 0));
for (int x = 0; x < GST_VIDEO_FRAME_HEIGHT(&sourceFrame); x++) {
for (int y = 0; y < GST_VIDEO_FRAME_WIDTH(&sourceFrame); y++) {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
unsigned short alpha = source[3];
destination[0] = (source[0] * alpha + 128) / 255;
destination[1] = (source[1] * alpha + 128) / 255;
destination[2] = (source[2] * alpha + 128) / 255;
destination[3] = alpha;
#else
unsigned short alpha = source[0];
destination[0] = alpha;
destination[1] = (source[1] * alpha + 128) / 255;
destination[2] = (source[2] * alpha + 128) / 255;
destination[3] = (source[3] * alpha + 128) / 255;
#endif
source += 4;
destination += 4;
}
}
gst_video_frame_unmap(&sourceFrame);
gst_video_frame_unmap(&destinationFrame);
gst_buffer_unref(buffer);
buffer = priv->buffer = newBuffer;
}
#endif
gst_object_ref(sink);
priv->timeoutSource.schedule("[WebKit] webkitVideoSinkTimeoutCallback", std::function<void()>(std::bind(webkitVideoSinkTimeoutCallback, sink)), G_PRIORITY_DEFAULT,
[sink] { gst_object_unref(sink); });
g_cond_wait(priv->dataCondition, priv->bufferMutex);
return GST_FLOW_OK;
}
static void webkitVideoSinkDispose(GObject* object)
{
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
WebKitVideoSinkPrivate* priv = sink->priv;
if (priv->dataCondition) {
g_cond_clear(priv->dataCondition);
delete priv->dataCondition;
priv->dataCondition = 0;
}
if (priv->bufferMutex) {
g_mutex_clear(priv->bufferMutex);
delete priv->bufferMutex;
priv->bufferMutex = 0;
}
G_OBJECT_CLASS(parent_class)->dispose(object);
}
static void webkitVideoSinkFinalize(GObject* object)
{
WEBKIT_VIDEO_SINK(object)->priv->~WebKitVideoSinkPrivate();
G_OBJECT_CLASS(parent_class)->finalize(object);
}
static void webkitVideoSinkGetProperty(GObject* object, guint propertyId, GValue* value, GParamSpec* parameterSpec)
{
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
WebKitVideoSinkPrivate* priv = sink->priv;
switch (propertyId) {
case PROP_CAPS: {
GstCaps* caps = priv->currentCaps;
if (caps)
gst_caps_ref(caps);
g_value_take_boxed(value, caps);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, parameterSpec);
}
}
static void unlockBufferMutex(WebKitVideoSinkPrivate* priv)
{
GMutexLocker lock(priv->bufferMutex);
if (priv->buffer) {
gst_buffer_unref(priv->buffer);
priv->buffer = 0;
}
priv->unlocked = true;
g_cond_signal(priv->dataCondition);
}
static gboolean webkitVideoSinkUnlock(GstBaseSink* baseSink)
{
WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
unlockBufferMutex(sink->priv);
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;
{
GMutexLocker lock(priv->bufferMutex);
priv->unlocked = false;
}
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;
unlockBufferMutex(priv);
if (priv->currentCaps) {
gst_caps_unref(priv->currentCaps);
priv->currentCaps = 0;
}
return TRUE;
}
static gboolean webkitVideoSinkStart(GstBaseSink* baseSink)
{
WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(baseSink)->priv;
GMutexLocker lock(priv->bufferMutex);
priv->unlocked = false;
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, 0);
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, 0);
gst_query_add_allocation_meta(query, GST_VIDEO_CROP_META_API_TYPE, 0);
#if GST_CHECK_VERSION(1, 1, 0)
gst_query_add_allocation_meta(query, GST_VIDEO_GL_TEXTURE_UPLOAD_META_API_TYPE, 0);
#endif
return 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->dispose = webkitVideoSinkDispose;
gobjectClass->finalize = webkitVideoSinkFinalize;
gobjectClass->get_property = webkitVideoSinkGetProperty;
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;
g_object_class_install_property(gobjectClass, PROP_CAPS,
g_param_spec_boxed("current-caps", "Current-Caps", "Current caps", GST_TYPE_CAPS, G_PARAM_READABLE));
webkitVideoSinkSignals[REPAINT_REQUESTED] = g_signal_new("repaint-requested",
G_TYPE_FROM_CLASS(klass),
static_cast<GSignalFlags>(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
0, 0, 0, g_cclosure_marshal_generic,
G_TYPE_NONE, 1, GST_TYPE_BUFFER);
}
GstElement* webkitVideoSinkNew()
{
return GST_ELEMENT(g_object_new(WEBKIT_TYPE_VIDEO_SINK, 0));
}
#endif // ENABLE(VIDEO) && USE(GSTREAMER)