dom/media/gstreamer/GStreamerFormatHelper.cpp
author Edwin Flores <eflores@mozilla.com>
Mon, 15 Jul 2013 19:39:00 +1200
changeset 250320 fc7893265f9d6f1ffd529f19097a110f40390862
parent 236670 277005c35f059e79bbcf07cecb1523a484eae981
child 250321 2c20766b4493c2c009496fab1fd4988fae8aed21
permissions -rw-r--r--
Bug 981869 - Blacklist crashy flump3dec gstreamer plugin. r=kinetik, a=lmandel

/* -*- 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<uint32_t>(-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,
                                                  FactoryType aTypes)
{
  if (!IsBlacklistEnabled()) {
    return false;
  }

  const gchar *className =
    gst_element_factory_get_klass(GST_ELEMENT_FACTORY_CAST(aFeature));

  const gchar *factoryName =
    gst_plugin_feature_get_name(aFeature);

  if ((!(aTypes & FactoryTypeDecoder) && strstr(className, "Decoder")) ||
      (!(aTypes & FactoryTypeDemuxer) && strstr(className, "Demuxer")) ||
      (!(aTypes & FactoryTypeParser) && strstr(className, "Parser"))) {
    return false;
  }

  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;
  }

  return !GStreamerFormatHelper::IsPluginFeatureBlacklisted(aFeature,
                                                            (FactoryType)(FactoryTypeDecoder|FactoryTypeDemuxer));
}

/**
 * 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<GstStaticPadTemplate *>(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