diff --git a/Makefile.in b/Makefile.in index 4740932549..429bcabab3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -220,14 +220,14 @@ ifneq ($(filter-out maybe_clobber_profiledbuild,$(MAKECMDGOALS)),) GARBAGE_DIRS += dist _tests endif -# MSVC PGO builds don't perform a clean before the 2nd pass. So, we want +# Windows PGO builds don't perform a clean before the 2nd pass. So, we want # to preserve content for the 2nd pass on Windows. Everywhere else, we always # process the install manifests as part of export. # For the binaries rule, not all the install manifests matter, so force only # the interesting ones to be done. ifdef MOZ_PROFILE_USE ifndef NO_PROFILE_GUIDED_OPTIMIZE -ifneq ($(OS_ARCH)_$(GNU_CC)_$(CLANG_CL), WINNT__) +ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_) recurse_pre-export:: install-manifests binaries:: @$(MAKE) install-manifests install_manifests=dist/include @@ -247,10 +247,6 @@ recurse_artifact: $(topsrcdir)/mach --log-no-times artifact install ifndef JS_STANDALONE -# PGO training is also needed in release builds configured without tests. -pgo-profile-run: - $(PYTHON) $(topsrcdir)/build/pgo/profileserver.py $(EXTRA_TEST_ARGS) - ifdef ENABLE_TESTS # Additional makefile targets to call automated test suites include $(topsrcdir)/testing/testsuite-targets.mk @@ -360,7 +356,7 @@ pretty-installer: #XXX: this is a hack, since we don't want to clobber for MSVC # PGO support, but we can't do this test in client.mk -ifneq ($(OS_ARCH)_$(GNU_CC)_$(CLANG_CL), WINNT__) +ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_) # No point in clobbering if PGO has been explicitly disabled. ifndef NO_PROFILE_GUIDED_OPTIMIZE maybe_clobber_profiledbuild: clean diff --git a/README.md b/README.md index c9e0e61bc7..bce6376429 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Some advantages over upstream, roytam1's Serpent, Eclipse Hydra and Firefox are: - Support for extensions in the Chrome and Edge Web Store (soon) - Support for extensions released for both Pale Moon, Basilisk and Firefox - WebExtensions support comparable to Firefox ESR 78 -- Literally being the fastest XUL browser (48.7 in Speedometer 2.1 compared to 22.4 in Pale Moon 34.2 AVX2) +- Literally being the fastest XUL browser (33.7 in Speedometer 2.1 compared to 22.4 in Pale Moon 34.2 AVX2) - Re-add support for iOS, Android, Windows 2000 and later, NetBSD (soon OpenBSD) ## Interested in running Dactyloidae in Win9x? diff --git a/aclocal.m4 b/aclocal.m4 index 0e85c6570b..f14ddbf29c 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -17,7 +17,6 @@ builtin(include, build/autoconf/altoptions.m4)dnl builtin(include, build/autoconf/mozprog.m4)dnl builtin(include, build/autoconf/mozheader.m4)dnl builtin(include, build/autoconf/lto.m4)dnl -builtin(include, build/autoconf/clang-cl-pgo.m4)dnl builtin(include, build/autoconf/frameptr.m4)dnl builtin(include, build/autoconf/compiler-opts.m4)dnl builtin(include, build/autoconf/expandlibs.m4)dnl diff --git a/build/autoconf/clang-cl-pgo.m4 b/build/autoconf/clang-cl-pgo.m4 deleted file mode 100644 index bf4588848f..0000000000 --- a/build/autoconf/clang-cl-pgo.m4 +++ /dev/null @@ -1,30 +0,0 @@ -dnl This Source Code Form is subject to the terms of the Mozilla Public -dnl License, v. 2.0. If a copy of the MPL was not distributed with this -dnl file, You can obtain one at http://mozilla.org/MPL/2.0/. - -AC_DEFUN([MOZ_CLANG_CL_PGO], [ -if test -n "$CLANG_CL"; then - dnl Override the MSVC/GCC flags: clang-cl uses LLVM instrumentation. - PROFILE_GEN_CFLAGS="-clang:-fprofile-instr-generate" - PROFILE_USE_CFLAGS='-clang:-fprofile-instr-use="$(DEPTH)/merged.profdata"' - PROFILE_GEN_LDFLAGS= - PROFILE_USE_LDFLAGS= - - if test -n "$MOZ_PGO"; then - AC_PATH_PROG(LLVM_PROFDATA, llvm-profdata.exe) - if test -z "$LLVM_PROFDATA"; then - AC_MSG_ERROR([clang-cl PGO requires llvm-profdata from the compiler's LLVM installation on PATH]) - fi - dnl The training harness uses native Windows Python, not an MSYS shell. - LLVM_PROFDATA="$(cd "$(dirname "$LLVM_PROFDATA")" && pwd -W)/$(basename "$LLVM_PROFDATA")" - dnl clang-cl emits a default-library directive for its profile runtime. - dnl We invoke the linker directly, so supply the runtime search path. - clang_resource_dir=`$CC -print-resource-dir | tr '\\' '/'` - if test ! -d "$clang_resource_dir/lib/windows"; then - AC_MSG_ERROR([clang-cl PGO requires the Windows compiler-rt profile runtime]) - fi - PROFILE_GEN_LDFLAGS="-LIBPATH:\"$clang_resource_dir/lib/windows\"" - fi -fi -AC_SUBST(LLVM_PROFDATA) -]) diff --git a/build/moz.configure/windows.configure b/build/moz.configure/windows.configure index e2cb128265..4a28470d34 100644 --- a/build/moz.configure/windows.configure +++ b/build/moz.configure/windows.configure @@ -84,14 +84,7 @@ def get_sdk_dirs(sdk, subdir): include_dirs['winv6.3'] = include_dirs['include'] del include_dirs['include'] - def build_num(v): - parts = v.split('.') - try: - return int(parts[2]) if len(parts) >= 3 else 0 - except ValueError: - return 0 - valid_versions = sorted((v for v in set(include_dirs) & set(lib_dirs) - if build_num(v) <= 19041), reverse=True) + valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True) if valid_versions: return namespace( path=sdk, @@ -143,13 +136,6 @@ def valid_windows_sdk_dir(compiler, windows_sdk_dir, target_version, 'WINDOWSSDKDIR (%s). Please verify it contains a valid and ' 'complete SDK installation.' % windows_sdk_dir_env) - def sdk_build(path): - parts = os.path.basename(path).split('.') - try: - return int(parts[2]) if len(parts) >= 3 else 0 - except ValueError: - return 0 - sdks = {k: v for k, v in sdks.items() if sdk_build(k) <= 19041} valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) if valid_sdks: biggest_version, sdk = sdks[valid_sdks[0]] @@ -240,7 +226,6 @@ def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env): 'The SDK in WINDOWSSDKDIR (%s) does not contain the Universal ' 'CRT.' % windows_sdk_dir_env) - sdks = {k: v for k, v in sdks.items() if int(str(v[0]).split('.')[2]) <= 19041} valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) if not valid_sdks: raise FatalCheckError('Cannot find the Universal CRT SDK. ' diff --git a/build/pgo/llvm_pgo.py b/build/pgo/llvm_pgo.py deleted file mode 100644 index f6cff701c7..0000000000 --- a/build/pgo/llvm_pgo.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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/. - -"""Collect LLVM profiles outside the directories cleaned between PGO passes.""" - -import glob -import os -import subprocess -import tempfile - - -class LLVMProfile(object): - def __init__(self, topobjdir, profdata): - if not profdata or not os.path.isfile(profdata): - raise RuntimeError("clang-cl PGO requires a configured llvm-profdata") - self.topobjdir = os.path.abspath(topobjdir) - self.profdata = profdata - self.output = os.path.join(self.topobjdir, 'merged.profdata') - self.directory = None - - def prepare(self, env): - # Never reuse a previous training run's merged or raw profiles. - if os.path.exists(self.output): - os.remove(self.output) - self.directory = tempfile.mkdtemp(prefix='pgo-profiles-', dir=self.topobjdir) - # %m distinguishes instrumented DLLs; %p distinguishes child processes. - env['LLVM_PROFILE_FILE'] = os.path.join(self.directory, '%m-%p.profraw') - - def merge(self): - profiles = sorted(glob.glob(os.path.join(self.directory, '*.profraw'))) - if not profiles or not any(os.path.getsize(p) for p in profiles): - raise RuntimeError("PGO training produced no LLVM profile data") - # An input list avoids Windows command-line length limits. Keep profiles - # on failure so that corrupt or incompatible data can be diagnosed. - inputs = os.path.join(self.directory, 'profiles.list') - with open(inputs, 'w') as stream: - for profile in profiles: - stream.write(profile + '\n') - subprocess.check_call([self.profdata, 'merge', '-o', self.output, - '-f', inputs]) - if not os.path.isfile(self.output) or not os.path.getsize(self.output): - raise RuntimeError("llvm-profdata did not produce a merged profile") diff --git a/build/pgo/profileserver.py b/build/pgo/profileserver.py index c75587ddb3..adc93d9b13 100644 --- a/build/pgo/profileserver.py +++ b/build/pgo/profileserver.py @@ -18,20 +18,9 @@ import tempfile from datetime import datetime from mozbuild.base import MozbuildObject from buildconfig import substs -from llvm_pgo import LLVMProfile PORT = 8888 - -def wait_for_training(runner, timeout): - result = runner.wait(timeout=timeout) - if result is None: - runner.stop() - raise RuntimeError("PGO training browser timed out after %s seconds" % timeout) - if result != 0: - raise RuntimeError("PGO training browser exited unsuccessfully") - - if __name__ == '__main__': cli = CLI() debug_args, interactive = cli.debugger_arguments() @@ -65,15 +54,6 @@ if __name__ == '__main__': env = os.environ.copy() env["XPCOM_DEBUG_BREAK"] = "warn" - # Keep the training workload in the parent process. In particular, the - # initialization javascript: URL must work without a content subprocess. - # This environment belongs only to the temporary training browser. - env["MOZ_FORCE_DISABLE_E10S"] = "1" - - llvm_profile = None - if substs.get("CLANG_CL") and substs.get("MOZ_PGO"): - llvm_profile = LLVMProfile(build.topobjdir, substs.get("LLVM_PROFDATA")) - llvm_profile.prepare(env) # For VC12+, make sure we can find the right bitness of pgort1x0.dll if not substs.get('HAVE_64BIT_BUILD'): @@ -92,7 +72,7 @@ if __name__ == '__main__': cmdargs=['javascript:Quitter.quit()'], env=env) runner.start() - wait_for_training(runner, 180) + runner.wait() jarlog = os.getenv("JARLOG_FILE") if jarlog: @@ -105,9 +85,7 @@ if __name__ == '__main__': cmdargs=cmdargs, env=env) runner.start(debug_args=debug_args, interactive=interactive) - wait_for_training(runner, 600) - if llvm_profile: - llvm_profile.merge() - finally: + runner.wait() httpd.stop() + finally: shutil.rmtree(profilePath) diff --git a/build/pgo/test_llvm_pgo.py b/build/pgo/test_llvm_pgo.py deleted file mode 100644 index 782aa34c35..0000000000 --- a/build/pgo/test_llvm_pgo.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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/. - -import os -import shutil -import subprocess -import sys -import tempfile -import unittest -from distutils.spawn import find_executable - -from llvm_pgo import LLVMProfile - - -class LLVMProfileTest(unittest.TestCase): - def setUp(self): - self.directory = tempfile.mkdtemp(prefix='llvm pgo test ') - self.profile = LLVMProfile(self.directory, sys.executable) - self.check_call = subprocess.check_call - - def tearDown(self): - subprocess.check_call = self.check_call - shutil.rmtree(self.directory) - - def write(self, path, data): - with open(path, 'w') as stream: - stream.write(data) - - def test_missing_tool(self): - with self.assertRaises(RuntimeError): - LLVMProfile(self.directory, None) - - def test_prepare_isolates_runs(self): - self.write(self.profile.output, 'stale merged profile') - env = {} - self.profile.prepare(env) - first = self.profile.directory - self.write(os.path.join(first, 'stale.profraw'), 'stale raw profile') - self.profile.prepare(env) - self.assertNotEqual(first, self.profile.directory) - self.assertFalse(os.path.exists(self.profile.output)) - self.assertEqual(env['LLVM_PROFILE_FILE'], - os.path.join(self.profile.directory, '%m-%p.profraw')) - with self.assertRaises(RuntimeError): - self.profile.merge() - - def test_empty_profiles(self): - self.profile.prepare({}) - self.write(os.path.join(self.profile.directory, 'empty.profraw'), '') - with self.assertRaises(RuntimeError): - self.profile.merge() - - def test_merge_uses_input_list(self): - self.profile.prepare({}) - raw = os.path.join(self.profile.directory, 'module-process.profraw') - self.write(raw, 'raw profile') - def merge(args): - self.assertEqual(args[:4], [sys.executable, 'merge', '-o', - self.profile.output]) - self.assertEqual(args[4], '-f') - with open(args[5]) as stream: - self.assertEqual(stream.read().splitlines(), [raw]) - self.write(self.profile.output, 'merged profile') - subprocess.check_call = merge - self.profile.merge() - - def test_merge_failure_propagates(self): - self.profile.prepare({}) - self.write(os.path.join(self.profile.directory, 'bad.profraw'), 'bad') - def fail(args): - raise subprocess.CalledProcessError(1, args) - subprocess.check_call = fail - with self.assertRaises(subprocess.CalledProcessError): - self.profile.merge() - - def test_merge_requires_output(self): - self.profile.prepare({}) - self.write(os.path.join(self.profile.directory, 'data.profraw'), 'raw') - subprocess.check_call = lambda args: None - with self.assertRaises(RuntimeError): - self.profile.merge() - - -class PGOConfigurationTest(unittest.TestCase): - def setUp(self): - self.make = (os.environ.get('MAKE') or find_executable('mozmake') or - find_executable('make')) - if not self.make: - self.skipTest('GNU make is required') - self.directory = tempfile.mkdtemp(prefix='pgo configuration ') - config = os.path.join(os.path.dirname(__file__), '..', '..', - 'config', 'config.mk') - with open(config) as stream: - source = stream.read() - start = source.index('# Reject stale Windows PGO configuration') - end = source.index('# Enable profile-based feedback', start) - self.makefile = os.path.join(self.directory, 'Makefile') - with open(self.makefile, 'w') as stream: - stream.write(source[start:end] + '\nall:;\n') - - def tearDown(self): - if hasattr(self, 'directory'): - shutil.rmtree(self.directory) - - def run_make(self, *variables): - process = subprocess.Popen([self.make, '-f', self.makefile] + - list(variables), stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - output = process.communicate()[0].decode('utf-8') - return process.returncode, output - - def test_missing_profdata_fails_before_build(self): - code, output = self.run_make('MOZ_PGO=1', 'CLANG_CL=1', 'LLVM_PROFDATA=') - self.assertNotEqual(code, 0) - self.assertIn('requires LLVM_PROFDATA', output) - - def test_msvc_flags_rejected_for_clang(self): - code, output = self.run_make('MOZ_PGO=1', 'CLANG_CL=1', - 'LLVM_PROFDATA=llvm-profdata.exe', - 'PROFILE_GEN_CFLAGS=-GL') - self.assertNotEqual(code, 0) - self.assertIn('requires LLVM instrumentation flags', output) - - def test_llvm_configuration_accepted(self): - code, output = self.run_make('MOZ_PGO=1', 'CLANG_CL=1', - 'LLVM_PROFDATA=llvm-profdata.exe', - 'PROFILE_GEN_CFLAGS=-clang:-fprofile-instr-generate') - self.assertEqual(code, 0, output) - - def test_non_pgo_and_msvc_unaffected(self): - for variables in [('MOZ_PGO=', 'CLANG_CL=1'), - ('MOZ_PGO=1', 'CLANG_CL=')]: - code, output = self.run_make(*variables) - self.assertEqual(code, 0, output) - - -if __name__ == '__main__': - unittest.main() diff --git a/config/config.mk b/config/config.mk index c35ff0233b..ca04bb25af 100644 --- a/config/config.mk +++ b/config/config.mk @@ -210,25 +210,13 @@ ifdef CPP_UNIT_TESTS NO_PROFILE_GUIDED_OPTIMIZE = 1 endif -# Reject stale Windows PGO configuration before compiling the first pass. -ifdef MOZ_PGO -ifdef CLANG_CL -ifeq ($(strip $(LLVM_PROFDATA)),) -$(error clang-cl PGO requires LLVM_PROFDATA; rerun ./mach configure) -endif -ifeq ($(filter -clang:-fprofile-instr-generate,$(PROFILE_GEN_CFLAGS)),) -$(error clang-cl PGO requires LLVM instrumentation flags; rerun ./mach configure) -endif -endif -endif - # Enable profile-based feedback ifneq (1,$(NO_PROFILE_GUIDED_OPTIMIZE)) ifdef MOZ_PROFILE_GENERATE OS_CFLAGS += $(if $(filter $(notdir $<),$(notdir $(NO_PROFILE_GUIDED_OPTIMIZE))),,$(PROFILE_GEN_CFLAGS)) OS_CXXFLAGS += $(if $(filter $(notdir $<),$(notdir $(NO_PROFILE_GUIDED_OPTIMIZE))),,$(PROFILE_GEN_CFLAGS)) OS_LDFLAGS += $(PROFILE_GEN_LDFLAGS) -ifeq ($(OS_ARCH)_$(CLANG_CL),WINNT_) +ifeq (WINNT,$(OS_ARCH)) AR_FLAGS += -LTCG endif endif # MOZ_PROFILE_GENERATE @@ -237,7 +225,7 @@ ifdef MOZ_PROFILE_USE OS_CFLAGS += $(if $(filter $(notdir $<),$(notdir $(NO_PROFILE_GUIDED_OPTIMIZE))),,$(PROFILE_USE_CFLAGS)) OS_CXXFLAGS += $(if $(filter $(notdir $<),$(notdir $(NO_PROFILE_GUIDED_OPTIMIZE))),,$(PROFILE_USE_CFLAGS)) OS_LDFLAGS += $(PROFILE_USE_LDFLAGS) -ifeq ($(OS_ARCH)_$(CLANG_CL),WINNT_) +ifeq (WINNT,$(OS_ARCH)) AR_FLAGS += -LTCG endif endif # MOZ_PROFILE_USE diff --git a/config/rules.mk b/config/rules.mk index f86d8ead20..2078a5160c 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -531,7 +531,7 @@ endif ############################################## ifneq (1,$(NO_PROFILE_GUIDED_OPTIMIZE)) ifdef MOZ_PROFILE_USE -ifeq ($(OS_ARCH)_$(GNU_CC)_$(CLANG_CL), WINNT__) +ifeq ($(OS_ARCH)_$(GNU_CC), WINNT_) # When building with PGO, we have to make sure to re-link # in the MOZ_PROFILE_USE phase if we linked in the # MOZ_PROFILE_GENERATE phase. We'll touch this pgo.relink @@ -563,7 +563,7 @@ endif # MOZ_PROFILE_USE ifdef MOZ_PROFILE_GENERATE # Clean up profiling data during PROFILE_GENERATE phase export:: -ifeq ($(OS_ARCH)_$(GNU_CC)_$(CLANG_CL), WINNT__) +ifeq ($(OS_ARCH)_$(GNU_CC), WINNT_) $(foreach pgd,$(wildcard *.pgd),pgomgr -clear $(pgd);) else ifdef GNU_CC diff --git a/dom/media/DecoderTraits.cpp b/dom/media/DecoderTraits.cpp index 891da0e488..0795830528 100644 --- a/dom/media/DecoderTraits.cpp +++ b/dom/media/DecoderTraits.cpp @@ -34,6 +34,11 @@ #include "FlacDecoder.h" #include "FlacDemuxer.h" +#ifdef MOZ_DIRECTSHOW +#include "DirectShowDecoder.h" +#include "DirectShowReader.h" +#endif + #include "nsPluginHost.h" #include "MediaPrefs.h" @@ -138,6 +143,14 @@ IsFlacSupportedType(const nsACString& aType, return FlacDecoder::CanHandleMediaType(aType, aCodecs); } +#ifdef MOZ_DIRECTSHOW +static bool +IsDirectShowSupportedType(const nsACString& aType) +{ + return DirectShowDecoder::GetSupportedCodecs(aType, nullptr); +} +#endif + static CanPlayStatus CanHandleCodecsType(const MediaContentType& aType, @@ -197,6 +210,11 @@ CanHandleCodecsType(const MediaContentType& aType, if (IsFlacSupportedType(aType.GetMIMEType(), aType.GetCodecs())) { return CANPLAY_YES; } +#ifdef MOZ_DIRECTSHOW + if (IsDirectShowSupportedType(aType.GetMIMEType())) { + return CANPLAY_YES; + } +#endif if (!codecList) { return CANPLAY_MAYBE; } @@ -258,6 +276,11 @@ CanHandleMediaType(const MediaContentType& aType, if (IsFlacSupportedType(aType.GetMIMEType())) { return CANPLAY_MAYBE; } +#ifdef MOZ_DIRECTSHOW + if (IsDirectShowSupportedType(aType.GetMIMEType())) { + return CANPLAY_MAYBE; + } +#endif return CANPLAY_NO; } @@ -347,6 +370,13 @@ InstantiateDecoder(const nsACString& aType, return decoder.forget(); } +#ifdef MOZ_DIRECTSHOW + if (IsDirectShowSupportedType(aType)) { + decoder = new DirectShowDecoder(aOwner); + return decoder.forget(); + } +#endif + return nullptr; } @@ -393,6 +423,11 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac decoderReader = new MediaFormatReader(aDecoder, new WebMDemuxer(aDecoder->GetResource())); } +#ifdef MOZ_DIRECTSHOW + else if (IsDirectShowSupportedType(aType)) { + decoderReader = new DirectShowReader(aDecoder); + } +#endif return decoderReader; } @@ -418,6 +453,9 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType) IsAACSupportedType(aType) || IsWaveSupportedType(aType) || IsFlacSupportedType(aType) || +#ifdef MOZ_DIRECTSHOW + IsDirectShowSupportedType(aType) || +#endif false; } diff --git a/dom/media/directshow/AudioSinkFilter.cpp b/dom/media/directshow/AudioSinkFilter.cpp new file mode 100644 index 0000000000..58a329338f --- /dev/null +++ b/dom/media/directshow/AudioSinkFilter.cpp @@ -0,0 +1,290 @@ +/* -*- 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 "SampleSink.h" +#include "AudioSinkFilter.h" +#include "AudioSinkInputPin.h" +#include "VideoUtils.h" +#include "mozilla/Logging.h" + + +#include +#include + +#define DELETE_RESET(p) { delete (p) ; (p) = nullptr ;} + +DEFINE_GUID(CLSID_MozAudioSinkFilter, 0x1872d8c8, 0xea8d, 0x4c34, 0xae, 0x96, 0x69, 0xde, + 0xf1, 0x33, 0x7b, 0x33); + +using namespace mozilla::media; + +namespace mozilla { + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +AudioSinkFilter::AudioSinkFilter(const wchar_t* aObjectName, HRESULT* aOutResult) + : BaseFilter(aObjectName, CLSID_MozAudioSinkFilter), + mFilterCritSec("AudioSinkFilter::mFilterCritSec") +{ + (*aOutResult) = S_OK; + mInputPin = new AudioSinkInputPin(L"AudioSinkInputPin", + this, + &mFilterCritSec, + aOutResult); +} + +AudioSinkFilter::~AudioSinkFilter() +{ +} + +int +AudioSinkFilter::GetPinCount() +{ + return 1; +} + +BasePin* +AudioSinkFilter::GetPin(int aIndex) +{ + CriticalSectionAutoEnter lockFilter(mFilterCritSec); + return (aIndex == 0) ? static_cast(mInputPin) : nullptr; +} + +HRESULT +AudioSinkFilter::Pause() +{ + CriticalSectionAutoEnter lockFilter(mFilterCritSec); + if (mState == State_Stopped) { + // Change the state, THEN activate the input pin. + mState = State_Paused; + if (mInputPin && mInputPin->IsConnected()) { + mInputPin->Active(); + } + } else if (mState == State_Running) { + mState = State_Paused; + } + return S_OK; +} + +HRESULT +AudioSinkFilter::Stop() +{ + CriticalSectionAutoEnter lockFilter(mFilterCritSec); + mState = State_Stopped; + if (mInputPin) { + mInputPin->Inactive(); + } + + GetSampleSink()->Flush(); + + return S_OK; +} + +HRESULT +AudioSinkFilter::Run(REFERENCE_TIME tStart) +{ + LOG("AudioSinkFilter::Run(%lld) [%4.2lf]", + RefTimeToUsecs(tStart), + double(RefTimeToUsecs(tStart)) / USECS_PER_S); + return media::BaseFilter::Run(tStart); +} + +HRESULT +AudioSinkFilter::GetClassID( OUT CLSID * pCLSID ) +{ + (* pCLSID) = CLSID_MozAudioSinkFilter; + return S_OK; +} + +HRESULT +AudioSinkFilter::QueryInterface(REFIID aIId, void **aInterface) +{ + if (aIId == IID_IMediaSeeking) { + *aInterface = static_cast(this); + AddRef(); + return S_OK; + } + return mozilla::media::BaseFilter::QueryInterface(aIId, aInterface); +} + +ULONG +AudioSinkFilter::AddRef() +{ + return ::InterlockedIncrement(&mRefCnt); +} + +ULONG +AudioSinkFilter::Release() +{ + unsigned long newRefCnt = ::InterlockedDecrement(&mRefCnt); + if (!newRefCnt) { + delete this; + } + return newRefCnt; +} + +SampleSink* +AudioSinkFilter::GetSampleSink() +{ + return mInputPin->GetSampleSink(); +} + +const ::VIDEOINFOHEADER* +AudioSinkFilter::GetVideoInfo() const +{ + return mInputPin->GetVideoInfo(); +} + + +// IMediaSeeking implementation. +// +// Calls to IMediaSeeking are forwarded to the output pin that the +// AudioSinkInputPin is connected to, i.e. upstream towards the parser and +// source filters, which actually implement seeking. +#define ENSURE_CONNECTED_PIN_SEEKING \ + if (!mInputPin) { \ + return E_NOTIMPL; \ + } \ + RefPtr pinSeeking = mInputPin->GetConnectedPinSeeking(); \ + if (!pinSeeking) { \ + return E_NOTIMPL; \ + } + +HRESULT +AudioSinkFilter::GetCapabilities(DWORD* aCapabilities) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetCapabilities(aCapabilities); +} + +HRESULT +AudioSinkFilter::CheckCapabilities(DWORD* aCapabilities) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->CheckCapabilities(aCapabilities); +} + +HRESULT +AudioSinkFilter::IsFormatSupported(const GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->IsFormatSupported(aFormat); +} + +HRESULT +AudioSinkFilter::QueryPreferredFormat(GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->QueryPreferredFormat(aFormat); +} + +HRESULT +AudioSinkFilter::GetTimeFormat(GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetTimeFormat(aFormat); +} + +HRESULT +AudioSinkFilter::IsUsingTimeFormat(const GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->IsUsingTimeFormat(aFormat); +} + +HRESULT +AudioSinkFilter::SetTimeFormat(const GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->SetTimeFormat(aFormat); +} + +HRESULT +AudioSinkFilter::GetDuration(LONGLONG* aDuration) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetDuration(aDuration); +} + +HRESULT +AudioSinkFilter::GetStopPosition(LONGLONG* aStop) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetStopPosition(aStop); +} + +HRESULT +AudioSinkFilter::GetCurrentPosition(LONGLONG* aCurrent) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetCurrentPosition(aCurrent); +} + +HRESULT +AudioSinkFilter::ConvertTimeFormat(LONGLONG* aTarget, + const GUID* aTargetFormat, + LONGLONG aSource, + const GUID* aSourceFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->ConvertTimeFormat(aTarget, + aTargetFormat, + aSource, + aSourceFormat); +} + +HRESULT +AudioSinkFilter::SetPositions(LONGLONG* aCurrent, + DWORD aCurrentFlags, + LONGLONG* aStop, + DWORD aStopFlags) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->SetPositions(aCurrent, + aCurrentFlags, + aStop, + aStopFlags); +} + +HRESULT +AudioSinkFilter::GetPositions(LONGLONG* aCurrent, + LONGLONG* aStop) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetPositions(aCurrent, aStop); +} + +HRESULT +AudioSinkFilter::GetAvailable(LONGLONG* aEarliest, + LONGLONG* aLatest) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetAvailable(aEarliest, aLatest); +} + +HRESULT +AudioSinkFilter::SetRate(double aRate) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->SetRate(aRate); +} + +HRESULT +AudioSinkFilter::GetRate(double* aRate) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetRate(aRate); +} + +HRESULT +AudioSinkFilter::GetPreroll(LONGLONG* aPreroll) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetPreroll(aPreroll); +} + +} // namespace mozilla diff --git a/dom/media/directshow/AudioSinkFilter.h b/dom/media/directshow/AudioSinkFilter.h new file mode 100644 index 0000000000..2170d45f00 --- /dev/null +++ b/dom/media/directshow/AudioSinkFilter.h @@ -0,0 +1,96 @@ +/* -*- 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/. */ + +#if !defined(AudioSinkFilter_h_) +#define AudioSinkFilter_h_ + +#include "BaseFilter.h" +#include "DirectShowUtils.h" +#include "nsAutoPtr.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class AudioSinkInputPin; +class SampleSink; + +// Filter that acts as the end of the graph. Audio samples input into +// this filter block the calling thread, and the calling thread is +// unblocked when the decode thread extracts the sample. The samples +// input into this filter are stored in the SampleSink, where the blocking +// is implemented. The input pin owns the SampleSink. +class AudioSinkFilter: public mozilla::media::BaseFilter, + public IMediaSeeking +{ + +public: + AudioSinkFilter(const wchar_t* aObjectName, HRESULT* aOutResult); + virtual ~AudioSinkFilter(); + + // Gets the input pin's sample sink. + SampleSink* GetSampleSink(); + const ::VIDEOINFOHEADER* GetVideoInfo() const; + + // IUnknown implementation. + STDMETHODIMP QueryInterface(REFIID aIId, void **aInterface); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // -------------------------------------------------------------------- + // CBaseFilter methods + int GetPinCount (); + mozilla::media::BasePin* GetPin ( IN int Index); + STDMETHODIMP Pause (); + STDMETHODIMP Stop (); + STDMETHODIMP GetClassID ( OUT CLSID * pCLSID); + STDMETHODIMP Run(REFERENCE_TIME tStart); + // IMediaSeeking Methods... + + // We defer to SourceFilter, but we must expose the interface on + // the output pins. Seeking commands come upstream from the renderers, + // but they must be actioned at the source filters. + STDMETHODIMP GetCapabilities(DWORD* aCapabilities); + STDMETHODIMP CheckCapabilities(DWORD* aCapabilities); + STDMETHODIMP IsFormatSupported(const GUID* aFormat); + STDMETHODIMP QueryPreferredFormat(GUID* aFormat); + STDMETHODIMP GetTimeFormat(GUID* aFormat); + STDMETHODIMP IsUsingTimeFormat(const GUID* aFormat); + STDMETHODIMP SetTimeFormat(const GUID* aFormat); + STDMETHODIMP GetDuration(LONGLONG* pDuration); + STDMETHODIMP GetStopPosition(LONGLONG* pStop); + STDMETHODIMP GetCurrentPosition(LONGLONG* aCurrent); + STDMETHODIMP ConvertTimeFormat(LONGLONG* aTarget, + const GUID* aTargetFormat, + LONGLONG aSource, + const GUID* aSourceFormat); + STDMETHODIMP SetPositions(LONGLONG* aCurrent, + DWORD aCurrentFlags, + LONGLONG* aStop, + DWORD aStopFlags); + STDMETHODIMP GetPositions(LONGLONG* aCurrent, + LONGLONG* aStop); + STDMETHODIMP GetAvailable(LONGLONG* aEarliest, + LONGLONG* aLatest); + STDMETHODIMP SetRate(double aRate); + STDMETHODIMP GetRate(double* aRate); + STDMETHODIMP GetPreroll(LONGLONG* aPreroll); + + // -------------------------------------------------------------------- + // class factory calls this + static IUnknown * CreateInstance (IN LPUNKNOWN punk, OUT HRESULT * phr); + +private: + CriticalSection mFilterCritSec; + + // Note: The input pin defers its refcounting to the sink filter, so when + // the input pin is addrefed, what actually happens is the sink filter is + // addrefed. + nsAutoPtr mInputPin; +}; + +} // namespace mozilla + +#endif // AudioSinkFilter_h_ diff --git a/dom/media/directshow/AudioSinkInputPin.cpp b/dom/media/directshow/AudioSinkInputPin.cpp new file mode 100644 index 0000000000..c1ff9d55ea --- /dev/null +++ b/dom/media/directshow/AudioSinkInputPin.cpp @@ -0,0 +1,210 @@ +/* -*- 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 "AudioSinkInputPin.h" +#include "AudioSinkFilter.h" +#include "SampleSink.h" +#include "mozilla/Logging.h" + +#include + +using namespace mozilla::media; + +namespace mozilla { + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +AudioSinkInputPin::AudioSinkInputPin(wchar_t* aObjectName, + AudioSinkFilter* aFilter, + mozilla::CriticalSection* aLock, + HRESULT* aOutResult) + : BaseInputPin(aObjectName, aFilter, aLock, aOutResult, aObjectName), + mSegmentStartTime(0) +{ + MOZ_COUNT_CTOR(AudioSinkInputPin); + mSampleSink = new SampleSink(); + memset(&mVideoInfo, 0, sizeof(mVideoInfo)); +} + +AudioSinkInputPin::~AudioSinkInputPin() +{ + MOZ_COUNT_DTOR(AudioSinkInputPin); +} + +HRESULT +AudioSinkInputPin::GetMediaType(int aPosition, MediaType* aOutMediaType) +{ + NS_ENSURE_TRUE(aPosition >= 0, E_INVALIDARG); + NS_ENSURE_TRUE(aOutMediaType, E_POINTER); + + // Note: We set output as PCM, as IEEE_FLOAT only works when using the + // MP3 decoder as an MFT, and we can't do that while using DirectShow. + aOutMediaType->SetType(&MEDIATYPE_Audio); + aOutMediaType->SetSubtype(&MEDIASUBTYPE_PCM); + aOutMediaType->SetFormatType(&FORMAT_WaveFormatEx); + aOutMediaType->SetTemporalCompression(FALSE); + + if (aPosition == 0) { + return S_OK; + } + if (aPosition == 1) { + aOutMediaType->SetType(&MEDIATYPE_Video); + aOutMediaType->SetSubtype(&MEDIASUBTYPE_YUY2); + aOutMediaType->SetFormatType(&FORMAT_VideoInfo); + aOutMediaType->SetTemporalCompression(FALSE); + return S_OK; + } + return S_FALSE; +} + +HRESULT +AudioSinkInputPin::CheckMediaType(const MediaType* aMediaType) +{ + if (!aMediaType) { + return E_INVALIDARG; + } + + GUID majorType = *aMediaType->Type(); + if (majorType == MEDIATYPE_Video && + *aMediaType->Subtype() == MEDIASUBTYPE_YUY2 && + *aMediaType->FormatType() == FORMAT_VideoInfo) { + if (aMediaType->cbFormat >= sizeof(VIDEOINFOHEADER)) { + memcpy(&mVideoInfo, aMediaType->pbFormat, sizeof(VIDEOINFOHEADER)); + } + return S_OK; + } + + if (majorType != MEDIATYPE_Audio && majorType != WMMEDIATYPE_Audio) { + return E_INVALIDARG; + } + + if (*aMediaType->Subtype() != MEDIASUBTYPE_PCM) { + return E_INVALIDARG; + } + + if (*aMediaType->FormatType() != FORMAT_WaveFormatEx) { + return E_INVALIDARG; + } + + // We accept the media type, stash its layout format! + WAVEFORMATEX* wfx = (WAVEFORMATEX*)(aMediaType->pbFormat); + GetSampleSink()->SetAudioFormat(wfx); + + return S_OK; +} + +AudioSinkFilter* +AudioSinkInputPin::GetAudioSinkFilter() +{ + return reinterpret_cast(mFilter); +} + +SampleSink* +AudioSinkInputPin::GetSampleSink() +{ + return mSampleSink; +} + +HRESULT +AudioSinkInputPin::SetAbsoluteMediaTime(IMediaSample* aSample) +{ + HRESULT hr; + REFERENCE_TIME start = 0, end = 0; + hr = aSample->GetTime(&start, &end); + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + { + CriticalSectionAutoEnter lock(*mLock); + start += mSegmentStartTime; + end += mSegmentStartTime; + } + hr = aSample->SetMediaTime(&start, &end); + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + return S_OK; +} + +HRESULT +AudioSinkInputPin::Receive(IMediaSample* aSample ) +{ + HRESULT hr; + NS_ENSURE_TRUE(aSample, E_POINTER); + + hr = BaseInputPin::Receive(aSample); + if (SUCCEEDED(hr) && hr != S_FALSE) { // S_FALSE == flushing + // Set the timestamp of the sample after being adjusted for + // seeking/segments in the "media time" attribute. When we seek, + // DirectShow starts a new "segment", and starts labeling samples + // from time=0 again, so we need to correct for this to get the + // actual timestamps after seeking. + hr = SetAbsoluteMediaTime(aSample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + hr = GetSampleSink()->Receive(aSample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + } + return S_OK; +} + +already_AddRefed +AudioSinkInputPin::GetConnectedPinSeeking() +{ + RefPtr peer = GetConnected(); + if (!peer) + return nullptr; + RefPtr seeking; + peer->QueryInterface(static_cast(getter_AddRefs(seeking))); + return seeking.forget(); +} + +HRESULT +AudioSinkInputPin::BeginFlush() +{ + HRESULT hr = media::BaseInputPin::BeginFlush(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + GetSampleSink()->Flush(); + + return S_OK; +} + +HRESULT +AudioSinkInputPin::EndFlush() +{ + HRESULT hr = media::BaseInputPin::EndFlush(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Reset the EOS flag, so that if we're called after a seek we still work. + GetSampleSink()->Reset(); + + return S_OK; +} + +HRESULT +AudioSinkInputPin::EndOfStream(void) +{ + HRESULT hr = media::BaseInputPin::EndOfStream(); + if (FAILED(hr) || hr == S_FALSE) { + // Pin is stil flushing. + return hr; + } + GetSampleSink()->SetEOS(); + + return S_OK; +} + + +HRESULT +AudioSinkInputPin::NewSegment(REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + CriticalSectionAutoEnter lock(*mLock); + // Record the start time of the new segment, so that we can store the + // correct absolute timestamp in the "media time" each incoming sample. + mSegmentStartTime = tStart; + return S_OK; +} + +} // namespace mozilla diff --git a/dom/media/directshow/AudioSinkInputPin.h b/dom/media/directshow/AudioSinkInputPin.h new file mode 100644 index 0000000000..17650774f0 --- /dev/null +++ b/dom/media/directshow/AudioSinkInputPin.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#if !defined(AudioSinkInputPin_h_) +#define AudioSinkInputPin_h_ + +#include "BaseInputPin.h" +#include "DirectShowUtils.h" +#include "mozilla/RefPtr.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +namespace media { + class MediaType; +} + +class AudioSinkFilter; +class SampleSink; + + +// Input pin for capturing audio output of a DirectShow filter graph. +// This is the input pin for the AudioSinkFilter. +class AudioSinkInputPin: public mozilla::media::BaseInputPin +{ +public: + AudioSinkInputPin(wchar_t* aObjectName, + AudioSinkFilter* aFilter, + mozilla::CriticalSection* aLock, + HRESULT* aOutResult); + virtual ~AudioSinkInputPin(); + + HRESULT GetMediaType (IN int iPos, OUT mozilla::media::MediaType * pmt); + HRESULT CheckMediaType (IN const mozilla::media::MediaType * pmt); + STDMETHODIMP Receive (IN IMediaSample *); + STDMETHODIMP BeginFlush() override; + STDMETHODIMP EndFlush() override; + + // Called when we start decoding a new segment, that happens directly after + // a seek. This captures the segment's start time. Samples decoded by the + // MP3 decoder have their timestamps offset from the segment start time. + // Storing the segment start time enables us to set each sample's MediaTime + // as an offset in the stream relative to the start of the stream, rather + // than the start of the segment, i.e. its absolute time in the stream. + STDMETHODIMP NewSegment(REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) override; + + STDMETHODIMP EndOfStream() override; + + // Returns the IMediaSeeking interface of the connected output pin. + // We forward seeking requests upstream from the sink to the source + // filters. + already_AddRefed GetConnectedPinSeeking(); + + SampleSink* GetSampleSink(); + const VIDEOINFOHEADER* GetVideoInfo() const { return &mVideoInfo; } + +private: + AudioSinkFilter* GetAudioSinkFilter(); + + // Sets the media time on the media sample, relative to the segment + // start time. + HRESULT SetAbsoluteMediaTime(IMediaSample* aSample); + + nsAutoPtr mSampleSink; + + // Synchronized by the filter lock; BaseInputPin::mLock. + REFERENCE_TIME mSegmentStartTime; + VIDEOINFOHEADER mVideoInfo; +}; + +} // namespace mozilla + +#endif // AudioSinkInputPin_h_ diff --git a/dom/media/directshow/DirectShowDecoder.cpp b/dom/media/directshow/DirectShowDecoder.cpp new file mode 100644 index 0000000000..d10c850e2f --- /dev/null +++ b/dom/media/directshow/DirectShowDecoder.cpp @@ -0,0 +1,78 @@ +/* -*- 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 "DirectShowDecoder.h" +#include "DirectShowReader.h" +#include "DirectShowUtils.h" +#include "MediaDecoderStateMachine.h" +#include "mozilla/Preferences.h" +#include "mozilla/WindowsVersion.h" + +namespace mozilla { + +MediaDecoderStateMachine* DirectShowDecoder::CreateStateMachine() +{ + return new MediaDecoderStateMachine(this, new DirectShowReader(this)); +} + +/* static */ +bool +DirectShowDecoder::GetSupportedCodecs(const nsACString& aType, + char const *const ** aCodecList) +{ + if (!IsEnabled()) { + return false; + } + + static char const *const mp3AudioCodecs[] = { + "mp3", + nullptr + }; + if (aType.EqualsASCII("audio/mpeg") || + aType.EqualsASCII("audio/mp3")) { + if (aCodecList) { + *aCodecList = mp3AudioCodecs; + } + return true; + } + + static char const *const h264VideoCodecs[] = { + "avc1", + "h264", + nullptr + }; + if ((aType.EqualsASCII("video/h264") || + aType.EqualsASCII("video/avc")) && + CanDecodeH264UsingDirectShow()) { + if (aCodecList) { + *aCodecList = h264VideoCodecs; + } + return true; + } + + return false; +} + +/* static */ +bool +DirectShowDecoder::IsEnabled() +{ + return Preferences::GetBool("media.directshow.enabled") && + (CanDecodeMP3UsingDirectShow() || CanDecodeH264UsingDirectShow()); +} + +DirectShowDecoder::DirectShowDecoder(MediaDecoderOwner* aOwner) + : MediaDecoder(aOwner) +{ + MOZ_COUNT_CTOR(DirectShowDecoder); +} + +DirectShowDecoder::~DirectShowDecoder() +{ + MOZ_COUNT_DTOR(DirectShowDecoder); +} + +} // namespace mozilla diff --git a/dom/media/directshow/DirectShowDecoder.h b/dom/media/directshow/DirectShowDecoder.h new file mode 100644 index 0000000000..90b72aef1e --- /dev/null +++ b/dom/media/directshow/DirectShowDecoder.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#if !defined(DirectShowDecoder_h_) +#define DirectShowDecoder_h_ + +#include "MediaDecoder.h" + +namespace mozilla { + +// Decoder that uses the legacy DirectShow graph on systems without WMF. +class DirectShowDecoder : public MediaDecoder +{ +public: + + explicit DirectShowDecoder(MediaDecoderOwner* aOwner); + virtual ~DirectShowDecoder(); + + MediaDecoder* Clone(MediaDecoderOwner* aOwner) override { + if (!IsEnabled()) { + return nullptr; + } + return new DirectShowDecoder(aOwner); + } + + MediaDecoderStateMachine* CreateStateMachine() override; + + // Returns true if aType is a MIME type that we render with the + // DirectShow backend. If aCodecList is non null, + // it is filled with a (static const) null-terminated list of strings + // denoting the codecs we'll playback. Note that playback is strictly + // limited to MP3 only. + static bool GetSupportedCodecs(const nsACString& aType, + char const *const ** aCodecList); + + // Returns true if the DirectShow backend is preffed on. + static bool IsEnabled(); +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/directshow/DirectShowReader.cpp b/dom/media/directshow/DirectShowReader.cpp new file mode 100644 index 0000000000..cc7278146e --- /dev/null +++ b/dom/media/directshow/DirectShowReader.cpp @@ -0,0 +1,417 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "DirectShowReader.h" +#include "MediaDecoderReader.h" +#include "mozilla/RefPtr.h" +#include "DirectShowUtils.h" +#include "AudioSinkFilter.h" +#include "SourceFilter.h" +#include "SampleSink.h" +#include "VideoUtils.h" +#include "mozilla/Preferences.h" + +using namespace mozilla::media; + +namespace mozilla { + +// Windows XP's MP3 decoder filter. This is available on XP only, on Vista +// and later we can use the DMO Wrapper filter and MP3 decoder DMO. +const GUID DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER = +{ 0x38BE3000, 0xDBF4, 0x11D0, {0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D} }; + + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder) + : MediaDecoderReader(aDecoder), +#ifdef DIRECTSHOW_REGISTER_GRAPH + mRotRegister(0), +#endif + mNumChannels(0), + mAudioRate(0), + mBytesPerSample(0), + mIsH264(aDecoder->GetResource()->GetContentType().EqualsLiteral("video/h264") || + aDecoder->GetResource()->GetContentType().EqualsLiteral("video/avc")) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + MOZ_COUNT_CTOR(DirectShowReader); + if (mIsH264) { + Preferences::SetBool("media.directshow.h264.active", true); + } +} + +DirectShowReader::~DirectShowReader() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + MOZ_COUNT_DTOR(DirectShowReader); + if (mIsH264) { + Preferences::SetBool("media.directshow.h264.active", false); + } +#ifdef DIRECTSHOW_REGISTER_GRAPH + if (mRotRegister) { + RemoveGraphFromRunningObjectTable(mRotRegister); + } +#endif +} + +nsresult +DirectShowReader::ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) +{ + MOZ_ASSERT(OnTaskQueue()); + HRESULT hr; + nsresult rv; + + // Create the filter graph, reference it by the GraphBuilder interface, + // to make graph building more convenient. + hr = CoCreateInstance(CLSID_FilterGraph, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IGraphBuilder, + reinterpret_cast(static_cast(getter_AddRefs(mGraph)))); + NS_ENSURE_TRUE(SUCCEEDED(hr) && mGraph, NS_ERROR_FAILURE); + +#ifdef DIRECTSHOW_REGISTER_GRAPH + hr = AddGraphToRunningObjectTable(mGraph, &mRotRegister); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + #endif + + // Extract the interface pointers we'll need from the filter graph. + hr = mGraph->QueryInterface(static_cast(getter_AddRefs(mControl))); + NS_ENSURE_TRUE(SUCCEEDED(hr) && mControl, NS_ERROR_FAILURE); + + hr = mGraph->QueryInterface(static_cast(getter_AddRefs(mMediaSeeking))); + NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE); + + if (mIsH264) { + return ReadH264Metadata(aInfo, aTags); + } + + // Build the graph. Create the filters we need, and connect them. We + // build the entire graph ourselves to prevent other decoders installed + // on the system being created and used. + + // Our source filters, wraps the MediaResource. + mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio); + NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE); + + rv = mSourceFilter->Init(mDecoder->GetResource(), 0); + NS_ENSURE_SUCCESS(rv, rv); + + hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource"); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // The MPEG demuxer. + RefPtr demuxer; + hr = CreateAndAddFilter(mGraph, + CLSID_MPEG1Splitter, + L"MPEG1Splitter", + getter_AddRefs(demuxer)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // Platform MP3 decoder. + RefPtr decoder; + // Firstly try to create the MP3 decoder filter that ships with WinXP + // directly. This filter doesn't normally exist on later versions of + // Windows. + hr = CreateAndAddFilter(mGraph, + CLSID_MPEG_LAYER_3_DECODER_FILTER, + L"MPEG Layer 3 Decoder", + getter_AddRefs(decoder)); + if (FAILED(hr)) { + // Failed to create MP3 decoder filter. Try to instantiate + // the MP3 decoder DMO. + hr = AddMP3DMOWrapperFilter(mGraph, getter_AddRefs(decoder)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + } + + // Sink, captures audio samples and inserts them into our pipeline. + static const wchar_t* AudioSinkFilterName = L"MozAudioSinkFilter"; + mAudioSinkFilter = new AudioSinkFilter(AudioSinkFilterName, &hr); + NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE); + hr = mGraph->AddFilter(mAudioSinkFilter, AudioSinkFilterName); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // Join the filters. + hr = ConnectFilters(mGraph, mSourceFilter, demuxer); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = ConnectFilters(mGraph, demuxer, decoder); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + WAVEFORMATEX format; + mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format); + NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE); + + mInfo.mAudio.mChannels = mNumChannels = format.nChannels; + mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec; + mInfo.mAudio.mBitDepth = format.wBitsPerSample; + mBytesPerSample = format.wBitsPerSample / 8; + + // Begin decoding! + hr = mControl->Run(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + DWORD seekCaps = 0; + hr = mMediaSeeking->GetCapabilities(&seekCaps); + mInfo.mMediaSeekable = SUCCEEDED(hr) && (AM_SEEKING_CanSeekAbsolute & seekCaps); + + LOG("Successfully initialized DirectShow MP3 decoder."); + LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d", + mInfo.mAudio.mChannels, + mInfo.mAudio.mRate, + 0LL, + mBytesPerSample); + + *aInfo = mInfo; + // Note: The SourceFilter strips ID3v2 tags out of the stream. + *aTags = nullptr; + + return NS_OK; +} + +nsresult +DirectShowReader::ReadH264Metadata(MediaInfo* aInfo, MetadataTags** aTags) +{ + HRESULT hr; + mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_H264); + NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_OUT_OF_MEMORY); + nsresult rv = mSourceFilter->Init(mDecoder->GetResource(), 0); + NS_ENSURE_SUCCESS(rv, rv); + hr = mGraph->AddFilter(mSourceFilter, L"Mozilla H.264 source"); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + RefPtr decoder; + hr = AddH264DecoderFilter(mGraph, getter_AddRefs(decoder)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + mAudioSinkFilter = new AudioSinkFilter(L"Mozilla H.264 video sink", &hr); + NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE); + hr = mGraph->AddFilter(mAudioSinkFilter, L"Mozilla H.264 video sink"); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + hr = ConnectFilters(mGraph, mSourceFilter, decoder); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + const VIDEOINFOHEADER* vih = mAudioSinkFilter->GetVideoInfo(); + int32_t width = abs(vih->bmiHeader.biWidth); + int32_t height = abs(vih->bmiHeader.biHeight); + NS_ENSURE_TRUE(width > 0 && height > 0, NS_ERROR_FAILURE); + mInfo.mVideo = VideoInfo(width, height); + mInfo.mVideo.mMimeType = "video/h264"; + hr = mControl->Run(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + *aInfo = mInfo; + *aTags = nullptr; + return NS_OK; +} + +inline float +UnsignedByteToAudioSample(uint8_t aValue) +{ + return aValue * (2.0f / UINT8_MAX) - 1.0f; +} + +bool +DirectShowReader::Finish(HRESULT aStatus) +{ + MOZ_ASSERT(OnTaskQueue()); + + LOG("DirectShowReader::Finish(0x%x)", aStatus); + // Notify the filter graph of end of stream. + RefPtr eventSink; + HRESULT hr = mGraph->QueryInterface(static_cast(getter_AddRefs(eventSink))); + if (SUCCEEDED(hr) && eventSink) { + eventSink->Notify(EC_COMPLETE, aStatus, 0); + } + return false; +} + +class DirectShowCopy +{ +public: + DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample, + uint32_t aSamples, uint32_t aChannels) + : mSource(aSource) + , mBytesPerSample(aBytesPerSample) + , mSamples(aSamples) + , mChannels(aChannels) + , mNextSample(0) + { } + + uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples) + { + uint32_t maxSamples = std::min(aSamples, mSamples - mNextSample); + uint32_t frames = maxSamples / mChannels; + size_t byteOffset = mNextSample * mBytesPerSample; + if (mBytesPerSample == 1) { + for (uint32_t i = 0; i < maxSamples; ++i) { + uint8_t *sample = mSource + byteOffset; + aBuffer[i] = UnsignedByteToAudioSample(*sample); + byteOffset += mBytesPerSample; + } + } else if (mBytesPerSample == 2) { + for (uint32_t i = 0; i < maxSamples; ++i) { + int16_t *sample = reinterpret_cast(mSource + byteOffset); + aBuffer[i] = AudioSampleToFloat(*sample); + byteOffset += mBytesPerSample; + } + } + mNextSample += maxSamples; + return frames; + } + +private: + uint8_t * const mSource; + const uint32_t mBytesPerSample; + const uint32_t mSamples; + const uint32_t mChannels; + uint32_t mNextSample; +}; + +bool +DirectShowReader::DecodeAudioData() +{ + MOZ_ASSERT(OnTaskQueue()); + HRESULT hr; + + SampleSink* sink = mAudioSinkFilter->GetSampleSink(); + if (sink->AtEOS()) { + // End of stream. + return Finish(S_OK); + } + + // Get the next chunk of audio samples. This blocks until the sample + // arrives, or an error occurs (like the stream is shutdown). + RefPtr sample; + hr = sink->Extract(sample); + if (FAILED(hr) || hr == S_FALSE) { + return Finish(hr); + } + + int64_t start = 0, end = 0; + sample->GetMediaTime(&start, &end); + LOG("DirectShowReader::DecodeAudioData [%4.2lf-%4.2lf]", + RefTimeToSeconds(start), + RefTimeToSeconds(end)); + + LONG length = sample->GetActualDataLength(); + LONG numSamples = length / mBytesPerSample; + LONG numFrames = length / mBytesPerSample / mNumChannels; + + BYTE* data = nullptr; + hr = sample->GetPointer(&data); + NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr)); + + mAudioCompactor.Push(mDecoder->GetResource()->Tell(), + RefTimeToUsecs(start), + mInfo.mAudio.mRate, + numFrames, + mNumChannels, + DirectShowCopy(reinterpret_cast(data), + mBytesPerSample, + numSamples, + mNumChannels)); + return true; +} + +bool +DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) +{ + MOZ_ASSERT(OnTaskQueue()); + if (!mIsH264) { + return false; + } + RefPtr sample; + HRESULT hr = mAudioSinkFilter->GetSampleSink()->Extract(sample); + if (FAILED(hr) || hr == S_FALSE) { + return Finish(hr); + } + REFERENCE_TIME start = 0, end = 0; + sample->GetMediaTime(&start, &end); + BYTE* data = nullptr; + NS_ENSURE_TRUE(SUCCEEDED(sample->GetPointer(&data)), Finish(E_FAIL)); + LONG length = sample->GetActualDataLength(); + int32_t width = mInfo.mVideo.mImage.width; + int32_t height = mInfo.mVideo.mImage.height; + int32_t stride = width * 2; + NS_ENSURE_TRUE(length >= stride * height, Finish(E_FAIL)); + nsTArray y; + nsTArray u; + nsTArray v; + y.SetLength(width * height); + u.SetLength((width / 2) * (height / 2)); + v.SetLength((width / 2) * (height / 2)); + for (int32_t row = 0; row < height; ++row) { + const uint8_t* src = data + row * stride; + for (int32_t x = 0; x < width; x += 2) { + int32_t p = row * width + x; + y[p] = src[0]; y[p + 1] = src[2]; + if (!(row & 1)) { + u[(row / 2) * (width / 2) + x / 2] = src[1]; + v[(row / 2) * (width / 2) + x / 2] = src[3]; + } + src += 4; + } + } + VideoData::YCbCrBuffer buffer; + buffer.mPlanes[0] = { y.Elements(), uint32_t(width), uint32_t(height), uint32_t(width), 0, 1 }; + buffer.mPlanes[1] = { u.Elements(), uint32_t(width / 2), uint32_t(height / 2), uint32_t(width / 2), 0, 1 }; + buffer.mPlanes[2] = { v.Elements(), uint32_t(width / 2), uint32_t(height / 2), uint32_t(width / 2), 0, 1 }; + RefPtr video = VideoData::CreateAndCopyData( + mInfo.mVideo, mDecoder->GetImageContainer(), 0, RefTimeToUsecs(start), + RefTimeToUsecs(end - start), buffer, true, -1, + gfx::IntRect(0, 0, width, height)); + NS_ENSURE_TRUE(video, Finish(E_OUTOFMEMORY)); + VideoQueue().Push(video); + return true; +} + +RefPtr +DirectShowReader::Seek(SeekTarget aTarget, int64_t aEndTime) +{ + nsresult res = SeekInternal(aTarget.GetTime().ToMicroseconds()); + if (NS_FAILED(res)) { + return SeekPromise::CreateAndReject(res, __func__); + } else { + return SeekPromise::CreateAndResolve(aTarget.GetTime(), __func__); + } +} + +nsresult +DirectShowReader::SeekInternal(int64_t aTargetUs) +{ + HRESULT hr; + MOZ_ASSERT(OnTaskQueue()); + + LOG("DirectShowReader::Seek() target=%lld", aTargetUs); + + hr = mControl->Pause(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + nsresult rv = ResetDecode(); + NS_ENSURE_SUCCESS(rv, rv); + + LONGLONG seekPosition = UsecsToRefTime(aTargetUs); + hr = mMediaSeeking->SetPositions(&seekPosition, + AM_SEEKING_AbsolutePositioning, + nullptr, + AM_SEEKING_NoPositioning); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = mControl->Run(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/directshow/DirectShowReader.h b/dom/media/directshow/DirectShowReader.h new file mode 100644 index 0000000000..32972517a2 --- /dev/null +++ b/dom/media/directshow/DirectShowReader.h @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +#if !defined(DirectShowReader_h_) +#define DirectShowReader_h_ + +#include "windows.h" // HRESULT, DWORD +#include "MediaDecoderReader.h" +#include "MediaResource.h" +#include "mozilla/RefPtr.h" + +// Add the graph to the Running Object Table so that we can connect +// to this graph with GraphEdit/GraphStudio. Note: you must +// also regsvr32 proppage.dll from the Windows SDK. +// See: http://msdn.microsoft.com/en-us/library/ms787252(VS.85).aspx +// #define DIRECTSHOW_REGISTER_GRAPH + +struct IGraphBuilder; +struct IMediaControl; +struct IMediaSeeking; + +namespace mozilla { + +class AudioSinkFilter; +class SourceFilter; + +// Decoder backend for decoding legacy media using DirectShow. DirectShow operates as +// a filter graph. The basic design of the DirectShowReader is that we have +// a SourceFilter that wraps the MediaResource that connects to the +// MP3 decoder filter. The MP3 decoder filter "pulls" data as it requires it +// downstream on its own thread. When the MP3 decoder has produced a block of +// decoded samples, its thread calls downstream into our AudioSinkFilter, +// passing the decoded buffer in. The AudioSinkFilter inserts the samples into +// a SampleSink object. The SampleSink blocks the MP3 decoder's thread until +// the decode thread calls DecodeAudioData(), whereupon the SampleSink +// releases the decoded samples to the decode thread, and unblocks the MP3 +// decoder's thread. The MP3 decoder can then request more data from the +// SourceFilter, and decode more data. If the decode thread calls +// DecodeAudioData() and there's no decoded samples waiting to be extracted +// in the SampleSink, the SampleSink blocks the decode thread until the MP3 +// decoder produces a decoded sample. +class DirectShowReader : public MediaDecoderReader +{ +public: + DirectShowReader(AbstractMediaDecoder* aDecoder); + + virtual ~DirectShowReader(); + + bool DecodeAudioData() override; + bool DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) override; + + nsresult ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) override; + + RefPtr + Seek(SeekTarget aTarget, int64_t aEndTime) override; + + static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER; + +private: + // Notifies the filter graph that playback is complete. aStatus is + // the code to send to the filter graph. Always returns false, so + // that we can just "return Finish()" from DecodeAudioData(). + bool Finish(HRESULT aStatus); + + nsresult SeekInternal(int64_t aTime); + nsresult ReadH264Metadata(MediaInfo* aInfo, MetadataTags** aTags); + + // DirectShow filter graph, and associated playback and seeking + // control interfaces. + RefPtr mGraph; + RefPtr mControl; + RefPtr mMediaSeeking; + + // Wraps the MediaResource, and feeds undecoded data into the filter graph. + RefPtr mSourceFilter; + + // Sits at the end of the graph, removing decoded samples from the graph. + // The graph will block while this is blocked, i.e. it will pause decoding. + RefPtr mAudioSinkFilter; + bool mIsH264; + +#ifdef DIRECTSHOW_REGISTER_GRAPH + // Used to add/remove the filter graph to the Running Object Table. You can + // connect GraphEdit/GraphStudio to the graph to observe and/or debug its + // topology and state. + DWORD mRotRegister; +#endif + + // Number of channels in the audio stream. + uint32_t mNumChannels; + + // Samples per second in the audio stream. + uint32_t mAudioRate; + + // Number of bytes per sample. Can be either 1 or 2. + uint32_t mBytesPerSample; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/directshow/DirectShowUtils.cpp b/dom/media/directshow/DirectShowUtils.cpp new file mode 100644 index 0000000000..e76b1b2b24 --- /dev/null +++ b/dom/media/directshow/DirectShowUtils.cpp @@ -0,0 +1,457 @@ +/* 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 "DirectShowUtils.h" +#include "DirectShowReader.h" +#include "dmodshow.h" +#include "wmcodecdsp.h" +#include "dmoreg.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/RefPtr.h" +#include "nsPrintfCString.h" +#include + +#define WARN(...) NS_WARNING(nsPrintfCString(__VA_ARGS__).get()) + +namespace mozilla { + +// Create a table which maps GUIDs to a string representation of the GUID. +// This is useful for debugging purposes, for logging the GUIDs of media types. +// This is only available when logging is enabled, i.e. not in release builds. +struct GuidToName { + const char* name; + const GUID guid; +}; + +#pragma push_macro("OUR_GUID_ENTRY") +#undef OUR_GUID_ENTRY +#define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + { #name, {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}} }, + +static const GuidToName GuidToNameTable[] = { +#include +}; + +#pragma pop_macro("OUR_GUID_ENTRY") + +const char* +GetDirectShowGuidName(const GUID& aGuid) +{ + const size_t len = ArrayLength(GuidToNameTable); + for (unsigned i = 0; i < len; i++) { + if (IsEqualGUID(aGuid, GuidToNameTable[i].guid)) { + return GuidToNameTable[i].name; + } + } + return "Unknown"; +} + +void +RemoveGraphFromRunningObjectTable(DWORD aRotRegister) +{ + RefPtr runningObjectTable; + if (SUCCEEDED(GetRunningObjectTable(0, getter_AddRefs(runningObjectTable)))) { + runningObjectTable->Revoke(aRotRegister); + } +} + +HRESULT +AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister) +{ + HRESULT hr; + + RefPtr moniker; + RefPtr runningObjectTable; + + hr = GetRunningObjectTable(0, getter_AddRefs(runningObjectTable)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + const size_t STRING_LENGTH = 256; + WCHAR wsz[STRING_LENGTH]; + + StringCchPrintfW(wsz, + STRING_LENGTH, + L"FilterGraph %08x pid %08x", + (DWORD_PTR)aUnkGraph, + GetCurrentProcessId()); + + hr = CreateItemMoniker(L"!", wsz, getter_AddRefs(moniker)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = runningObjectTable->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, + aUnkGraph, + moniker, + aOutRotRegister); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + return S_OK; +} + +const char* +GetGraphNotifyString(long evCode) +{ +#define CASE(x) case x: return #x + switch(evCode) { + CASE(EC_ACTIVATE); // A video window is being activated or deactivated. + CASE(EC_BANDWIDTHCHANGE); // Not supported. + CASE(EC_BUFFERING_DATA); // The graph is buffering data, or has stopped buffering data. + CASE(EC_BUILT); // Send by the Video Control when a graph has been built. Not forwarded to applications. + CASE(EC_CLOCK_CHANGED); // The reference clock has changed. + CASE(EC_CLOCK_UNSET); // The clock provider was disconnected. + CASE(EC_CODECAPI_EVENT); // Sent by an encoder to signal an encoding event. + CASE(EC_COMPLETE); // All data from a particular stream has been rendered. + CASE(EC_CONTENTPROPERTY_CHANGED); // Not supported. + CASE(EC_DEVICE_LOST); // A Plug and Play device was removed or has become available again. + CASE(EC_DISPLAY_CHANGED); // The display mode has changed. + CASE(EC_END_OF_SEGMENT); // The end of a segment has been reached. + CASE(EC_EOS_SOON); // Not supported. + CASE(EC_ERROR_STILLPLAYING); // An asynchronous command to run the graph has failed. + CASE(EC_ERRORABORT); // An operation was aborted because of an error. + CASE(EC_ERRORABORTEX); // An operation was aborted because of an error. + CASE(EC_EXTDEVICE_MODE_CHANGE); // Not supported. + CASE(EC_FILE_CLOSED); // The source file was closed because of an unexpected event. + CASE(EC_FULLSCREEN_LOST); // The video renderer is switching out of full-screen mode. + CASE(EC_GRAPH_CHANGED); // The filter graph has changed. + CASE(EC_LENGTH_CHANGED); // The length of a source has changed. + CASE(EC_LOADSTATUS); // Notifies the application of progress when opening a network file. + CASE(EC_MARKER_HIT); // Not supported. + CASE(EC_NEED_RESTART); // A filter is requesting that the graph be restarted. + CASE(EC_NEW_PIN); // Not supported. + CASE(EC_NOTIFY_WINDOW); // Notifies a filter of the video renderer's window. + CASE(EC_OLE_EVENT); // A filter is passing a text string to the application. + CASE(EC_OPENING_FILE); // The graph is opening a file, or has finished opening a file. + CASE(EC_PALETTE_CHANGED); // The video palette has changed. + CASE(EC_PAUSED); // A pause request has completed. + CASE(EC_PLEASE_REOPEN); // The source file has changed. + CASE(EC_PREPROCESS_COMPLETE); // Sent by the WM ASF Writer filter when it completes the pre-processing for multipass encoding. + CASE(EC_PROCESSING_LATENCY); // Indicates the amount of time that a component is taking to process each sample. + CASE(EC_QUALITY_CHANGE); // The graph is dropping samples, for quality control. + //CASE(EC_RENDER_FINISHED); // Not supported. + CASE(EC_REPAINT); // A video renderer requires a repaint. + CASE(EC_SAMPLE_LATENCY); // Specifies how far behind schedule a component is for processing samples. + //CASE(EC_SAMPLE_NEEDED); // Requests a new input sample from the Enhanced Video Renderer (EVR) filter. + CASE(EC_SCRUB_TIME); // Specifies the time stamp for the most recent frame step. + CASE(EC_SEGMENT_STARTED); // A new segment has started. + CASE(EC_SHUTTING_DOWN); // The filter graph is shutting down, prior to being destroyed. + CASE(EC_SNDDEV_IN_ERROR); // A device error has occurred in an audio capture filter. + CASE(EC_SNDDEV_OUT_ERROR); // A device error has occurred in an audio renderer filter. + CASE(EC_STARVATION); // A filter is not receiving enough data. + CASE(EC_STATE_CHANGE); // The filter graph has changed state. + CASE(EC_STATUS); // Contains two arbitrary status strings. + CASE(EC_STEP_COMPLETE); // A filter performing frame stepping has stepped the specified number of frames. + CASE(EC_STREAM_CONTROL_STARTED); // A stream-control start command has taken effect. + CASE(EC_STREAM_CONTROL_STOPPED); // A stream-control stop command has taken effect. + CASE(EC_STREAM_ERROR_STILLPLAYING); // An error has occurred in a stream. The stream is still playing. + CASE(EC_STREAM_ERROR_STOPPED); // A stream has stopped because of an error. + CASE(EC_TIMECODE_AVAILABLE); // Not supported. + CASE(EC_UNBUILT); // Send by the Video Control when a graph has been torn down. Not forwarded to applications. + CASE(EC_USERABORT); // The user has terminated playback. + CASE(EC_VIDEO_SIZE_CHANGED); // The native video size has changed. + CASE(EC_VIDEOFRAMEREADY); // A video frame is ready for display. + CASE(EC_VMR_RECONNECTION_FAILED); // Sent by the VMR-7 and the VMR-9 when it was unable to accept a dynamic format change request from the upstream decoder. + CASE(EC_VMR_RENDERDEVICE_SET); // Sent when the VMR has selected its rendering mechanism. + CASE(EC_VMR_SURFACE_FLIPPED); // Sent when the VMR-7's allocator presenter has called the DirectDraw Flip method on the surface being presented. + CASE(EC_WINDOW_DESTROYED); // The video renderer was destroyed or removed from the graph. + CASE(EC_WMT_EVENT); // Sent by the WM ASF Reader filter when it reads ASF files protected by digital rights management (DRM). + CASE(EC_WMT_INDEX_EVENT); // Sent when an application uses the WM ASF Writer to index Windows Media Video files. + CASE(S_OK); // Success. + CASE(VFW_S_AUDIO_NOT_RENDERED); // Partial success; the audio was not rendered. + CASE(VFW_S_DUPLICATE_NAME); // Success; the Filter Graph Manager modified a filter name to avoid duplication. + CASE(VFW_S_PARTIAL_RENDER); // Partial success; some of the streams in this movie are in an unsupported format. + CASE(VFW_S_VIDEO_NOT_RENDERED); // Partial success; the video was not rendered. + CASE(E_ABORT); // Operation aborted. + CASE(E_OUTOFMEMORY); // Insufficient memory. + CASE(E_POINTER); // Null pointer argument. + CASE(VFW_E_CANNOT_CONNECT); // No combination of intermediate filters could be found to make the connection. + CASE(VFW_E_CANNOT_RENDER); // No combination of filters could be found to render the stream. + CASE(VFW_E_NO_ACCEPTABLE_TYPES); // There is no common media type between these pins. + CASE(VFW_E_NOT_IN_GRAPH); + + default: + return "Unknown Code"; + }; +#undef CASE +} + +HRESULT +CreateAndAddFilter(IGraphBuilder* aGraph, + REFGUID aFilterClsId, + LPCWSTR aFilterName, + IBaseFilter **aOutFilter) +{ + NS_ENSURE_TRUE(aGraph, E_POINTER); + NS_ENSURE_TRUE(aOutFilter, E_POINTER); + HRESULT hr; + + RefPtr filter; + hr = CoCreateInstance(aFilterClsId, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter)); + if (FAILED(hr)) { + // Object probably not available on this system. + WARN("CoCreateInstance failed, hr=%x", hr); + return hr; + } + + hr = aGraph->AddFilter(filter, aFilterName); + if (FAILED(hr)) { + WARN("AddFilter failed, hr=%x", hr); + return hr; + } + + filter.forget(aOutFilter); + + return S_OK; +} + +HRESULT +CreateMP3DMOWrapperFilter(IBaseFilter **aOutFilter) +{ + NS_ENSURE_TRUE(aOutFilter, E_POINTER); + HRESULT hr; + + // Create the wrapper filter. + RefPtr filter; + hr = CoCreateInstance(CLSID_DMOWrapperFilter, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter)); + if (FAILED(hr)) { + WARN("CoCreateInstance failed, hr=%x", hr); + return hr; + } + + // Query for IDMOWrapperFilter. + RefPtr dmoWrapper; + hr = filter->QueryInterface(IID_IDMOWrapperFilter, + getter_AddRefs(dmoWrapper)); + if (FAILED(hr)) { + WARN("QueryInterface failed, hr=%x", hr); + return hr; + } + + hr = dmoWrapper->Init(CLSID_CMP3DecMediaObject, DMOCATEGORY_AUDIO_DECODER); + if (FAILED(hr)) { + // Can't instantiate MP3 DMO. It doesn't exist on Windows XP, we're + // probably hitting that. Don't log warning to console, this is an + // expected error. + WARN("dmoWrapper Init failed, hr=%x", hr); + return hr; + } + + filter.forget(aOutFilter); + + return S_OK; +} + +HRESULT +AddMP3DMOWrapperFilter(IGraphBuilder* aGraph, + IBaseFilter **aOutFilter) +{ + NS_ENSURE_TRUE(aGraph, E_POINTER); + NS_ENSURE_TRUE(aOutFilter, E_POINTER); + HRESULT hr; + + // Create the wrapper filter. + RefPtr filter; + hr = CreateMP3DMOWrapperFilter(getter_AddRefs(filter)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Add the wrapper filter to graph. + hr = aGraph->AddFilter(filter, L"MP3 Decoder DMO"); + if (FAILED(hr)) { + WARN("AddFilter failed, hr=%x", hr); + return hr; + } + + filter.forget(aOutFilter); + + return S_OK; +} + +bool +CanDecodeMP3UsingDirectShow() +{ + RefPtr filter; + + // Can we create the MP3 demuxer filter? + if (FAILED(CoCreateInstance(CLSID_MPEG1Splitter, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter)))) { + return false; + } + + // Can we create either the WinXP MP3 decoder filter or the MP3 DMO decoder? + if (FAILED(CoCreateInstance(DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter))) && + FAILED(CreateMP3DMOWrapperFilter(getter_AddRefs(filter)))) { + return false; + } + + // Else, we can create all of the components we need. Assume + // DirectShow is going to work... + return true; +} + +bool +CanDecodeH264UsingDirectShow() +{ + RefPtr devEnum; + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, + CLSCTX_INPROC_SERVER, + IID_ICreateDevEnum, getter_AddRefs(devEnum)); + if (FAILED(hr)) { + return false; + } + + RefPtr filters; + hr = devEnum->CreateClassEnumerator(CLSID_LegacyAmFilterCategory, + getter_AddRefs(filters), 0); + if (hr != S_OK) { + return false; + } + + RefPtr moniker; + ULONG fetched = 0; + while (filters->Next(1, getter_AddRefs(moniker), &fetched) == S_OK) { + RefPtr bag; + if (SUCCEEDED(moniker->BindToStorage(nullptr, nullptr, + IID_IPropertyBag, + getter_AddRefs(bag)))) { + VARIANT value; + VariantInit(&value); + if (SUCCEEDED(bag->Read(L"FriendlyName", &value, nullptr)) && + value.vt == VT_BSTR && value.bstrVal && + (wcsstr(value.bstrVal, L"H264") || + wcsstr(value.bstrVal, L"H.264") || + wcsstr(value.bstrVal, L"AVC"))) { + VariantClear(&value); + return true; + } + VariantClear(&value); + } + moniker = nullptr; + } + return false; +} + +HRESULT +AddH264DecoderFilter(IGraphBuilder* aGraph, IBaseFilter** aOutFilter) +{ + NS_ENSURE_TRUE(aGraph && aOutFilter, E_POINTER); + *aOutFilter = nullptr; + RefPtr devEnum; + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, + CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, + getter_AddRefs(devEnum)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + RefPtr filters; + hr = devEnum->CreateClassEnumerator(CLSID_LegacyAmFilterCategory, + getter_AddRefs(filters), 0); + NS_ENSURE_TRUE(hr == S_OK, VFW_E_NOT_FOUND); + RefPtr moniker; + ULONG fetched = 0; + while (filters->Next(1, getter_AddRefs(moniker), &fetched) == S_OK) { + RefPtr bag; + VARIANT value; + VariantInit(&value); + bool h264 = false; + if (SUCCEEDED(moniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, + getter_AddRefs(bag))) && + SUCCEEDED(bag->Read(L"FriendlyName", &value, nullptr)) && + value.vt == VT_BSTR && value.bstrVal) { + h264 = wcsstr(value.bstrVal, L"H264") || + wcsstr(value.bstrVal, L"H.264") || + wcsstr(value.bstrVal, L"AVC"); + } + VariantClear(&value); + if (h264 && SUCCEEDED(moniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, + reinterpret_cast(aOutFilter)))) { + hr = aGraph->AddFilter(*aOutFilter, L"DirectShow H.264 decoder"); + if (SUCCEEDED(hr)) { + return hr; + } + (*aOutFilter)->Release(); + *aOutFilter = nullptr; + } + moniker = nullptr; + } + return VFW_E_NOT_FOUND; +} + +// Match a pin by pin direction and connection state. +HRESULT +MatchUnconnectedPin(IPin* aPin, + PIN_DIRECTION aPinDir, + bool *aOutMatches) +{ + NS_ENSURE_TRUE(aPin, E_POINTER); + NS_ENSURE_TRUE(aOutMatches, E_POINTER); + + // Ensure the pin is unconnected. + RefPtr peer; + HRESULT hr = aPin->ConnectedTo(getter_AddRefs(peer)); + if (hr != VFW_E_NOT_CONNECTED) { + *aOutMatches = false; + return hr; + } + + // Ensure the pin is of the specified direction. + PIN_DIRECTION pinDir; + hr = aPin->QueryDirection(&pinDir); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + *aOutMatches = (pinDir == aPinDir); + return S_OK; +} + +// Return the first unconnected input pin or output pin. +already_AddRefed +GetUnconnectedPin(IBaseFilter* aFilter, PIN_DIRECTION aPinDir) +{ + RefPtr enumPins; + + HRESULT hr = aFilter->EnumPins(getter_AddRefs(enumPins)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + // Test each pin to see if it matches the direction we're looking for. + RefPtr pin; + while (S_OK == enumPins->Next(1, getter_AddRefs(pin), nullptr)) { + bool matches = FALSE; + if (SUCCEEDED(MatchUnconnectedPin(pin, aPinDir, &matches)) && + matches) { + return pin.forget(); + } + } + + return nullptr; +} + +HRESULT +ConnectFilters(IGraphBuilder* aGraph, + IBaseFilter* aOutputFilter, + IBaseFilter* aInputFilter) +{ + RefPtr output = GetUnconnectedPin(aOutputFilter, PINDIR_OUTPUT); + NS_ENSURE_TRUE(output, E_FAIL); + + RefPtr input = GetUnconnectedPin(aInputFilter, PINDIR_INPUT); + NS_ENSURE_TRUE(output, E_FAIL); + + return aGraph->Connect(output, input); +} + +} // namespace mozilla + +// avoid redefined macro in unified build +#undef WARN diff --git a/dom/media/directshow/DirectShowUtils.h b/dom/media/directshow/DirectShowUtils.h new file mode 100644 index 0000000000..8043c8b30e --- /dev/null +++ b/dom/media/directshow/DirectShowUtils.h @@ -0,0 +1,134 @@ +/* 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/. */ + +#ifndef _DirectShowUtils_h_ +#define _DirectShowUtils_h_ + +#include +#include "dshow.h" + +// XXXbz windowsx.h defines GetFirstChild, GetNextSibling, +// GetPrevSibling are macros, apparently... Eeevil. We have functions +// called that on some classes, so undef them. +#undef GetFirstChild +#undef GetNextSibling +#undef GetPrevSibling + +#include "DShowTools.h" +#include "mozilla/Logging.h" + +namespace mozilla { + +// Win32 "Event" wrapper. Must be paired with a CriticalSection to create a +// Java-style "monitor". +class Signal { +public: + + Signal(CriticalSection* aLock) + : mLock(aLock) + { + CriticalSectionAutoEnter lock(*mLock); + mEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + } + + ~Signal() { + CriticalSectionAutoEnter lock(*mLock); + CloseHandle(mEvent); + } + + // Lock must be held. + void Notify() { + SetEvent(mEvent); + } + + // Lock must be held. Check the wait condition before waiting! + HRESULT Wait() { + mLock->Leave(); + DWORD result = WaitForSingleObject(mEvent, INFINITE); + mLock->Enter(); + return result == WAIT_OBJECT_0 ? S_OK : E_FAIL; + } + +private: + CriticalSection* mLock; + HANDLE mEvent; +}; + +HRESULT +AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister); + +void +RemoveGraphFromRunningObjectTable(DWORD aRotRegister); + +const char* +GetGraphNotifyString(long evCode); + +// Creates a filter and adds it to a graph. +HRESULT +CreateAndAddFilter(IGraphBuilder* aGraph, + REFGUID aFilterClsId, + LPCWSTR aFilterName, + IBaseFilter **aOutFilter); + +HRESULT +AddMP3DMOWrapperFilter(IGraphBuilder* aGraph, + IBaseFilter **aOutFilter); + +// Connects the output pin on aOutputFilter to an input pin on +// aInputFilter, in aGraph. +HRESULT +ConnectFilters(IGraphBuilder* aGraph, + IBaseFilter* aOutputFilter, + IBaseFilter* aInputFilter); + +HRESULT +MatchUnconnectedPin(IPin* aPin, + PIN_DIRECTION aPinDir, + bool *aOutMatches); + +// Converts from microseconds to DirectShow "Reference Time" +// (hundreds of nanoseconds). +inline int64_t +UsecsToRefTime(const int64_t aUsecs) +{ + return aUsecs * 10; +} + +// Converts from DirectShow "Reference Time" (hundreds of nanoseconds) +// to microseconds. +inline int64_t +RefTimeToUsecs(const int64_t hRefTime) +{ + return hRefTime / 10; +} + +// Converts from DirectShow "Reference Time" (hundreds of nanoseconds) +// to seconds. +inline double +RefTimeToSeconds(const REFERENCE_TIME aRefTime) +{ + return double(aRefTime) / 10000000; +} + +const char* +GetDirectShowGuidName(const GUID& aGuid); + +// Returns true if we can instantiate an MP3 demuxer and decoder filters. +// Use this to detect whether MP3 support is installed. +bool +CanDecodeMP3UsingDirectShow(); + +// Returns true when a registered DirectShow filter advertises H.264/AVC +// decoding. This works on Windows 2000 because it uses the legacy filter +// enumerator instead of Media Foundation. +bool +CanDecodeH264UsingDirectShow(); + +HRESULT +AddH264DecoderFilter(IGraphBuilder* aGraph, IBaseFilter** aOutFilter); + +} // namespace mozilla + +#endif diff --git a/dom/media/directshow/SampleSink.cpp b/dom/media/directshow/SampleSink.cpp new file mode 100644 index 0000000000..fa5dc8d19c --- /dev/null +++ b/dom/media/directshow/SampleSink.cpp @@ -0,0 +1,159 @@ +/* -*- 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 "SampleSink.h" +#include "AudioSinkFilter.h" +#include "AudioSinkInputPin.h" +#include "VideoUtils.h" +#include "mozilla/Logging.h" + +using namespace mozilla::media; + +namespace mozilla { + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +SampleSink::SampleSink() + : mMonitor("SampleSink"), + mIsFlushing(false), + mAtEOS(false) +{ + MOZ_COUNT_CTOR(SampleSink); +} + +SampleSink::~SampleSink() +{ + MOZ_COUNT_DTOR(SampleSink); +} + +void +SampleSink::SetAudioFormat(const WAVEFORMATEX* aInFormat) +{ + NS_ENSURE_TRUE(aInFormat, ); + ReentrantMonitorAutoEnter mon(mMonitor); + memcpy(&mAudioFormat, aInFormat, sizeof(WAVEFORMATEX)); +} + +void +SampleSink::GetAudioFormat(WAVEFORMATEX* aOutFormat) +{ + MOZ_ASSERT(aOutFormat); + ReentrantMonitorAutoEnter mon(mMonitor); + memcpy(aOutFormat, &mAudioFormat, sizeof(WAVEFORMATEX)); +} + +HRESULT +SampleSink::Receive(IMediaSample* aSample) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + while (true) { + if (mIsFlushing) { + return S_FALSE; + } + if (!mSample) { + break; + } + if (mAtEOS) { + return E_UNEXPECTED; + } + // Wait until the consumer thread consumes the sample. + mon.Wait(); + } + + if (MOZ_LOG_TEST(gDirectShowLog, LogLevel::Debug)) { + REFERENCE_TIME start = 0, end = 0; + HRESULT hr = aSample->GetMediaTime(&start, &end); + LOG("SampleSink::Receive() [%4.2lf-%4.2lf]", + (double)RefTimeToUsecs(start) / USECS_PER_S, + (double)RefTimeToUsecs(end) / USECS_PER_S); + } + + mSample = aSample; + // Notify the signal, to awaken the consumer thread in WaitForSample() + // if necessary. + mon.NotifyAll(); + return S_OK; +} + +HRESULT +SampleSink::Extract(RefPtr& aOutSample) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + // Loop until we have a sample, or we should abort. + while (true) { + if (mIsFlushing) { + return S_FALSE; + } + if (mSample) { + break; + } + if (mAtEOS) { + // Order is important here, if we have a sample, we should return it + // before reporting EOS. + return E_UNEXPECTED; + } + // Wait until the producer thread gives us a sample. + mon.Wait(); + } + aOutSample = mSample; + + if (MOZ_LOG_TEST(gDirectShowLog, LogLevel::Debug)) { + int64_t start = 0, end = 0; + mSample->GetMediaTime(&start, &end); + LOG("SampleSink::Extract() [%4.2lf-%4.2lf]", + (double)RefTimeToUsecs(start) / USECS_PER_S, + (double)RefTimeToUsecs(end) / USECS_PER_S); + } + + mSample = nullptr; + // Notify the signal, to awaken the producer thread in Receive() + // if necessary. + mon.NotifyAll(); + return S_OK; +} + +void +SampleSink::Flush() +{ + LOG("SampleSink::Flush()"); + ReentrantMonitorAutoEnter mon(mMonitor); + mIsFlushing = true; + mSample = nullptr; + mon.NotifyAll(); +} + +void +SampleSink::Reset() +{ + LOG("SampleSink::Reset()"); + ReentrantMonitorAutoEnter mon(mMonitor); + mIsFlushing = false; + mAtEOS = false; +} + +void +SampleSink::SetEOS() +{ + LOG("SampleSink::SetEOS()"); + ReentrantMonitorAutoEnter mon(mMonitor); + mAtEOS = true; + // Notify to unblock any threads waiting for samples in + // Extract() or Receive(). Now that we're at EOS, no more samples + // will come! + mon.NotifyAll(); +} + +bool +SampleSink::AtEOS() +{ + ReentrantMonitorAutoEnter mon(mMonitor); + return mAtEOS && !mSample; +} + +} // namespace mozilla + diff --git a/dom/media/directshow/SampleSink.h b/dom/media/directshow/SampleSink.h new file mode 100644 index 0000000000..6a1af9fee4 --- /dev/null +++ b/dom/media/directshow/SampleSink.h @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +#if !defined(SampleSink_h_) +#define SampleSink_h_ + +#include "BaseFilter.h" +#include "DirectShowUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ReentrantMonitor.h" + +namespace mozilla { + +class SampleSink { +public: + SampleSink(); + virtual ~SampleSink(); + + // Sets the audio format of the incoming samples. The upstream filter + // calls this. This makes a copy. + void SetAudioFormat(const WAVEFORMATEX* aInFormat); + + // Copies the format of incoming audio samples into into *aOutFormat. + void GetAudioFormat(WAVEFORMATEX* aOutFormat); + + // Called when a sample is delivered by the DirectShow graph to the sink. + // The decode thread retrieves the sample by calling WaitForSample(). + // Blocks if there's already a sample waiting to be consumed by the decode + // thread. + HRESULT Receive(IMediaSample* aSample); + + // Retrieves a sample from the sample queue, blocking until one becomes + // available, or until an error occurs. Returns S_FALSE on EOS. + HRESULT Extract(RefPtr& aOutSample); + + // Unblocks any threads waiting in GetSample(). + // Clears mSample, which unblocks upstream stream. + void Flush(); + + // Opens up the sink to receive more samples in PutSample(). + // Clears EOS flag. + void Reset(); + + // Marks that we've reacehd the end of stream. + void SetEOS(); + + // Returns whether we're at end of stream. + bool AtEOS(); + +private: + // All data in this class is syncronized by mMonitor. + ReentrantMonitor mMonitor; + RefPtr mSample; + + // Format of the audio stream we're receiving. + WAVEFORMATEX mAudioFormat; + + bool mIsFlushing; + bool mAtEOS; +}; + +} // namespace mozilla + +#endif // SampleSink_h_ diff --git a/dom/media/directshow/SourceFilter.cpp b/dom/media/directshow/SourceFilter.cpp new file mode 100644 index 0000000000..f5ef11ec78 --- /dev/null +++ b/dom/media/directshow/SourceFilter.cpp @@ -0,0 +1,682 @@ +/* -*- 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 "SourceFilter.h" +#include "MediaResource.h" +#include "mozilla/RefPtr.h" +#include "DirectShowUtils.h" +#include "mozilla/Logging.h" +#include + +using namespace mozilla::media; + +namespace mozilla { + +// Define to trace what's on... +//#define DEBUG_SOURCE_TRACE 1 + +#if defined (DEBUG_SOURCE_TRACE) +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define DIRECTSHOW_LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#else +#define DIRECTSHOW_LOG(...) +#endif + +static HRESULT +DoGetInterface(IUnknown* aUnknown, void** aInterface) +{ + if (!aInterface) + return E_POINTER; + *aInterface = aUnknown; + aUnknown->AddRef(); + return S_OK; +} + +// Stores details of IAsyncReader::Request(). +class ReadRequest { +public: + + ReadRequest(IMediaSample* aSample, + DWORD_PTR aDwUser, + uint32_t aOffset, + uint32_t aCount) + : mSample(aSample), + mDwUser(aDwUser), + mOffset(aOffset), + mCount(aCount) + { + MOZ_COUNT_CTOR(ReadRequest); + } + + ~ReadRequest() { + MOZ_COUNT_DTOR(ReadRequest); + } + + RefPtr mSample; + DWORD_PTR mDwUser; + uint32_t mOffset; + uint32_t mCount; +}; + +// A wrapper around media resource that presents only a partition of the +// underlying resource to the caller to use. The partition returned is from +// an offset to the end of stream, and this object deals with ensuring +// the offsets and lengths etc are translated from the reduced partition +// exposed to the caller, to the absolute offsets of the underlying stream. +class MediaResourcePartition { +public: + MediaResourcePartition(MediaResource* aResource, + int64_t aDataStart) + : mResource(aResource), + mDataOffset(aDataStart) + {} + + int64_t GetLength() { + int64_t len = mResource.GetLength(); + if (len == -1) { + return len; + } + return std::max(0, len - mDataOffset); + } + nsresult ReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) + { + return mResource.ReadAt(aOffset + mDataOffset, + aBuffer, + aCount, + aBytes); + } + int64_t GetCachedDataEnd() { + int64_t tell = mResource.GetResource()->Tell(); + int64_t dataEnd = + mResource.GetResource()->GetCachedDataEnd(tell) - mDataOffset; + return dataEnd; + } +private: + // MediaResource from which we read data. + MediaResourceIndex mResource; + int64_t mDataOffset; +}; + + +// Output pin for SourceFilter, which implements IAsyncReader, to +// allow downstream filters to pull/read data from it. Downstream pins +// register to read data using Request(), and asynchronously wait for the +// reads to complete using WaitForNext(). They may also synchronously read +// using SyncRead(). This class is a delegate (tear off) of +// SourceFilter. +// +// We can expose only a segment of the MediaResource to the filter graph. +// This is used to strip off the ID3v2 tags from the stream, as DirectShow +// has trouble parsing some headers. +// +// Implements: +// * IAsyncReader +// * IPin +// * IQualityControl +// * IUnknown +// +class DECLSPEC_UUID("18e5cfb2-1015-440c-a65c-e63853235894") +OutputPin : public IAsyncReader, + public BasePin +{ +public: + + OutputPin(MediaResource* aMediaResource, + SourceFilter* aParent, + CriticalSection& aFilterLock, + int64_t aMP3DataStart); + virtual ~OutputPin(); + + // IUnknown + // Defer to ref counting to BasePin, which defers to owning nsBaseFilter. + STDMETHODIMP_(ULONG) AddRef() override { return BasePin::AddRef(); } + STDMETHODIMP_(ULONG) Release() override { return BasePin::Release(); } + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override; + + // BasePin Overrides. + // Determines if the pin accepts a specific media type. + HRESULT CheckMediaType(const MediaType* aMediaType) override; + + // Retrieves a preferred media type, by index value. + HRESULT GetMediaType(int aPosition, MediaType* aMediaType) override; + + // Releases the pin from a connection. + HRESULT BreakConnect(void) override; + + // Determines whether a pin connection is suitable. + HRESULT CheckConnect(IPin* aPin) override; + + + // IAsyncReader overrides + + // The RequestAllocator method requests an allocator during the + // pin connection. + STDMETHODIMP RequestAllocator(IMemAllocator* aPreferred, + ALLOCATOR_PROPERTIES* aProps, + IMemAllocator** aActual) override; + + // The Request method queues an asynchronous request for data. Downstream + // will call WaitForNext() when they want to retrieve the result. + STDMETHODIMP Request(IMediaSample* aSample, DWORD_PTR aUserData) override; + + // The WaitForNext method waits for the next pending read request + // to complete. This method fails if the graph is flushing. + // Defers to SyncRead/5. + STDMETHODIMP WaitForNext(DWORD aTimeout, + IMediaSample** aSamples, + DWORD_PTR* aUserData) override; + + // The SyncReadAligned method performs a synchronous read. The method + // blocks until the request is completed. Defers to SyncRead/5. This + // method does not fail if the graph is flushing. + STDMETHODIMP SyncReadAligned(IMediaSample* aSample) override; + + // The SyncRead method performs a synchronous read. The method blocks + // until the request is completed. Defers to SyncRead/5. This + // method does not fail if the graph is flushing. + STDMETHODIMP SyncRead(LONGLONG aPosition, LONG aLength, BYTE* aBuffer) override; + + // The Length method retrieves the total length of the stream. + STDMETHODIMP Length(LONGLONG* aTotal, LONGLONG* aAvailable) override; + + // IPin Overrides + STDMETHODIMP BeginFlush(void) override; + STDMETHODIMP EndFlush(void) override; + + uint32_t GetAndResetBytesConsumedCount(); + +private: + + // Protects thread-shared data/structures (mFlushCount, mPendingReads). + // WaitForNext() also waits on this monitor + CriticalSection& mPinLock; + + // Signal used with mPinLock to implement WaitForNext(). + Signal mSignal; + + // The filter that owns us. Weak reference, as we're a delegate (tear off). + SourceFilter* mParentSource; + + MediaResourcePartition mResource; + + // Counter, inc'd in BeginFlush(), dec'd in EndFlush(). Calls to this can + // come from multiple threads and can interleave, hence the counter. + int32_t mFlushCount; + + // Number of bytes that have been read from the output pin since the last + // time GetAndResetBytesConsumedCount() was called. + uint32_t mBytesConsumed; + + // Deque of ReadRequest* for reads that are yet to be serviced. + // nsReadRequest's are stored on the heap, popper must delete them. + nsDeque mPendingReads; + + // Flags if the downstream pin has QI'd for IAsyncReader. We refuse + // connection if they don't query, as it means they're assuming that we're + // a push filter, and we're not. + bool mQueriedForAsyncReader; + +}; + +// For mingw __uuidof support +#ifdef __CRT_UUID_DECL +} +__CRT_UUID_DECL(mozilla::OutputPin, 0x18e5cfb2,0x1015,0x440c,0xa6,0x5c,0xe6,0x38,0x53,0x23,0x58,0x94); +namespace mozilla { +#endif + +OutputPin::OutputPin(MediaResource* aResource, + SourceFilter* aParent, + CriticalSection& aFilterLock, + int64_t aMP3DataStart) + : BasePin(static_cast(aParent), + &aFilterLock, + L"MozillaOutputPin", + PINDIR_OUTPUT), + mPinLock(aFilterLock), + mSignal(&mPinLock), + mParentSource(aParent), + mResource(aResource, aMP3DataStart), + mFlushCount(0), + mBytesConsumed(0), + mQueriedForAsyncReader(false) +{ + MOZ_COUNT_CTOR(OutputPin); + DIRECTSHOW_LOG("OutputPin::OutputPin()"); +} + +OutputPin::~OutputPin() +{ + MOZ_COUNT_DTOR(OutputPin); + DIRECTSHOW_LOG("OutputPin::~OutputPin()"); +} + +HRESULT +OutputPin::BreakConnect() +{ + mQueriedForAsyncReader = false; + return BasePin::BreakConnect(); +} + +STDMETHODIMP +OutputPin::QueryInterface(REFIID aIId, void** aInterface) +{ + if (aIId == IID_IAsyncReader) { + mQueriedForAsyncReader = true; + return DoGetInterface(static_cast(this), aInterface); + } + + if (aIId == __uuidof(OutputPin)) { + AddRef(); + *aInterface = this; + return S_OK; + } + + return BasePin::QueryInterface(aIId, aInterface); +} + +HRESULT +OutputPin::CheckConnect(IPin* aPin) +{ + // Our connection is only suitable if the downstream pin knows + // that we're asynchronous (i.e. it queried for IAsyncReader). + return mQueriedForAsyncReader ? S_OK : S_FALSE; +} + +HRESULT +OutputPin::CheckMediaType(const MediaType* aMediaType) +{ + const MediaType *myMediaType = mParentSource->GetMediaType(); + + if (IsEqualGUID(aMediaType->majortype, myMediaType->majortype) && + IsEqualGUID(aMediaType->subtype, myMediaType->subtype) && + IsEqualGUID(aMediaType->formattype, myMediaType->formattype)) + { + DIRECTSHOW_LOG("OutputPin::CheckMediaType() Match: major=%s minor=%s TC=%d FSS=%d SS=%u", + GetDirectShowGuidName(aMediaType->majortype), + GetDirectShowGuidName(aMediaType->subtype), + aMediaType->TemporalCompression(), + aMediaType->bFixedSizeSamples, + aMediaType->SampleSize()); + return S_OK; + } + + DIRECTSHOW_LOG("OutputPin::CheckMediaType() Failed to match: major=%s minor=%s TC=%d FSS=%d SS=%u", + GetDirectShowGuidName(aMediaType->majortype), + GetDirectShowGuidName(aMediaType->subtype), + aMediaType->TemporalCompression(), + aMediaType->bFixedSizeSamples, + aMediaType->SampleSize()); + return S_FALSE; +} + +HRESULT +OutputPin::GetMediaType(int aPosition, MediaType* aMediaType) +{ + if (!aMediaType) + return E_POINTER; + + if (aPosition == 0) { + aMediaType->Assign(mParentSource->GetMediaType()); + return S_OK; + } + return VFW_S_NO_MORE_ITEMS; +} + +static inline bool +IsPowerOf2(int32_t x) { + return ((-x & x) != x); +} + +STDMETHODIMP +OutputPin::RequestAllocator(IMemAllocator* aPreferred, + ALLOCATOR_PROPERTIES* aProps, + IMemAllocator** aActual) +{ + // Require the downstream pin to suggest what they want... + if (!aPreferred) return E_POINTER; + if (!aProps) return E_POINTER; + if (!aActual) return E_POINTER; + + // We only care about alignment - our allocator will reject anything + // which isn't power-of-2 aligned, so so try a 4-byte aligned allocator. + ALLOCATOR_PROPERTIES props; + memcpy(&props, aProps, sizeof(ALLOCATOR_PROPERTIES)); + if (aProps->cbAlign == 0 || IsPowerOf2(aProps->cbAlign)) { + props.cbAlign = 4; + } + + // Limit allocator's number of buffers. We know that the media will most + // likely be bound by network speed, not by decoding speed. We also + // store the incoming data in a Gecko stream, if we don't limit buffers + // here we'll end up duplicating a lot of storage. We must have enough + // space for audio key frames to fit in the first batch of buffers however, + // else pausing may fail for some downstream decoders. + if (props.cBuffers > BaseFilter::sMaxNumBuffers) { + props.cBuffers = BaseFilter::sMaxNumBuffers; + } + + // The allocator properties that are actually used. We don't store + // this, we need it for SetProperties() below to succeed. + ALLOCATOR_PROPERTIES actualProps; + HRESULT hr; + + if (aPreferred) { + // Play nice and prefer the downstream pin's preferred allocator. + hr = aPreferred->SetProperties(&props, &actualProps); + if (SUCCEEDED(hr)) { + aPreferred->AddRef(); + *aActual = aPreferred; + return S_OK; + } + } + + // Else downstream hasn't requested a specific allocator, so create one... + + // Just create a default allocator. It's highly unlikely that we'll use + // this anyway, as most parsers insist on using their own allocators. + RefPtr allocator; + hr = CoCreateInstance(CLSID_MemoryAllocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IMemAllocator, + getter_AddRefs(allocator)); + if(FAILED(hr) || (allocator == nullptr)) { + NS_WARNING("Can't create our own DirectShow allocator."); + return hr; + } + + // See if we can make it suitable + hr = allocator->SetProperties(&props, &actualProps); + if (SUCCEEDED(hr)) { + // We need to release our refcount on pAlloc, and addref + // it to pass a refcount to the caller - this is a net nothing. + allocator.forget(aActual); + return S_OK; + } + + NS_WARNING("Failed to pick an allocator"); + return hr; +} + +STDMETHODIMP +OutputPin::Request(IMediaSample* aSample, DWORD_PTR aDwUser) +{ + if (!aSample) return E_FAIL; + + CriticalSectionAutoEnter lock(*mLock); + NS_ASSERTION(!mFlushCount, "Request() while flushing"); + + if (mFlushCount) + return VFW_E_WRONG_STATE; + + REFERENCE_TIME refStart = 0, refEnd = 0; + if (FAILED(aSample->GetTime(&refStart, &refEnd))) { + NS_WARNING("Sample incorrectly timestamped"); + return VFW_E_SAMPLE_TIME_NOT_SET; + } + + // Convert reference time to bytes. + uint32_t start = (uint32_t)(refStart / 10000000); + uint32_t end = (uint32_t)(refEnd / 10000000); + + uint32_t numBytes = end - start; + + ReadRequest* request = new ReadRequest(aSample, + aDwUser, + start, + numBytes); + // Memory for |request| is free when it's popped from the completed + // reads list. + + // Push this onto the queue of reads to be serviced. + mPendingReads.Push(request); + + // Notify any threads blocked in WaitForNext() which are waiting for mPendingReads + // to become non-empty. + mSignal.Notify(); + + return S_OK; +} + +STDMETHODIMP +OutputPin::WaitForNext(DWORD aTimeout, + IMediaSample** aOutSample, + DWORD_PTR* aOutDwUser) +{ + NS_ASSERTION(aTimeout == 0 || aTimeout == INFINITE, + "Oops, we don't handle this!"); + + *aOutSample = nullptr; + *aOutDwUser = 0; + + LONGLONG offset = 0; + LONG count = 0; + BYTE* buf = nullptr; + + { + CriticalSectionAutoEnter lock(*mLock); + + // Wait until there's a pending read to service. + while (aTimeout && mPendingReads.GetSize() == 0 && !mFlushCount) { + // Note: No need to guard against shutdown-during-wait here, as + // typically the thread doing the pull will have already called + // Request(), so we won't Wait() here anyway. SyncRead() will fail + // on shutdown. + mSignal.Wait(); + } + + nsAutoPtr request(reinterpret_cast(mPendingReads.PopFront())); + if (!request) + return VFW_E_WRONG_STATE; + + *aOutSample = request->mSample; + *aOutDwUser = request->mDwUser; + + offset = request->mOffset; + count = request->mCount; + buf = nullptr; + request->mSample->GetPointer(&buf); + NS_ASSERTION(buf != nullptr, "Invalid buffer!"); + + if (mFlushCount) { + return VFW_E_TIMEOUT; + } + } + + return SyncRead(offset, count, buf); +} + +STDMETHODIMP +OutputPin::SyncReadAligned(IMediaSample* aSample) +{ + { + // Ignore reads while flushing. + CriticalSectionAutoEnter lock(*mLock); + if (mFlushCount) { + return S_FALSE; + } + } + + if (!aSample) + return E_FAIL; + + REFERENCE_TIME lStart = 0, lEnd = 0; + if (FAILED(aSample->GetTime(&lStart, &lEnd))) { + NS_WARNING("Sample incorrectly timestamped"); + return VFW_E_SAMPLE_TIME_NOT_SET; + } + + // Convert reference time to bytes. + int32_t start = (int32_t)(lStart / 10000000); + int32_t end = (int32_t)(lEnd / 10000000); + + int32_t numBytes = end - start; + + // If the range extends off the end of stream, truncate to the end of stream + // as per IAsyncReader specificiation. + int64_t streamLength = mResource.GetLength(); + if (streamLength != -1) { + // We know the exact length of the stream, fail if the requested offset + // is beyond it. + if (start > streamLength) { + return VFW_E_BADALIGN; + } + + // If the end of the chunk to read is off the end of the stream, + // truncate it to the end of the stream. + if ((start + numBytes) > streamLength) { + numBytes = (uint32_t)(streamLength - start); + } + } + + BYTE* buf=0; + aSample->GetPointer(&buf); + + return SyncRead(start, numBytes, buf); +} + +STDMETHODIMP +OutputPin::SyncRead(LONGLONG aPosition, + LONG aLength, + BYTE* aBuffer) +{ + MOZ_ASSERT(!NS_IsMainThread()); + NS_ENSURE_TRUE(aPosition >= 0, E_FAIL); + NS_ENSURE_TRUE(aLength > 0, E_FAIL); + NS_ENSURE_TRUE(aBuffer, E_POINTER); + + DIRECTSHOW_LOG("OutputPin::SyncRead(%lld, %d)", aPosition, aLength); + { + // Ignore reads while flushing. + CriticalSectionAutoEnter lock(*mLock); + if (mFlushCount) { + return S_FALSE; + } + } + + uint32_t totalBytesRead = 0; + nsresult rv = mResource.ReadAt(aPosition, + reinterpret_cast(aBuffer), + aLength, + &totalBytesRead); + if (NS_FAILED(rv)) { + return E_FAIL; + } + if (totalBytesRead > 0) { + CriticalSectionAutoEnter lock(*mLock); + mBytesConsumed += totalBytesRead; + } + return (totalBytesRead == aLength) ? S_OK : S_FALSE; +} + +STDMETHODIMP +OutputPin::Length(LONGLONG* aTotal, LONGLONG* aAvailable) +{ + HRESULT hr = S_OK; + int64_t length = mResource.GetLength(); + if (length == -1) { + hr = VFW_S_ESTIMATED; + // Don't have a length. Just lie, it seems to work... + *aTotal = INT32_MAX; + } else { + *aTotal = length; + } + if (aAvailable) { + *aAvailable = mResource.GetCachedDataEnd(); + } + + DIRECTSHOW_LOG("OutputPin::Length() len=%lld avail=%lld", *aTotal, *aAvailable); + + return hr; +} + +STDMETHODIMP +OutputPin::BeginFlush() +{ + CriticalSectionAutoEnter lock(*mLock); + mFlushCount++; + mSignal.Notify(); + return S_OK; +} + +STDMETHODIMP +OutputPin::EndFlush(void) +{ + CriticalSectionAutoEnter lock(*mLock); + mFlushCount--; + return S_OK; +} + +uint32_t +OutputPin::GetAndResetBytesConsumedCount() +{ + CriticalSectionAutoEnter lock(*mLock); + uint32_t bytesConsumed = mBytesConsumed; + mBytesConsumed = 0; + return bytesConsumed; +} + +SourceFilter::SourceFilter(const GUID& aMajorType, + const GUID& aSubType) + : BaseFilter(L"MozillaDirectShowSource", __uuidof(SourceFilter)) +{ + MOZ_COUNT_CTOR(SourceFilter); + mMediaType.majortype = aMajorType; + mMediaType.subtype = aSubType; + + DIRECTSHOW_LOG("SourceFilter Constructor(%s, %s)", + GetDirectShowGuidName(aMajorType), + GetDirectShowGuidName(aSubType)); +} + +SourceFilter::~SourceFilter() +{ + MOZ_COUNT_DTOR(SourceFilter); + DIRECTSHOW_LOG("SourceFilter Destructor()"); +} + +BasePin* +SourceFilter::GetPin(int n) +{ + if (n == 0) { + NS_ASSERTION(mOutputPin != 0, "GetPin with no pin!"); + return static_cast(mOutputPin); + } else { + return nullptr; + } +} + +// Get's the media type we're supplying. +const MediaType* +SourceFilter::GetMediaType() const +{ + return &mMediaType; +} + +nsresult +SourceFilter::Init(MediaResource* aResource, int64_t aMP3Offset) +{ + DIRECTSHOW_LOG("SourceFilter::Init()"); + + mOutputPin = new OutputPin(aResource, + this, + mLock, + aMP3Offset); + NS_ENSURE_TRUE(mOutputPin != nullptr, NS_ERROR_FAILURE); + + return NS_OK; +} + +uint32_t +SourceFilter::GetAndResetBytesConsumedCount() +{ + return mOutputPin->GetAndResetBytesConsumedCount(); +} + + +} // namespace mozilla diff --git a/dom/media/directshow/SourceFilter.h b/dom/media/directshow/SourceFilter.h new file mode 100644 index 0000000000..d5ce2770e9 --- /dev/null +++ b/dom/media/directshow/SourceFilter.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +#if !defined(nsDirectShowSource_h___) +#define nsDirectShowSource_h___ + +#include "BaseFilter.h" +#include "BasePin.h" +#include "MediaType.h" + +#include "nsDeque.h" +#include "nsAutoPtr.h" +#include "DirectShowUtils.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class MediaResource; +class OutputPin; + + +// SourceFilter is an asynchronous DirectShow source filter which +// reads from an MediaResource, and supplies data via a pull model downstream +// using OutputPin. It us used to supply a generic byte stream into +// DirectShow. +// +// Implements: +// * IBaseFilter +// * IMediaFilter +// * IPersist +// * IUnknown +// +class DECLSPEC_UUID("5c2a7ad0-ba82-4659-9178-c4719a2765d6") +SourceFilter : public media::BaseFilter +{ +public: + + // Constructs source filter to deliver given media type. + SourceFilter(const GUID& aMajorType, const GUID& aSubType); + ~SourceFilter(); + + nsresult Init(MediaResource *aResource, int64_t aMP3Offset); + + // BaseFilter overrides. + // Only one output - the byte stream. + int GetPinCount() override { return 1; } + + media::BasePin* GetPin(int n) override; + + // Get's the media type we're supplying. + const media::MediaType* GetMediaType() const; + + uint32_t GetAndResetBytesConsumedCount(); + +protected: + + // Our async pull output pin. + nsAutoPtr mOutputPin; + + // Type of byte stream we output. + media::MediaType mMediaType; + +}; + +} // namespace mozilla + +// For mingw __uuidof support +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(mozilla::SourceFilter, 0x5c2a7ad0,0xba82,0x4659,0x91,0x78,0xc4,0x71,0x9a,0x27,0x65,0xd6); +#endif + +#endif diff --git a/dom/media/directshow/moz.build b/dom/media/directshow/moz.build new file mode 100644 index 0000000000..06cae45fff --- /dev/null +++ b/dom/media/directshow/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'AudioSinkFilter.h', + 'AudioSinkInputPin.h', + 'DirectShowDecoder.h', + 'DirectShowReader.h', + 'DirectShowUtils.h', +] + +UNIFIED_SOURCES += [ + 'DirectShowDecoder.cpp', + 'DirectShowUtils.cpp', + 'SourceFilter.cpp', +] + +SOURCES += [ + 'AudioSinkFilter.cpp', + 'AudioSinkInputPin.cpp', + 'DirectShowReader.cpp', + 'SampleSink.cpp', +] + +# If WebRTC isn't being built, we need to compile the DirectShow base classes so that +# they're available at link time. +if not CONFIG['MOZ_WEBRTC']: + SOURCES += [ + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/BaseFilter.cpp', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/BaseInputPin.cpp', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/BasePin.cpp', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/MediaType.cpp', + ] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/media', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows', +] diff --git a/dom/media/moz.build b/dom/media/moz.build index 300c6ae347..bd20936d7a 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -42,6 +42,12 @@ DIRS += [ 'standalone', ] +if CONFIG['MOZ_DIRECTSHOW']: + DIRS += ['directshow'] + LOCAL_INCLUDES += [ + '/media/webrtc/trunk/webrtc/modules/video_capture/windows', + ] + if CONFIG['MOZ_FMP4']: DIRS += ['fmp4'] diff --git a/gfx/layers/d3d11/CompositorD3D11.cpp b/gfx/layers/d3d11/CompositorD3D11.cpp index 58befb3fca..121794840f 100644 --- a/gfx/layers/d3d11/CompositorD3D11.cpp +++ b/gfx/layers/d3d11/CompositorD3D11.cpp @@ -7,7 +7,6 @@ #include "TextureD3D11.h" #include "CompositorD3D11Shaders.h" -#include "GpuRasterD3D11.h" #include "gfxWindowsPlatform.h" #include "nsIWidget.h" @@ -144,7 +143,6 @@ private: CompositorD3D11::CompositorD3D11(CompositorBridgeParent* aParent, widget::CompositorWidget* aWidget) : Compositor(aWidget, aParent) - , mGpuRasterViewport(false) , mAttachments(nullptr) , mHwnd(nullptr) , mDisableSequenceForNextFrame(false) @@ -201,16 +199,6 @@ CompositorD3D11::Initialize(nsCString* const out_failureReason) mFeatureLevel = mDevice->GetFeatureLevel(); - D3D11_FEATURE_DATA_D3D11_OPTIONS options = {}; - if (gfxPrefs::D3D11GpuRectangleFills() && - mFeatureLevel >= D3D_FEATURE_LEVEL_10_0 && - SUCCEEDED(mDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, - &options, sizeof(options))) && - options.ClearView) { - mContext->QueryInterface(__uuidof(ID3D11DeviceContext1), - getter_AddRefs(mGpuRasterContext)); - } - mHwnd = mWidget->AsWindows()->GetHwnd(); memset(&mVSConstants, 0, sizeof(VertexShaderConstants)); @@ -668,20 +656,6 @@ CompositorD3D11::GetPSForEffect(Effect* aEffect, MaskType aMaskType) void CompositorD3D11::ClearRect(const gfx::Rect& aRect) { - if (mGpuRasterContext && mGpuRasterViewport && mCurrentRT && - !mCurrentRT->HasComplexProjection()) { - GpuRasterRect rect = { aRect.x, aRect.y, aRect.XMost(), aRect.YMost() }; - IntSize size = mCurrentRT->GetSize(); - D3D11_RECT clip = { 0, 0, size.width, size.height }; - const float clear[4] = { 0, 0, 0, 0 }; - if (GpuRasterD3D11FillRect(mGpuRasterContext, mCurrentRT->mRTView, - size.width, size.height, &rect, &clip, clear)) { - // BeginFrame relies on ClearRect to establish premultiplied blending. - mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, 0xFFFFFFFF); - return; - } - } - mContext->OMSetBlendState(mAttachments->mDisabledBlendState, sBlendFactor, 0xFFFFFFFF); Matrix4x4 identity; @@ -762,39 +736,6 @@ CompositorD3D11::DrawQuad(const gfx::Rect& aRect, MOZ_ASSERT(mCurrentRT, "No render target"); - // Opaque, integer-aligned translated rectangles can be filled directly by - // the GPU without shader binding or per-quad constant-buffer uploads. - if (mGpuRasterContext && mGpuRasterViewport && !mCurrentRT->HasComplexProjection() && - aEffectChain.mPrimaryEffect->mType == EffectTypes::SOLID_COLOR && - !aEffectChain.mSecondaryEffects[EffectTypes::MASK] && - !aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE] && - aOpacity == 1.0f && aTransform.Is2D() && aTransform.As2D().IsTranslation()) { - const Color& color = static_cast(aEffectChain.mPrimaryEffect.get())->mColor; - if (color.a == 1.0f) { - IntPoint origin = mCurrentRT->GetOrigin(); - // Transform both edges in the shader's order before subtracting the - // target origin; moving x/y and then adding width/height can round - // differently for large coordinates. - float left = aRect.x + aTransform._41; - float right = aRect.XMost() + aTransform._41; - float top = aRect.y + aTransform._42; - float bottom = aRect.YMost() + aTransform._42; - GpuRasterRect rect = { left - origin.x, top - origin.y, - right - origin.x, bottom - origin.y }; - IntRect clipRect = aClipRect; - if (mCurrentRT == mDefaultRT) { - clipRect = clipRect.Intersect(mCurrentClip); - } - D3D11_RECT clip = { clipRect.x, clipRect.y, clipRect.XMost(), clipRect.YMost() }; - IntSize size = mCurrentRT->GetSize(); - const float fill[4] = { color.r, color.g, color.b, color.a }; - if (GpuRasterD3D11FillRect(mGpuRasterContext, mCurrentRT->mRTView, - size.width, size.height, &rect, &clip, fill)) { - return; - } - } - } - memcpy(&mVSConstants.layerTransform, &aTransform._11, 64); IntPoint origin = mCurrentRT->GetOrigin(); mVSConstants.renderTargetOffset[0] = origin.x; @@ -1252,7 +1193,6 @@ CompositorD3D11::PrepareViewport(const gfx::IntSize& aSize) projection._33 = 0.0f; PrepareViewport(aSize, projection, 0.0f, 1.0f); - mGpuRasterViewport = true; } void @@ -1273,7 +1213,6 @@ CompositorD3D11::PrepareViewport(const gfx::IntSize& aSize, const gfx::Matrix4x4& aProjection, float aZNear, float aZFar) { - mGpuRasterViewport = false; D3D11_VIEWPORT viewport; viewport.MaxDepth = aZFar; viewport.MinDepth = aZNear; diff --git a/gfx/layers/d3d11/CompositorD3D11.h b/gfx/layers/d3d11/CompositorD3D11.h index d26a3d6916..ca775cd60b 100644 --- a/gfx/layers/d3d11/CompositorD3D11.h +++ b/gfx/layers/d3d11/CompositorD3D11.h @@ -11,7 +11,6 @@ #include "mozilla/layers/Compositor.h" #include "TextureD3D11.h" #include -#include class nsWidget; @@ -175,8 +174,6 @@ private: RefPtr* aOutView); RefPtr mContext; - RefPtr mGpuRasterContext; - bool mGpuRasterViewport; RefPtr mDevice; RefPtr mSwapChain; RefPtr mDefaultRT; diff --git a/gfx/layers/d3d11/GpuRasterD3D11.c b/gfx/layers/d3d11/GpuRasterD3D11.c deleted file mode 100644 index 6e85300502..0000000000 --- a/gfx/layers/d3d11/GpuRasterD3D11.c +++ /dev/null @@ -1,84 +0,0 @@ -/* 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/. */ - -#define COBJMACROS -#include "GpuRasterD3D11.h" - -#include - -static BOOL -IntegerEdge(double value) -{ - /* Ordered comparisons reject NaNs and infinities before integer conversion. */ - return value >= LONG_MIN && value <= LONG_MAX && value == (LONG)value; -} - -BOOL -GpuRasterD3D11ClipRect(const GpuRasterRect* rect, const D3D11_RECT* clip, - LONG width, LONG height, D3D11_RECT* result) -{ - LONG left, top, right, bottom; - if (!rect || !clip || !result || width <= 0 || height <= 0 || - !IntegerEdge(rect->left) || !IntegerEdge(rect->top) || - !IntegerEdge(rect->right) || !IntegerEdge(rect->bottom) || - rect->right < rect->left || rect->bottom < rect->top) { - return FALSE; - } - - left = (LONG)rect->left; - top = (LONG)rect->top; - right = (LONG)rect->right; - bottom = (LONG)rect->bottom; - if (left < clip->left) left = clip->left; - if (top < clip->top) top = clip->top; - if (right > clip->right) right = clip->right; - if (bottom > clip->bottom) bottom = clip->bottom; - if (left < 0) left = 0; - if (top < 0) top = 0; - if (right > width) right = width; - if (bottom > height) bottom = height; - - if (right <= left || bottom <= top) { - result->left = result->top = result->right = result->bottom = 0; - } else { - result->left = left; - result->top = top; - result->right = right; - result->bottom = bottom; - } - return TRUE; -} - -BOOL -GpuRasterD3D11FillRect(ID3D11DeviceContext1* context, - ID3D11RenderTargetView* target, - LONG width, LONG height, - const GpuRasterRect* rect, const D3D11_RECT* clip, - const float color[4]) -{ - D3D11_RECT clipped; - D3D11_RENDER_TARGET_VIEW_DESC desc; - unsigned i; - if (!context || !target || !color || - !GpuRasterD3D11ClipRect(rect, clip, width, height, &clipped)) { - return FALSE; - } - for (i = 0; i < 4; ++i) { - if (!(color[i] >= 0.0f && color[i] <= 1.0f)) { - return FALSE; - } - } - - ID3D11RenderTargetView_GetDesc(target, &desc); - if ((desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM && - desc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) || - desc.ViewDimension != D3D11_RTV_DIMENSION_TEXTURE2D || - desc.Texture2D.MipSlice != 0) { - return FALSE; - } - if (clipped.right != clipped.left && clipped.bottom != clipped.top) { - ID3D11DeviceContext1_ClearView(context, (ID3D11View*)target, color, &clipped, 1); - } - return TRUE; -} diff --git a/gfx/layers/d3d11/GpuRasterD3D11.h b/gfx/layers/d3d11/GpuRasterD3D11.h deleted file mode 100644 index ae7c219e69..0000000000 --- a/gfx/layers/d3d11/GpuRasterD3D11.h +++ /dev/null @@ -1,36 +0,0 @@ -/* 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/. */ - -#ifndef GFX_GPU_RASTER_D3D11_H -#define GFX_GPU_RASTER_D3D11_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Device-space edges. Fractional or non-finite edges require normal drawing. */ -typedef struct GpuRasterRect { - double left, top, right, bottom; -} GpuRasterRect; - -/* FALSE means unsupported input; TRUE may produce an empty clipped rectangle. */ -BOOL GpuRasterD3D11ClipRect(const GpuRasterRect* rect, const D3D11_RECT* clip, - LONG width, LONG height, D3D11_RECT* result); - -/* Source replacement, not alpha blending. The caller must check ClearView - * device support and supply the dimensions of the render target. No pipeline - * bindings are changed. FALSE leaves the target untouched for shader fallback. */ -BOOL GpuRasterD3D11FillRect(ID3D11DeviceContext1* context, - ID3D11RenderTargetView* target, - LONG width, LONG height, - const GpuRasterRect* rect, const D3D11_RECT* clip, - const float color[4]); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 1b081a780b..dfec86adbf 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -80,7 +80,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': ] SOURCES += [ 'd3d11/CompositorD3D11.cpp', - 'd3d11/GpuRasterD3D11.c', 'd3d11/ReadbackManagerD3D11.cpp', ] UNIFIED_SOURCES += [ diff --git a/gfx/tests/gtest/TestGpuRasterD3D11.cpp b/gfx/tests/gtest/TestGpuRasterD3D11.cpp deleted file mode 100644 index 27670ecc65..0000000000 --- a/gfx/tests/gtest/TestGpuRasterD3D11.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* 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/. */ - -#ifndef NOMINMAX -# define NOMINMAX -#endif -#include "d3d11/GpuRasterD3D11.h" - -#include -#include -#include - -#ifndef GPU_RASTER_STANDALONE -# include "gtest/gtest.h" -#endif - -#define CHECK_GPU(condition) do { \ - if (!(condition)) { fprintf(stderr, "GPU rectangle test failed at line %d\n", __LINE__); \ - return false; } \ -} while (0) - -static bool TestGpuRectangleClipping() -{ - D3D11_RECT clip = { 2, 3, 12, 13 }, output; - GpuRasterRect rect = { -4, -5, 20, 21 }; - CHECK_GPU(GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - CHECK_GPU(output.left == 2 && output.top == 3 && output.right == 10 && output.bottom == 10); - rect = { 20, 20, 25, 25 }; - CHECK_GPU(GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - CHECK_GPU(output.left == output.right && output.top == output.bottom); - rect = { 1.5, 2, 4, 5 }; - CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - rect.left = std::numeric_limits::quiet_NaN(); - CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - rect.left = std::numeric_limits::infinity(); - CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - rect.left = 2147483648.0; - CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - rect = { 6, 2, 4, 5 }; - CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); - CHECK_GPU(!GpuRasterD3D11ClipRect(nullptr, &clip, 10, 10, &output)); - CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 0, 10, &output)); - return true; -} - -#ifdef GPU_RASTER_STANDALONE -struct GpuRasterTestDevice { - ID3D11Device* device = nullptr; - ID3D11DeviceContext* context = nullptr; - ID3D11DeviceContext1* context1 = nullptr; - ID3D11Texture2D* texture = nullptr; - ID3D11Texture2D* readback = nullptr; - ID3D11RenderTargetView* view = nullptr; - - ~GpuRasterTestDevice() { - if (view) view->Release(); - if (readback) readback->Release(); - if (texture) texture->Release(); - if (context1) context1->Release(); - if (context) context->Release(); - if (device) device->Release(); - } -}; - -static bool TestGpuRectanglePixels(DXGI_FORMAT format) -{ - GpuRasterTestDevice gpu; - CHECK_GPU(SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, - 0, nullptr, 0, D3D11_SDK_VERSION, - &gpu.device, nullptr, &gpu.context))); - CHECK_GPU(SUCCEEDED(gpu.context->QueryInterface(__uuidof(ID3D11DeviceContext1), - (void**)&gpu.context1))); - D3D11_FEATURE_DATA_D3D11_OPTIONS options = {}; - CHECK_GPU(SUCCEEDED(gpu.device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, - &options, sizeof(options)))); - CHECK_GPU(options.ClearView); - D3D11_TEXTURE2D_DESC desc = {}; - desc.Width = desc.Height = 16; - desc.MipLevels = desc.ArraySize = desc.SampleDesc.Count = 1; - desc.Format = format; - desc.BindFlags = D3D11_BIND_RENDER_TARGET; - CHECK_GPU(SUCCEEDED(gpu.device->CreateTexture2D(&desc, nullptr, &gpu.texture))); - CHECK_GPU(SUCCEEDED(gpu.device->CreateRenderTargetView(gpu.texture, nullptr, &gpu.view))); - desc.Usage = D3D11_USAGE_STAGING; - desc.BindFlags = 0; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - CHECK_GPU(SUCCEEDED(gpu.device->CreateTexture2D(&desc, nullptr, &gpu.readback))); - - const float blue[4] = { 0, 0, 1, 1 }, red[4] = { 1, 0, 0, 1 }, clear[4] = { 0, 0, 0, 0 }; - gpu.context->ClearRenderTargetView(gpu.view, blue); - GpuRasterRect rect = { -3, 2, 12, 20 }; - D3D11_RECT clip = { 3, 4, 10, 11 }; - // ClearView must use the explicit clip, not the context's scissor state. - D3D11_RECT scissor = { 0, 0, 1, 1 }; - gpu.context->RSSetScissorRects(1, &scissor); - gpu.context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - CHECK_GPU(GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, red)); - rect = { 5, 5, 7, 7 }; - clip = { 0, 0, 16, 16 }; - CHECK_GPU(GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, clear)); - rect = { 50, 50, 60, 60 }; - CHECK_GPU(GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, clear)); - rect = { 0.5, 0, 16, 16 }; - CHECK_GPU(!GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, clear)); - CHECK_GPU(!GpuRasterD3D11FillRect(nullptr, gpu.view, 16, 16, &rect, &clip, clear)); - - D3D11_PRIMITIVE_TOPOLOGY topology; - gpu.context->IAGetPrimitiveTopology(&topology); - CHECK_GPU(topology == D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - UINT count = 1; - gpu.context->RSGetScissorRects(&count, &scissor); - CHECK_GPU(count == 1 && scissor.left == 0 && scissor.top == 0 && - scissor.right == 1 && scissor.bottom == 1); - - gpu.context->CopyResource(gpu.readback, gpu.texture); - D3D11_MAPPED_SUBRESOURCE map; - CHECK_GPU(SUCCEEDED(gpu.context->Map(gpu.readback, 0, D3D11_MAP_READ, 0, &map))); - bool equal = true; - for (unsigned y = 0; y < 16; ++y) { - const uint32_t* row = reinterpret_cast( - static_cast(map.pData) + y * map.RowPitch); - for (unsigned x = 0; x < 16; ++x) { - bool isRed = x >= 3 && x < 10 && y >= 4 && y < 11; - uint32_t expected = (isRed == (format == DXGI_FORMAT_R8G8B8A8_UNORM)) - ? 0xff0000ff : 0xffff0000; - if (x >= 5 && x < 7 && y >= 5 && y < 7) expected = 0; - equal = equal && row[x] == expected; - } - } - gpu.context->Unmap(gpu.readback, 0); - CHECK_GPU(equal); - return true; -} - -int main() -{ - return TestGpuRectangleClipping() && - TestGpuRectanglePixels(DXGI_FORMAT_R8G8B8A8_UNORM) && - TestGpuRectanglePixels(DXGI_FORMAT_B8G8R8A8_UNORM) ? 0 : 1; -} -#else -TEST(GpuRasterD3D11, ClipRect) { EXPECT_TRUE(TestGpuRectangleClipping()); } -// WARP/Context1 is not available on every supported Windows installation. -// Run the standalone pixel tests on Windows 8+ with a D3D11.1 runtime. -#endif - -#undef CHECK_GPU diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build index ea1102f71d..66851abc70 100644 --- a/gfx/tests/gtest/moz.build +++ b/gfx/tests/gtest/moz.build @@ -53,9 +53,6 @@ LOCAL_INCLUDES += [ FINAL_LIBRARY = 'xul-gtest' -if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows' and CONFIG['MOZ_ENABLE_D3D10_LAYER']: - SOURCES += ['TestGpuRasterD3D11.cpp'] - CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] if CONFIG['GNU_CXX']: diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index adab38e25b..eb447c5667 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -468,7 +468,6 @@ private: DECL_GFX_PREF(Once, "layers.componentalpha.enabled", ComponentAlphaEnabled, bool, true); DECL_GFX_PREF(Live, "layers.composer2d.enabled", Composer2DCompositionEnabled, bool, false); DECL_GFX_PREF(Once, "layers.d3d11.force-warp", LayersD3D11ForceWARP, bool, false); - DECL_GFX_PREF(Once, "layers.d3d11.gpu-rectangle-fills.enabled", D3D11GpuRectangleFills, bool, false); DECL_GFX_PREF(Live, "layers.deaa.enabled", LayersDEAAEnabled, bool, false); DECL_GFX_PREF(Live, "layers.draw-bigimage-borders", DrawBigImageBorders, bool, false); DECL_GFX_PREF(Live, "layers.draw-borders", DrawLayerBorders, bool, false); diff --git a/js/src/aclocal.m4 b/js/src/aclocal.m4 index 495438e15e..22c0911873 100644 --- a/js/src/aclocal.m4 +++ b/js/src/aclocal.m4 @@ -16,7 +16,6 @@ builtin(include, ../../build/autoconf/altoptions.m4)dnl builtin(include, ../../build/autoconf/mozprog.m4)dnl builtin(include, ../../build/autoconf/mozheader.m4)dnl builtin(include, ../../build/autoconf/lto.m4)dnl -builtin(include, ../../build/autoconf/clang-cl-pgo.m4)dnl builtin(include, ../../build/autoconf/frameptr.m4)dnl builtin(include, ../../build/autoconf/compiler-opts.m4)dnl builtin(include, ../../build/autoconf/expandlibs.m4)dnl diff --git a/js/src/jit-test/tests/latin1/sse2-search.js b/js/src/jit-test/tests/latin1/sse2-search.js deleted file mode 100644 index 064e802b87..0000000000 --- a/js/src/jit-test/tests/latin1/sse2-search.js +++ /dev/null @@ -1,21 +0,0 @@ -// Exercise vector-sized spans, scalar tails, and mixed character encodings. -for (var length of [0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65]) { - for (var needle of ["\0", "\x7f", "\x80", "\xff", "\u0100", "\ud800", "\uffff"]) { - for (var position = 0; position <= length; ++position) { - var text = "a".repeat(position) + needle + "a".repeat(length - position); - for (var start of [0, position, position + 1, text.length]) { - var expected = start <= position ? position : -1; - assertEq(text.indexOf(needle, start), expected); - assertEq(text.includes(needle, start), expected !== -1); - } - assertEq(text.indexOf(needle + "b"), -1); - assertEq(text.indexOf(needle + "a"), position < length ? position : -1); - } - } - var latin1 = "\0".repeat(length) + "\xff"; - assertEq(latin1.indexOf("\u0100"), -1); // Must not narrow to NUL on x86. - assertEq(latin1.indexOf("\uffff"), -1); // Must not narrow to 0xff on x86. - var wide = "\u0100" + latin1; - assertEq(wide.indexOf("\xff"), length + 1); - assertEq(wide.indexOf(latin1), 1); -} diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index f5d32d6c8f..1b8730c49d 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -17,7 +17,6 @@ UNIFIED_SOURCES += [ 'testBug604087.cpp', 'testCallArgs.cpp', 'testCallNonGenericMethodOnProxy.cpp', - 'testCharacterOperations.cpp', 'testChromeBuffer.cpp', 'testClassGetter.cpp', 'testCloneScript.cpp', diff --git a/js/src/jsapi-tests/testCharacterOperations.cpp b/js/src/jsapi-tests/testCharacterOperations.cpp deleted file mode 100644 index eb8fdb5ba8..0000000000 --- a/js/src/jsapi-tests/testCharacterOperations.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* 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 "vm/CharacterOperations.h" - -#include - -#ifndef JS_CHARACTER_OPERATIONS_STANDALONE -# include "jsapi-tests/tests.h" -# include "jswin.h" -#elif defined(_WIN32) -# include -#endif - -template -static bool -CheckCharacterSearch() -{ - CharT buffer[128]; - const uint32_t needles[] = {0, 0x7f, 0x80, 0xff, 0x100, 0xd800, 0xdc00, 0xffff}; - for (uint32_t value : needles) { - if (sizeof(CharT) == 1 && value > 0xff) - continue; - const CharT needle = CharT(value); - const CharT other = CharT(value ^ 1); - for (size_t offset = 0; offset < 16 / sizeof(CharT); ++offset) { - CharT* text = buffer + offset; - for (size_t length = 0; length <= 96; ++length) { - for (size_t i = 0; i <= length; ++i) - text[i] = other; - // A match just outside the span must never be returned. - text[length] = needle; - if (js::FindCharacter(text, length, needle)) - return false; - for (size_t position = 0; position < length; ++position) { - text[position] = needle; - text[length - 1] = needle; - if (js::FindCharacter(text, length, needle) != text + position) - return false; - text[position] = other; - text[length - 1] = other; - } - } - } - } - return true; -} - -static bool -CheckLatin1Detection() -{ - char16_t buffer[128]; - const char16_t nonLatin1[] = {0x100, 0x8000, 0xd800, 0xdc00, 0xffff}; - for (size_t offset = 0; offset < 8; ++offset) { - char16_t* text = buffer + offset; - for (size_t length = 0; length <= 96; ++length) { - for (size_t i = 0; i < length; ++i) - text[i] = (i & 1) ? 0xff : 0x80; - text[length] = 0xffff; - if (!js::CharactersFitInLatin1(text, length)) - return false; - for (char16_t invalid : nonLatin1) { - for (size_t position = 0; position < length; ++position) { - const char16_t saved = text[position]; - text[position] = invalid; - if (js::CharactersFitInLatin1(text, length)) - return false; - text[position] = saved; - } - } - } - } - return true; -} - -#ifdef _WIN32 -static bool -CheckCharacterPageBoundary() -{ - SYSTEM_INFO info; - GetSystemInfo(&info); - char* pages = static_cast(VirtualAlloc(nullptr, 2 * info.dwPageSize, - MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); - if (!pages) - return false; - DWORD oldProtection; - char* end = pages + info.dwPageSize; - bool ok = !!VirtualProtect(end, info.dwPageSize, PAGE_NOACCESS, &oldProtection); - if (ok) { - for (size_t length = 0; length <= 64; ++length) { - char* bytes = end - length; - for (size_t i = 0; i < length; ++i) - bytes[i] = 'a'; - ok = ok && !js::FindCharacter(bytes, length, 'b'); - if (length) { - bytes[length - 1] = 'b'; - ok = ok && js::FindCharacter(bytes, length, 'b') == bytes + length - 1; - } - char16_t* wide = reinterpret_cast(end) - length; - for (size_t i = 0; i < length; ++i) - wide[i] = 0xff; - ok = ok && !js::FindCharacter(wide, length, char16_t(0x100)); - ok = ok && js::CharactersFitInLatin1(wide, length); - if (length) { - wide[length - 1] = 0x100; - ok = ok && js::FindCharacter(wide, length, char16_t(0x100)) == wide + length - 1; - ok = ok && !js::CharactersFitInLatin1(wide, length); - } - } - } - VirtualFree(pages, 0, MEM_RELEASE); - return ok; -} -#endif - -static bool -CheckCharacterOperations() -{ - return CheckCharacterSearch() && CheckCharacterSearch() && - CheckCharacterSearch() && CheckLatin1Detection() -#ifdef _WIN32 - && CheckCharacterPageBoundary() -#endif - ; -} - -// Allow testing these native helpers without rebuilding/linking SpiderMonkey. -#ifdef JS_CHARACTER_OPERATIONS_STANDALONE -int main() -{ - if (!CheckCharacterOperations()) { - fprintf(stderr, "Character operation regression test failed\n"); - return 1; - } - return 0; -} -#else -BEGIN_TEST(testCharacterOperations) -{ - CHECK(CheckCharacterOperations()); - return true; -} -END_TEST(testCharacterOperations) -#endif diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index d8374e8e43..67de5d7236 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -38,7 +38,6 @@ #include "js/UniquePtr.h" #include "unicode/uchar.h" #include "unicode/unorm2.h" -#include "vm/CharacterOperations.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/Opcodes.h" @@ -1641,11 +1640,7 @@ FirstCharMatcherUnrolled(const TextChar* text, uint32_t n, const PatChar pat) static const char* FirstCharMatcher8bit(const char* text, uint32_t n, const char pat) { -#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS - if (n >= 16) - return FindCharacter(text, n, pat); -#endif -#if defined(__clang__) +#if defined(__clang__) return FirstCharMatcherUnrolled(text, n, pat); #else return reinterpret_cast(memchr(text, pat, n)); @@ -1655,10 +1650,6 @@ FirstCharMatcher8bit(const char* text, uint32_t n, const char pat) static const char16_t* FirstCharMatcher16bit(const char16_t* text, uint32_t n, const char16_t pat) { -#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS - if (n >= 8) - return FindCharacter(text, n, pat); -#endif #if defined(XP_DARWIN) || defined(XP_WIN) /* * Performance of memchr is horrible in OSX. Windows is better, @@ -1743,14 +1734,18 @@ StringMatch(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t return -1; #if defined(__i386__) || defined(_M_IX86) || defined(__i386) - // Avoid the generic substring matcher for a single character on x86. - // FindCharacter uses SSE2 where available, including mixed encodings. + /* + * Given enough registers, the unrolled loop below is faster than the + * following loop. 32-bit x86 does not have enough registers. + */ if (patLen == 1) { - // A two-byte needle cannot match Latin1 text if it exceeds 0xff. - if (sizeof(TextChar) == 1 && uint32_t(*pat) > 0xff) - return -1; - const TextChar* match = FindCharacter(text, textLen, TextChar(*pat)); - return match ? int(match - text) : -1; + const PatChar p0 = *pat; + const TextChar* end = text + textLen; + for (const TextChar* c = text; c != end; ++c) { + if (*c == p0) + return c - text; + } + return -1; } #endif diff --git a/js/src/old-configure.in b/js/src/old-configure.in index c80d90bd7e..b046699f17 100644 --- a/js/src/old-configure.in +++ b/js/src/old-configure.in @@ -711,17 +711,7 @@ case "$target" in if test "$AS_BIN"; then AS="$(basename "$AS_BIN")" fi - if test -n "$CLANG_CL"; then - dnl Microsoft lib.exe cannot archive LLVM bitcode from ThinLTO. - AC_PATH_PROG(LLVM_LIB, llvm-lib.exe) - if test -z "$LLVM_LIB"; then - AC_MSG_ERROR([clang-cl requires llvm-lib.exe on PATH]) - fi - dnl expandlibs invokes AR from native Windows Python. - AR="$(cd "$(dirname "$LLVM_LIB")" && pwd -W)/$(basename "$LLVM_LIB")" - else - AR='lib' - fi + AR='lib' AR_FLAGS='-NOLOGO -OUT:$@' AR_EXTRACT= RANLIB='echo not_ranlib' @@ -2015,9 +2005,6 @@ if test "$ac_cv_struct_tm_zone_tm_gmtoff" = "yes" ; then fi fi # ! SKIP_COMPILER_CHECKS -dnl Windows skips the GCC probes above, but still needs LLVM PGO setup. -MOZ_CLANG_CL_PGO - AC_DEFINE(CPP_THROW_NEW, [throw()]) AC_LANG_C diff --git a/js/src/vm/CharacterOperations.h b/js/src/vm/CharacterOperations.h deleted file mode 100644 index 591a3216bf..0000000000 --- a/js/src/vm/CharacterOperations.h +++ /dev/null @@ -1,81 +0,0 @@ -/* 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/. */ - -#ifndef vm_CharacterOperations_h -#define vm_CharacterOperations_h - -#include "mozilla/MathAlgorithms.h" - -#include -#include - -// Use intrinsics only when SSE2 is part of the compiler's target baseline. -// Builds for other architectures (or pre-SSE2 x86) retain scalar operations. -#if defined(__SSE2__) || defined(_M_X64) || \ - (defined(_M_IX86_FP) && _M_IX86_FP >= 2) -# define JS_HAS_SSE2_CHARACTER_OPERATIONS -# include -#endif - -namespace js { - -template -inline const CharT* -FindCharacter(const CharT* chars, size_t length, CharT match) -{ - static_assert(sizeof(CharT) == 1 || sizeof(CharT) == 2, "character width"); -#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS - const size_t lanes = 16 / sizeof(CharT); - if (length >= lanes) { - const __m128i needle = sizeof(CharT) == 1 - ? _mm_set1_epi8(static_cast(match)) - : _mm_set1_epi16(static_cast(match)); - do { - // Never read beyond the supplied span, even at a page boundary. - const __m128i block = _mm_loadu_si128(reinterpret_cast(chars)); - const __m128i equal = sizeof(CharT) == 1 - ? _mm_cmpeq_epi8(block, needle) - : _mm_cmpeq_epi16(block, needle); - const uint32_t mask = static_cast(_mm_movemask_epi8(equal)); - if (mask) - return chars + mozilla::CountTrailingZeroes32(mask) / sizeof(CharT); - chars += lanes; - length -= lanes; - } while (length >= lanes); - } -#endif - for (; length; --length, ++chars) { - if (*chars == match) - return chars; - } - return nullptr; -} - -inline bool -CharactersFitInLatin1(const char16_t* chars, size_t length) -{ -#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS - if (length >= 8) { - const __m128i highBytes = _mm_set1_epi16(static_cast(0xff00)); - const __m128i zero = _mm_setzero_si128(); - do { - const __m128i block = _mm_loadu_si128(reinterpret_cast(chars)); - const __m128i fits = _mm_cmpeq_epi16(_mm_and_si128(block, highBytes), zero); - if (_mm_movemask_epi8(fits) != 0xffff) - return false; - chars += 8; - length -= 8; - } while (length >= 8); - } -#endif - for (; length; --length, ++chars) { - if (*chars > 0xff) - return false; - } - return true; -} - -} // namespace js - -#endif // vm_CharacterOperations_h diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 3ef6fb229d..538e9c09eb 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -15,7 +15,6 @@ #include "gc/Marking.h" #include "js/UbiNode.h" -#include "vm/CharacterOperations.h" #include "vm/SPSProfiler.h" #include "jscntxtinlines.h" @@ -1148,8 +1147,12 @@ js::NewDependentString(JSContext* cx, JSString* baseArg, size_t start, size_t le static bool CanStoreCharsAsLatin1(const char16_t* s, size_t length) { - static_assert(JSString::MAX_LATIN1_CHAR == 0xff, "Latin1 character range"); - return CharactersFitInLatin1(s, length); + for (const char16_t* end = s + length; s < end; ++s) { + if (*s > JSString::MAX_LATIN1_CHAR) + return false; + } + + return true; } static bool diff --git a/memory/mozalloc/mozalloc.h b/memory/mozalloc/mozalloc.h index 9c5b693288..de8c549b31 100644 --- a/memory/mozalloc/mozalloc.h +++ b/memory/mozalloc/mozalloc.h @@ -33,12 +33,6 @@ #define MOZALLOC_HAVE_XMALLOC -/* Workaround build problems with v142+ MSVC*/ -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable: 5043) -#endif - /* Workaround build problem with Sun Studio 12 */ #if defined(__SUNPRO_C) || defined(__SUNPRO_CC) # undef MOZ_MUST_USE diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 9fbcbcab9c..5e124ff937 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -392,6 +392,11 @@ pref("media.wave.play-stand-alone", false); pref("media.hardware-video-decoding.enabled", true); pref("media.hardware-video-decoding.force-enabled", false); +#ifdef MOZ_DIRECTSHOW +pref("media.directshow.enabled", true); +pref("media.directshow.h264.active", false); +#endif + #ifdef MOZ_FMP4 pref("media.mp4.enabled", true); // Specifies whether the PDMFactory can create a test decoder that @@ -4565,8 +4570,6 @@ pref("gfx.direct2d.force-enabled", false); pref("layers.prefer-opengl", false); pref("layers.prefer-d3d9", false); -// Experimental C GPU rectangle path. Requires D3D11.1 ClearView support and a restart. -pref("layers.d3d11.gpu-rectangle-fills.enabled", false); // Enable fallback if d3d11 can't be used. See bug #1262187 pref("layers.allow-d3d9-fallback", true); #endif diff --git a/moz.configure b/moz.configure index f4cbbed1c8..ec5b027219 100644 --- a/moz.configure +++ b/moz.configure @@ -5,6 +5,16 @@ include('build/moz.configure/init.configure') +# DirectShow is the legacy Windows media backend. Keep this independent of +# the old configure scripts so the backend is actually compiled on Windows. +@depends(target) +def directshow_enabled(target): + if target.kernel == 'WINNT': + return True + +set_config('MOZ_DIRECTSHOW', directshow_enabled) +set_define('MOZ_DIRECTSHOW', directshow_enabled) + # Note: # - Gecko-specific options and rules should go in toolkit/moz.configure. # - Firefox-specific options and rules should go in browser/moz.configure. diff --git a/old-configure.in b/old-configure.in index 364027bcee..8744628eae 100644 --- a/old-configure.in +++ b/old-configure.in @@ -876,17 +876,7 @@ case "$target" in if test "$AS_BIN"; then AS="$(basename "$AS_BIN")" fi - if test -n "$CLANG_CL"; then - dnl Microsoft lib.exe cannot archive LLVM bitcode from ThinLTO. - AC_PATH_PROG(LLVM_LIB, llvm-lib.exe) - if test -z "$LLVM_LIB"; then - AC_MSG_ERROR([clang-cl requires llvm-lib.exe on PATH]) - fi - dnl expandlibs invokes AR from native Windows Python. - AR="$(cd "$(dirname "$LLVM_LIB")" && pwd -W)/$(basename "$LLVM_LIB")" - else - AR='lib' - fi + AR='lib' AR_FLAGS='-NOLOGO -OUT:$@' AR_EXTRACT= RANLIB='echo not_ranlib' @@ -4557,9 +4547,6 @@ AC_SUBST(PROFILE_USE_LDFLAGS) fi # ! SKIP_COMPILER_CHECKS -dnl Windows skips the GCC probes above, but still needs LLVM PGO setup. -MOZ_CLANG_CL_PGO - AC_DEFINE(CPP_THROW_NEW, [throw()]) AC_LANG_C diff --git a/testing/testsuite-targets.mk b/testing/testsuite-targets.mk index 68f5ff54d8..f10bd079c2 100644 --- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -143,6 +143,9 @@ cppunittests-remote: jetpack-tests: cd $(topsrcdir)/addon-sdk/source && $(PYTHON) bin/cfx -b $(abspath $(browser_path)) --parseable testpkgs +pgo-profile-run: + $(PYTHON) $(topsrcdir)/build/pgo/profileserver.py $(EXTRA_TEST_ARGS) + # Package up the tests and test harnesses include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js index 9684e80ba7..a1825c7cad 100644 --- a/toolkit/content/aboutSupport.js +++ b/toolkit/content/aboutSupport.js @@ -337,6 +337,16 @@ var snapshotFormatters = { addRowFromKey("features", "webgl2Extensions"); addRowFromKey("features", "supportsHardwareH264", "hardwareH264"); addRowFromKey("features", "currentAudioBackend", "audioBackend"); + if ("directShowEnabled" in data) { + addRow("features", "directShowEnabled", + strings.GetStringFromName(data.directShowEnabled ? "yes" : "no")); + delete data.directShowEnabled; + } + if ("directShowH264Active" in data) { + addRow("features", "directShowH264Active", + strings.GetStringFromName(data.directShowH264Active ? "yes" : "no")); + delete data.directShowH264Active; + } addRowFromKey("features", "direct2DEnabled", "#Direct2D"); if ("directWriteEnabled" in data) { diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build index 49ff260b47..470f10124b 100644 --- a/toolkit/library/moz.build +++ b/toolkit/library/moz.build @@ -173,6 +173,9 @@ if CONFIG['OS_ARCH'] == 'WINNT': 'crypt32', 'shell32', 'ole32', + 'strmiids', + 'dmoguids', + 'msdmo', 'version', 'winspool', ] diff --git a/toolkit/locales/en-US/chrome/global/aboutSupport.properties b/toolkit/locales/en-US/chrome/global/aboutSupport.properties index b452514158..629651d81f 100644 --- a/toolkit/locales/en-US/chrome/global/aboutSupport.properties +++ b/toolkit/locales/en-US/chrome/global/aboutSupport.properties @@ -60,6 +60,8 @@ clearTypeParameters = ClearType Parameters compositing = Compositing hardwareH264 = Hardware H264 Decoding audioBackend = Audio Backend +directShowEnabled = DirectShow H264 Backend Enabled +directShowH264Active = DirectShow H264 Decoding Active mainThreadNoOMTC = main thread, no OMTC yes = Yes no = No diff --git a/toolkit/modules/Troubleshoot.jsm b/toolkit/modules/Troubleshoot.jsm index c25cbce433..1cf7333ed5 100644 --- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -358,6 +358,8 @@ var dataProviders = { } catch (e) {} data.currentAudioBackend = winUtils.currentAudioBackend; + data.directShowEnabled = Services.prefs.getBoolPref("media.directshow.enabled", false); + data.directShowH264Active = Services.prefs.getBoolPref("media.directshow.h264.active", false); if (!data.numAcceleratedWindows && gfxInfo) { #ifdef XP_WIN @@ -551,4 +553,3 @@ var dataProviders = { }); } }; -