1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DecryptThroughputLimit_h
#define DecryptThroughputLimit_h
#include <deque>
#include "MediaTimer.h"
#include "PlatformDecoderModule.h"
namespace mozilla {
// We throttle our decrypt so that we don't decrypt more than a certain
// duration of samples per second. This is to work around bugs in the
// Widevine CDM. See bugs 1338924, 1342822, 1718223.
class DecryptThroughputLimit {
public:
explicit DecryptThroughputLimit(nsISerialEventTarget* aTargetThread,
uint32_t aMaxThroughputMs)
: mThrottleScheduler(aTargetThread),
mMaxThroughput(aMaxThroughputMs / 1000.0) {}
typedef MozPromise<RefPtr<MediaRawData>, MediaResult, true> ThrottlePromise;
// Resolves promise after a delay if necessary in order to reduce the
// throughput of samples sent through the CDM for decryption.
RefPtr<ThrottlePromise> Throttle(MediaRawData* aSample) {
// We should only have one decrypt request being processed at once.
MOZ_RELEASE_ASSERT(!mThrottleScheduler.IsScheduled());
const TimeDuration WindowSize = TimeDuration::FromSeconds(0.1);
const TimeDuration MaxThroughput =
TimeDuration::FromSeconds(mMaxThroughput);
// Forget decrypts that happened before the start of our window.
const TimeStamp now = TimeStamp::Now();
while (!mDecrypts.empty() &&
mDecrypts.front().mTimestamp < now - WindowSize) {
mDecrypts.pop_front();
}
// How much time duration of the media would we have decrypted inside the
// time window if we did decrypt this block?
TimeDuration sampleDuration = aSample->mDuration.ToTimeDuration();
TimeDuration durationDecrypted = sampleDuration;
for (const DecryptedJob& job : mDecrypts) {
durationDecrypted += job.mSampleDuration;
}
if (durationDecrypted < MaxThroughput) {
// If we decrypted a sample of this duration, we would *not* have
// decrypted more than our threshold for max throughput, over the
// preceding wall time window. So we're safe to proceed with this
// decrypt.
mDecrypts.push_back(DecryptedJob({now, sampleDuration}));
return ThrottlePromise::CreateAndResolve(aSample, __func__);
}
// Otherwise, we need to delay until decrypting won't exceed our
// throughput threshold.
RefPtr<ThrottlePromise> p = mPromiseHolder.Ensure(__func__);
TimeDuration delay = durationDecrypted - MaxThroughput;
TimeStamp target = now + delay;
RefPtr<MediaRawData> sample(aSample);
mThrottleScheduler.Ensure(
target,
[this, sample, sampleDuration]() {
mThrottleScheduler.CompleteRequest();
mDecrypts.push_back(DecryptedJob({TimeStamp::Now(), sampleDuration}));
mPromiseHolder.Resolve(sample, __func__);
},
[]() { MOZ_DIAGNOSTIC_ASSERT(false); });
return p;
}
void Flush() {
mThrottleScheduler.Reset();
mPromiseHolder.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
private:
DelayedScheduler mThrottleScheduler;
MozPromiseHolder<ThrottlePromise> mPromiseHolder;
double mMaxThroughput;
struct DecryptedJob {
TimeStamp mTimestamp;
TimeDuration mSampleDuration;
};
std::deque<DecryptedJob> mDecrypts;
};
} // namespace mozilla
#endif // DecryptThroughputLimit_h
|