Source code

Revision control

Other Tools

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 "MediaResource.h"
8
#include "mozilla/DebugOnly.h"
9
#include "mozilla/Logging.h"
10
#include "mozilla/MathAlgorithms.h"
11
#include "mozilla/SystemGroup.h"
12
#include "mozilla/ErrorNames.h"
13
14
using mozilla::media::TimeUnit;
15
16
#undef ILOG
17
18
mozilla::LazyLogModule gMediaResourceIndexLog("MediaResourceIndex");
19
// Debug logging macro with object pointer and class name.
20
#define ILOG(msg, ...) \
21
DDMOZ_LOG(gMediaResourceIndexLog, mozilla::LogLevel::Debug, msg, \
22
##__VA_ARGS__)
23
24
namespace mozilla {
25
26
void MediaResource::Destroy() {
27
// Ensures we only delete the MediaResource on the main thread.
28
if (NS_IsMainThread()) {
29
delete this;
30
return;
31
}
32
nsresult rv = SystemGroup::Dispatch(
33
TaskCategory::Other,
34
NewNonOwningRunnableMethod("MediaResource::Destroy", this,
35
&MediaResource::Destroy));
36
MOZ_ALWAYS_SUCCEEDS(rv);
37
}
38
39
NS_IMPL_ADDREF(MediaResource)
40
NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
41
42
static const uint32_t kMediaResourceIndexCacheSize = 8192;
43
static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize),
44
"kMediaResourceIndexCacheSize cache size must be a power of 2");
45
46
MediaResourceIndex::MediaResourceIndex(MediaResource* aResource)
47
: mResource(aResource),
48
mOffset(0),
49
mCacheBlockSize(
50
aResource->ShouldCacheReads() ? kMediaResourceIndexCacheSize : 0),
51
mCachedOffset(0),
52
mCachedBytes(0),
53
mCachedBlock(MakeUnique<char[]>(mCacheBlockSize)) {
54
DDLINKCHILD("resource", aResource);
55
}
56
57
nsresult MediaResourceIndex::Read(char* aBuffer, uint32_t aCount,
58
uint32_t* aBytes) {
59
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
60
61
// We purposefuly don't check that we may attempt to read past
62
// mResource->GetLength() as the resource's length may change over time.
63
64
nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
65
if (NS_FAILED(rv)) {
66
return rv;
67
}
68
mOffset += *aBytes;
69
if (mOffset < 0) {
70
// Very unlikely overflow; just return to position 0.
71
mOffset = 0;
72
}
73
return NS_OK;
74
}
75
76
static nsCString ResultName(nsresult aResult) {
77
nsCString name;
78
GetErrorName(aResult, name);
79
return name;
80
}
81
82
nsresult MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer,
83
uint32_t aCount, uint32_t* aBytes) {
84
if (mCacheBlockSize == 0) {
85
return UncachedReadAt(aOffset, aBuffer, aCount, aBytes);
86
}
87
88
*aBytes = 0;
89
90
if (aCount == 0) {
91
return NS_OK;
92
}
93
94
const int64_t endOffset = aOffset + aCount;
95
if (aOffset < 0 || endOffset < aOffset) {
96
return NS_ERROR_ILLEGAL_VALUE;
97
}
98
99
const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1);
100
101
if (mCachedBytes != 0 && mCachedOffset + mCachedBytes >= aOffset &&
102
mCachedOffset < endOffset) {
103
// There is data in the cache that is not completely before aOffset and not
104
// completely after endOffset, so it could be usable (with potential
105
// top-up).
106
if (aOffset < mCachedOffset) {
107
// We need to read before the cached data.
108
const uint32_t toRead = uint32_t(mCachedOffset - aOffset);
109
MOZ_ASSERT(toRead > 0);
110
MOZ_ASSERT(toRead < aCount);
111
uint32_t read = 0;
112
nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
113
if (NS_FAILED(rv)) {
114
ILOG("ReadAt(%" PRIu32 "@%" PRId64
115
") uncached read before cache -> %s, %" PRIu32,
116
aCount, aOffset, ResultName(rv).get(), *aBytes);
117
return rv;
118
}
119
*aBytes = read;
120
if (read < toRead) {
121
// Could not read everything we wanted, we're done.
122
ILOG("ReadAt(%" PRIu32 "@%" PRId64
123
") uncached read before cache, incomplete -> OK, %" PRIu32,
124
aCount, aOffset, *aBytes);
125
return NS_OK;
126
}
127
ILOG("ReadAt(%" PRIu32 "@%" PRId64
128
") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32
129
"@%" PRId64 "...",
130
aCount, aOffset, read, aCount - read, aOffset + read);
131
aOffset += read;
132
aBuffer += read;
133
aCount -= read;
134
// We should have reached the cache.
135
MOZ_ASSERT(aOffset == mCachedOffset);
136
}
137
MOZ_ASSERT(aOffset >= mCachedOffset);
138
139
// We've reached our cache.
140
const uint32_t toCopy =
141
std::min(aCount, uint32_t(mCachedOffset + mCachedBytes - aOffset));
142
// Note that we could in fact be just after the last byte of the cache, in
143
// which case we can't actually read from it! (But we will top-up next.)
144
if (toCopy != 0) {
145
memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy);
146
*aBytes += toCopy;
147
aCount -= toCopy;
148
if (aCount == 0) {
149
// All done!
150
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32
151
") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32,
152
aCount, aOffset, toCopy, mCachedBytes, mCachedOffset, *aBytes);
153
return NS_OK;
154
}
155
aOffset += toCopy;
156
aBuffer += toCopy;
157
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32
158
" from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32
159
"@%" PRId64 "...",
160
aCount + toCopy, aOffset - toCopy, toCopy, mCachedBytes,
161
mCachedOffset, aCount, aOffset);
162
}
163
164
if (aOffset - 1 >= lastBlockOffset) {
165
// We were already reading cached data from the last block, we need more
166
// from it -> try to top-up, read what we can, and we'll be done.
167
MOZ_ASSERT(aOffset == mCachedOffset + mCachedBytes);
168
MOZ_ASSERT(endOffset <= lastBlockOffset + mCacheBlockSize);
169
return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
170
}
171
172
// We were not in the last block (but we may just have crossed the line now)
173
MOZ_ASSERT(aOffset <= lastBlockOffset);
174
// Continue below...
175
} else if (aOffset >= lastBlockOffset) {
176
// There was nothing we could get from the cache.
177
// But we're already in the last block -> Cache or read what we can.
178
// Make sure to invalidate the cache first.
179
mCachedBytes = 0;
180
return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
181
}
182
183
// If we're here, either there was nothing usable in the cache, or we've just
184
// read what was in the cache but there's still more to read.
185
186
if (aOffset < lastBlockOffset) {
187
// We need to read before the last block.
188
// Start with an uncached read up to the last block.
189
const uint32_t toRead = uint32_t(lastBlockOffset - aOffset);
190
MOZ_ASSERT(toRead > 0);
191
MOZ_ASSERT(toRead < aCount);
192
uint32_t read = 0;
193
nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
194
if (NS_FAILED(rv)) {
195
ILOG("ReadAt(%" PRIu32 "@%" PRId64
196
") uncached read before last block failed -> %s, %" PRIu32,
197
aCount, aOffset, ResultName(rv).get(), *aBytes);
198
return rv;
199
}
200
if (read == 0) {
201
ILOG("ReadAt(%" PRIu32 "@%" PRId64
202
") uncached read 0 before last block -> OK, %" PRIu32,
203
aCount, aOffset, *aBytes);
204
return NS_OK;
205
}
206
*aBytes += read;
207
if (read < toRead) {
208
// Could not read everything we wanted, we're done.
209
ILOG("ReadAt(%" PRIu32 "@%" PRId64
210
") uncached read before last block, incomplete -> OK, %" PRIu32,
211
aCount, aOffset, *aBytes);
212
return NS_OK;
213
}
214
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32
215
" before last block, remaining: %" PRIu32 "@%" PRId64 "...",
216
aCount, aOffset, read, aCount - read, aOffset + read);
217
aOffset += read;
218
aBuffer += read;
219
aCount -= read;
220
}
221
222
// We should just have reached the start of the last block.
223
MOZ_ASSERT(aOffset == lastBlockOffset);
224
MOZ_ASSERT(aCount <= mCacheBlockSize);
225
// Make sure to invalidate the cache first.
226
mCachedBytes = 0;
227
return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
228
}
229
230
nsresult MediaResourceIndex::CacheOrReadAt(int64_t aOffset, char* aBuffer,
231
uint32_t aCount, uint32_t* aBytes) {
232
// We should be here because there is more data to read.
233
MOZ_ASSERT(aCount > 0);
234
// We should be in the last block, so we shouldn't try to read past it.
235
MOZ_ASSERT(IndexInCache(aOffset) + aCount <= mCacheBlockSize);
236
237
const int64_t length = GetLength();
238
// If length is unknown (-1), look at resource-cached data.
239
// If length is known and equal or greater than requested, also look at
240
// resource-cached data.
241
// Otherwise, if length is known but same, or less than(!?), requested, don't
242
// attempt to access resource-cached data, as we're not expecting it to ever
243
// be greater than the length.
244
if (length < 0 || length >= aOffset + aCount) {
245
// Is there cached data covering at least the requested range?
246
const int64_t cachedDataEnd = mResource->GetCachedDataEnd(aOffset);
247
if (cachedDataEnd >= aOffset + aCount) {
248
// Try to read as much resource-cached data as can fill our local cache.
249
// Assume we can read as much as is cached without blocking.
250
const uint32_t cacheIndex = IndexInCache(aOffset);
251
const uint32_t toRead = uint32_t(std::min(
252
cachedDataEnd - aOffset, int64_t(mCacheBlockSize - cacheIndex)));
253
MOZ_ASSERT(toRead >= aCount);
254
uint32_t read = 0;
255
// We would like `toRead` if possible, but ok with at least `aCount`.
256
nsresult rv = UncachedRangedReadAt(aOffset, &mCachedBlock[cacheIndex],
257
aCount, toRead - aCount, &read);
258
if (NS_SUCCEEDED(rv)) {
259
if (read == 0) {
260
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
261
"..%" PRIu32 "@%" PRId64
262
") to top-up succeeded but read nothing -> OK anyway",
263
aCount, aOffset, aCount, toRead, aOffset);
264
// Couldn't actually read anything, but didn't error out, so count
265
// that as success.
266
return NS_OK;
267
}
268
if (mCachedOffset + mCachedBytes == aOffset) {
269
// We were topping-up the cache, just update its size.
270
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
271
"..%" PRIu32 "@%" PRId64 ") to top-up succeeded to read %" PRIu32
272
"...",
273
aCount, aOffset, aCount, toRead, aOffset, read);
274
mCachedBytes += read;
275
} else {
276
// We were filling the cache from scratch, save new cache information.
277
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
278
"..%" PRIu32 "@%" PRId64
279
") to fill cache succeeded to read %" PRIu32 "...",
280
aCount, aOffset, aCount, toRead, aOffset, read);
281
mCachedOffset = aOffset;
282
mCachedBytes = read;
283
}
284
// Copy relevant part into output.
285
uint32_t toCopy = std::min(aCount, read);
286
memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy);
287
*aBytes += toCopy;
288
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64
289
" -> OK, %" PRIu32,
290
aCount, aOffset, toCopy, aOffset, *aBytes);
291
// We may not have read all that was requested, but we got everything
292
// we could get, so we're done.
293
return NS_OK;
294
}
295
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
296
"..%" PRIu32 "@%" PRId64
297
") failed: %s, will fallback to blocking read...",
298
aCount, aOffset, aCount, toRead, aOffset, ResultName(rv).get());
299
// Failure during reading. Note that this may be due to the cache
300
// changing between `GetCachedDataEnd` and `ReadAt`, so it's not
301
// totally unexpected, just hopefully rare; but we do need to handle it.
302
303
// Invalidate part of cache that may have been partially overridden.
304
if (mCachedOffset + mCachedBytes == aOffset) {
305
// We were topping-up the cache, just keep the old untouched data.
306
// (i.e., nothing to do here.)
307
} else {
308
// We were filling the cache from scratch, invalidate cache.
309
mCachedBytes = 0;
310
}
311
} else {
312
ILOG("ReadAt(%" PRIu32 "@%" PRId64
313
") - no cached data, will fallback to blocking read...",
314
aCount, aOffset);
315
}
316
} else {
317
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - length is %" PRId64
318
" (%s), will fallback to blocking read as the caller requested...",
319
aCount, aOffset, length, length < 0 ? "unknown" : "too short!");
320
}
321
uint32_t read = 0;
322
nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read);
323
if (NS_SUCCEEDED(rv)) {
324
*aBytes += read;
325
ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32
326
" bytes -> %s, %" PRIu32,
327
aCount, aOffset, read, ResultName(rv).get(), *aBytes);
328
} else {
329
ILOG("ReadAt(%" PRIu32 "@%" PRId64
330
") - fallback uncached read failed -> %s, %" PRIu32,
331
aCount, aOffset, ResultName(rv).get(), *aBytes);
332
}
333
return rv;
334
}
335
336
nsresult MediaResourceIndex::UncachedReadAt(int64_t aOffset, char* aBuffer,
337
uint32_t aCount,
338
uint32_t* aBytes) const {
339
if (aOffset < 0) {
340
return NS_ERROR_ILLEGAL_VALUE;
341
}
342
if (aCount == 0) {
343
*aBytes = 0;
344
return NS_OK;
345
}
346
return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
347
}
348
349
nsresult MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
350
char* aBuffer,
351
uint32_t aRequestedCount,
352
uint32_t aExtraCount,
353
uint32_t* aBytes) const {
354
uint32_t count = aRequestedCount + aExtraCount;
355
if (aOffset < 0 || count < aRequestedCount) {
356
return NS_ERROR_ILLEGAL_VALUE;
357
}
358
if (count == 0) {
359
*aBytes = 0;
360
return NS_OK;
361
}
362
return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
363
}
364
365
nsresult MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset) {
366
switch (aWhence) {
367
case SEEK_SET:
368
break;
369
case SEEK_CUR:
370
aOffset += mOffset;
371
break;
372
case SEEK_END: {
373
int64_t length = mResource->GetLength();
374
if (length == -1 || length - aOffset < 0) {
375
return NS_ERROR_FAILURE;
376
}
377
aOffset = mResource->GetLength() - aOffset;
378
} break;
379
default:
380
return NS_ERROR_FAILURE;
381
}
382
383
if (aOffset < 0) {
384
return NS_ERROR_ILLEGAL_VALUE;
385
}
386
mOffset = aOffset;
387
388
return NS_OK;
389
}
390
391
already_AddRefed<MediaByteBuffer> MediaResourceIndex::MediaReadAt(
392
int64_t aOffset, uint32_t aCount) const {
393
NS_ENSURE_TRUE(aOffset >= 0, nullptr);
394
RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
395
bool ok = bytes->SetLength(aCount, fallible);
396
NS_ENSURE_TRUE(ok, nullptr);
397
398
uint32_t bytesRead = 0;
399
nsresult rv = mResource->ReadAt(
400
aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
401
NS_ENSURE_SUCCESS(rv, nullptr);
402
403
bytes->SetLength(bytesRead);
404
return bytes.forget();
405
}
406
407
already_AddRefed<MediaByteBuffer> MediaResourceIndex::CachedMediaReadAt(
408
int64_t aOffset, uint32_t aCount) const {
409
RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
410
bool ok = bytes->SetLength(aCount, fallible);
411
NS_ENSURE_TRUE(ok, nullptr);
412
char* curr = reinterpret_cast<char*>(bytes->Elements());
413
nsresult rv = mResource->ReadFromCache(curr, aOffset, aCount);
414
NS_ENSURE_SUCCESS(rv, nullptr);
415
return bytes.forget();
416
}
417
418
// Get the length of the stream in bytes. Returns -1 if not known.
419
// This can change over time; after a seek operation, a misbehaving
420
// server may give us a resource of a different length to what it had
421
// reported previously --- or it may just lie in its Content-Length
422
// header and give us more or less data than it reported. We will adjust
423
// the result of GetLength to reflect the data that's actually arriving.
424
int64_t MediaResourceIndex::GetLength() const { return mResource->GetLength(); }
425
426
uint32_t MediaResourceIndex::IndexInCache(int64_t aOffsetInFile) const {
427
const uint32_t index = uint32_t(aOffsetInFile) & (mCacheBlockSize - 1);
428
MOZ_ASSERT(index == aOffsetInFile % mCacheBlockSize);
429
return index;
430
}
431
432
int64_t MediaResourceIndex::CacheOffsetContaining(int64_t aOffsetInFile) const {
433
const int64_t offset = aOffsetInFile & ~(int64_t(mCacheBlockSize) - 1);
434
MOZ_ASSERT(offset == aOffsetInFile - IndexInCache(aOffsetInFile));
435
return offset;
436
}
437
438
} // namespace mozilla
439
440
// avoid redefined macro in unified build
441
#undef ILOG