blob: 01605fc91497e462ba07730478e84a8551e6723f [file] [log] [blame]
/*
* Copyright (C) 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
*/
#include "config.h"
#include "GStreamerCommon.h"
#if USE(GSTREAMER)
#include "GLVideoSinkGStreamer.h"
#include "GStreamerAudioMixer.h"
#include "GstAllocatorFastMalloc.h"
#include "IntSize.h"
#include "RuntimeApplicationChecks.h"
#include "SharedBuffer.h"
#include "WebKitAudioSinkGStreamer.h"
#include <gst/audio/audio-info.h>
#include <gst/gst.h>
#include <mutex>
#include <wtf/FileSystem.h>
#include <wtf/Scope.h>
#include <wtf/glib/GUniquePtr.h>
#include <wtf/glib/RunLoopSourcePriority.h>
#if USE(GSTREAMER_FULL)
#include <gst/gstinitstaticplugins.h>
#endif
#if USE(GSTREAMER_MPEGTS)
#define GST_USE_UNSTABLE_API
#include <gst/mpegts/mpegts.h>
#undef GST_USE_UNSTABLE_API
#endif
#if ENABLE(MEDIA_SOURCE)
#include "WebKitMediaSourceGStreamer.h"
#endif
#if ENABLE(MEDIA_STREAM)
#include "GStreamerMediaStreamSource.h"
#endif
#if ENABLE(ENCRYPTED_MEDIA)
#include "WebKitClearKeyDecryptorGStreamer.h"
#if ENABLE(THUNDER)
#include "WebKitThunderDecryptorGStreamer.h"
#endif
#endif
#if ENABLE(VIDEO)
#include "WebKitWebSourceGStreamer.h"
#endif
GST_DEBUG_CATEGORY(webkit_gst_common_debug);
#define GST_CAT_DEFAULT webkit_gst_common_debug
namespace WebCore {
GstPad* webkitGstGhostPadFromStaticTemplate(GstStaticPadTemplate* staticPadTemplate, const gchar* name, GstPad* target)
{
GstPad* pad;
GstPadTemplate* padTemplate = gst_static_pad_template_get(staticPadTemplate);
if (target)
pad = gst_ghost_pad_new_from_template(name, target, padTemplate);
else
pad = gst_ghost_pad_new_no_target_from_template(name, padTemplate);
gst_object_unref(padTemplate);
return pad;
}
#if ENABLE(VIDEO)
bool getVideoSizeAndFormatFromCaps(const GstCaps* caps, WebCore::IntSize& size, GstVideoFormat& format, int& pixelAspectRatioNumerator, int& pixelAspectRatioDenominator, int& stride)
{
if (!doCapsHaveType(caps, GST_VIDEO_CAPS_TYPE_PREFIX)) {
GST_WARNING("Failed to get the video size and format, these are not a video caps");
return false;
}
if (areEncryptedCaps(caps)) {
GstStructure* structure = gst_caps_get_structure(caps, 0);
format = GST_VIDEO_FORMAT_ENCODED;
stride = 0;
int width = 0, height = 0;
gst_structure_get_int(structure, "width", &width);
gst_structure_get_int(structure, "height", &height);
if (!gst_structure_get_fraction(structure, "pixel-aspect-ratio", &pixelAspectRatioNumerator, &pixelAspectRatioDenominator)) {
pixelAspectRatioNumerator = 1;
pixelAspectRatioDenominator = 1;
}
size.setWidth(width);
size.setHeight(height);
} else {
GstVideoInfo info;
gst_video_info_init(&info);
if (!gst_video_info_from_caps(&info, caps))
return false;
format = GST_VIDEO_INFO_FORMAT(&info);
size.setWidth(GST_VIDEO_INFO_WIDTH(&info));
size.setHeight(GST_VIDEO_INFO_HEIGHT(&info));
pixelAspectRatioNumerator = GST_VIDEO_INFO_PAR_N(&info);
pixelAspectRatioDenominator = GST_VIDEO_INFO_PAR_D(&info);
stride = GST_VIDEO_INFO_PLANE_STRIDE(&info, 0);
}
return true;
}
std::optional<FloatSize> getVideoResolutionFromCaps(const GstCaps* caps)
{
if (!doCapsHaveType(caps, GST_VIDEO_CAPS_TYPE_PREFIX)) {
GST_WARNING("Failed to get the video resolution, these are not a video caps");
return std::nullopt;
}
int width = 0, height = 0;
int pixelAspectRatioNumerator = 1, pixelAspectRatioDenominator = 1;
if (areEncryptedCaps(caps)) {
GstStructure* structure = gst_caps_get_structure(caps, 0);
gst_structure_get_int(structure, "width", &width);
gst_structure_get_int(structure, "height", &height);
gst_structure_get_fraction(structure, "pixel-aspect-ratio", &pixelAspectRatioNumerator, &pixelAspectRatioDenominator);
} else {
GstVideoInfo info;
gst_video_info_init(&info);
if (!gst_video_info_from_caps(&info, caps))
return std::nullopt;
width = GST_VIDEO_INFO_WIDTH(&info);
height = GST_VIDEO_INFO_HEIGHT(&info);
pixelAspectRatioNumerator = GST_VIDEO_INFO_PAR_N(&info);
pixelAspectRatioDenominator = GST_VIDEO_INFO_PAR_D(&info);
}
return std::make_optional(FloatSize(width, height * (static_cast<float>(pixelAspectRatioDenominator) / static_cast<float>(pixelAspectRatioNumerator))));
}
bool getSampleVideoInfo(GstSample* sample, GstVideoInfo& videoInfo)
{
if (!GST_IS_SAMPLE(sample))
return false;
GstCaps* caps = gst_sample_get_caps(sample);
if (!caps)
return false;
gst_video_info_init(&videoInfo);
if (!gst_video_info_from_caps(&videoInfo, caps))
return false;
return true;
}
#endif
const char* capsMediaType(const GstCaps* caps)
{
ASSERT(caps);
GstStructure* structure = gst_caps_get_structure(caps, 0);
if (!structure) {
GST_WARNING("caps are empty");
return nullptr;
}
#if ENABLE(ENCRYPTED_MEDIA)
if (gst_structure_has_name(structure, "application/x-cenc") || gst_structure_has_name(structure, "application/x-cbcs") || gst_structure_has_name(structure, "application/x-webm-enc"))
return gst_structure_get_string(structure, "original-media-type");
#endif
return gst_structure_get_name(structure);
}
bool doCapsHaveType(const GstCaps* caps, const char* type)
{
const char* mediaType = capsMediaType(caps);
if (!mediaType) {
GST_WARNING("Failed to get MediaType");
return false;
}
return g_str_has_prefix(mediaType, type);
}
bool areEncryptedCaps(const GstCaps* caps)
{
ASSERT(caps);
#if ENABLE(ENCRYPTED_MEDIA)
GstStructure* structure = gst_caps_get_structure(caps, 0);
if (!structure) {
GST_WARNING("caps are empty");
return false;
}
return gst_structure_has_name(structure, "application/x-cenc") || gst_structure_has_name(structure, "application/x-webm-enc");
#else
UNUSED_PARAM(caps);
return false;
#endif
}
static std::optional<Vector<String>> s_UIProcessCommandLineOptions;
void setGStreamerOptionsFromUIProcess(Vector<String>&& options)
{
s_UIProcessCommandLineOptions = WTFMove(options);
}
Vector<String> extractGStreamerOptionsFromCommandLine()
{
GUniqueOutPtr<char> contents;
gsize length;
if (!g_file_get_contents("/proc/self/cmdline", &contents.outPtr(), &length, nullptr))
return { };
Vector<String> options;
auto optionsString = String::fromUTF8(contents.get(), length);
optionsString.split('\0', [&options](StringView item) {
if (item.startsWith("--gst"))
options.append(item.toString());
});
return options;
}
bool ensureGStreamerInitialized()
{
RELEASE_ASSERT(isInWebProcess());
static std::once_flag onceFlag;
static bool isGStreamerInitialized;
std::call_once(onceFlag, [] {
isGStreamerInitialized = false;
// USE_PLAYBIN3 is dangerous for us because its potential sneaky effect
// is to register the playbin3 element under the playbin namespace. We
// can't allow this, when we create playbin, we want playbin2, not
// playbin3.
if (g_getenv("USE_PLAYBIN3"))
WTFLogAlways("The USE_PLAYBIN3 variable was detected in the environment. Expect playback issues or please unset it.");
#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO)
Vector<String> parameters = s_UIProcessCommandLineOptions.value_or(extractGStreamerOptionsFromCommandLine());
s_UIProcessCommandLineOptions.reset();
char** argv = g_new0(char*, parameters.size() + 2);
int argc = parameters.size() + 1;
argv[0] = g_strdup(FileSystem::currentExecutableName().data());
for (unsigned i = 0; i < parameters.size(); i++)
argv[i + 1] = g_strdup(parameters[i].utf8().data());
GUniqueOutPtr<GError> error;
isGStreamerInitialized = gst_init_check(&argc, &argv, &error.outPtr());
ASSERT_WITH_MESSAGE(isGStreamerInitialized, "GStreamer initialization failed: %s", error ? error->message : "unknown error occurred");
g_strfreev(argv);
GST_DEBUG_CATEGORY_INIT(webkit_gst_common_debug, "webkitcommon", 0, "WebKit Common utilities");
if (isFastMallocEnabled()) {
const char* disableFastMalloc = getenv("WEBKIT_GST_DISABLE_FAST_MALLOC");
if (!disableFastMalloc || !strcmp(disableFastMalloc, "0"))
gst_allocator_set_default(GST_ALLOCATOR(g_object_new(gst_allocator_fast_malloc_get_type(), nullptr)));
}
#if USE(GSTREAMER_MPEGTS)
if (isGStreamerInitialized)
gst_mpegts_initialize();
#endif
#endif
});
return isGStreamerInitialized;
}
#if ENABLE(ENCRYPTED_MEDIA) && ENABLE(THUNDER)
// WebM does not specify a protection system ID so it can happen that
// the ClearKey decryptor is chosen instead of the Thunder one for
// Widevine (and viceversa) which can can create chaos. This is an
// environment variable to set in run time if we prefer to rank higher
// Thunder or ClearKey. If we want to run tests with Thunder, we need
// to set this environment variable to Thunder and that decryptor will
// be ranked higher when there is no protection system set (as in
// WebM).
// FIXME: In https://bugs.webkit.org/show_bug.cgi?id=214826 we say we
// should migrate to use GST_PLUGIN_FEATURE_RANK but we can't yet
// because our lowest dependency is 1.16.
bool isThunderRanked()
{
const char* value = g_getenv("WEBKIT_GST_EME_RANK_PRIORITY");
return value && equalIgnoringASCIICase(value, "Thunder");
}
#endif
void registerWebKitGStreamerElements()
{
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
#if USE(GSTREAMER_FULL)
gst_init_static_plugins();
#endif
#if ENABLE(ENCRYPTED_MEDIA)
gst_element_register(nullptr, "webkitclearkey", GST_RANK_PRIMARY + 200, WEBKIT_TYPE_MEDIA_CK_DECRYPT);
#endif
#if ENABLE(MEDIA_STREAM)
gst_element_register(nullptr, "mediastreamsrc", GST_RANK_PRIMARY, WEBKIT_TYPE_MEDIA_STREAM_SRC);
#endif
#if ENABLE(MEDIA_SOURCE)
gst_element_register(nullptr, "webkitmediasrc", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_MEDIA_SRC);
#endif
#if ENABLE(VIDEO)
gst_element_register(0, "webkitwebsrc", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_WEB_SRC);
#if USE(GSTREAMER_GL)
gst_element_register(0, "webkitglvideosink", GST_RANK_NONE, WEBKIT_TYPE_GL_VIDEO_SINK);
#endif
#endif
// We don't want autoaudiosink to autoplug our sink.
gst_element_register(0, "webkitaudiosink", GST_RANK_NONE, WEBKIT_TYPE_AUDIO_SINK);
// If the FDK-AAC decoder is available, promote it and downrank the
// libav AAC decoders, due to their broken LC support, as reported in:
// https://ffmpeg.org/pipermail/ffmpeg-devel/2019-July/247063.html
GRefPtr<GstElementFactory> elementFactory = adoptGRef(gst_element_factory_find("fdkaacdec"));
if (elementFactory) {
gst_plugin_feature_set_rank(GST_PLUGIN_FEATURE_CAST(elementFactory.get()), GST_RANK_PRIMARY);
const char* const elementNames[] = {"avdec_aac", "avdec_aac_fixed", "avdec_aac_latm"};
for (unsigned i = 0; i < G_N_ELEMENTS(elementNames); i++) {
GRefPtr<GstElementFactory> avAACDecoderFactory = adoptGRef(gst_element_factory_find(elementNames[i]));
if (avAACDecoderFactory)
gst_plugin_feature_set_rank(GST_PLUGIN_FEATURE_CAST(avAACDecoderFactory.get()), GST_RANK_MARGINAL);
}
}
});
}
unsigned getGstPlayFlag(const char* nick)
{
static GFlagsClass* flagsClass = static_cast<GFlagsClass*>(g_type_class_ref(g_type_from_name("GstPlayFlags")));
ASSERT(flagsClass);
GFlagsValue* flag = g_flags_get_value_by_nick(flagsClass, nick);
if (!flag)
return 0;
return flag->value;
}
// Convert a MediaTime in seconds to a GstClockTime. Note that we can get MediaTime objects with a time scale that isn't a GST_SECOND, since they can come to
// us through the internal testing API, the DOM and internally. It would be nice to assert the format of the incoming time, but all the media APIs assume time
// is passed around in fractional seconds, so we'll just have to assume the same.
uint64_t toGstUnsigned64Time(const MediaTime& mediaTime)
{
MediaTime time = mediaTime.toTimeScale(GST_SECOND);
if (time.isInvalid())
return GST_CLOCK_TIME_NONE;
return time.timeValue();
}
static void simpleBusMessageCallback(GstBus*, GstMessage* message, GstBin* pipeline)
{
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
GST_ERROR_OBJECT(pipeline, "Got message: %" GST_PTR_FORMAT, message);
{
String dotFileName = makeString(GST_OBJECT_NAME(pipeline), "_error");
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(pipeline, GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.utf8().data());
}
break;
case GST_MESSAGE_STATE_CHANGED:
if (GST_MESSAGE_SRC(message) == GST_OBJECT(pipeline)) {
GstState oldState, newState, pending;
gst_message_parse_state_changed(message, &oldState, &newState, &pending);
GST_INFO_OBJECT(pipeline, "State changed (old: %s, new: %s, pending: %s)",
gst_element_state_get_name(oldState),
gst_element_state_get_name(newState),
gst_element_state_get_name(pending));
String dotFileName = makeString(
GST_OBJECT_NAME(pipeline), '_',
gst_element_state_get_name(oldState), '_',
gst_element_state_get_name(newState));
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.utf8().data());
}
break;
default:
break;
}
}
void disconnectSimpleBusMessageCallback(GstElement* pipeline)
{
auto bus = adoptGRef(gst_pipeline_get_bus(GST_PIPELINE(pipeline)));
g_signal_handlers_disconnect_by_func(bus.get(), reinterpret_cast<gpointer>(simpleBusMessageCallback), pipeline);
gst_bus_remove_signal_watch(bus.get());
}
void connectSimpleBusMessageCallback(GstElement* pipeline)
{
auto bus = adoptGRef(gst_pipeline_get_bus(GST_PIPELINE(pipeline)));
gst_bus_add_signal_watch_full(bus.get(), RunLoopSourcePriority::RunLoopDispatcher);
g_signal_connect(bus.get(), "message", G_CALLBACK(simpleBusMessageCallback), pipeline);
}
Vector<uint8_t> GstMappedBuffer::createVector() const
{
return { data(), size() };
}
Ref<SharedBuffer> GstMappedOwnedBuffer::createSharedBuffer()
{
return SharedBuffer::create(*this);
}
bool isGStreamerPluginAvailable(const char* name)
{
GRefPtr<GstPlugin> plugin = adoptGRef(gst_registry_find_plugin(gst_registry_get(), name));
if (!plugin)
GST_WARNING("Plugin %s not found. Please check your GStreamer installation", name);
return plugin;
}
bool gstElementFactoryEquals(GstElement* element, const char* name)
{
return equal(GST_OBJECT_NAME(gst_element_get_factory(element)), name);
}
GstElement* createPlatformAudioSink()
{
GstElement* audioSink = webkitAudioSinkNew();
if (!audioSink) {
// This means the WebKit audio sink configuration failed. It can happen for the following reasons:
// - audio mixing was not requested using the WEBKIT_GST_ENABLE_AUDIO_MIXER
// - audio mixing was requested using the WEBKIT_GST_ENABLE_AUDIO_MIXER but the audio mixer
// runtime requirements are not fullfilled.
// - the sink was created for the WPE port, audio mixing was not requested and no
// WPEBackend-FDO audio receiver has been registered at runtime.
audioSink = makeGStreamerElement("autoaudiosink", nullptr);
}
return audioSink;
}
bool webkitGstSetElementStateSynchronously(GstElement* pipeline, GstState targetState, Function<bool(GstMessage*)>&& messageHandler)
{
GST_DEBUG_OBJECT(pipeline, "Setting state to %s", gst_element_state_get_name(targetState));
GstState currentState;
auto result = gst_element_get_state(pipeline, &currentState, nullptr, 10);
if (result == GST_STATE_CHANGE_SUCCESS && currentState == targetState) {
GST_DEBUG_OBJECT(pipeline, "Target state already reached");
return true;
}
auto bus = adoptGRef(gst_pipeline_get_bus(GST_PIPELINE(pipeline)));
gst_bus_enable_sync_message_emission(bus.get());
auto cleanup = makeScopeExit([bus = GRefPtr<GstBus>(bus), pipeline, targetState] {
gst_bus_disable_sync_message_emission(bus.get());
GstState currentState;
auto result = gst_element_get_state(pipeline, &currentState, nullptr, 0);
GST_DEBUG_OBJECT(pipeline, "Task finished, result: %s, target state reached: %s", gst_element_state_change_return_get_name(result), boolForPrinting(currentState == targetState));
});
result = gst_element_set_state(pipeline, targetState);
if (result == GST_STATE_CHANGE_FAILURE)
return false;
if (result == GST_STATE_CHANGE_ASYNC) {
while (auto message = adoptGRef(gst_bus_timed_pop_filtered(bus.get(), GST_CLOCK_TIME_NONE, GST_MESSAGE_STATE_CHANGED))) {
if (!messageHandler(message.get()))
return false;
result = gst_element_get_state(pipeline, &currentState, nullptr, 10);
if (result == GST_STATE_CHANGE_FAILURE)
return false;
if (currentState == targetState)
return true;
}
}
return true;
}
GstBuffer* gstBufferNewWrappedFast(void* data, size_t length)
{
return gst_buffer_new_wrapped_full(static_cast<GstMemoryFlags>(0), data, length, 0, length, data, fastFree);
}
GstElement* makeGStreamerElement(const char* factoryName, const char* name)
{
auto* element = gst_element_factory_make(factoryName, name);
RELEASE_ASSERT_WITH_MESSAGE(element, "GStreamer element %s not found. Please install it", factoryName);
return element;
}
GstElement* makeGStreamerBin(const char* description, bool ghostUnlinkedPads)
{
GUniqueOutPtr<GError> error;
auto* bin = gst_parse_bin_from_description(description, ghostUnlinkedPads, &error.outPtr());
RELEASE_ASSERT_WITH_MESSAGE(bin, "Unable to create bin for description: \"%s\". Error: %s", description, error->message);
return bin;
}
}
#endif // USE(GSTREAMER)