chromium/media/base/win/media_foundation_package_runtime_locator.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/win/media_foundation_package_runtime_locator.h"

#include <windows.h>

#include <map>
#include <optional>
#include <string>
#include <vector>

#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/native_library.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "media/base/win/media_foundation_package_locator_helper.h"

namespace media {

namespace {

constexpr wchar_t kMfPackageFamilyName_HEVC[] =
    L"Microsoft.HEVCVideoExtension_8wekyb3d8bbwe";
constexpr wchar_t kMfPackageFamilyName_HEVC_2[] =
    L"Microsoft.HEVCVideoExtensions_8wekyb3d8bbwe";
constexpr wchar_t kMfPackageFamilyName_DolbyVision[] =
    L"DolbyLaboratories.DolbyVisionAccess_rz1tebttyb220";
constexpr wchar_t kMfPackageFamilyName_DolbyVision_2[] =
    L"DolbyLaboratories.DolbyVisionHDR_rz1tebttyb220";
constexpr wchar_t kMfPackageFamilyName_AC4[] =
    L"DolbyLaboratories.DolbyAC4DecoderOEM_rz1tebttyb220";
constexpr wchar_t kMfPackageFamilyName_EAC3[] =
    L"DolbyLaboratories.DolbyDigitalPlusDecoderOEM_rz1tebttyb220";
constexpr wchar_t kMfLibraryName_AC4[] = L"DolbyAc4DecMft.dll";
constexpr wchar_t kMfLibraryName_EAC3[] = L"DolbyDDPDecMft.dll";

constexpr MediaFoundationCodecPackage kMfCodecPackages[] = {
    MediaFoundationCodecPackage::kEAC3, MediaFoundationCodecPackage::kAC4,
    MediaFoundationCodecPackage::kHEVC,
    MediaFoundationCodecPackage::kDolbyVision};

std::wstring GetMfCodecPackageName(MediaFoundationCodecPackage codec_package) {
  switch (codec_package) {
    case MediaFoundationCodecPackage::kAV1:
      return L"kAV1";
    case MediaFoundationCodecPackage::kHEVC:
      return L"kHEVC";
    case MediaFoundationCodecPackage::kVP9:
      return L"kVP9";
    case MediaFoundationCodecPackage::kDolbyVision:
      return L"kDolbyVision";
    case MediaFoundationCodecPackage::kAC4:
      return L"kAC4";
    case MediaFoundationCodecPackage::kEAC3:
      return L"kEAC3";
  }
}

std::vector<std::wstring_view> GetMfCodecPackageFamilyNames(
    MediaFoundationCodecPackage codec_package) {
  std::vector<std::wstring_view> package_family_names;
  switch (codec_package) {
    case MediaFoundationCodecPackage::kHEVC:
      package_family_names.push_back(kMfPackageFamilyName_HEVC);
      package_family_names.push_back(kMfPackageFamilyName_HEVC_2);
      break;
    case MediaFoundationCodecPackage::kDolbyVision:
      package_family_names.push_back(kMfPackageFamilyName_DolbyVision);
      package_family_names.push_back(kMfPackageFamilyName_DolbyVision_2);
      break;
    case MediaFoundationCodecPackage::kAC4:
      package_family_names.push_back(kMfPackageFamilyName_AC4);
      break;
    case MediaFoundationCodecPackage::kEAC3:
      package_family_names.push_back(kMfPackageFamilyName_EAC3);
      break;
    default:
      break;
  }
  return package_family_names;
}

std::wstring GetMfCodecPackageLibraryName(
    MediaFoundationCodecPackage codec_package) {
  switch (codec_package) {
    case MediaFoundationCodecPackage::kAC4:
      return kMfLibraryName_AC4;
    case MediaFoundationCodecPackage::kEAC3:
      return kMfLibraryName_EAC3;
    default:
      return L"";
  }
}

struct DllModuleInfo {
  std::vector<base::FilePath> Paths;
  std::optional<bool> Loaded;
};

// This helper class, based on media_foundation_package_locator_helper.h, offers
// two primary functionalities:
//   1. Detection of the existence of specific MediaFoundation codec packages on
//   the user's device. This capability can be utilized by the W3C Media
//   Capability checking API, including methods such as HTMLMediaElement:
//   canPlayType(), MediaSource: isTypeSupported(), and
//   mediaCapabilities.decodingInfo().
//   2. Loading codec libraries from codec packages into process memory.
//   Typically, the codec library is loaded into the Chromium GPU process before
//   it is sandboxed, aiming to resolve 'access denied' issues.
// It provides a static method to obtain a single instance of
// MediaFoundationPackageRuntimeLocator on-demand, which persists in process
// memory until the termination of the process.
class MediaFoundationPackageRuntimeLocator {
 public:
  static MediaFoundationPackageRuntimeLocator& GetInstance();
  MediaFoundationPackageRuntimeLocator(
      const MediaFoundationPackageRuntimeLocator&) = delete;
  MediaFoundationPackageRuntimeLocator& operator=(
      const MediaFoundationPackageRuntimeLocator&) = delete;
  bool FoundModule(MediaFoundationCodecPackage codec) const;
  bool LoadModule(MediaFoundationCodecPackage codec);

 private:
  friend class base::NoDestructor<MediaFoundationPackageRuntimeLocator>;
  MediaFoundationPackageRuntimeLocator();
  ~MediaFoundationPackageRuntimeLocator() = default;
  std::map<MediaFoundationCodecPackage, DllModuleInfo> mf_dll_module_;
};

// static method to get a single MediaFoundationPackageRuntimeLocator
// instance on-demand.
MediaFoundationPackageRuntimeLocator&
MediaFoundationPackageRuntimeLocator::GetInstance() {
  static base::NoDestructor<MediaFoundationPackageRuntimeLocator> instance;
  return *instance;
}

MediaFoundationPackageRuntimeLocator::MediaFoundationPackageRuntimeLocator() {
  DVLOG(1) << __func__;
  for (auto& codec_package : kMfCodecPackages) {
    auto package_names = GetMfCodecPackageFamilyNames(codec_package);
    auto package_library = GetMfCodecPackageLibraryName(codec_package);
    std::vector<base::FilePath> paths = MediaFoundationPackageInstallPaths(
        package_names, package_library, codec_package);
    if (paths.empty()) {
      DVLOG(2) << __func__ << ": MediaFoundation codec package ("
               << GetMfCodecPackageName(codec_package) << ") is not installed.";
      continue;
    }

    mf_dll_module_[codec_package] = DllModuleInfo{std::move(paths)};
  }
}

bool MediaFoundationPackageRuntimeLocator::FoundModule(
    MediaFoundationCodecPackage codec_package) const {
  auto result = mf_dll_module_.find(codec_package);
  if (result == mf_dll_module_.cend()) {
    DVLOG(2) << __func__ << ": Can not find codec package ("
             << GetMfCodecPackageName(codec_package) << ")";
    return false;
  }

  auto& module_info = result->second;
  if (module_info.Paths.empty()) {
    DVLOG(2) << __func__ << ": Found codec package ("
             << GetMfCodecPackageName(codec_package)
             << "), but it's library path is empty";
    return false;
  }

  if (module_info.Loaded.has_value() && !module_info.Loaded.value()) {
    DVLOG(2) << __func__ << ": Found codec package ("
             << GetMfCodecPackageName(codec_package)
             << "), but load it's library module failed";
    return false;
  }

  return true;
}

bool MediaFoundationPackageRuntimeLocator::LoadModule(
    MediaFoundationCodecPackage codec_package) {
  auto result = mf_dll_module_.find(codec_package);
  if (result == mf_dll_module_.cend()) {
    DVLOG(2) << __func__ << ": Can not find codec package ("
             << GetMfCodecPackageName(codec_package) << ")";
    return false;
  }

  auto& module_info = result->second;
  if (module_info.Loaded.has_value()) {
    return module_info.Loaded.value();
  }

  if (module_info.Paths.empty()) {
    DVLOG(2) << __func__ << ": Found codec package ("
             << GetMfCodecPackageName(codec_package)
             << "), but it's library path is empty";
    return false;
  }

  for (auto& path : module_info.Paths) {
    if (LoadNativeLibrary(path, nullptr)) {
      // Intentionally don't call UnloadNativeLibrary, to ensure it
      // stays loaded
      module_info.Loaded = true;
      DVLOG(3) << __func__ << ": MediaFoundation package "
               << GetMfCodecPackageName(codec_package) << " loaded";
      return true;
    }
  }
  module_info.Loaded = false;
  return false;
}

std::optional<MediaFoundationCodecPackage>
AudioCodecToMediaFoundationCodecPackage(AudioCodec codec) {
  std::optional<MediaFoundationCodecPackage> package_type{std::nullopt};
  switch (codec) {
    case AudioCodec::kAC3:
    case AudioCodec::kEAC3:
      package_type = MediaFoundationCodecPackage::kEAC3;
      break;
    case AudioCodec::kAC4:
      package_type = MediaFoundationCodecPackage::kAC4;
      break;
    default:
      DVLOG(3) << "Audio codec " << GetCodecName(codec)
               << " has no MediaFoundation package.";
      break;
  }
  return package_type;
}

}  // namespace

bool LoadMediaFoundationPackageDecoder(AudioCodec codec) {
  auto codec_package = AudioCodecToMediaFoundationCodecPackage(codec);
  return codec_package.has_value()
             ? MediaFoundationPackageRuntimeLocator::GetInstance().LoadModule(
                   codec_package.value())
             : false;
}

bool FindMediaFoundationPackageDecoder(AudioCodec codec) {
  auto codec_package = AudioCodecToMediaFoundationCodecPackage(codec);
  return codec_package.has_value()
             ? MediaFoundationPackageRuntimeLocator::GetInstance().FoundModule(
                   codec_package.value())
             : false;
}

}  // namespace media