MediaPlayerPrivateGStreamerBase.cpp [plain text]
#include "config.h"
#include "MediaPlayerPrivateGStreamerBase.h"
#if ENABLE(VIDEO) && USE(GSTREAMER)
#include "GStreamerUtilities.h"
#include "GraphicsContext.h"
#include "GraphicsTypes.h"
#include "ImageGStreamer.h"
#include "ImageOrientation.h"
#include "IntRect.h"
#include "Logging.h"
#include "MediaPlayer.h"
#include "NotImplemented.h"
#include "VideoSinkGStreamer.h"
#include "WebKitWebSourceGStreamer.h"
#include <wtf/glib/GMutexLocker.h>
#include <wtf/glib/GUniquePtr.h>
#include <wtf/text/AtomicString.h>
#include <wtf/text/CString.h>
#include <wtf/MathExtras.h>
#include <gst/audio/streamvolume.h>
#include <gst/video/gstvideometa.h>
#if USE(GSTREAMER_GL)
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GST_GL_CAPS_FORMAT "{ BGRx, BGRA }"
#define TEXTURE_MAPPER_COLOR_CONVERT_FLAG TextureMapperGL::ShouldConvertTextureBGRAToRGBA
#define TEXTURE_COPIER_COLOR_CONVERT_FLAG VideoTextureCopierGStreamer::ColorConversion::ConvertBGRAToRGBA
#else
#define GST_GL_CAPS_FORMAT "{ xRGB, ARGB }"
#define TEXTURE_MAPPER_COLOR_CONVERT_FLAG TextureMapperGL::ShouldConvertTextureARGBToRGBA
#define TEXTURE_COPIER_COLOR_CONVERT_FLAG VideoTextureCopierGStreamer::ColorConversion::ConvertARGBToRGBA
#endif
#include <gst/app/gstappsink.h>
#if USE(LIBEPOXY)
#include <epoxy/gl.h>
#endif
#define GST_USE_UNSTABLE_API
#include <gst/gl/gl.h>
#undef GST_USE_UNSTABLE_API
#include "GLContext.h"
#if USE(GLX)
#include "GLContextGLX.h"
#include <gst/gl/x11/gstgldisplay_x11.h>
#endif
#if USE(EGL)
#include "GLContextEGL.h"
#include <gst/gl/egl/gstgldisplay_egl.h>
#endif
#if PLATFORM(X11)
#include "PlatformDisplayX11.h"
#endif
#if PLATFORM(WAYLAND)
#include "PlatformDisplayWayland.h"
#elif PLATFORM(WPE)
#include "PlatformDisplayWPE.h"
#endif
#if PLATFORM(X11) && GST_GL_HAVE_PLATFORM_EGL
#undef None
#endif // PLATFORM(X11) && GST_GL_HAVE_PLATFORM_EGL
#include "VideoTextureCopierGStreamer.h"
#endif // USE(GSTREAMER_GL)
#if USE(TEXTURE_MAPPER_GL)
#include "BitmapTextureGL.h"
#include "BitmapTexturePool.h"
#include "TextureMapperGL.h"
#include "TextureMapperPlatformLayerBuffer.h"
#if USE(CAIRO) && ENABLE(ACCELERATED_2D_CANVAS)
#include <cairo-gl.h>
#endif
#endif // USE(TEXTURE_MAPPER_GL)
#if ENABLE(ENCRYPTED_MEDIA)
#include "SharedBuffer.h"
#include "WebKitClearKeyDecryptorGStreamer.h"
#endif
GST_DEBUG_CATEGORY(webkit_media_player_debug);
#define GST_CAT_DEFAULT webkit_media_player_debug
using namespace std;
namespace WebCore {
void registerWebKitGStreamerElements()
{
#if ENABLE(ENCRYPTED_MEDIA)
if (!webkitGstCheckVersion(1, 6, 1))
return;
GRefPtr<GstElementFactory> clearKeyDecryptorFactory = gst_element_factory_find("webkitclearkey");
if (!clearKeyDecryptorFactory)
gst_element_register(nullptr, "webkitclearkey", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_MEDIA_CK_DECRYPT);
#endif
}
bool MediaPlayerPrivateGStreamerBase::initializeGStreamerAndRegisterWebKitElements()
{
if (!initializeGStreamer())
return false;
registerWebKitGStreamerElements();
GRefPtr<GstElementFactory> srcFactory = adoptGRef(gst_element_factory_find("webkitwebsrc"));
if (!srcFactory) {
GST_DEBUG_CATEGORY_INIT(webkit_media_player_debug, "webkitmediaplayer", 0, "WebKit media player");
gst_element_register(0, "webkitwebsrc", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_WEB_SRC);
}
return true;
}
static int greatestCommonDivisor(int a, int b)
{
while (b) {
int temp = a;
a = b;
b = temp % b;
}
return ABS(a);
}
#if USE(TEXTURE_MAPPER_GL)
static inline TextureMapperGL::Flags texMapFlagFromOrientation(const ImageOrientation& orientation)
{
switch (orientation) {
case DefaultImageOrientation:
return 0;
case OriginRightTop:
return TextureMapperGL::ShouldRotateTexture90;
case OriginBottomRight:
return TextureMapperGL::ShouldRotateTexture180;
case OriginLeftBottom:
return TextureMapperGL::ShouldRotateTexture270;
default:
ASSERT_NOT_REACHED();
}
return 0;
}
#endif
#if USE(GSTREAMER_GL)
class GstVideoFrameHolder : public TextureMapperPlatformLayerBuffer::UnmanagedBufferDataHolder {
public:
explicit GstVideoFrameHolder(GstSample* sample, TextureMapperGL::Flags flags)
{
GstVideoInfo videoInfo;
if (UNLIKELY(!getSampleVideoInfo(sample, videoInfo)))
return;
m_size = IntSize(GST_VIDEO_INFO_WIDTH(&videoInfo), GST_VIDEO_INFO_HEIGHT(&videoInfo));
m_flags = flags | (GST_VIDEO_INFO_HAS_ALPHA(&videoInfo) ? TextureMapperGL::ShouldBlend : 0) | TEXTURE_MAPPER_COLOR_CONVERT_FLAG;
GstBuffer* buffer = gst_sample_get_buffer(sample);
if (UNLIKELY(!gst_video_frame_map(&m_videoFrame, &videoInfo, buffer, static_cast<GstMapFlags>(GST_MAP_READ | GST_MAP_GL))))
return;
m_textureID = *reinterpret_cast<GLuint*>(m_videoFrame.data[0]);
m_isValid = true;
}
virtual ~GstVideoFrameHolder()
{
if (UNLIKELY(!m_isValid))
return;
gst_video_frame_unmap(&m_videoFrame);
}
const IntSize& size() const { return m_size; }
TextureMapperGL::Flags flags() const { return m_flags; }
GLuint textureID() const { return m_textureID; }
bool isValid() const { return m_isValid; }
private:
GstVideoFrame m_videoFrame;
IntSize m_size;
TextureMapperGL::Flags m_flags;
GLuint m_textureID;
bool m_isValid { false };
};
#endif // USE(GSTREAMER_GL)
MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase(MediaPlayer* player)
: m_notifier(MainThreadNotifier<MainThreadNotification>::create())
, m_player(player)
, m_fpsSink(nullptr)
, m_readyState(MediaPlayer::HaveNothing)
, m_networkState(MediaPlayer::Empty)
, m_drawTimer(RunLoop::main(), this, &MediaPlayerPrivateGStreamerBase::repaint)
#if USE(TEXTURE_MAPPER_GL)
, m_platformLayerProxy(adoptRef(new TextureMapperPlatformLayerProxy()))
#endif
{
g_mutex_init(&m_sampleMutex);
}
MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase()
{
m_notifier->invalidate();
cancelRepaint();
if (m_videoSink) {
g_signal_handlers_disconnect_matched(m_videoSink.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
#if USE(GSTREAMER_GL)
if (GST_IS_BIN(m_videoSink.get())) {
GRefPtr<GstElement> appsink = adoptGRef(gst_bin_get_by_name(GST_BIN_CAST(m_videoSink.get()), "webkit-gl-video-sink"));
g_signal_handlers_disconnect_by_data(appsink.get(), this);
}
#endif
}
g_mutex_clear(&m_sampleMutex);
m_player = nullptr;
if (m_volumeElement)
g_signal_handlers_disconnect_matched(m_volumeElement.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
if (m_pipeline)
gst_element_set_state(m_pipeline.get(), GST_STATE_NULL);
}
void MediaPlayerPrivateGStreamerBase::setPipeline(GstElement* pipeline)
{
m_pipeline = pipeline;
}
bool MediaPlayerPrivateGStreamerBase::handleSyncMessage(GstMessage* message)
{
UNUSED_PARAM(message);
if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_NEED_CONTEXT)
return false;
const gchar* contextType;
gst_message_parse_context_type(message, &contextType);
#if USE(GSTREAMER_GL)
GRefPtr<GstContext> elementContext = adoptGRef(requestGLContext(contextType, this));
if (elementContext) {
gst_element_set_context(GST_ELEMENT(message->src), elementContext.get());
return true;
}
#endif // USE(GSTREAMER_GL)
return false;
}
#if USE(GSTREAMER_GL)
GstContext* MediaPlayerPrivateGStreamerBase::requestGLContext(const gchar* contextType, MediaPlayerPrivateGStreamerBase* player)
{
if (!player->ensureGstGLContext())
return nullptr;
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, player->gstGLDisplay());
return 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, 11, 0)
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, player->gstGLContext(), nullptr);
#else
gst_structure_set(structure, "context", GST_GL_TYPE_CONTEXT, player->gstGLContext(), nullptr);
#endif
return appContext;
}
return nullptr;
}
bool MediaPlayerPrivateGStreamerBase::ensureGstGLContext()
{
if (m_glContext)
return true;
auto& sharedDisplay = PlatformDisplay::sharedDisplayForCompositing();
if (!m_glDisplay) {
#if PLATFORM(X11)
#if USE(GLX)
if (is<PlatformDisplayX11>(sharedDisplay))
m_glDisplay = GST_GL_DISPLAY(gst_gl_display_x11_new_with_display(downcast<PlatformDisplayX11>(sharedDisplay).native()));
#elif USE(EGL)
if (is<PlatformDisplayX11>(sharedDisplay))
m_glDisplay = GST_GL_DISPLAY(gst_gl_display_egl_new_with_egl_display(downcast<PlatformDisplayX11>(sharedDisplay).eglDisplay()));
#endif
#endif
#if PLATFORM(WAYLAND)
if (is<PlatformDisplayWayland>(sharedDisplay))
m_glDisplay = GST_GL_DISPLAY(gst_gl_display_egl_new_with_egl_display(downcast<PlatformDisplayWayland>(sharedDisplay).eglDisplay()));
#endif
#if PLATFORM(WPE)
ASSERT(is<PlatformDisplayWPE>(sharedDisplay));
m_glDisplay = GST_GL_DISPLAY(gst_gl_display_egl_new_with_egl_display(downcast<PlatformDisplayWPE>(sharedDisplay).eglDisplay()));
#endif
ASSERT(m_glDisplay);
}
GLContext* webkitContext = sharedDisplay.sharingGLContext();
GstGLPlatform glPlatform = webkitContext->isEGLContext() ? GST_GL_PLATFORM_EGL : GST_GL_PLATFORM_GLX;
#if USE(OPENGL_ES_2)
GstGLAPI glAPI = GST_GL_API_GLES2;
#elif USE(OPENGL)
GstGLAPI glAPI = GST_GL_API_OPENGL;
#else
ASSERT_NOT_REACHED();
#endif
PlatformGraphicsContext3D contextHandle = webkitContext->platformContext();
if (!contextHandle)
return false;
m_glContext = gst_gl_context_new_wrapped(m_glDisplay.get(), reinterpret_cast<guintptr>(contextHandle), glPlatform, glAPI);
return true;
}
#endif // USE(GSTREAMER_GL)
FloatSize MediaPlayerPrivateGStreamerBase::naturalSize() const
{
if (!hasVideo())
return FloatSize();
if (!m_videoSize.isEmpty())
return m_videoSize;
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
if (!GST_IS_SAMPLE(m_sample.get()))
return FloatSize();
GstCaps* caps = gst_sample_get_caps(m_sample.get());
if (!caps)
return FloatSize();
int pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride;
IntSize originalSize;
GstVideoFormat format;
if (!getVideoSizeAndFormatFromCaps(caps, originalSize, format, pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride))
return FloatSize();
#if USE(TEXTURE_MAPPER_GL)
if (m_renderingCanBeAccelerated) {
if (m_videoSourceOrientation.usesWidthAsHeight())
originalSize = originalSize.transposedSize();
}
#endif
GST_DEBUG("Original video size: %dx%d", originalSize.width(), originalSize.height());
GST_DEBUG("Pixel aspect ratio: %d/%d", pixelAspectRatioNumerator, pixelAspectRatioDenominator);
int displayWidth = originalSize.width() * pixelAspectRatioNumerator;
int displayHeight = originalSize.height() * pixelAspectRatioDenominator;
int displayAspectRatioGCD = greatestCommonDivisor(displayWidth, displayHeight);
displayWidth /= displayAspectRatioGCD;
displayHeight /= displayAspectRatioGCD;
guint64 width = 0, height = 0;
if (!(originalSize.height() % displayHeight)) {
GST_DEBUG("Keeping video original height");
width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
height = static_cast<guint64>(originalSize.height());
} else if (!(originalSize.width() % displayWidth)) {
GST_DEBUG("Keeping video original width");
height = gst_util_uint64_scale_int(originalSize.width(), displayHeight, displayWidth);
width = static_cast<guint64>(originalSize.width());
} else {
GST_DEBUG("Approximating while keeping original video height");
width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
height = static_cast<guint64>(originalSize.height());
}
GST_DEBUG("Natural size: %" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT, width, height);
m_videoSize = FloatSize(static_cast<int>(width), static_cast<int>(height));
return m_videoSize;
}
void MediaPlayerPrivateGStreamerBase::setVolume(float volume)
{
if (!m_volumeElement)
return;
GST_DEBUG("Setting volume: %f", volume);
gst_stream_volume_set_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_CUBIC, static_cast<double>(volume));
}
float MediaPlayerPrivateGStreamerBase::volume() const
{
if (!m_volumeElement)
return 0;
return gst_stream_volume_get_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_CUBIC);
}
void MediaPlayerPrivateGStreamerBase::notifyPlayerOfVolumeChange()
{
if (!m_player || !m_volumeElement)
return;
double volume;
volume = gst_stream_volume_get_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_CUBIC);
volume = CLAMP(volume, 0.0, 1.0);
m_player->volumeChanged(static_cast<float>(volume));
}
void MediaPlayerPrivateGStreamerBase::volumeChangedCallback(MediaPlayerPrivateGStreamerBase* player)
{
GST_DEBUG("Volume changed to: %f", player->volume());
player->m_notifier->notify(MainThreadNotification::VolumeChanged, [player] { player->notifyPlayerOfVolumeChange(); });
}
MediaPlayer::NetworkState MediaPlayerPrivateGStreamerBase::networkState() const
{
return m_networkState;
}
MediaPlayer::ReadyState MediaPlayerPrivateGStreamerBase::readyState() const
{
return m_readyState;
}
void MediaPlayerPrivateGStreamerBase::sizeChanged()
{
notImplemented();
}
void MediaPlayerPrivateGStreamerBase::setMuted(bool muted)
{
if (!m_volumeElement)
return;
g_object_set(m_volumeElement.get(), "mute", muted, nullptr);
}
bool MediaPlayerPrivateGStreamerBase::muted() const
{
if (!m_volumeElement)
return false;
gboolean muted;
g_object_get(m_volumeElement.get(), "mute", &muted, nullptr);
return muted;
}
void MediaPlayerPrivateGStreamerBase::notifyPlayerOfMute()
{
if (!m_player || !m_volumeElement)
return;
gboolean muted;
g_object_get(m_volumeElement.get(), "mute", &muted, nullptr);
m_player->muteChanged(static_cast<bool>(muted));
}
void MediaPlayerPrivateGStreamerBase::muteChangedCallback(MediaPlayerPrivateGStreamerBase* player)
{
player->m_notifier->notify(MainThreadNotification::MuteChanged, [player] { player->notifyPlayerOfMute(); });
}
void MediaPlayerPrivateGStreamerBase::acceleratedRenderingStateChanged()
{
m_renderingCanBeAccelerated = m_player && m_player->client().mediaPlayerAcceleratedCompositingEnabled();
}
#if USE(TEXTURE_MAPPER_GL)
void MediaPlayerPrivateGStreamerBase::updateTexture(BitmapTextureGL& texture, GstVideoInfo& videoInfo)
{
GstBuffer* buffer = gst_sample_get_buffer(m_sample.get());
GstVideoGLTextureUploadMeta* meta;
if ((meta = gst_buffer_get_video_gl_texture_upload_meta(buffer))) {
if (meta->n_textures == 1) { guint ids[4] = { texture.id(), 0, 0, 0 };
if (gst_video_gl_texture_upload_meta_upload(meta, ids))
return;
}
}
ASSERT(GST_VIDEO_INFO_N_PLANES(&videoInfo) == 1);
GstVideoFrame videoFrame;
if (!gst_video_frame_map(&videoFrame, &videoInfo, buffer, GST_MAP_READ))
return;
int stride = GST_VIDEO_FRAME_PLANE_STRIDE(&videoFrame, 0);
const void* srcData = GST_VIDEO_FRAME_PLANE_DATA(&videoFrame, 0);
texture.updateContents(srcData, WebCore::IntRect(0, 0, GST_VIDEO_INFO_WIDTH(&videoInfo), GST_VIDEO_INFO_HEIGHT(&videoInfo)), WebCore::IntPoint(0, 0), stride, BitmapTexture::UpdateCannotModifyOriginalImageData);
gst_video_frame_unmap(&videoFrame);
}
#endif
#if USE(TEXTURE_MAPPER_GL)
void MediaPlayerPrivateGStreamerBase::pushTextureToCompositor()
{
#if !USE(GSTREAMER_GL)
class ConditionNotifier {
public:
ConditionNotifier(Lock& lock, Condition& condition)
: m_locker(lock), m_condition(condition)
{
}
~ConditionNotifier()
{
m_condition.notifyOne();
}
private:
LockHolder m_locker;
Condition& m_condition;
};
ConditionNotifier notifier(m_drawMutex, m_drawCondition);
#endif
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
if (!GST_IS_SAMPLE(m_sample.get()))
return;
LockHolder holder(m_platformLayerProxy->lock());
if (!m_platformLayerProxy->isActive())
return;
#if USE(GSTREAMER_GL)
std::unique_ptr<GstVideoFrameHolder> frameHolder = std::make_unique<GstVideoFrameHolder>(m_sample.get(), texMapFlagFromOrientation(m_videoSourceOrientation));
if (UNLIKELY(!frameHolder->isValid()))
return;
std::unique_ptr<TextureMapperPlatformLayerBuffer> layerBuffer = std::make_unique<TextureMapperPlatformLayerBuffer>(frameHolder->textureID(), frameHolder->size(), frameHolder->flags(), GraphicsContext3D::RGBA);
layerBuffer->setUnmanagedBufferDataHolder(WTFMove(frameHolder));
m_platformLayerProxy->pushNextBuffer(WTFMove(layerBuffer));
#else
GstVideoInfo videoInfo;
if (UNLIKELY(!getSampleVideoInfo(m_sample.get(), videoInfo)))
return;
IntSize size = IntSize(GST_VIDEO_INFO_WIDTH(&videoInfo), GST_VIDEO_INFO_HEIGHT(&videoInfo));
std::unique_ptr<TextureMapperPlatformLayerBuffer> buffer = m_platformLayerProxy->getAvailableBuffer(size, GraphicsContext3D::DONT_CARE);
if (UNLIKELY(!buffer)) {
if (UNLIKELY(!m_context3D))
m_context3D = GraphicsContext3D::create(GraphicsContext3DAttributes(), nullptr, GraphicsContext3D::RenderToCurrentGLContext);
auto texture = BitmapTextureGL::create(*m_context3D);
texture->reset(size, GST_VIDEO_INFO_HAS_ALPHA(&videoInfo) ? BitmapTexture::SupportsAlpha : BitmapTexture::NoFlag);
buffer = std::make_unique<TextureMapperPlatformLayerBuffer>(WTFMove(texture));
}
updateTexture(buffer->textureGL(), videoInfo);
buffer->setExtraFlags(texMapFlagFromOrientation(m_videoSourceOrientation) | (GST_VIDEO_INFO_HAS_ALPHA(&videoInfo) ? TextureMapperGL::ShouldBlend : 0));
m_platformLayerProxy->pushNextBuffer(WTFMove(buffer));
#endif // USE(GSTREAMER_GL)
}
#endif // USE(TEXTURE_MAPPER_GL)
void MediaPlayerPrivateGStreamerBase::repaint()
{
ASSERT(m_sample);
ASSERT(isMainThread());
m_player->repaint();
#if USE(GSTREAMER_GL)
bool shouldNotifyDraw = !m_renderingCanBeAccelerated;
#else
bool shouldNotifyDraw = true;
#endif
if (shouldNotifyDraw) {
LockHolder lock(m_drawMutex);
m_drawCondition.notifyOne();
}
}
void MediaPlayerPrivateGStreamerBase::triggerRepaint(GstSample* sample)
{
bool triggerResize;
{
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
triggerResize = !m_sample;
m_sample = sample;
}
if (triggerResize) {
GST_DEBUG("First sample reached the sink, triggering video dimensions update");
m_notifier->notify(MainThreadNotification::SizeChanged, [this] { m_player->sizeChanged(); });
}
if (!m_renderingCanBeAccelerated) {
LockHolder locker(m_drawMutex);
m_drawTimer.startOneShot(0_s);
m_drawCondition.wait(m_drawMutex);
return;
}
#if USE(TEXTURE_MAPPER_GL)
#if USE(GSTREAMER_GL)
pushTextureToCompositor();
#else
{
LockHolder lock(m_drawMutex);
if (!m_platformLayerProxy->scheduleUpdateOnCompositorThread([this] { this->pushTextureToCompositor(); }))
return;
m_drawCondition.wait(m_drawMutex);
}
#endif
#endif // USE(TEXTURE_MAPPER_GL)
}
void MediaPlayerPrivateGStreamerBase::repaintCallback(MediaPlayerPrivateGStreamerBase* player, GstSample* sample)
{
player->triggerRepaint(sample);
}
void MediaPlayerPrivateGStreamerBase::cancelRepaint()
{
#if USE(GSTREAMER_GL)
bool shouldCancelRepaint = !m_renderingCanBeAccelerated;
#else
bool shouldCancelRepaint = true;
#endif
if (shouldCancelRepaint) {
m_drawTimer.stop();
LockHolder locker(m_drawMutex);
m_drawCondition.notifyOne();
}
}
void MediaPlayerPrivateGStreamerBase::repaintCancelledCallback(MediaPlayerPrivateGStreamerBase* player)
{
player->cancelRepaint();
}
#if USE(GSTREAMER_GL)
GstFlowReturn MediaPlayerPrivateGStreamerBase::newSampleCallback(GstElement* sink, MediaPlayerPrivateGStreamerBase* player)
{
GRefPtr<GstSample> sample = adoptGRef(gst_app_sink_pull_sample(GST_APP_SINK(sink)));
player->triggerRepaint(sample.get());
return GST_FLOW_OK;
}
GstFlowReturn MediaPlayerPrivateGStreamerBase::newPrerollCallback(GstElement* sink, MediaPlayerPrivateGStreamerBase* player)
{
GRefPtr<GstSample> sample = adoptGRef(gst_app_sink_pull_preroll(GST_APP_SINK(sink)));
player->triggerRepaint(sample.get());
return GST_FLOW_OK;
}
void MediaPlayerPrivateGStreamerBase::flushCurrentBuffer()
{
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
m_sample.clear();
{
LockHolder locker(m_platformLayerProxy->lock());
if (m_platformLayerProxy->isActive())
m_platformLayerProxy->dropCurrentBufferWhilePreservingTexture();
}
}
#endif
void MediaPlayerPrivateGStreamerBase::setSize(const IntSize& size)
{
m_size = size;
}
void MediaPlayerPrivateGStreamerBase::paint(GraphicsContext& context, const FloatRect& rect)
{
if (context.paintingDisabled())
return;
if (!m_player->visible())
return;
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
if (!GST_IS_SAMPLE(m_sample.get()))
return;
ImagePaintingOptions paintingOptions(CompositeCopy);
if (m_renderingCanBeAccelerated)
paintingOptions.m_orientationDescription.setImageOrientationEnum(m_videoSourceOrientation);
RefPtr<ImageGStreamer> gstImage = ImageGStreamer::createImage(m_sample.get());
if (!gstImage)
return;
if (Image* image = reinterpret_cast<Image*>(gstImage->image()))
context.drawImage(*image, rect, gstImage->rect(), paintingOptions);
}
#if USE(GSTREAMER_GL)
bool MediaPlayerPrivateGStreamerBase::copyVideoTextureToPlatformTexture(GraphicsContext3D* context, Platform3DObject outputTexture, GC3Denum outputTarget, GC3Dint level, GC3Denum internalFormat, GC3Denum format, GC3Denum type, bool premultiplyAlpha, bool flipY)
{
UNUSED_PARAM(context);
if (m_usingFallbackVideoSink)
return false;
if (premultiplyAlpha)
return false;
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
GstVideoInfo videoInfo;
if (!getSampleVideoInfo(m_sample.get(), videoInfo))
return false;
GstBuffer* buffer = gst_sample_get_buffer(m_sample.get());
GstVideoFrame videoFrame;
if (!gst_video_frame_map(&videoFrame, &videoInfo, buffer, static_cast<GstMapFlags>(GST_MAP_READ | GST_MAP_GL)))
return false;
IntSize size(GST_VIDEO_INFO_WIDTH(&videoInfo), GST_VIDEO_INFO_HEIGHT(&videoInfo));
if (m_videoSourceOrientation.usesWidthAsHeight())
size = size.transposedSize();
unsigned textureID = *reinterpret_cast<unsigned*>(videoFrame.data[0]);
if (!m_videoTextureCopier)
m_videoTextureCopier = std::make_unique<VideoTextureCopierGStreamer>(TEXTURE_COPIER_COLOR_CONVERT_FLAG);
bool copied = m_videoTextureCopier->copyVideoTextureToPlatformTexture(textureID, size, outputTexture, outputTarget, level, internalFormat, format, type, flipY, m_videoSourceOrientation);
gst_video_frame_unmap(&videoFrame);
return copied;
}
NativeImagePtr MediaPlayerPrivateGStreamerBase::nativeImageForCurrentTime()
{
#if USE(CAIRO) && ENABLE(ACCELERATED_2D_CANVAS)
if (m_usingFallbackVideoSink)
return nullptr;
WTF::GMutexLocker<GMutex> lock(m_sampleMutex);
GstVideoInfo videoInfo;
if (!getSampleVideoInfo(m_sample.get(), videoInfo))
return nullptr;
GstBuffer* buffer = gst_sample_get_buffer(m_sample.get());
GstVideoFrame videoFrame;
if (!gst_video_frame_map(&videoFrame, &videoInfo, buffer, static_cast<GstMapFlags>(GST_MAP_READ | GST_MAP_GL)))
return nullptr;
IntSize size(GST_VIDEO_INFO_WIDTH(&videoInfo), GST_VIDEO_INFO_HEIGHT(&videoInfo));
if (m_videoSourceOrientation.usesWidthAsHeight())
size = size.transposedSize();
GLContext* context = PlatformDisplay::sharedDisplayForCompositing().sharingGLContext();
context->makeContextCurrent();
if (!m_videoTextureCopier)
m_videoTextureCopier = std::make_unique<VideoTextureCopierGStreamer>(TEXTURE_COPIER_COLOR_CONVERT_FLAG);
unsigned textureID = *reinterpret_cast<unsigned*>(videoFrame.data[0]);
bool copied = m_videoTextureCopier->copyVideoTextureToPlatformTexture(textureID, size, 0, GraphicsContext3D::TEXTURE_2D, 0, GraphicsContext3D::RGBA, GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, false, m_videoSourceOrientation);
gst_video_frame_unmap(&videoFrame);
if (!copied)
return nullptr;
return adoptRef(cairo_gl_surface_create_for_texture(context->cairoDevice(), CAIRO_CONTENT_COLOR_ALPHA, m_videoTextureCopier->resultTexture(), size.width(), size.height()));
#else
return nullptr;
#endif
}
#endif // USE(GSTREAMER_GL)
void MediaPlayerPrivateGStreamerBase::setVideoSourceOrientation(const ImageOrientation& orientation)
{
if (m_videoSourceOrientation == orientation)
return;
m_videoSourceOrientation = orientation;
}
bool MediaPlayerPrivateGStreamerBase::supportsFullscreen() const
{
return true;
}
PlatformMedia MediaPlayerPrivateGStreamerBase::platformMedia() const
{
return NoPlatformMedia;
}
MediaPlayer::MovieLoadType MediaPlayerPrivateGStreamerBase::movieLoadType() const
{
if (m_readyState == MediaPlayer::HaveNothing)
return MediaPlayer::Unknown;
if (isLiveStream())
return MediaPlayer::LiveStream;
return MediaPlayer::Download;
}
#if USE(GSTREAMER_GL)
GstElement* MediaPlayerPrivateGStreamerBase::createGLAppSink()
{
if (!webkitGstCheckVersion(1, 8, 0))
return nullptr;
GstElement* appsink = gst_element_factory_make("appsink", "webkit-gl-video-sink");
if (!appsink)
return nullptr;
g_object_set(appsink, "enable-last-sample", FALSE, "emit-signals", TRUE, "max-buffers", 1, nullptr);
g_signal_connect(appsink, "new-sample", G_CALLBACK(newSampleCallback), this);
g_signal_connect(appsink, "new-preroll", G_CALLBACK(newPrerollCallback), this);
GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(appsink, "sink"));
gst_pad_add_probe (pad.get(), GST_PAD_PROBE_TYPE_EVENT_FLUSH, [] (GstPad*, GstPadProbeInfo* info, gpointer userData) -> GstPadProbeReturn {
if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_FLUSH_START)
return GST_PAD_PROBE_OK;
auto* player = static_cast<MediaPlayerPrivateGStreamerBase*>(userData);
player->flushCurrentBuffer();
return GST_PAD_PROBE_OK;
}, this, nullptr);
return appsink;
}
GstElement* MediaPlayerPrivateGStreamerBase::createVideoSinkGL()
{
if (!webkitGstCheckVersion(1, 8, 0))
return nullptr;
gboolean result = TRUE;
GstElement* videoSink = gst_bin_new(nullptr);
GstElement* upload = gst_element_factory_make("glupload", nullptr);
GstElement* colorconvert = gst_element_factory_make("glcolorconvert", nullptr);
GstElement* appsink = createGLAppSink();
if (!appsink || !upload || !colorconvert) {
GST_WARNING("Failed to create GstGL elements");
gst_object_unref(videoSink);
if (upload)
gst_object_unref(upload);
if (colorconvert)
gst_object_unref(colorconvert);
if (appsink)
gst_object_unref(appsink);
return nullptr;
}
gst_bin_add_many(GST_BIN(videoSink), upload, colorconvert, appsink, nullptr);
GRefPtr<GstCaps> caps = adoptGRef(gst_caps_from_string("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), format = (string) " GST_GL_CAPS_FORMAT));
result &= gst_element_link_pads(upload, "src", colorconvert, "sink");
result &= gst_element_link_pads_filtered(colorconvert, "src", appsink, "sink", caps.get());
GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(upload, "sink"));
gst_element_add_pad(videoSink, gst_ghost_pad_new("sink", pad.get()));
if (!result) {
GST_WARNING("Failed to link GstGL elements");
gst_object_unref(videoSink);
videoSink = nullptr;
}
return videoSink;
}
#endif // USE(GSTREAMER_GL)
GstElement* MediaPlayerPrivateGStreamerBase::createVideoSink()
{
acceleratedRenderingStateChanged();
#if USE(GSTREAMER_GL)
if (m_renderingCanBeAccelerated)
m_videoSink = createVideoSinkGL();
#endif
if (!m_videoSink) {
m_usingFallbackVideoSink = true;
m_videoSink = webkitVideoSinkNew();
g_signal_connect_swapped(m_videoSink.get(), "repaint-requested", G_CALLBACK(repaintCallback), this);
g_signal_connect_swapped(m_videoSink.get(), "repaint-cancelled", G_CALLBACK(repaintCancelledCallback), this);
}
GstElement* videoSink = nullptr;
m_fpsSink = gst_element_factory_make("fpsdisplaysink", "sink");
if (m_fpsSink) {
g_object_set(m_fpsSink.get(), "silent", TRUE , nullptr);
#if LOG_DISABLED
g_object_set(m_fpsSink.get(), "text-overlay", FALSE , nullptr);
#else
if (!isLogChannelEnabled("Media"))
g_object_set(m_fpsSink.get(), "text-overlay", FALSE , nullptr);
#endif // LOG_DISABLED
if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_fpsSink.get()), "video-sink")) {
g_object_set(m_fpsSink.get(), "video-sink", m_videoSink.get(), nullptr);
videoSink = m_fpsSink.get();
} else
m_fpsSink = nullptr;
}
if (!m_fpsSink)
videoSink = m_videoSink.get();
ASSERT(videoSink);
return videoSink;
}
void MediaPlayerPrivateGStreamerBase::setStreamVolumeElement(GstStreamVolume* volume)
{
ASSERT(!m_volumeElement);
m_volumeElement = volume;
if (!m_player->platformVolumeConfigurationRequired()) {
GST_DEBUG("Setting stream volume to %f", m_player->volume());
g_object_set(m_volumeElement.get(), "volume", m_player->volume(), nullptr);
} else
GST_DEBUG("Not setting stream volume, trusting system one");
GST_DEBUG("Setting stream muted %d", m_player->muted());
g_object_set(m_volumeElement.get(), "mute", m_player->muted(), nullptr);
g_signal_connect_swapped(m_volumeElement.get(), "notify::volume", G_CALLBACK(volumeChangedCallback), this);
g_signal_connect_swapped(m_volumeElement.get(), "notify::mute", G_CALLBACK(muteChangedCallback), this);
}
unsigned MediaPlayerPrivateGStreamerBase::decodedFrameCount() const
{
guint64 decodedFrames = 0;
if (m_fpsSink)
g_object_get(m_fpsSink.get(), "frames-rendered", &decodedFrames, nullptr);
return static_cast<unsigned>(decodedFrames);
}
unsigned MediaPlayerPrivateGStreamerBase::droppedFrameCount() const
{
guint64 framesDropped = 0;
if (m_fpsSink)
g_object_get(m_fpsSink.get(), "frames-dropped", &framesDropped, nullptr);
return static_cast<unsigned>(framesDropped);
}
unsigned MediaPlayerPrivateGStreamerBase::audioDecodedByteCount() const
{
GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES);
gint64 position = 0;
if (audioSink() && gst_element_query(audioSink(), query))
gst_query_parse_position(query, 0, &position);
gst_query_unref(query);
return static_cast<unsigned>(position);
}
unsigned MediaPlayerPrivateGStreamerBase::videoDecodedByteCount() const
{
GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES);
gint64 position = 0;
if (gst_element_query(m_videoSink.get(), query))
gst_query_parse_position(query, 0, &position);
gst_query_unref(query);
return static_cast<unsigned>(position);
}
bool MediaPlayerPrivateGStreamerBase::supportsKeySystem(const String& keySystem, const String& mimeType)
{
GST_INFO("Checking for KeySystem support with %s and type %s: false.", keySystem.utf8().data(), mimeType.utf8().data());
return false;
}
MediaPlayer::SupportsType MediaPlayerPrivateGStreamerBase::extendedSupportsType(const MediaEngineSupportParameters& parameters, MediaPlayer::SupportsType result)
{
UNUSED_PARAM(parameters);
return result;
}
}
#endif // USE(GSTREAMER)