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/. */
7 #include "MediaResource.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/Logging.h"
10 #include "mozilla/MathAlgorithms.h"
11 #include "mozilla/ErrorNames.h"
12 #include "mozilla/SchedulerGroup.h"
14 using mozilla::media::TimeUnit
;
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, \
26 static const uint32_t kMediaResourceIndexCacheSize
= 8192;
27 static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize
),
28 "kMediaResourceIndexCacheSize cache size must be a power of 2");
30 MediaResourceIndex::MediaResourceIndex(MediaResource
* aResource
)
31 : mResource(aResource
),
34 aResource
->ShouldCacheReads() ? kMediaResourceIndexCacheSize
: 0),
37 mCachedBlock(MakeUnique
<char[]>(mCacheBlockSize
)) {
38 DDLINKCHILD("resource", aResource
);
41 nsresult
MediaResourceIndex::Read(char* aBuffer
, uint32_t aCount
,
43 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
45 // We purposefuly don't check that we may attempt to read past
46 // mResource->GetLength() as the resource's length may change over time.
48 nsresult rv
= ReadAt(mOffset
, aBuffer
, aCount
, aBytes
);
54 // Very unlikely overflow; just return to position 0.
60 static nsCString
ResultName(nsresult aResult
) {
62 GetErrorName(aResult
, name
);
66 nsresult
MediaResourceIndex::ReadAt(int64_t aOffset
, char* aBuffer
,
67 uint32_t aCount
, uint32_t* aBytes
) {
68 if (mCacheBlockSize
== 0) {
69 return UncachedReadAt(aOffset
, aBuffer
, aCount
, aBytes
);
78 const int64_t endOffset
= aOffset
+ aCount
;
79 if (aOffset
< 0 || endOffset
< aOffset
) {
80 return NS_ERROR_ILLEGAL_VALUE
;
83 const int64_t lastBlockOffset
= CacheOffsetContaining(endOffset
- 1);
85 if (mCachedBytes
!= 0 && mCachedOffset
+ mCachedBytes
>= aOffset
&&
86 mCachedOffset
< endOffset
) {
87 // There is data in the cache that is not completely before aOffset and not
88 // completely after endOffset, so it could be usable (with potential
90 if (aOffset
< mCachedOffset
) {
91 // We need to read before the cached data.
92 const uint32_t toRead
= uint32_t(mCachedOffset
- aOffset
);
93 MOZ_ASSERT(toRead
> 0);
94 MOZ_ASSERT(toRead
< aCount
);
96 nsresult rv
= UncachedReadAt(aOffset
, aBuffer
, toRead
, &read
);
98 ILOG("ReadAt(%" PRIu32
"@%" PRId64
99 ") uncached read before cache -> %s, %" PRIu32
,
100 aCount
, aOffset
, ResultName(rv
).get(), *aBytes
);
105 // Could not read everything we wanted, we're done.
106 ILOG("ReadAt(%" PRIu32
"@%" PRId64
107 ") uncached read before cache, incomplete -> OK, %" PRIu32
,
108 aCount
, aOffset
, *aBytes
);
111 ILOG("ReadAt(%" PRIu32
"@%" PRId64
112 ") uncached read before cache: %" PRIu32
", remaining: %" PRIu32
114 aCount
, aOffset
, read
, aCount
- read
, aOffset
+ read
);
118 // We should have reached the cache.
119 MOZ_ASSERT(aOffset
== mCachedOffset
);
121 MOZ_ASSERT(aOffset
>= mCachedOffset
);
123 // We've reached our cache.
124 const uint32_t toCopy
=
125 std::min(aCount
, uint32_t(mCachedOffset
+ mCachedBytes
- aOffset
));
126 // Note that we could in fact be just after the last byte of the cache, in
127 // which case we can't actually read from it! (But we will top-up next.)
129 memcpy(aBuffer
, &mCachedBlock
[IndexInCache(aOffset
)], toCopy
);
134 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") copied everything (%" PRIu32
135 ") from cache(%" PRIu32
"@%" PRId64
") :-D -> OK, %" PRIu32
,
136 aCount
, aOffset
, toCopy
, mCachedBytes
, mCachedOffset
, *aBytes
);
141 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") copied %" PRIu32
142 " from cache(%" PRIu32
"@%" PRId64
") :-), remaining: %" PRIu32
144 aCount
+ toCopy
, aOffset
- toCopy
, toCopy
, mCachedBytes
,
145 mCachedOffset
, aCount
, aOffset
);
148 if (aOffset
- 1 >= lastBlockOffset
) {
149 // We were already reading cached data from the last block, we need more
150 // from it -> try to top-up, read what we can, and we'll be done.
151 MOZ_ASSERT(aOffset
== mCachedOffset
+ mCachedBytes
);
152 MOZ_ASSERT(endOffset
<= lastBlockOffset
+ mCacheBlockSize
);
153 return CacheOrReadAt(aOffset
, aBuffer
, aCount
, aBytes
);
156 // We were not in the last block (but we may just have crossed the line now)
157 MOZ_ASSERT(aOffset
<= lastBlockOffset
);
159 } else if (aOffset
>= lastBlockOffset
) {
160 // There was nothing we could get from the cache.
161 // But we're already in the last block -> Cache or read what we can.
162 // Make sure to invalidate the cache first.
164 return CacheOrReadAt(aOffset
, aBuffer
, aCount
, aBytes
);
167 // If we're here, either there was nothing usable in the cache, or we've just
168 // read what was in the cache but there's still more to read.
170 if (aOffset
< lastBlockOffset
) {
171 // We need to read before the last block.
172 // Start with an uncached read up to the last block.
173 const uint32_t toRead
= uint32_t(lastBlockOffset
- aOffset
);
174 MOZ_ASSERT(toRead
> 0);
175 MOZ_ASSERT(toRead
< aCount
);
177 nsresult rv
= UncachedReadAt(aOffset
, aBuffer
, toRead
, &read
);
179 ILOG("ReadAt(%" PRIu32
"@%" PRId64
180 ") uncached read before last block failed -> %s, %" PRIu32
,
181 aCount
, aOffset
, ResultName(rv
).get(), *aBytes
);
185 ILOG("ReadAt(%" PRIu32
"@%" PRId64
186 ") uncached read 0 before last block -> OK, %" PRIu32
,
187 aCount
, aOffset
, *aBytes
);
192 // Could not read everything we wanted, we're done.
193 ILOG("ReadAt(%" PRIu32
"@%" PRId64
194 ") uncached read before last block, incomplete -> OK, %" PRIu32
,
195 aCount
, aOffset
, *aBytes
);
198 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") read %" PRIu32
199 " before last block, remaining: %" PRIu32
"@%" PRId64
"...",
200 aCount
, aOffset
, read
, aCount
- read
, aOffset
+ read
);
206 // We should just have reached the start of the last block.
207 MOZ_ASSERT(aOffset
== lastBlockOffset
);
208 MOZ_ASSERT(aCount
<= mCacheBlockSize
);
209 // Make sure to invalidate the cache first.
211 return CacheOrReadAt(aOffset
, aBuffer
, aCount
, aBytes
);
214 nsresult
MediaResourceIndex::CacheOrReadAt(int64_t aOffset
, char* aBuffer
,
215 uint32_t aCount
, uint32_t* aBytes
) {
216 // We should be here because there is more data to read.
217 MOZ_ASSERT(aCount
> 0);
218 // We should be in the last block, so we shouldn't try to read past it.
219 MOZ_ASSERT(IndexInCache(aOffset
) + aCount
<= mCacheBlockSize
);
221 const int64_t length
= GetLength();
222 // If length is unknown (-1), look at resource-cached data.
223 // If length is known and equal or greater than requested, also look at
224 // resource-cached data.
225 // Otherwise, if length is known but same, or less than(!?), requested, don't
226 // attempt to access resource-cached data, as we're not expecting it to ever
227 // be greater than the length.
228 if (length
< 0 || length
>= aOffset
+ aCount
) {
229 // Is there cached data covering at least the requested range?
230 const int64_t cachedDataEnd
= mResource
->GetCachedDataEnd(aOffset
);
231 if (cachedDataEnd
>= aOffset
+ aCount
) {
232 // Try to read as much resource-cached data as can fill our local cache.
233 // Assume we can read as much as is cached without blocking.
234 const uint32_t cacheIndex
= IndexInCache(aOffset
);
235 const uint32_t toRead
= uint32_t(std::min(
236 cachedDataEnd
- aOffset
, int64_t(mCacheBlockSize
- cacheIndex
)));
237 MOZ_ASSERT(toRead
>= aCount
);
239 // We would like `toRead` if possible, but ok with at least `aCount`.
240 nsresult rv
= UncachedRangedReadAt(aOffset
, &mCachedBlock
[cacheIndex
],
241 aCount
, toRead
- aCount
, &read
);
242 if (NS_SUCCEEDED(rv
)) {
244 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - UncachedRangedReadAt(%" PRIu32
245 "..%" PRIu32
"@%" PRId64
246 ") to top-up succeeded but read nothing -> OK anyway",
247 aCount
, aOffset
, aCount
, toRead
, aOffset
);
248 // Couldn't actually read anything, but didn't error out, so count
252 if (mCachedOffset
+ mCachedBytes
== aOffset
) {
253 // We were topping-up the cache, just update its size.
254 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - UncachedRangedReadAt(%" PRIu32
255 "..%" PRIu32
"@%" PRId64
") to top-up succeeded to read %" PRIu32
257 aCount
, aOffset
, aCount
, toRead
, aOffset
, read
);
258 mCachedBytes
+= read
;
260 // We were filling the cache from scratch, save new cache information.
261 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - UncachedRangedReadAt(%" PRIu32
262 "..%" PRIu32
"@%" PRId64
263 ") to fill cache succeeded to read %" PRIu32
"...",
264 aCount
, aOffset
, aCount
, toRead
, aOffset
, read
);
265 mCachedOffset
= aOffset
;
268 // Copy relevant part into output.
269 uint32_t toCopy
= std::min(aCount
, read
);
270 memcpy(aBuffer
, &mCachedBlock
[cacheIndex
], toCopy
);
272 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - copied %" PRIu32
"@%" PRId64
274 aCount
, aOffset
, toCopy
, aOffset
, *aBytes
);
275 // We may not have read all that was requested, but we got everything
276 // we could get, so we're done.
279 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - UncachedRangedReadAt(%" PRIu32
280 "..%" PRIu32
"@%" PRId64
281 ") failed: %s, will fallback to blocking read...",
282 aCount
, aOffset
, aCount
, toRead
, aOffset
, ResultName(rv
).get());
283 // Failure during reading. Note that this may be due to the cache
284 // changing between `GetCachedDataEnd` and `ReadAt`, so it's not
285 // totally unexpected, just hopefully rare; but we do need to handle it.
287 // Invalidate part of cache that may have been partially overridden.
288 if (mCachedOffset
+ mCachedBytes
== aOffset
) {
289 // We were topping-up the cache, just keep the old untouched data.
290 // (i.e., nothing to do here.)
292 // We were filling the cache from scratch, invalidate cache.
296 ILOG("ReadAt(%" PRIu32
"@%" PRId64
297 ") - no cached data, will fallback to blocking read...",
301 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - length is %" PRId64
302 " (%s), will fallback to blocking read as the caller requested...",
303 aCount
, aOffset
, length
, length
< 0 ? "unknown" : "too short!");
306 nsresult rv
= UncachedReadAt(aOffset
, aBuffer
, aCount
, &read
);
307 if (NS_SUCCEEDED(rv
)) {
309 ILOG("ReadAt(%" PRIu32
"@%" PRId64
") - fallback uncached read got %" PRIu32
310 " bytes -> %s, %" PRIu32
,
311 aCount
, aOffset
, read
, ResultName(rv
).get(), *aBytes
);
313 ILOG("ReadAt(%" PRIu32
"@%" PRId64
314 ") - fallback uncached read failed -> %s, %" PRIu32
,
315 aCount
, aOffset
, ResultName(rv
).get(), *aBytes
);
320 nsresult
MediaResourceIndex::UncachedReadAt(int64_t aOffset
, char* aBuffer
,
322 uint32_t* aBytes
) const {
324 return NS_ERROR_ILLEGAL_VALUE
;
330 return mResource
->ReadAt(aOffset
, aBuffer
, aCount
, aBytes
);
333 nsresult
MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset
,
335 uint32_t aRequestedCount
,
336 uint32_t aExtraCount
,
337 uint32_t* aBytes
) const {
338 uint32_t count
= aRequestedCount
+ aExtraCount
;
339 if (aOffset
< 0 || count
< aRequestedCount
) {
340 return NS_ERROR_ILLEGAL_VALUE
;
346 return mResource
->ReadAt(aOffset
, aBuffer
, count
, aBytes
);
349 nsresult
MediaResourceIndex::Seek(int32_t aWhence
, int64_t aOffset
) {
357 int64_t length
= mResource
->GetLength();
358 if (length
== -1 || length
- aOffset
< 0) {
359 return NS_ERROR_FAILURE
;
361 aOffset
= mResource
->GetLength() - aOffset
;
364 return NS_ERROR_FAILURE
;
368 return NS_ERROR_ILLEGAL_VALUE
;
375 already_AddRefed
<MediaByteBuffer
> MediaResourceIndex::MediaReadAt(
376 int64_t aOffset
, uint32_t aCount
) const {
377 NS_ENSURE_TRUE(aOffset
>= 0, nullptr);
378 RefPtr
<MediaByteBuffer
> bytes
= new MediaByteBuffer();
379 bool ok
= bytes
->SetLength(aCount
, fallible
);
380 NS_ENSURE_TRUE(ok
, nullptr);
382 uint32_t bytesRead
= 0;
383 nsresult rv
= mResource
->ReadAt(
384 aOffset
, reinterpret_cast<char*>(bytes
->Elements()), aCount
, &bytesRead
);
385 NS_ENSURE_SUCCESS(rv
, nullptr);
387 bytes
->SetLength(bytesRead
);
388 return bytes
.forget();
391 already_AddRefed
<MediaByteBuffer
> MediaResourceIndex::CachedMediaReadAt(
392 int64_t aOffset
, uint32_t aCount
) const {
393 RefPtr
<MediaByteBuffer
> bytes
= new MediaByteBuffer();
394 bool ok
= bytes
->SetLength(aCount
, fallible
);
395 NS_ENSURE_TRUE(ok
, nullptr);
396 char* curr
= reinterpret_cast<char*>(bytes
->Elements());
397 nsresult rv
= mResource
->ReadFromCache(curr
, aOffset
, aCount
);
398 NS_ENSURE_SUCCESS(rv
, nullptr);
399 return bytes
.forget();
402 // Get the length of the stream in bytes. Returns -1 if not known.
403 // This can change over time; after a seek operation, a misbehaving
404 // server may give us a resource of a different length to what it had
405 // reported previously --- or it may just lie in its Content-Length
406 // header and give us more or less data than it reported. We will adjust
407 // the result of GetLength to reflect the data that's actually arriving.
408 int64_t MediaResourceIndex::GetLength() const { return mResource
->GetLength(); }
410 uint32_t MediaResourceIndex::IndexInCache(int64_t aOffsetInFile
) const {
411 const uint32_t index
= uint32_t(aOffsetInFile
) & (mCacheBlockSize
- 1);
412 MOZ_ASSERT(index
== aOffsetInFile
% mCacheBlockSize
);
416 int64_t MediaResourceIndex::CacheOffsetContaining(int64_t aOffsetInFile
) const {
417 const int64_t offset
= aOffsetInFile
& ~(int64_t(mCacheBlockSize
) - 1);
418 MOZ_ASSERT(offset
== aOffsetInFile
- IndexInCache(aOffsetInFile
));
422 } // namespace mozilla
424 // avoid redefined macro in unified build