Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this
3 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #include "VideoUtils.h"
6 :
7 : #include "ImageContainer.h"
8 : #include "MediaContainerType.h"
9 : #include "MediaPrefs.h"
10 : #include "MediaResource.h"
11 : #include "TimeUnits.h"
12 : #include "VorbisUtils.h"
13 : #include "mozilla/Base64.h"
14 : #include "mozilla/SharedThreadPool.h"
15 : #include "mozilla/TaskQueue.h"
16 : #include "mozilla/Telemetry.h"
17 : #include "nsCharSeparatedTokenizer.h"
18 : #include "nsContentTypeParser.h"
19 : #include "nsIConsoleService.h"
20 : #include "nsIRandomGenerator.h"
21 : #include "nsIServiceManager.h"
22 : #include "nsMathUtils.h"
23 : #include "nsServiceManagerUtils.h"
24 : #include "nsSize.h"
25 : #include "nsThreadUtils.h"
26 :
27 : #include <functional>
28 : #include <stdint.h>
29 :
30 : namespace mozilla {
31 :
32 3 : NS_NAMED_LITERAL_CSTRING(kEMEKeySystemClearkey, "org.w3.clearkey");
33 3 : NS_NAMED_LITERAL_CSTRING(kEMEKeySystemWidevine, "com.widevine.alpha");
34 :
35 : using layers::PlanarYCbCrImage;
36 : using media::TimeUnit;
37 :
38 0 : CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv) {
39 0 : int64_t major = aValue / aDiv;
40 0 : int64_t remainder = aValue % aDiv;
41 0 : return CheckedInt64(remainder) * aMul / aDiv + CheckedInt64(major) * aMul;
42 : }
43 :
44 : // Converts from number of audio frames to microseconds, given the specified
45 : // audio rate.
46 0 : CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
47 0 : return SaferMultDiv(aFrames, USECS_PER_S, aRate);
48 : }
49 :
50 0 : TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
51 0 : int64_t major = aFrames / aRate;
52 0 : int64_t remainder = aFrames % aRate;
53 0 : return TimeUnit::FromMicroseconds(major) * USECS_PER_S +
54 0 : (TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
55 : }
56 :
57 : // Converts from microseconds to number of audio frames, given the specified
58 : // audio rate.
59 0 : CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate) {
60 0 : return SaferMultDiv(aUsecs, aRate, USECS_PER_S);
61 : }
62 :
63 : // Format TimeUnit as number of frames at given rate.
64 0 : CheckedInt64 TimeUnitToFrames(const TimeUnit& aTime, uint32_t aRate) {
65 0 : return UsecsToFrames(aTime.ToMicroseconds(), aRate);
66 : }
67 :
68 0 : nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs) {
69 0 : if (aSeconds * double(USECS_PER_S) > INT64_MAX) {
70 0 : return NS_ERROR_FAILURE;
71 : }
72 0 : aOutUsecs = int64_t(aSeconds * double(USECS_PER_S));
73 0 : return NS_OK;
74 : }
75 :
76 0 : static int32_t ConditionDimension(float aValue)
77 : {
78 : // This will exclude NaNs and too-big values.
79 0 : if (aValue > 1.0 && aValue <= INT32_MAX)
80 0 : return int32_t(NS_round(aValue));
81 0 : return 0;
82 : }
83 :
84 0 : void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio)
85 : {
86 0 : if (aAspectRatio > 1.0) {
87 : // Increase the intrinsic width
88 0 : aDisplay.width = ConditionDimension(aAspectRatio * aDisplay.width);
89 : } else {
90 : // Increase the intrinsic height
91 0 : aDisplay.height = ConditionDimension(aDisplay.height / aAspectRatio);
92 : }
93 0 : }
94 :
95 0 : static int64_t BytesToTime(int64_t offset, int64_t length, int64_t durationUs) {
96 0 : NS_ASSERTION(length > 0, "Must have positive length");
97 0 : double r = double(offset) / double(length);
98 0 : if (r > 1.0)
99 0 : r = 1.0;
100 0 : return int64_t(double(durationUs) * r);
101 : }
102 :
103 0 : media::TimeIntervals GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStream,
104 : int64_t aDurationUsecs)
105 : {
106 0 : media::TimeIntervals buffered;
107 : // Nothing to cache if the media takes 0us to play.
108 0 : if (aDurationUsecs <= 0 || !aStream)
109 0 : return buffered;
110 :
111 : // Special case completely cached files. This also handles local files.
112 0 : if (aStream->IsDataCachedToEndOfResource(0)) {
113 : buffered +=
114 0 : media::TimeInterval(TimeUnit::Zero(),
115 0 : TimeUnit::FromMicroseconds(aDurationUsecs));
116 0 : return buffered;
117 : }
118 :
119 0 : int64_t totalBytes = aStream->GetLength();
120 :
121 : // If we can't determine the total size, pretend that we have nothing
122 : // buffered. This will put us in a state of eternally-low-on-undecoded-data
123 : // which is not great, but about the best we can do.
124 0 : if (totalBytes <= 0)
125 0 : return buffered;
126 :
127 0 : int64_t startOffset = aStream->GetNextCachedData(0);
128 0 : while (startOffset >= 0) {
129 0 : int64_t endOffset = aStream->GetCachedDataEnd(startOffset);
130 : // Bytes [startOffset..endOffset] are cached.
131 0 : NS_ASSERTION(startOffset >= 0, "Integer underflow in GetBuffered");
132 0 : NS_ASSERTION(endOffset >= 0, "Integer underflow in GetBuffered");
133 :
134 0 : int64_t startUs = BytesToTime(startOffset, totalBytes, aDurationUsecs);
135 0 : int64_t endUs = BytesToTime(endOffset, totalBytes, aDurationUsecs);
136 0 : if (startUs != endUs) {
137 : buffered +=
138 0 : media::TimeInterval(TimeUnit::FromMicroseconds(startUs),
139 0 : TimeUnit::FromMicroseconds(endUs));
140 : }
141 0 : startOffset = aStream->GetNextCachedData(endOffset);
142 : }
143 0 : return buffered;
144 : }
145 :
146 0 : void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer,
147 : uint32_t aFrames)
148 : {
149 0 : MOZ_ASSERT(aBuffer);
150 0 : const int channels = 2;
151 0 : for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
152 : #ifdef MOZ_SAMPLE_TYPE_FLOAT32
153 0 : float sample = 0.0;
154 : #else
155 : int sample = 0;
156 : #endif
157 : // The sample of the buffer would be interleaved.
158 0 : sample = (aBuffer[fIdx*channels] + aBuffer[fIdx*channels + 1]) * 0.5;
159 0 : aBuffer[fIdx*channels] = aBuffer[fIdx*channels + 1] = sample;
160 : }
161 0 : }
162 :
163 : bool
164 0 : IsVideoContentType(const nsCString& aContentType)
165 : {
166 0 : NS_NAMED_LITERAL_CSTRING(video, "video");
167 0 : if (FindInReadable(video, aContentType)) {
168 0 : return true;
169 : }
170 0 : return false;
171 : }
172 :
173 : bool
174 0 : IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture,
175 : const nsIntSize& aDisplay)
176 : {
177 : return
178 0 : aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
179 0 : aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
180 0 : aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
181 0 : aFrame.width * aFrame.height != 0 &&
182 0 : aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
183 0 : aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
184 0 : aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
185 0 : aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
186 0 : aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
187 0 : aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
188 0 : aPicture.width * aPicture.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
189 0 : aPicture.width * aPicture.height != 0 &&
190 0 : aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
191 0 : aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
192 0 : aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
193 0 : aDisplay.width * aDisplay.height != 0;
194 : }
195 :
196 0 : already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType)
197 : {
198 : const char *name;
199 0 : switch (aType) {
200 : case MediaThreadType::PLATFORM_DECODER:
201 0 : name = "MediaPDecoder";
202 0 : break;
203 : default:
204 0 : MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
205 : case MediaThreadType::PLAYBACK:
206 0 : name = "MediaPlayback";
207 0 : break;
208 : }
209 : return SharedThreadPool::
210 0 : Get(nsDependentCString(name), MediaPrefs::MediaThreadPoolDefaultCount());
211 : }
212 :
213 : bool
214 0 : ExtractH264CodecDetails(const nsAString& aCodec,
215 : int16_t& aProfile,
216 : int16_t& aLevel)
217 : {
218 : // H.264 codecs parameters have a type defined as avcN.PPCCLL, where
219 : // N = avc type. avc3 is avcc with SPS & PPS implicit (within stream)
220 : // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
221 : // We ignore the constraint_set flags, as it's not clear from any
222 : // documentation what constraints the platform decoders support.
223 : // See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
224 : // for more details.
225 0 : if (aCodec.Length() != strlen("avc1.PPCCLL")) {
226 0 : return false;
227 : }
228 :
229 : // Verify the codec starts with "avc1." or "avc3.".
230 0 : const nsAString& sample = Substring(aCodec, 0, 5);
231 0 : if (!sample.EqualsASCII("avc1.") && !sample.EqualsASCII("avc3.")) {
232 0 : return false;
233 : }
234 :
235 : // Extract the profile_idc and level_idc.
236 0 : nsresult rv = NS_OK;
237 0 : aProfile = PromiseFlatString(Substring(aCodec, 5, 2)).ToInteger(&rv, 16);
238 0 : NS_ENSURE_SUCCESS(rv, false);
239 :
240 0 : aLevel = PromiseFlatString(Substring(aCodec, 9, 2)).ToInteger(&rv, 16);
241 0 : NS_ENSURE_SUCCESS(rv, false);
242 :
243 0 : if (aLevel == 9) {
244 0 : aLevel = H264_LEVEL_1_b;
245 0 : } else if (aLevel <= 5) {
246 0 : aLevel *= 10;
247 : }
248 :
249 : // Capture the constraint_set flag value for the purpose of Telemetry.
250 : // We don't NS_ENSURE_SUCCESS here because ExtractH264CodecDetails doesn't
251 : // care about this, but we make sure constraints is above 4 (constraint_set5_flag)
252 : // otherwise collect 0 for unknown.
253 0 : uint8_t constraints = PromiseFlatString(Substring(aCodec, 7, 2)).ToInteger(&rv, 16);
254 0 : Telemetry::Accumulate(Telemetry::VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG,
255 0 : constraints >= 4 ? constraints : 0);
256 :
257 : // 244 is the highest meaningful profile value (High 4:4:4 Intra Profile)
258 : // that can be represented as single hex byte, otherwise collect 0 for unknown.
259 0 : Telemetry::Accumulate(Telemetry::VIDEO_CANPLAYTYPE_H264_PROFILE,
260 0 : aProfile <= 244 ? aProfile : 0);
261 :
262 : // Make sure aLevel represents a value between levels 1 and 5.2,
263 : // otherwise collect 0 for unknown.
264 0 : Telemetry::Accumulate(Telemetry::VIDEO_CANPLAYTYPE_H264_LEVEL,
265 0 : (aLevel >= 10 && aLevel <= 52) ? aLevel : 0);
266 :
267 0 : return true;
268 : }
269 :
270 : nsresult
271 0 : GenerateRandomName(nsCString& aOutSalt, uint32_t aLength)
272 : {
273 : nsresult rv;
274 : nsCOMPtr<nsIRandomGenerator> rg =
275 0 : do_GetService("@mozilla.org/security/random-generator;1", &rv);
276 0 : if (NS_FAILED(rv)) return rv;
277 :
278 : // For each three bytes of random data we will get four bytes of ASCII.
279 : const uint32_t requiredBytesLength =
280 0 : static_cast<uint32_t>((aLength + 3) / 4 * 3);
281 :
282 : uint8_t* buffer;
283 0 : rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
284 0 : if (NS_FAILED(rv)) return rv;
285 :
286 0 : nsAutoCString temp;
287 : nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
288 0 : requiredBytesLength);
289 0 : rv = Base64Encode(randomData, temp);
290 0 : free(buffer);
291 0 : buffer = nullptr;
292 0 : if (NS_FAILED (rv)) return rv;
293 :
294 0 : aOutSalt = temp;
295 0 : return NS_OK;
296 : }
297 :
298 : nsresult
299 0 : GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength)
300 : {
301 0 : nsresult rv = GenerateRandomName(aOutSalt, aLength);
302 0 : if (NS_FAILED(rv)) return rv;
303 :
304 : // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
305 : // to replace illegal characters -- notably '/'
306 0 : aOutSalt.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
307 0 : return NS_OK;
308 : }
309 :
310 : already_AddRefed<TaskQueue>
311 0 : CreateMediaDecodeTaskQueue(const char* aName)
312 : {
313 : RefPtr<TaskQueue> queue = new TaskQueue(
314 0 : GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), aName);
315 0 : return queue.forget();
316 : }
317 :
318 : void
319 0 : SimpleTimer::Cancel() {
320 0 : if (mTimer) {
321 : #ifdef DEBUG
322 0 : nsCOMPtr<nsIEventTarget> target;
323 0 : mTimer->GetTarget(getter_AddRefs(target));
324 : bool onCurrent;
325 0 : nsresult rv = target->IsOnCurrentThread(&onCurrent);
326 0 : MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrent);
327 : #endif
328 0 : mTimer->Cancel();
329 0 : mTimer = nullptr;
330 : }
331 0 : mTask = nullptr;
332 0 : }
333 :
334 : NS_IMETHODIMP
335 0 : SimpleTimer::Notify(nsITimer *timer) {
336 0 : RefPtr<SimpleTimer> deathGrip(this);
337 0 : if (mTask) {
338 0 : mTask->Run();
339 0 : mTask = nullptr;
340 : }
341 0 : return NS_OK;
342 : }
343 :
344 : nsresult
345 0 : SimpleTimer::Init(nsIRunnable* aTask, uint32_t aTimeoutMs, nsIEventTarget* aTarget)
346 : {
347 : nsresult rv;
348 :
349 : // Get target thread first, so we don't have to cancel the timer if it fails.
350 0 : nsCOMPtr<nsIEventTarget> target;
351 0 : if (aTarget) {
352 0 : target = aTarget;
353 : } else {
354 0 : target = GetMainThreadEventTarget();
355 0 : if (!target) {
356 0 : return NS_ERROR_NOT_AVAILABLE;
357 : }
358 : }
359 :
360 0 : nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
361 0 : if (NS_FAILED(rv)) {
362 0 : return rv;
363 : }
364 : // Note: set target before InitWithCallback in case the timer fires before
365 : // we change the event target.
366 0 : rv = timer->SetTarget(target);
367 0 : if (NS_FAILED(rv)) {
368 0 : timer->Cancel();
369 0 : return rv;
370 : }
371 0 : rv = timer->InitWithCallback(this, aTimeoutMs, nsITimer::TYPE_ONE_SHOT);
372 0 : if (NS_FAILED(rv)) {
373 0 : return rv;
374 : }
375 :
376 0 : mTimer = timer.forget();
377 0 : mTask = aTask;
378 0 : return NS_OK;
379 : }
380 :
381 0 : NS_IMPL_ISUPPORTS(SimpleTimer, nsITimerCallback)
382 :
383 : already_AddRefed<SimpleTimer>
384 0 : SimpleTimer::Create(nsIRunnable* aTask, uint32_t aTimeoutMs, nsIEventTarget* aTarget)
385 : {
386 0 : RefPtr<SimpleTimer> t(new SimpleTimer());
387 0 : if (NS_FAILED(t->Init(aTask, aTimeoutMs, aTarget))) {
388 0 : return nullptr;
389 : }
390 0 : return t.forget();
391 : }
392 :
393 : void
394 0 : LogToBrowserConsole(const nsAString& aMsg)
395 : {
396 0 : if (!NS_IsMainThread()) {
397 0 : nsString msg(aMsg);
398 0 : nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
399 0 : "LogToBrowserConsole", [msg]() { LogToBrowserConsole(msg); });
400 0 : SystemGroup::Dispatch("LogToBrowserConsole", TaskCategory::Other, task.forget());
401 0 : return;
402 : }
403 : nsCOMPtr<nsIConsoleService> console(
404 0 : do_GetService("@mozilla.org/consoleservice;1"));
405 0 : if (!console) {
406 0 : NS_WARNING("Failed to log message to console.");
407 0 : return;
408 : }
409 0 : nsAutoString msg(aMsg);
410 0 : console->LogStringMessage(msg.get());
411 : }
412 :
413 : bool
414 0 : ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs)
415 : {
416 0 : aOutCodecs.Clear();
417 0 : bool expectMoreTokens = false;
418 0 : nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
419 0 : while (tokenizer.hasMoreTokens()) {
420 0 : const nsAString& token = tokenizer.nextToken();
421 0 : expectMoreTokens = tokenizer.separatorAfterCurrentToken();
422 0 : aOutCodecs.AppendElement(token);
423 : }
424 0 : if (expectMoreTokens) {
425 : // Last codec name was empty
426 0 : return false;
427 : }
428 0 : return true;
429 : }
430 :
431 : bool
432 0 : ParseMIMETypeString(const nsAString& aMIMEType,
433 : nsString& aOutContainerType,
434 : nsTArray<nsString>& aOutCodecs)
435 : {
436 0 : nsContentTypeParser parser(aMIMEType);
437 0 : nsresult rv = parser.GetType(aOutContainerType);
438 0 : if (NS_FAILED(rv)) {
439 0 : return false;
440 : }
441 :
442 0 : nsString codecsStr;
443 0 : parser.GetParameter("codecs", codecsStr);
444 0 : return ParseCodecsString(codecsStr, aOutCodecs);
445 : }
446 :
447 : bool
448 0 : IsH264CodecString(const nsAString& aCodec)
449 : {
450 0 : int16_t profile = 0;
451 0 : int16_t level = 0;
452 0 : return ExtractH264CodecDetails(aCodec, profile, level);
453 : }
454 :
455 : bool
456 0 : IsAACCodecString(const nsAString& aCodec)
457 : {
458 : return
459 0 : aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
460 0 : aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
461 0 : aCodec.EqualsLiteral("mp4a.67") || // MPEG2 AAC-LC
462 0 : aCodec.EqualsLiteral("mp4a.40.29"); // MPEG4 HE-AACv2
463 : }
464 :
465 : bool
466 0 : IsVP8CodecString(const nsAString& aCodec)
467 : {
468 0 : return aCodec.EqualsLiteral("vp8") ||
469 0 : aCodec.EqualsLiteral("vp8.0");
470 : }
471 :
472 : bool
473 0 : IsVP9CodecString(const nsAString& aCodec)
474 : {
475 0 : return aCodec.EqualsLiteral("vp9") ||
476 0 : aCodec.EqualsLiteral("vp9.0");
477 : }
478 :
479 : template <int N>
480 : static bool
481 0 : StartsWith(const nsACString& string, const char (&prefix)[N])
482 : {
483 0 : if (N - 1 > string.Length()) {
484 0 : return false;
485 : }
486 0 : return memcmp(string.Data(), prefix, N - 1) == 0;
487 : }
488 :
489 : UniquePtr<TrackInfo>
490 0 : CreateTrackInfoWithMIMEType(const nsACString& aCodecMIMEType)
491 : {
492 0 : UniquePtr<TrackInfo> trackInfo;
493 0 : if (StartsWith(aCodecMIMEType, "audio/")) {
494 0 : trackInfo.reset(new AudioInfo());
495 0 : trackInfo->mMimeType = aCodecMIMEType;
496 0 : } else if (StartsWith(aCodecMIMEType, "video/")) {
497 0 : trackInfo.reset(new VideoInfo());
498 0 : trackInfo->mMimeType = aCodecMIMEType;
499 : }
500 0 : return trackInfo;
501 : }
502 :
503 : UniquePtr<TrackInfo>
504 0 : CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
505 : const nsACString& aCodecMIMEType,
506 : const MediaContainerType& aContainerType)
507 : {
508 0 : UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aCodecMIMEType);
509 0 : if (trackInfo) {
510 0 : VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
511 0 : if (videoInfo) {
512 0 : Maybe<int32_t> maybeWidth = aContainerType.ExtendedType().GetWidth();
513 0 : if (maybeWidth && *maybeWidth > 0) {
514 0 : videoInfo->mImage.width = *maybeWidth;
515 : }
516 0 : Maybe<int32_t> maybeHeight = aContainerType.ExtendedType().GetHeight();
517 0 : if (maybeHeight && *maybeHeight > 0) {
518 0 : videoInfo->mImage.height = *maybeHeight;
519 : }
520 : }
521 : }
522 0 : return trackInfo;
523 : }
524 :
525 : } // end namespace mozilla
|