1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "MP4Decoder.h"
8 #include "H264.h"
9 #include "MP4Demuxer.h"
10 #include "MediaContainerType.h"
11 #include "PDMFactory.h"
12 #include "PlatformDecoderModule.h"
13 #include "VideoUtils.h"
14 #include "mozilla/StaticPrefs_media.h"
15 #include "mozilla/gfx/Tools.h"
16 #include "nsMimeTypes.h"
17 
18 namespace mozilla {
19 
IsWhitelistedH264Codec(const nsAString & aCodec)20 static bool IsWhitelistedH264Codec(const nsAString& aCodec) {
21   uint8_t profile = 0, constraint = 0, level = 0;
22 
23   if (!ExtractH264CodecDetails(aCodec, profile, constraint, level)) {
24     return false;
25   }
26 
27   // Just assume what we can play on all platforms the codecs/formats that
28   // WMF can play, since we don't have documentation about what other
29   // platforms can play... According to the WMF documentation:
30   // http://msdn.microsoft.com/en-us/library/windows/desktop/dd797815%28v=vs.85%29.aspx
31   // "The Media Foundation H.264 video decoder is a Media Foundation Transform
32   // that supports decoding of Baseline, Main, and High profiles, up to level
33   // 5.1.". We extend the limit to level 5.2, relying on the decoder to handle
34   // any potential errors, the level limit being rather arbitrary.
35   // We also report that we can play Extended profile, as there are
36   // bitstreams that are Extended compliant that are also Baseline compliant.
37   return level >= H264_LEVEL_1 && level <= H264_LEVEL_5_2 &&
38          (profile == H264_PROFILE_BASE || profile == H264_PROFILE_MAIN ||
39           profile == H264_PROFILE_EXTENDED || profile == H264_PROFILE_HIGH);
40 }
41 
IsTypeValid(const MediaContainerType & aType)42 static bool IsTypeValid(const MediaContainerType& aType) {
43   // Whitelist MP4 types, so they explicitly match what we encounter on
44   // the web, as opposed to what we use internally (i.e. what our demuxers
45   // etc output).
46   return aType.Type() == MEDIAMIMETYPE("audio/mp4") ||
47          aType.Type() == MEDIAMIMETYPE("audio/x-m4a") ||
48          aType.Type() == MEDIAMIMETYPE("video/mp4") ||
49          aType.Type() == MEDIAMIMETYPE("video/quicktime") ||
50          aType.Type() == MEDIAMIMETYPE("video/x-m4v");
51 }
52 
53 /* statis */
GetTracksInfo(const MediaContainerType & aType,MediaResult & aError)54 nsTArray<UniquePtr<TrackInfo>> MP4Decoder::GetTracksInfo(
55     const MediaContainerType& aType, MediaResult& aError) {
56   nsTArray<UniquePtr<TrackInfo>> tracks;
57 
58   if (!IsTypeValid(aType)) {
59     aError = MediaResult(
60         NS_ERROR_DOM_MEDIA_FATAL_ERR,
61         RESULT_DETAIL("Invalid type:%s", aType.Type().AsString().get()));
62     return tracks;
63   }
64 
65   aError = NS_OK;
66 
67   const MediaCodecs& codecs = aType.ExtendedType().Codecs();
68   if (codecs.IsEmpty()) {
69     return tracks;
70   }
71 
72   const bool isVideo = aType.Type() == MEDIAMIMETYPE("video/mp4") ||
73                        aType.Type() == MEDIAMIMETYPE("video/quicktime") ||
74                        aType.Type() == MEDIAMIMETYPE("video/x-m4v");
75 
76   for (const auto& codec : codecs.Range()) {
77     if (IsAACCodecString(codec)) {
78       tracks.AppendElement(
79           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
80               "audio/mp4a-latm"_ns, aType));
81       continue;
82     }
83     if (codec.EqualsLiteral("mp3")) {
84       tracks.AppendElement(
85           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
86               "audio/mpeg"_ns, aType));
87       continue;
88     }
89     if (codec.EqualsLiteral("opus") || codec.EqualsLiteral("flac")) {
90       tracks.AppendElement(
91           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
92               "audio/"_ns + NS_ConvertUTF16toUTF8(codec), aType));
93       continue;
94     }
95     if (IsVP9CodecString(codec)) {
96       auto trackInfo =
97           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
98               "video/vp9"_ns, aType);
99       uint8_t profile = 0;
100       uint8_t level = 0;
101       uint8_t bitDepth = 0;
102       if (ExtractVPXCodecDetails(codec, profile, level, bitDepth)) {
103         trackInfo->GetAsVideoInfo()->mColorDepth =
104             gfx::ColorDepthForBitDepth(bitDepth);
105       }
106       tracks.AppendElement(std::move(trackInfo));
107       continue;
108     }
109 #ifdef MOZ_AV1
110     if (StaticPrefs::media_av1_enabled() && IsAV1CodecString(codec)) {
111       tracks.AppendElement(
112           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
113               "video/av1"_ns, aType));
114       continue;
115     }
116 #endif
117     if (isVideo && IsWhitelistedH264Codec(codec)) {
118       auto trackInfo =
119           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
120               "video/avc"_ns, aType);
121       uint8_t profile = 0, constraint = 0, level = 0;
122       MOZ_ALWAYS_TRUE(
123           ExtractH264CodecDetails(codec, profile, constraint, level));
124       uint32_t width = aType.ExtendedType().GetWidth().refOr(1280);
125       uint32_t height = aType.ExtendedType().GetHeight().refOr(720);
126       trackInfo->GetAsVideoInfo()->mExtraData =
127           H264::CreateExtraData(profile, constraint, level, {width, height});
128       tracks.AppendElement(std::move(trackInfo));
129       continue;
130     }
131     // Unknown codec
132     aError = MediaResult(
133         NS_ERROR_DOM_MEDIA_FATAL_ERR,
134         RESULT_DETAIL("Unknown codec:%s", NS_ConvertUTF16toUTF8(codec).get()));
135   }
136   return tracks;
137 }
138 
139 /* static */
IsSupportedType(const MediaContainerType & aType,DecoderDoctorDiagnostics * aDiagnostics)140 bool MP4Decoder::IsSupportedType(const MediaContainerType& aType,
141                                  DecoderDoctorDiagnostics* aDiagnostics) {
142   if (!IsEnabled()) {
143     return false;
144   }
145 
146   MediaResult rv = NS_OK;
147   auto tracks = GetTracksInfo(aType, rv);
148   if (NS_FAILED(rv)) {
149     return false;
150   }
151 
152   if (tracks.IsEmpty()) {
153     // No codecs specified. Assume H.264 or AAC
154     if (aType.Type() == MEDIAMIMETYPE("audio/mp4") ||
155         aType.Type() == MEDIAMIMETYPE("audio/x-m4a")) {
156       tracks.AppendElement(
157           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
158               "audio/mp4a-latm"_ns, aType));
159     } else {
160       tracks.AppendElement(
161           CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
162               "video/avc"_ns, aType));
163     }
164   }
165 
166   // Verify that we have a PDM that supports the whitelisted types.
167   RefPtr<PDMFactory> platform = new PDMFactory();
168   for (const auto& track : tracks) {
169     if (!track ||
170         !platform->Supports(SupportDecoderParams(*track), aDiagnostics)) {
171       return false;
172     }
173   }
174 
175   return true;
176 }
177 
178 /* static */
IsH264(const nsACString & aMimeType)179 bool MP4Decoder::IsH264(const nsACString& aMimeType) {
180   return aMimeType.EqualsLiteral("video/mp4") ||
181          aMimeType.EqualsLiteral("video/avc");
182 }
183 
184 /* static */
IsAAC(const nsACString & aMimeType)185 bool MP4Decoder::IsAAC(const nsACString& aMimeType) {
186   return aMimeType.EqualsLiteral("audio/mp4a-latm");
187 }
188 
189 /* static */
IsEnabled()190 bool MP4Decoder::IsEnabled() { return StaticPrefs::media_mp4_enabled(); }
191 
192 /* static */
GetTracksInfo(const MediaContainerType & aType)193 nsTArray<UniquePtr<TrackInfo>> MP4Decoder::GetTracksInfo(
194     const MediaContainerType& aType) {
195   MediaResult rv = NS_OK;
196   return GetTracksInfo(aType, rv);
197 }
198 
199 }  // namespace mozilla
200