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