gecko/content/media/DecoderTraits.cpp
Chris Pearce d0a93358ad Bug 986947 - Make MP3 contained in MP4 playback again on Windows with WMF backend. r=padenot
Report that we can play MP3 inside MP4 on Windows Vista and later in
HTMLMediaElement.canPlayType. Chrome and IE on Windows match this behaviour.

Add a test file with MP3 contained inside MP4. Note the B2G emulator can't play
this file, so I added a codecs parameter to the file's mime type so that
decoder backends have to opt-in to testing with it.

Enable playback of MP3 inside MP4 in WMFReader.

Change from reporting the IMFSourceReader's duration inside WMFReader, to
instead report the IMFSourceReader's duration as the media "end time". This is
needed because the MP3-contained-in-MP4 file's first sample output by the
IMFSourceReader has a non-zero timestamp, and the MediaDecoderStateMachine
assumes that the media samples will be in the range
[$firstSampleStartTime, $firstSampleStartTime+$reportedDuration]. But that's
not the case here, the IMFSourceReader seems to output samples in the range
[0,$reportedDuration]. This assumption mismatch means on the
MP3-contained-in-MP4 file we end up trying to seek after what the
IMFSourceReader assumes is the end of the file, which fails and causes
test failures.
2014-04-04 10:39:42 +13:00

680 lines
16 KiB
C++

/* -*- 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 "DecoderTraits.h"
#include "MediaDecoder.h"
#include "nsCharSeparatedTokenizer.h"
#include "mozilla/Preferences.h"
#ifdef MOZ_MEDIA_PLUGINS
#include "MediaPluginHost.h"
#endif
#include "OggDecoder.h"
#include "OggReader.h"
#ifdef MOZ_WAVE
#include "WaveDecoder.h"
#include "WaveReader.h"
#endif
#ifdef MOZ_WEBM
#include "WebMDecoder.h"
#include "WebMReader.h"
#endif
#ifdef MOZ_RAW
#include "RawDecoder.h"
#include "RawReader.h"
#endif
#ifdef MOZ_GSTREAMER
#include "GStreamerDecoder.h"
#include "GStreamerReader.h"
#endif
#ifdef MOZ_MEDIA_PLUGINS
#include "MediaPluginHost.h"
#include "MediaPluginDecoder.h"
#include "MediaPluginReader.h"
#include "MediaPluginHost.h"
#endif
#ifdef MOZ_OMX_DECODER
#include "MediaOmxDecoder.h"
#include "MediaOmxReader.h"
#include "nsIPrincipal.h"
#include "mozilla/dom/HTMLMediaElement.h"
#endif
#ifdef NECKO_PROTOCOL_rtsp
#include "RtspOmxDecoder.h"
#include "RtspOmxReader.h"
#endif
#ifdef MOZ_WMF
#include "WMFDecoder.h"
#include "WMFReader.h"
#endif
#ifdef MOZ_DIRECTSHOW
#include "DirectShowDecoder.h"
#include "DirectShowReader.h"
#endif
#ifdef MOZ_APPLEMEDIA
#include "AppleDecoder.h"
#include "AppleMP3Reader.h"
#endif
#ifdef MOZ_FMP4
#include "MP4Reader.h"
#include "MP4Decoder.h"
#endif
namespace mozilla
{
template <class String>
static bool
CodecListContains(char const *const * aCodecs, const String& aCodec)
{
for (int32_t i = 0; aCodecs[i]; ++i) {
if (aCodec.EqualsASCII(aCodecs[i]))
return true;
}
return false;
}
#ifdef MOZ_RAW
static const char* gRawTypes[3] = {
"video/x-raw",
"video/x-raw-yuv",
nullptr
};
static const char* gRawCodecs[1] = {
nullptr
};
static bool
IsRawType(const nsACString& aType)
{
if (!MediaDecoder::IsRawEnabled()) {
return false;
}
return CodecListContains(gRawTypes, aType);
}
#endif
// See http://www.rfc-editor.org/rfc/rfc5334.txt for the definitions
// of Ogg media types and codec types
static const char* const gOggTypes[4] = {
"video/ogg",
"audio/ogg",
"application/ogg",
nullptr
};
static char const *const gOggCodecs[3] = {
"vorbis",
"theora",
nullptr
};
static char const *const gOggCodecsWithOpus[4] = {
"vorbis",
"opus",
"theora",
nullptr
};
static bool
IsOggType(const nsACString& aType)
{
if (!MediaDecoder::IsOggEnabled()) {
return false;
}
return CodecListContains(gOggTypes, aType);
}
#ifdef MOZ_WAVE
// See http://www.rfc-editor.org/rfc/rfc2361.txt for the definitions
// of WAVE media types and codec types. However, the audio/vnd.wave
// MIME type described there is not used.
static const char* const gWaveTypes[5] = {
"audio/x-wav",
"audio/wav",
"audio/wave",
"audio/x-pn-wav",
nullptr
};
static char const *const gWaveCodecs[2] = {
"1", // Microsoft PCM Format
nullptr
};
static bool
IsWaveType(const nsACString& aType)
{
if (!MediaDecoder::IsWaveEnabled()) {
return false;
}
return CodecListContains(gWaveTypes, aType);
}
#endif
#ifdef MOZ_WEBM
static const char* const gWebMTypes[3] = {
"video/webm",
"audio/webm",
nullptr
};
static char const *const gWebMCodecs[7] = {
"vp8",
"vp8.0",
"vp9",
"vp9.0",
"vorbis",
"opus",
nullptr
};
static bool
IsWebMType(const nsACString& aType)
{
if (!MediaDecoder::IsWebMEnabled()) {
return false;
}
return CodecListContains(gWebMTypes, aType);
}
#endif
#ifdef MOZ_GSTREAMER
static bool
IsGStreamerSupportedType(const nsACString& aMimeType)
{
if (!MediaDecoder::IsGStreamerEnabled())
return false;
#ifdef MOZ_WEBM
if (IsWebMType(aMimeType) && !Preferences::GetBool("media.prefer-gstreamer", false))
return false;
#endif
if (IsOggType(aMimeType) && !Preferences::GetBool("media.prefer-gstreamer", false))
return false;
return GStreamerDecoder::CanHandleMediaType(aMimeType, nullptr);
}
#endif
#ifdef MOZ_OMX_DECODER
static const char* const gOmxTypes[7] = {
"audio/mpeg",
"audio/mp4",
"audio/amr",
"video/mp4",
"video/3gpp",
"video/quicktime",
nullptr
};
static bool
IsOmxSupportedType(const nsACString& aType)
{
if (!MediaDecoder::IsOmxEnabled()) {
return false;
}
return CodecListContains(gOmxTypes, aType);
}
static char const *const gH264Codecs[9] = {
"avc1.42E01E", // H.264 Constrained Baseline Profile Level 3.0
"avc1.42001E", // H.264 Baseline Profile Level 3.0
"avc1.58A01E", // H.264 Extended Profile Level 3.0
"avc1.4D401E", // H.264 Main Profile Level 3.0
"avc1.64001E", // H.264 High Profile Level 3.0
"avc1.64001F", // H.264 High Profile Level 3.1
"mp4v.20.3", // 3GPP
"mp4a.40.2", // AAC-LC
nullptr
};
static char const *const gMpegAudioCodecs[2] = {
"mp3", // MP3
nullptr
};
#endif
#ifdef NECKO_PROTOCOL_rtsp
static const char* const gRtspTypes[2] = {
"RTSP",
nullptr
};
static bool
IsRtspSupportedType(const nsACString& aMimeType)
{
return MediaDecoder::IsRtspEnabled() &&
CodecListContains(gRtspTypes, aMimeType);
}
#endif
/* static */
bool DecoderTraits::DecoderWaitsForOnConnected(const nsACString& aMimeType) {
#ifdef NECKO_PROTOCOL_rtsp
return CodecListContains(gRtspTypes, aMimeType);
#else
return false;
#endif
}
#ifdef MOZ_MEDIA_PLUGINS
static bool
IsMediaPluginsType(const nsACString& aType)
{
if (!MediaDecoder::IsMediaPluginsEnabled()) {
return false;
}
static const char* supportedTypes[] = {
"audio/mpeg", "audio/mp4", "video/mp4", nullptr
};
return CodecListContains(supportedTypes, aType);
}
#endif
#ifdef MOZ_WMF
static bool
IsWMFSupportedType(const nsACString& aType)
{
return WMFDecoder::CanPlayType(aType, NS_LITERAL_STRING(""));
}
#endif
#ifdef MOZ_DIRECTSHOW
static bool
IsDirectShowSupportedType(const nsACString& aType)
{
return DirectShowDecoder::GetSupportedCodecs(aType, nullptr);
}
#endif
#ifdef MOZ_FMP4
static bool
IsMP4SupportedType(const nsACString& aType)
{
return Preferences::GetBool("media.fragmented-mp4.exposed", false) &&
MP4Decoder::GetSupportedCodecs(aType, nullptr);
}
#endif
#ifdef MOZ_APPLEMEDIA
static const char * const gAppleMP3Types[] = {
"audio/mp3",
"audio/mpeg",
nullptr,
};
static const char * const gAppleMP3Codecs[] = {
"mp3",
nullptr
};
static bool
IsAppleMediaSupportedType(const nsACString& aType,
const char * const ** aCodecs = nullptr)
{
if (MediaDecoder::IsAppleMP3Enabled()
&& CodecListContains(gAppleMP3Types, aType)) {
if (aCodecs) {
*aCodecs = gAppleMP3Codecs;
}
return true;
}
// TODO MP4
return false;
}
#endif
/* static */
bool DecoderTraits::ShouldHandleMediaType(const char* aMIMEType)
{
#ifdef MOZ_WAVE
if (IsWaveType(nsDependentCString(aMIMEType))) {
// We should not return true for Wave types, since there are some
// Wave codecs actually in use in the wild that we don't support, and
// we should allow those to be handled by plugins or helper apps.
// Furthermore people can play Wave files on most platforms by other
// means.
return false;
}
#endif
return CanHandleMediaType(aMIMEType, false, EmptyString()) != CANPLAY_NO;
}
/* static */
CanPlayStatus
DecoderTraits::CanHandleMediaType(const char* aMIMEType,
bool aHaveRequestedCodecs,
const nsAString& aRequestedCodecs)
{
char const* const* codecList = nullptr;
CanPlayStatus result = CANPLAY_NO;
#ifdef MOZ_RAW
if (IsRawType(nsDependentCString(aMIMEType))) {
codecList = gRawCodecs;
result = CANPLAY_MAYBE;
}
#endif
if (IsOggType(nsDependentCString(aMIMEType))) {
codecList = MediaDecoder::IsOpusEnabled() ? gOggCodecsWithOpus : gOggCodecs;
result = CANPLAY_MAYBE;
}
#ifdef MOZ_WAVE
if (IsWaveType(nsDependentCString(aMIMEType))) {
codecList = gWaveCodecs;
result = CANPLAY_MAYBE;
}
#endif
#ifdef MOZ_WEBM
if (IsWebMType(nsDependentCString(aMIMEType))) {
codecList = gWebMCodecs;
result = CANPLAY_MAYBE;
}
#endif
#ifdef MOZ_GSTREAMER
if (GStreamerDecoder::CanHandleMediaType(nsDependentCString(aMIMEType),
aHaveRequestedCodecs ? &aRequestedCodecs : nullptr)) {
if (aHaveRequestedCodecs)
return CANPLAY_YES;
return CANPLAY_MAYBE;
}
#endif
#ifdef MOZ_OMX_DECODER
if (IsOmxSupportedType(nsDependentCString(aMIMEType))) {
result = CANPLAY_MAYBE;
if (nsDependentCString(aMIMEType).EqualsASCII("audio/mpeg")) {
codecList = gMpegAudioCodecs;
} else {
codecList = gH264Codecs;
}
}
#endif
#ifdef MOZ_DIRECTSHOW
// Note: DirectShow should come before WMF, so that we prefer DirectShow's
// MP3 support over WMF's.
if (DirectShowDecoder::GetSupportedCodecs(nsDependentCString(aMIMEType), &codecList)) {
result = CANPLAY_MAYBE;
}
#endif
#ifdef MOZ_WMF
if (IsWMFSupportedType(nsDependentCString(aMIMEType))) {
if (!aHaveRequestedCodecs) {
return CANPLAY_MAYBE;
}
return WMFDecoder::CanPlayType(nsDependentCString(aMIMEType),
aRequestedCodecs)
? CANPLAY_YES : CANPLAY_NO;
}
#endif
#ifdef MOZ_APPLEMEDIA
if (IsAppleMediaSupportedType(nsDependentCString(aMIMEType), &codecList)) {
result = CANPLAY_MAYBE;
}
#endif
#ifdef MOZ_MEDIA_PLUGINS
if (MediaDecoder::IsMediaPluginsEnabled() &&
GetMediaPluginHost()->FindDecoder(nsDependentCString(aMIMEType), &codecList))
result = CANPLAY_MAYBE;
#endif
#ifdef NECKO_PROTOCOL_rtsp
if (IsRtspSupportedType(nsDependentCString(aMIMEType))) {
result = CANPLAY_MAYBE;
}
#endif
if (result == CANPLAY_NO || !aHaveRequestedCodecs || !codecList) {
return result;
}
// See http://www.rfc-editor.org/rfc/rfc4281.txt for the description
// of the 'codecs' parameter
nsCharSeparatedTokenizer tokenizer(aRequestedCodecs, ',');
bool expectMoreTokens = false;
while (tokenizer.hasMoreTokens()) {
const nsSubstring& token = tokenizer.nextToken();
if (!CodecListContains(codecList, token)) {
// Totally unsupported codec
return CANPLAY_NO;
}
expectMoreTokens = tokenizer.separatorAfterCurrentToken();
}
if (expectMoreTokens) {
// Last codec name was empty
return CANPLAY_NO;
}
return CANPLAY_YES;
}
// Instantiates but does not initialize decoder.
static
already_AddRefed<MediaDecoder>
InstantiateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner)
{
nsRefPtr<MediaDecoder> decoder;
#ifdef MOZ_GSTREAMER
if (IsGStreamerSupportedType(aType)) {
decoder = new GStreamerDecoder();
return decoder.forget();
}
#endif
#ifdef MOZ_RAW
if (IsRawType(aType)) {
decoder = new RawDecoder();
return decoder.forget();
}
#endif
if (IsOggType(aType)) {
decoder = new OggDecoder();
return decoder.forget();
}
#ifdef MOZ_WAVE
if (IsWaveType(aType)) {
decoder = new WaveDecoder();
return decoder.forget();
}
#endif
#ifdef MOZ_OMX_DECODER
if (IsOmxSupportedType(aType)) {
// AMR audio is enabled for MMS, but we are discouraging Web and App
// developers from using AMR, thus we only allow AMR to be played on WebApps.
if (aType.EqualsASCII("audio/amr")) {
dom::HTMLMediaElement* element = aOwner->GetMediaElement();
if (!element) {
return nullptr;
}
nsIPrincipal* principal = element->NodePrincipal();
if (!principal) {
return nullptr;
}
if (principal->GetAppStatus() < nsIPrincipal::APP_STATUS_PRIVILEGED) {
return nullptr;
}
}
decoder = new MediaOmxDecoder();
return decoder.forget();
}
#endif
#ifdef NECKO_PROTOCOL_rtsp
if (IsRtspSupportedType(aType)) {
decoder = new RtspOmxDecoder();
return decoder.forget();
}
#endif
#ifdef MOZ_MEDIA_PLUGINS
if (MediaDecoder::IsMediaPluginsEnabled() &&
GetMediaPluginHost()->FindDecoder(aType, nullptr)) {
decoder = new MediaPluginDecoder(aType);
return decoder.forget();
}
#endif
#ifdef MOZ_WEBM
if (IsWebMType(aType)) {
decoder = new WebMDecoder();
return decoder.forget();
}
#endif
#ifdef MOZ_DIRECTSHOW
// Note: DirectShow should come before WMF, so that we prefer DirectShow's
// MP3 support over WMF's.
if (IsDirectShowSupportedType(aType)) {
decoder = new DirectShowDecoder();
return decoder.forget();
}
#endif
#ifdef MOZ_FMP4
if (IsMP4SupportedType(aType)) {
decoder = new MP4Decoder();
return decoder.forget();
}
#endif
#ifdef MOZ_WMF
if (IsWMFSupportedType(aType)) {
decoder = new WMFDecoder();
return decoder.forget();
}
#endif
#ifdef MOZ_APPLEMEDIA
if (IsAppleMediaSupportedType(aType)) {
decoder = new AppleDecoder();
return decoder.forget();
}
#endif
NS_ENSURE_TRUE(decoder != nullptr, nullptr);
NS_ENSURE_TRUE(decoder->Init(aOwner), nullptr);
return nullptr;
}
/* static */
already_AddRefed<MediaDecoder>
DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner)
{
nsRefPtr<MediaDecoder> decoder(InstantiateDecoder(aType, aOwner));
NS_ENSURE_TRUE(decoder != nullptr, nullptr);
NS_ENSURE_TRUE(decoder->Init(aOwner), nullptr);
return decoder.forget();
}
/* static */
MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, AbstractMediaDecoder* aDecoder)
{
MediaDecoderReader* decoderReader = nullptr;
#ifdef MOZ_GSTREAMER
if (IsGStreamerSupportedType(aType)) {
decoderReader = new GStreamerReader(aDecoder);
} else
#endif
#ifdef MOZ_RAW
if (IsRawType(aType)) {
decoderReader = new RawReader(aDecoder);
} else
#endif
if (IsOggType(aType)) {
decoderReader = new OggReader(aDecoder);
} else
#ifdef MOZ_WAVE
if (IsWaveType(aType)) {
decoderReader = new WaveReader(aDecoder);
} else
#endif
#ifdef MOZ_OMX_DECODER
if (IsOmxSupportedType(aType)) {
decoderReader = new MediaOmxReader(aDecoder);
} else
#endif
#ifdef MOZ_MEDIA_PLUGINS
if (MediaDecoder::IsMediaPluginsEnabled() &&
GetMediaPluginHost()->FindDecoder(aType, nullptr)) {
decoderReader = new MediaPluginReader(aDecoder, aType);
} else
#endif
#ifdef MOZ_WEBM
if (IsWebMType(aType)) {
decoderReader = new WebMReader(aDecoder);
} else
#endif
#ifdef MOZ_DIRECTSHOW
// Note: DirectShowReader is preferred for MP3, but if it's disabled we
// fallback to the WMFReader.
if (IsDirectShowSupportedType(aType)) {
decoderReader = new DirectShowReader(aDecoder);
} else
#endif
#ifdef MOZ_FMP4
if (IsMP4SupportedType(aType)) {
decoderReader = new MP4Reader(aDecoder);
} else
#endif
#ifdef MOZ_WMF
if (IsWMFSupportedType(aType)) {
decoderReader = new WMFReader(aDecoder);
} else
#endif
#ifdef MOZ_APPLEMEDIA
if (IsAppleMediaSupportedType(aType)) {
decoderReader = new AppleMP3Reader(aDecoder);
} else
#endif
if (false) {} // dummy if to take care of the dangling else
return decoderReader;
}
/* static */
bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
{
return
IsOggType(aType) ||
#ifdef MOZ_OMX_DECODER
// We support amr inside WebApps on firefoxOS but not in general web content.
// Ensure we dont create a VideoDocument when accessing amr URLs directly.
(IsOmxSupportedType(aType) && !aType.EqualsASCII("audio/amr")) ||
#endif
#ifdef MOZ_WEBM
IsWebMType(aType) ||
#endif
#ifdef MOZ_GSTREAMER
IsGStreamerSupportedType(aType) ||
#endif
#ifdef MOZ_MEDIA_PLUGINS
(MediaDecoder::IsMediaPluginsEnabled() && IsMediaPluginsType(aType)) ||
#endif
#ifdef MOZ_FMP4
IsMP4SupportedType(aType) ||
#endif
#ifdef MOZ_WMF
(IsWMFSupportedType(aType) &&
Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true)) ||
#endif
#ifdef MOZ_DIRECTSHOW
IsDirectShowSupportedType(aType) ||
#endif
#ifdef MOZ_APPLEMEDIA
IsAppleMediaSupportedType(aType) ||
#endif
#ifdef NECKO_PROTOCOL_rtsp
IsRtspSupportedType(aType) ||
#endif
false;
}
}