blob: ebb90902d66842e2f5c045fcafe58144307c1295 [file] [log] [blame]
/*
* Copyright (C) 2010 Igalia S.L
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "ImageGStreamer.h"
#if ENABLE(VIDEO) && USE(GSTREAMER)
#include "GStreamerCommon.h"
#include <cairo.h>
#include <gst/gst.h>
#include <gst/video/gstvideometa.h>
namespace WebCore {
ImageGStreamer::ImageGStreamer(GstSample* sample)
{
GstCaps* caps = gst_sample_get_caps(sample);
GstVideoInfo videoInfo;
gst_video_info_init(&videoInfo);
if (!gst_video_info_from_caps(&videoInfo, caps))
return;
// The frame has to RGB so we can paint it.
ASSERT(GST_VIDEO_INFO_IS_RGB(&videoInfo));
GstBuffer* buffer = gst_sample_get_buffer(sample);
if (UNLIKELY(!GST_IS_BUFFER(buffer)))
return;
m_frameMapped = gst_video_frame_map(&m_videoFrame, &videoInfo, buffer, GST_MAP_READ);
if (!m_frameMapped)
return;
// The video buffer may have these formats in these cases:
// { BGRx, BGRA }: on little endian:
// - When GStreamer-gl is disabled (being AC enabled or not) as VideoSinkGStreamer is used.
// - When GStreamer-gl is enabled, but the caps used in the sink are not RGB and it's converted by the player to paint it.
// { xRGB, ARGB }: on big endian:
// - When GStreamer-gl is disabled (being AC enabled or not) as VideoSinkGStreamer is used.
// - When GStreamer-gl is enabled, but the caps used in the sink are not RGB and it's converted by the player to paint it.
// { RGBx, RGBA }
// - When GStreamer-gl is enabled and the caps used in the sink are RGBx/RGBA.
//
// Internally cairo uses BGRA for CAIRO_FORMAT_ARGB32 on little endian and ARGB on big endian, so both { BGRx, BGRA }
// and { xRGB, ARGB } can be passed directly to cairo. But for { RGBx, RGBA } we need to swap the R and B components.
// Also, GStreamer uses straight alpha while cairo requires it to be premultiplied, so if the format has alpha
// we need to premultiply the color components. So in these cases we need to create a modified copy of the original
// buffer.
m_hasAlpha = GST_VIDEO_INFO_HAS_ALPHA(&videoInfo);
bool componentSwapRequired = GST_VIDEO_FRAME_FORMAT(&m_videoFrame) == GST_VIDEO_FORMAT_RGBA || GST_VIDEO_FRAME_FORMAT(&m_videoFrame) == GST_VIDEO_FORMAT_RGBx;
unsigned char* bufferData = reinterpret_cast<unsigned char*>(GST_VIDEO_FRAME_PLANE_DATA(&m_videoFrame, 0));
int stride = GST_VIDEO_FRAME_PLANE_STRIDE(&m_videoFrame, 0);
int width = GST_VIDEO_FRAME_WIDTH(&m_videoFrame);
int height = GST_VIDEO_FRAME_HEIGHT(&m_videoFrame);
RefPtr<cairo_surface_t> surface;
if (m_hasAlpha || componentSwapRequired) {
uint8_t* surfaceData = static_cast<uint8_t*>(fastMalloc(height * stride));
uint8_t* surfacePixel = surfaceData;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// These store the source pixel components.
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t alpha;
// These store the component offset inside the pixel for the destination surface.
uint8_t redIndex;
uint8_t greenIndex;
uint8_t blueIndex;
uint8_t alphaIndex;
if (componentSwapRequired) {
// Source is RGBA or RGBx.
red = bufferData[0];
green = bufferData[1];
blue = bufferData[2];
alpha = bufferData[3];
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
// Destination is BGRA.
redIndex = 2;
greenIndex = 1;
blueIndex = 0;
alphaIndex = 3;
#else
// Destination is ARGB.
redIndex = 1;
greenIndex = 2;
blueIndex = 3;
alphaIndex = 0;
#endif
} else {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
// BGRA or BGRx both source and destination.
red = bufferData[2];
green = bufferData[1];
blue = bufferData[0];
alpha = bufferData[3];
redIndex = 2;
greenIndex = 1;
blueIndex = 0;
alphaIndex = 3;
#else
// ARGB ot xRGB both source and destination.
red = bufferData[1];
green = bufferData[2];
blue = bufferData[3];
alpha = bufferData[0];
redIndex = 1;
greenIndex = 2;
blueIndex = 3;
alphaIndex = 0;
#endif
}
if (m_hasAlpha) {
surfacePixel[redIndex] = red * alpha / 255;
surfacePixel[greenIndex] = green * alpha / 255;
surfacePixel[blueIndex] = blue * alpha / 255;
surfacePixel[alphaIndex] = alpha;
} else {
surfacePixel[redIndex] = red;
surfacePixel[greenIndex] = green;
surfacePixel[blueIndex] = blue;
surfacePixel[alphaIndex] = alpha;
}
bufferData += 4;
surfacePixel += 4;
}
}
surface = adoptRef(cairo_image_surface_create_for_data(surfaceData, CAIRO_FORMAT_ARGB32, width, height, stride));
static cairo_user_data_key_t s_surfaceDataKey;
cairo_surface_set_user_data(surface.get(), &s_surfaceDataKey, surfaceData, [](void* data) { fastFree(data); });
} else
surface = adoptRef(cairo_image_surface_create_for_data(bufferData, CAIRO_FORMAT_ARGB32, width, height, stride));
ASSERT(cairo_surface_status(surface.get()) == CAIRO_STATUS_SUCCESS);
m_image = BitmapImage::create(WTFMove(surface));
if (GstVideoCropMeta* cropMeta = gst_buffer_get_video_crop_meta(buffer))
setCropRect(FloatRect(cropMeta->x, cropMeta->y, cropMeta->width, cropMeta->height));
}
ImageGStreamer::~ImageGStreamer()
{
if (m_image)
m_image = nullptr;
// We keep the buffer memory mapped until the image is destroyed because the internal
// cairo_surface_t was created using cairo_image_surface_create_for_data().
if (m_frameMapped)
gst_video_frame_unmap(&m_videoFrame);
}
} // namespace WebCore
#endif // USE(GSTREAMER)