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 "MediaContentType.h"
9 #include "MediaDecoderStateMachine.h"
10 #include "MP4Demuxer.h"
11 #include "mozilla/Preferences.h"
12 #include "nsCharSeparatedTokenizer.h"
13 #include "mozilla/CDMProxy.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/SharedThreadPool.h"
16 #include "nsMimeTypes.h"
17 #include "VideoUtils.h"
18
19 #ifdef XP_WIN
20 #include "mozilla/WindowsVersion.h"
21 #endif
22 #ifdef MOZ_WIDGET_ANDROID
23 #include "nsIGfxInfo.h"
24 #endif
25 #include "mozilla/layers/LayersTypes.h"
26
27 #include "PDMFactory.h"
28
29 namespace mozilla {
30
MP4Decoder(MediaDecoderOwner * aOwner)31 MP4Decoder::MP4Decoder(MediaDecoderOwner* aOwner)
32 : MediaDecoder(aOwner)
33 {
34 }
35
CreateStateMachine()36 MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
37 {
38 mReader =
39 new MediaFormatReader(this,
40 new MP4Demuxer(GetResource()),
41 GetVideoFrameContainer());
42
43 return new MediaDecoderStateMachine(this, mReader);
44 }
45
46 static bool
IsWhitelistedH264Codec(const nsAString & aCodec)47 IsWhitelistedH264Codec(const nsAString& aCodec)
48 {
49 int16_t profile = 0, level = 0;
50
51 if (!ExtractH264CodecDetails(aCodec, profile, level)) {
52 return false;
53 }
54
55 #ifdef XP_WIN
56 // Disable 4k video on windows vista since it performs poorly.
57 if (!IsWin7OrLater() &&
58 level >= H264_LEVEL_5) {
59 return false;
60 }
61 #endif
62
63 // Just assume what we can play on all platforms the codecs/formats that
64 // WMF can play, since we don't have documentation about what other
65 // platforms can play... According to the WMF documentation:
66 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd797815%28v=vs.85%29.aspx
67 // "The Media Foundation H.264 video decoder is a Media Foundation Transform
68 // that supports decoding of Baseline, Main, and High profiles, up to level
69 // 5.1.". We also report that we can play Extended profile, as there are
70 // bitstreams that are Extended compliant that are also Baseline compliant.
71 return level >= H264_LEVEL_1 &&
72 level <= H264_LEVEL_5_1 &&
73 (profile == H264_PROFILE_BASE ||
74 profile == H264_PROFILE_MAIN ||
75 profile == H264_PROFILE_EXTENDED ||
76 profile == H264_PROFILE_HIGH);
77 }
78
79 /* static */
80 bool
CanHandleMediaType(const MediaContentType & aType,DecoderDoctorDiagnostics * aDiagnostics)81 MP4Decoder::CanHandleMediaType(const MediaContentType& aType,
82 DecoderDoctorDiagnostics* aDiagnostics)
83 {
84 if (!IsEnabled()) {
85 return false;
86 }
87
88 // Whitelist MP4 types, so they explicitly match what we encounter on
89 // the web, as opposed to what we use internally (i.e. what our demuxers
90 // etc output).
91 const bool isMP4Audio = aType.GetMIMEType().EqualsASCII("audio/mp4") ||
92 aType.GetMIMEType().EqualsASCII("audio/x-m4a");
93 const bool isMP4Video =
94 // On B2G, treat 3GPP as MP4 when Gonk PDM is available.
95 #ifdef MOZ_GONK_MEDIACODEC
96 aType.GetMIMEType().EqualsASCII(VIDEO_3GPP) ||
97 #endif
98 aType.GetMIMEType().EqualsASCII("video/mp4") ||
99 aType.GetMIMEType().EqualsASCII("video/quicktime") ||
100 aType.GetMIMEType().EqualsASCII("video/x-m4v");
101 if (!isMP4Audio && !isMP4Video) {
102 return false;
103 }
104
105 nsTArray<UniquePtr<TrackInfo>> trackInfos;
106 if (aType.GetCodecs().IsEmpty()) {
107 // No codecs specified. Assume H.264
108 if (isMP4Audio) {
109 trackInfos.AppendElement(
110 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
111 NS_LITERAL_CSTRING("audio/mp4a-latm"), aType));
112 } else {
113 MOZ_ASSERT(isMP4Video);
114 trackInfos.AppendElement(
115 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
116 NS_LITERAL_CSTRING("video/avc"), aType));
117 }
118 } else {
119 // Verify that all the codecs specified are ones that we expect that
120 // we can play.
121 nsTArray<nsString> codecs;
122 if (!ParseCodecsString(aType.GetCodecs(), codecs)) {
123 return false;
124 }
125 for (const nsString& codec : codecs) {
126 if (IsAACCodecString(codec)) {
127 trackInfos.AppendElement(
128 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
129 NS_LITERAL_CSTRING("audio/mp4a-latm"), aType));
130 continue;
131 }
132 if (codec.EqualsLiteral("mp3")) {
133 trackInfos.AppendElement(
134 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
135 NS_LITERAL_CSTRING("audio/mpeg"), aType));
136 continue;
137 }
138 if (codec.EqualsLiteral("opus")) {
139 trackInfos.AppendElement(
140 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
141 NS_LITERAL_CSTRING("audio/opus"), aType));
142 continue;
143 }
144 if (codec.EqualsLiteral("flac")) {
145 trackInfos.AppendElement(
146 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
147 NS_LITERAL_CSTRING("audio/flac"), aType));
148 continue;
149 }
150 // Note: Only accept H.264 in a video content type, not in an audio
151 // content type.
152 if (IsWhitelistedH264Codec(codec) && isMP4Video) {
153 trackInfos.AppendElement(
154 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
155 NS_LITERAL_CSTRING("video/avc"), aType));
156 continue;
157 }
158 // Some unsupported codec.
159 return false;
160 }
161 }
162
163 // Verify that we have a PDM that supports the whitelisted types.
164 RefPtr<PDMFactory> platform = new PDMFactory();
165 for (const auto& trackInfo : trackInfos) {
166 if (!trackInfo || !platform->Supports(*trackInfo, aDiagnostics)) {
167 return false;
168 }
169 }
170
171 return true;
172 }
173
174 /* static */
175 bool
IsH264(const nsACString & aMimeType)176 MP4Decoder::IsH264(const nsACString& aMimeType)
177 {
178 return aMimeType.EqualsLiteral("video/mp4") ||
179 aMimeType.EqualsLiteral("video/avc");
180 }
181
182 /* static */
183 bool
IsAAC(const nsACString & aMimeType)184 MP4Decoder::IsAAC(const nsACString& aMimeType)
185 {
186 return aMimeType.EqualsLiteral("audio/mp4a-latm");
187 }
188
189 /* static */
190 bool
IsEnabled()191 MP4Decoder::IsEnabled()
192 {
193 return Preferences::GetBool("media.mp4.enabled", true);
194 }
195
196 // sTestH264ExtraData represents the content of the avcC atom found in
197 // an AVC1 h264 video. It contains the H264 SPS and PPS NAL.
198 // the structure of the avcC atom is as follow:
199 // write(0x1); // version, always 1
200 // write(sps[0].data[1]); // profile
201 // write(sps[0].data[2]); // compatibility
202 // write(sps[0].data[3]); // level
203 // write(0xFC | 3); // reserved (6 bits), NULA length size - 1 (2 bits)
204 // write(0xE0 | 1); // reserved (3 bits), num of SPS (5 bits)
205 // write_word(sps[0].size); // 2 bytes for length of SPS
206 // for(size_t i=0 ; i < sps[0].size ; ++i)
207 // write(sps[0].data[i]); // data of SPS
208 // write(&b, pps.size()); // num of PPS
209 // for(size_t i=0 ; i < pps.size() ; ++i) {
210 // write_word(pps[i].size); // 2 bytes for length of PPS
211 // for(size_t j=0 ; j < pps[i].size ; ++j)
212 // write(pps[i].data[j]); // data of PPS
213 // }
214 // }
215 // here we have a h264 Baseline, 640x360
216 // We use a 640x360 extradata, as some video framework (Apple VT) will never
217 // attempt to use hardware decoding for small videos.
218 static const uint8_t sTestH264ExtraData[] = {
219 0x01, 0x42, 0xc0, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x42,
220 0xc0, 0x1e, 0xbb, 0x40, 0x50, 0x17, 0xfc, 0xb8, 0x08, 0x80,
221 0x00, 0x00, 0x32, 0x00, 0x00, 0x0b, 0xb5, 0x07, 0x8b, 0x17,
222 0x50, 0x01, 0x00, 0x04, 0x68, 0xce, 0x32, 0xc8
223 };
224
225 static already_AddRefed<MediaDataDecoder>
CreateTestH264Decoder(layers::KnowsCompositor * aKnowsCompositor,VideoInfo & aConfig,TaskQueue * aTaskQueue)226 CreateTestH264Decoder(layers::KnowsCompositor* aKnowsCompositor,
227 VideoInfo& aConfig,
228 TaskQueue* aTaskQueue)
229 {
230 aConfig.mMimeType = "video/avc";
231 aConfig.mId = 1;
232 aConfig.mDuration = 40000;
233 aConfig.mMediaTime = 0;
234 aConfig.mImage = aConfig.mDisplay = nsIntSize(640, 360);
235 aConfig.mExtraData = new MediaByteBuffer();
236 aConfig.mExtraData->AppendElements(sTestH264ExtraData,
237 MOZ_ARRAY_LENGTH(sTestH264ExtraData));
238
239 RefPtr<PDMFactory> platform = new PDMFactory();
240 RefPtr<MediaDataDecoder> decoder(platform->CreateDecoder({ aConfig, aTaskQueue, aKnowsCompositor }));
241
242 return decoder.forget();
243 }
244
245 /* static */ already_AddRefed<dom::Promise>
IsVideoAccelerated(layers::KnowsCompositor * aKnowsCompositor,nsIGlobalObject * aParent)246 MP4Decoder::IsVideoAccelerated(layers::KnowsCompositor* aKnowsCompositor, nsIGlobalObject* aParent)
247 {
248 MOZ_ASSERT(NS_IsMainThread());
249
250 ErrorResult rv;
251 RefPtr<dom::Promise> promise;
252 promise = dom::Promise::Create(aParent, rv);
253 if (rv.Failed()) {
254 rv.SuppressException();
255 return nullptr;
256 }
257
258 RefPtr<TaskQueue> taskQueue =
259 new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER));
260 VideoInfo config;
261 RefPtr<MediaDataDecoder> decoder(CreateTestH264Decoder(aKnowsCompositor, config, taskQueue));
262 if (!decoder) {
263 taskQueue->BeginShutdown();
264 taskQueue->AwaitShutdownAndIdle();
265 promise->MaybeResolve(NS_LITERAL_STRING("No; Failed to create H264 decoder"));
266 return promise.forget();
267 }
268
269 decoder->Init()
270 ->Then(AbstractThread::MainThread(), __func__,
271 [promise, decoder, taskQueue] (TrackInfo::TrackType aTrack) {
272 nsCString failureReason;
273 bool ok = decoder->IsHardwareAccelerated(failureReason);
274 nsAutoString result;
275 if (ok) {
276 result.AssignLiteral("Yes");
277 } else {
278 result.AssignLiteral("No");
279 }
280 if (failureReason.Length()) {
281 result.AppendLiteral("; ");
282 AppendUTF8toUTF16(failureReason, result);
283 }
284 decoder->Shutdown();
285 taskQueue->BeginShutdown();
286 taskQueue->AwaitShutdownAndIdle();
287 promise->MaybeResolve(result);
288 },
289 [promise, decoder, taskQueue] (MediaResult aError) {
290 decoder->Shutdown();
291 taskQueue->BeginShutdown();
292 taskQueue->AwaitShutdownAndIdle();
293 promise->MaybeResolve(NS_LITERAL_STRING("No; Failed to initialize H264 decoder"));
294 });
295
296 return promise.forget();
297 }
298
299 void
GetMozDebugReaderData(nsAString & aString)300 MP4Decoder::GetMozDebugReaderData(nsAString& aString)
301 {
302 if (mReader) {
303 mReader->GetMozDebugReaderData(aString);
304 }
305 }
306
307 } // namespace mozilla
308