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