MediaPlayerPrivateGStreamerBase.cpp [plain text]
#include "config.h"
#include "MediaPlayerPrivateGStreamerBase.h"
#if ENABLE(VIDEO) && USE(GSTREAMER)
#include "ColorSpace.h"
#include "FullscreenVideoControllerGStreamer.h"
#include "GStreamerGWorld.h"
#include "GStreamerUtilities.h"
#include "GStreamerVersioning.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 <gst/gst.h>
#include <gst/video/video.h>
#include <wtf/text/CString.h>
#ifdef GST_API_VERSION_1
#include <gst/audio/streamvolume.h>
#else
#include <gst/interfaces/streamvolume.h>
#endif
#if GST_CHECK_VERSION(1, 1, 0) && USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL)
#include "TextureMapperGL.h"
#endif
GST_DEBUG_CATEGORY(webkit_media_player_debug);
#define GST_CAT_DEFAULT webkit_media_player_debug
using namespace std;
namespace WebCore {
static int greatestCommonDivisor(int a, int b)
{
while (b) {
int temp = a;
a = b;
b = temp % b;
}
return ABS(a);
}
static void mediaPlayerPrivateVolumeChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamerBase* player)
{
player->volumeChanged();
}
static gboolean mediaPlayerPrivateVolumeChangeTimeoutCallback(MediaPlayerPrivateGStreamerBase* player)
{
player->notifyPlayerOfVolumeChange();
return FALSE;
}
static void mediaPlayerPrivateMuteChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamerBase* player)
{
player->muteChanged();
}
static gboolean mediaPlayerPrivateMuteChangeTimeoutCallback(MediaPlayerPrivateGStreamerBase* player)
{
player->notifyPlayerOfMute();
return FALSE;
}
static void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer *buffer, MediaPlayerPrivateGStreamerBase* playerPrivate)
{
playerPrivate->triggerRepaint(buffer);
}
MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase(MediaPlayer* player)
: m_player(player)
, m_fpsSink(0)
, m_readyState(MediaPlayer::HaveNothing)
, m_networkState(MediaPlayer::Empty)
, m_buffer(0)
, m_volumeTimerHandler(0)
, m_muteTimerHandler(0)
, m_repaintHandler(0)
, m_volumeSignalHandler(0)
, m_muteSignalHandler(0)
#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
, m_texture(0)
#endif
{
#if GLIB_CHECK_VERSION(2, 31, 0)
m_bufferMutex = WTF::fastNew<GMutex>();
g_mutex_init(m_bufferMutex);
#else
m_bufferMutex = g_mutex_new();
#endif
}
MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase()
{
g_signal_handler_disconnect(m_webkitVideoSink.get(), m_repaintHandler);
#if GLIB_CHECK_VERSION(2, 31, 0)
g_mutex_clear(m_bufferMutex);
WTF::fastDelete(m_bufferMutex);
#else
g_mutex_free(m_bufferMutex);
#endif
if (m_buffer)
gst_buffer_unref(m_buffer);
m_buffer = 0;
m_player = 0;
if (m_muteTimerHandler)
g_source_remove(m_muteTimerHandler);
if (m_volumeTimerHandler)
g_source_remove(m_volumeTimerHandler);
if (m_volumeSignalHandler) {
g_signal_handler_disconnect(m_volumeElement.get(), m_volumeSignalHandler);
m_volumeSignalHandler = 0;
}
if (m_muteSignalHandler) {
g_signal_handler_disconnect(m_volumeElement.get(), m_muteSignalHandler);
m_muteSignalHandler = 0;
}
#if USE(NATIVE_FULLSCREEN_VIDEO)
if (m_fullscreenVideoController)
exitFullscreen();
#endif
}
IntSize MediaPlayerPrivateGStreamerBase::naturalSize() const
{
if (!hasVideo())
return IntSize();
if (!m_videoSize.isEmpty())
return m_videoSize;
#ifdef GST_API_VERSION_1
GRefPtr<GstCaps> caps = webkitGstGetPadCaps(m_videoSinkPad.get());
#else
g_mutex_lock(m_bufferMutex);
GRefPtr<GstCaps> caps = m_buffer ? GST_BUFFER_CAPS(m_buffer) : 0;
g_mutex_unlock(m_bufferMutex);
#endif
if (!caps)
return IntSize();
int pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride;
IntSize originalSize;
GstVideoFormat format;
if (!getVideoSizeAndFormatFromCaps(caps.get(), originalSize, format, pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride))
return IntSize();
LOG_MEDIA_MESSAGE("Original video size: %dx%d", originalSize.width(), originalSize.height());
LOG_MEDIA_MESSAGE("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)) {
LOG_MEDIA_MESSAGE("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)) {
LOG_MEDIA_MESSAGE("Keeping video original width");
height = gst_util_uint64_scale_int(originalSize.width(), displayHeight, displayWidth);
width = static_cast<guint64>(originalSize.width());
} else {
LOG_MEDIA_MESSAGE("Approximating while keeping original video height");
width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
height = static_cast<guint64>(originalSize.height());
}
LOG_MEDIA_MESSAGE("Natural size: %" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT, width, height);
m_videoSize = IntSize(static_cast<int>(width), static_cast<int>(height));
return m_videoSize;
}
void MediaPlayerPrivateGStreamerBase::setVolume(float volume)
{
if (!m_volumeElement)
return;
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()
{
m_volumeTimerHandler = 0;
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::volumeChanged()
{
if (m_volumeTimerHandler)
g_source_remove(m_volumeTimerHandler);
m_volumeTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVolumeChangeTimeoutCallback), this);
}
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, NULL);
}
bool MediaPlayerPrivateGStreamerBase::muted() const
{
if (!m_volumeElement)
return false;
bool muted;
g_object_get(m_volumeElement.get(), "mute", &muted, NULL);
return muted;
}
void MediaPlayerPrivateGStreamerBase::notifyPlayerOfMute()
{
m_muteTimerHandler = 0;
if (!m_player || !m_volumeElement)
return;
gboolean muted;
g_object_get(m_volumeElement.get(), "mute", &muted, NULL);
m_player->muteChanged(static_cast<bool>(muted));
}
void MediaPlayerPrivateGStreamerBase::muteChanged()
{
if (m_muteTimerHandler)
g_source_remove(m_muteTimerHandler);
m_muteTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateMuteChangeTimeoutCallback), this);
}
#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
void MediaPlayerPrivateGStreamerBase::updateTexture(GstBuffer* buffer)
{
if (!m_texture)
return;
if (!client())
return;
const void* srcData = 0;
IntSize size = naturalSize();
if (m_texture->size() != size)
m_texture->reset(size);
#if GST_CHECK_VERSION(1, 1, 0)
GstVideoGLTextureUploadMeta* meta;
if ((meta = gst_buffer_get_video_gl_texture_upload_meta(buffer))) {
if (meta->n_textures == 1) { const BitmapTextureGL* textureGL = static_cast<const BitmapTextureGL*>(m_texture.get());
guint ids[4] = { textureGL->id(), 0, 0, 0 };
if (gst_video_gl_texture_upload_meta_upload(meta, ids)) {
client()->setPlatformLayerNeedsDisplay();
return;
}
}
}
#endif
#ifdef GST_API_VERSION_1
GstMapInfo srcInfo;
gst_buffer_map(buffer, &srcInfo, GST_MAP_READ);
srcData = srcInfo.data;
#else
srcData = GST_BUFFER_DATA(buffer);
#endif
m_texture->updateContents(srcData, WebCore::IntRect(WebCore::IntPoint(0, 0), size), WebCore::IntPoint(0, 0), size.width() * 4, BitmapTexture::UpdateCannotModifyOriginalImageData);
#ifdef GST_API_VERSION_1
gst_buffer_unmap(buffer, &srcInfo);
#endif
client()->setPlatformLayerNeedsDisplay();
}
#endif
void MediaPlayerPrivateGStreamerBase::triggerRepaint(GstBuffer* buffer)
{
g_return_if_fail(GST_IS_BUFFER(buffer));
#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
updateTexture(buffer);
else
#endif
{
g_mutex_lock(m_bufferMutex);
gst_buffer_replace(&m_buffer, buffer);
g_mutex_unlock(m_bufferMutex);
m_player->repaint();
}
}
void MediaPlayerPrivateGStreamerBase::setSize(const IntSize& size)
{
m_size = size;
}
void MediaPlayerPrivateGStreamerBase::paint(GraphicsContext* context, const IntRect& rect)
{
#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
if (m_texture)
return;
#endif
if (context->paintingDisabled())
return;
if (!m_player->visible())
return;
g_mutex_lock(m_bufferMutex);
if (!m_buffer) {
g_mutex_unlock(m_bufferMutex);
return;
}
#ifdef GST_API_VERSION_1
GRefPtr<GstCaps> caps = webkitGstGetPadCaps(m_videoSinkPad.get());
#else
GRefPtr<GstCaps> caps = GST_BUFFER_CAPS(m_buffer);
#endif
if (!caps) {
g_mutex_unlock(m_bufferMutex);
return;
}
RefPtr<ImageGStreamer> gstImage = ImageGStreamer::createImage(m_buffer, caps.get());
if (!gstImage) {
g_mutex_unlock(m_bufferMutex);
return;
}
context->drawImage(reinterpret_cast<Image*>(gstImage->image().get()), ColorSpaceSRGB,
rect, gstImage->rect(), CompositeCopy, DoNotRespectImageOrientation, false);
g_mutex_unlock(m_bufferMutex);
}
#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
void MediaPlayerPrivateGStreamerBase::paintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity)
{
if (textureMapper->accelerationMode() != TextureMapper::OpenGLMode)
return;
if (!m_texture) {
m_texture = textureMapper->acquireTextureFromPool(naturalSize());
return;
}
textureMapper->drawTexture(*m_texture.get(), targetRect, matrix, opacity);
}
#endif
#if USE(NATIVE_FULLSCREEN_VIDEO)
void MediaPlayerPrivateGStreamerBase::enterFullscreen()
{
ASSERT(!m_fullscreenVideoController);
m_fullscreenVideoController = FullscreenVideoControllerGStreamer::create(this);
if (m_fullscreenVideoController)
m_fullscreenVideoController->enterFullscreen();
}
void MediaPlayerPrivateGStreamerBase::exitFullscreen()
{
if (!m_fullscreenVideoController)
return;
m_fullscreenVideoController->exitFullscreen();
m_fullscreenVideoController.release();
}
#endif
bool MediaPlayerPrivateGStreamerBase::supportsFullscreen() const
{
return true;
}
PlatformMedia MediaPlayerPrivateGStreamerBase::platformMedia() const
{
#if USE(NATIVE_FULLSCREEN_VIDEO)
PlatformMedia p;
p.type = PlatformMedia::GStreamerGWorldType;
p.media.gstreamerGWorld = m_gstGWorld.get();
return p;
#else
return NoPlatformMedia;
#endif
}
MediaPlayer::MovieLoadType MediaPlayerPrivateGStreamerBase::movieLoadType() const
{
if (m_readyState == MediaPlayer::HaveNothing)
return MediaPlayer::Unknown;
if (isLiveStream())
return MediaPlayer::LiveStream;
return MediaPlayer::Download;
}
GstElement* MediaPlayerPrivateGStreamerBase::createVideoSink(GstElement* pipeline)
{
if (!initializeGStreamer())
return 0;
#if USE(NATIVE_FULLSCREEN_VIDEO)
m_gstGWorld = GStreamerGWorld::createGWorld(pipeline);
m_webkitVideoSink = webkitVideoSinkNew(m_gstGWorld.get());
#else
UNUSED_PARAM(pipeline);
m_webkitVideoSink = webkitVideoSinkNew();
#endif
m_videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink.get(), "sink"));
m_repaintHandler = g_signal_connect(m_webkitVideoSink.get(), "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this);
#if USE(NATIVE_FULLSCREEN_VIDEO)
m_videoSinkBin = gst_bin_new("video-sink");
GstElement* videoTee = gst_element_factory_make("tee", "videoTee");
GstElement* queue = gst_element_factory_make("queue", 0);
#ifdef GST_API_VERSION_1
GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(videoTee, "sink"));
GST_OBJECT_FLAG_SET(GST_OBJECT(sinkPad.get()), GST_PAD_FLAG_PROXY_ALLOCATION);
#endif
gst_bin_add_many(GST_BIN(m_videoSinkBin.get()), videoTee, queue, NULL);
gst_element_link_pads_full(videoTee, 0, queue, "sink", GST_PAD_LINK_CHECK_NOTHING);
#endif
GstElement* actualVideoSink = 0;
m_fpsSink = gst_element_factory_make("fpsdisplaysink", "sink");
if (m_fpsSink) {
GstElementFactory* factory = GST_ELEMENT_FACTORY(GST_ELEMENT_GET_CLASS(m_fpsSink)->elementfactory);
if (gst_plugin_feature_check_version(GST_PLUGIN_FEATURE(factory), 0, 10, 22)) {
g_object_set(m_fpsSink, "silent", TRUE , NULL);
#if LOG_DISABLED
g_object_set(m_fpsSink, "text-overlay", FALSE , NULL);
#else
WTFLogChannel* channel = getChannelFromName("Media");
if (channel->state != WTFLogChannelOn)
g_object_set(m_fpsSink, "text-overlay", FALSE , NULL);
#endif // LOG_DISABLED
if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_fpsSink), "video-sink")) {
g_object_set(m_fpsSink, "video-sink", m_webkitVideoSink.get(), NULL);
#if USE(NATIVE_FULLSCREEN_VIDEO)
gst_bin_add(GST_BIN(m_videoSinkBin.get()), m_fpsSink);
#endif
actualVideoSink = m_fpsSink;
} else
m_fpsSink = 0;
} else
m_fpsSink = 0;
}
if (!m_fpsSink) {
#if USE(NATIVE_FULLSCREEN_VIDEO)
gst_bin_add(GST_BIN(m_videoSinkBin.get()), m_webkitVideoSink.get());
#endif
actualVideoSink = m_webkitVideoSink.get();
}
ASSERT(actualVideoSink);
#if USE(NATIVE_FULLSCREEN_VIDEO)
gst_element_link_pads_full(queue, "src", actualVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING);
GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(videoTee, "sink"));
gst_element_add_pad(m_videoSinkBin.get(), gst_ghost_pad_new("sink", pad.get()));
return m_videoSinkBin.get();
#else
return actualVideoSink;
#endif
}
void MediaPlayerPrivateGStreamerBase::setStreamVolumeElement(GstStreamVolume* volume)
{
ASSERT(!m_volumeElement);
m_volumeElement = volume;
g_object_set(m_volumeElement.get(), "mute", m_player->muted(), "volume", m_player->volume(), NULL);
m_volumeSignalHandler = g_signal_connect(m_volumeElement.get(), "notify::volume", G_CALLBACK(mediaPlayerPrivateVolumeChangedCallback), this);
m_muteSignalHandler = g_signal_connect(m_volumeElement.get(), "notify::mute", G_CALLBACK(mediaPlayerPrivateMuteChangedCallback), this);
}
unsigned MediaPlayerPrivateGStreamerBase::decodedFrameCount() const
{
guint64 decodedFrames = 0;
if (m_fpsSink)
g_object_get(m_fpsSink, "frames-rendered", &decodedFrames, NULL);
return static_cast<unsigned>(decodedFrames);
}
unsigned MediaPlayerPrivateGStreamerBase::droppedFrameCount() const
{
guint64 framesDropped = 0;
if (m_fpsSink)
g_object_get(m_fpsSink, "frames-dropped", &framesDropped, NULL);
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_webkitVideoSink.get(), query))
gst_query_parse_position(query, 0, &position);
gst_query_unref(query);
return static_cast<unsigned>(position);
}
}
#endif // USE(GSTREAMER)