/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GStreamerFormatHelper.h" #include "nsCharSeparatedTokenizer.h" #include "nsString.h" #include "GStreamerLoader.h" #include "mozilla/Preferences.h" #define ENTRY_FORMAT(entry) entry[0] #define ENTRY_CAPS(entry) entry[1] namespace mozilla { GStreamerFormatHelper* GStreamerFormatHelper::gInstance = nullptr; bool GStreamerFormatHelper::sLoadOK = false; GStreamerFormatHelper* GStreamerFormatHelper::Instance() { if (!gInstance) { if ((sLoadOK = load_gstreamer())) { gst_init(nullptr, nullptr); } gInstance = new GStreamerFormatHelper(); } return gInstance; } void GStreamerFormatHelper::Shutdown() { delete gInstance; gInstance = nullptr; } static char const *const sContainers[6][2] = { {"video/mp4", "video/quicktime"}, {"video/quicktime", "video/quicktime"}, {"audio/mp4", "audio/x-m4a"}, {"audio/x-m4a", "audio/x-m4a"}, {"audio/mpeg", "audio/mpeg, mpegversion=(int)1"}, {"audio/mp3", "audio/mpeg, mpegversion=(int)1"}, }; static char const *const sCodecs[9][2] = { {"avc1.42E01E", "video/x-h264"}, {"avc1.42001E", "video/x-h264"}, {"avc1.58A01E", "video/x-h264"}, {"avc1.4D401E", "video/x-h264"}, {"avc1.64001E", "video/x-h264"}, {"avc1.64001F", "video/x-h264"}, {"mp4v.20.3", "video/3gpp"}, {"mp4a.40.2", "audio/mpeg, mpegversion=(int)4"}, {"mp3", "audio/mpeg, mpegversion=(int)1"}, }; static char const * const sDefaultCodecCaps[][2] = { {"video/mp4", "video/x-h264"}, {"video/quicktime", "video/x-h264"}, {"audio/mp4", "audio/mpeg, mpegversion=(int)4"}, {"audio/x-m4a", "audio/mpeg, mpegversion=(int)4"}, {"audio/mp3", "audio/mpeg, layer=(int)3"}, {"audio/mpeg", "audio/mpeg, layer=(int)3"} }; static char const * const sPluginBlacklist[] = { "flump3dec", }; GStreamerFormatHelper::GStreamerFormatHelper() : mFactories(nullptr), mCookie(static_cast(-1)) { if (!sLoadOK) { return; } mSupportedContainerCaps = gst_caps_new_empty(); for (unsigned int i = 0; i < G_N_ELEMENTS(sContainers); i++) { const char* capsString = sContainers[i][1]; GstCaps* caps = gst_caps_from_string(capsString); gst_caps_append(mSupportedContainerCaps, caps); } mSupportedCodecCaps = gst_caps_new_empty(); for (unsigned int i = 0; i < G_N_ELEMENTS(sCodecs); i++) { const char* capsString = sCodecs[i][1]; GstCaps* caps = gst_caps_from_string(capsString); gst_caps_append(mSupportedCodecCaps, caps); } } GStreamerFormatHelper::~GStreamerFormatHelper() { if (!sLoadOK) { return; } gst_caps_unref(mSupportedContainerCaps); gst_caps_unref(mSupportedCodecCaps); if (mFactories) g_list_free(mFactories); } static GstCaps * GetContainerCapsFromMIMEType(const char *aType) { /* convert aMIMEType to gst container caps */ const char* capsString = nullptr; for (uint32_t i = 0; i < G_N_ELEMENTS(sContainers); i++) { if (!strcmp(ENTRY_FORMAT(sContainers[i]), aType)) { capsString = ENTRY_CAPS(sContainers[i]); break; } } if (!capsString) { /* we couldn't find any matching caps */ return nullptr; } return gst_caps_from_string(capsString); } static GstCaps * GetDefaultCapsFromMIMEType(const char *aType) { GstCaps *caps = GetContainerCapsFromMIMEType(aType); for (uint32_t i = 0; i < G_N_ELEMENTS(sDefaultCodecCaps); i++) { if (!strcmp(sDefaultCodecCaps[i][0], aType)) { GstCaps *tmp = gst_caps_from_string(sDefaultCodecCaps[i][1]); gst_caps_append(caps, tmp); return caps; } } return nullptr; } bool GStreamerFormatHelper::CanHandleMediaType(const nsACString& aMIMEType, const nsAString* aCodecs) { if (!sLoadOK) { return false; } const char *type; NS_CStringGetData(aMIMEType, &type, nullptr); GstCaps *caps; if (aCodecs && !aCodecs->IsEmpty()) { caps = ConvertFormatsToCaps(type, aCodecs); } else { // Get a minimal set of codec caps for this MIME type we should support so // that we don't overreport MIME types we are able to play. caps = GetDefaultCapsFromMIMEType(type); } if (!caps) { return false; } bool ret = HaveElementsToProcessCaps(caps); gst_caps_unref(caps); return ret; } GstCaps* GStreamerFormatHelper::ConvertFormatsToCaps(const char* aMIMEType, const nsAString* aCodecs) { NS_ASSERTION(sLoadOK, "GStreamer library not linked"); unsigned int i; GstCaps *caps = GetContainerCapsFromMIMEType(aMIMEType); if (!caps) { return nullptr; } /* container only */ if (!aCodecs) { return caps; } nsCharSeparatedTokenizer tokenizer(*aCodecs, ','); while (tokenizer.hasMoreTokens()) { const nsSubstring& codec = tokenizer.nextToken(); const char *capsString = nullptr; for (i = 0; i < G_N_ELEMENTS(sCodecs); i++) { if (codec.EqualsASCII(ENTRY_FORMAT(sCodecs[i]))) { capsString = ENTRY_CAPS(sCodecs[i]); break; } } if (!capsString) { gst_caps_unref(caps); return nullptr; } GstCaps* tmp = gst_caps_from_string(capsString); /* appends and frees tmp */ gst_caps_append(caps, tmp); } return caps; } /* static */ bool GStreamerFormatHelper::IsBlacklistEnabled() { static bool sBlacklistEnabled; static bool sBlacklistEnabledCached = false; if (!sBlacklistEnabledCached) { Preferences::AddBoolVarCache(&sBlacklistEnabled, "media.gstreamer.enable-blacklist", true); sBlacklistEnabledCached = true; } return sBlacklistEnabled; } /* static */ bool GStreamerFormatHelper::IsPluginFeatureBlacklisted(GstPluginFeature *aFeature) { if (!IsBlacklistEnabled()) { return false; } const gchar *factoryName = gst_plugin_feature_get_name(aFeature); for (unsigned int i = 0; i < G_N_ELEMENTS(sPluginBlacklist); i++) { if (!strcmp(factoryName, sPluginBlacklist[i])) { return true; } } return false; } static gboolean FactoryFilter(GstPluginFeature *aFeature, gpointer) { if (!GST_IS_ELEMENT_FACTORY(aFeature)) { return FALSE; } const gchar *className = gst_element_factory_get_klass(GST_ELEMENT_FACTORY_CAST(aFeature)); if (!strstr(className, "Decoder") && !strstr(className, "Demux")) { return FALSE; } return gst_plugin_feature_get_rank(aFeature) >= GST_RANK_MARGINAL && !GStreamerFormatHelper::IsPluginFeatureBlacklisted(aFeature); } /** * Returns true if any |aFactory| caps intersect with |aCaps| */ static bool SupportsCaps(GstElementFactory *aFactory, GstCaps *aCaps) { for (const GList *iter = gst_element_factory_get_static_pad_templates(aFactory); iter; iter = iter->next) { GstStaticPadTemplate *templ = static_cast(iter->data); if (templ->direction == GST_PAD_SRC) { continue; } GstCaps *caps = gst_static_caps_get(&templ->static_caps); if (!caps) { continue; } if (gst_caps_can_intersect(gst_static_caps_get(&templ->static_caps), aCaps)) { return true; } } return false; } bool GStreamerFormatHelper::HaveElementsToProcessCaps(GstCaps* aCaps) { NS_ASSERTION(sLoadOK, "GStreamer library not linked"); GList* factories = GetFactories(); /* here aCaps contains [containerCaps, [codecCaps1, [codecCaps2, ...]]] so process * caps structures individually as we want one element for _each_ * structure */ for (unsigned int i = 0; i < gst_caps_get_size(aCaps); i++) { GstStructure* s = gst_caps_get_structure(aCaps, i); GstCaps* caps = gst_caps_new_full(gst_structure_copy(s), nullptr); bool found = false; for (GList *elem = factories; elem; elem = elem->next) { if (SupportsCaps(GST_ELEMENT_FACTORY_CAST(elem->data), caps)) { found = true; break; } } if (!found) { return false; } gst_caps_unref(caps); } return true; } bool GStreamerFormatHelper::CanHandleContainerCaps(GstCaps* aCaps) { NS_ASSERTION(sLoadOK, "GStreamer library not linked"); return gst_caps_can_intersect(aCaps, mSupportedContainerCaps); } bool GStreamerFormatHelper::CanHandleCodecCaps(GstCaps* aCaps) { NS_ASSERTION(sLoadOK, "GStreamer library not linked"); return gst_caps_can_intersect(aCaps, mSupportedCodecCaps); } GList* GStreamerFormatHelper::GetFactories() { NS_ASSERTION(sLoadOK, "GStreamer library not linked"); #if GST_VERSION_MAJOR >= 1 uint32_t cookie = gst_registry_get_feature_list_cookie(gst_registry_get()); #else uint32_t cookie = gst_default_registry_get_feature_list_cookie(); #endif if (cookie != mCookie) { g_list_free(mFactories); #if GST_VERSION_MAJOR >= 1 mFactories = gst_registry_feature_filter(gst_registry_get(), (GstPluginFeatureFilter)FactoryFilter, false, nullptr); #else mFactories = gst_default_registry_feature_filter((GstPluginFeatureFilter)FactoryFilter, false, nullptr); #endif mCookie = cookie; } return mFactories; } } // namespace mozilla