MediaResource.cpp (16427B)
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 9 #include "mozilla/ErrorNames.h" 10 #include "mozilla/Logging.h" 11 #include "mozilla/MathAlgorithms.h" 12 #include "mozilla/SchedulerGroup.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 static const uint32_t kMediaResourceIndexCacheSize = 8192; 27 static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize), 28 "kMediaResourceIndexCacheSize cache size must be a power of 2"); 29 30 MediaResourceIndex::MediaResourceIndex(MediaResource* aResource) 31 : mResource(aResource), 32 mOffset(0), 33 mCacheBlockSize( 34 aResource->ShouldCacheReads() ? kMediaResourceIndexCacheSize : 0), 35 mCachedOffset(0), 36 mCachedBytes(0), 37 mCachedBlock(MakeUnique<char[]>(mCacheBlockSize)) { 38 DDLINKCHILD("resource", aResource); 39 } 40 41 nsresult MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, 42 uint32_t* aBytes) { 43 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); 44 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. 47 48 nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes); 49 if (NS_FAILED(rv)) { 50 return rv; 51 } 52 mOffset += *aBytes; 53 if (mOffset < 0) { 54 // Very unlikely overflow; just return to position 0. 55 mOffset = 0; 56 } 57 return NS_OK; 58 } 59 60 static nsCString ResultName(nsresult aResult) { 61 nsCString name; 62 GetErrorName(aResult, name); 63 return name; 64 } 65 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); 70 } 71 72 *aBytes = 0; 73 74 if (aCount == 0) { 75 return NS_OK; 76 } 77 78 const int64_t endOffset = aOffset + aCount; 79 if (aOffset < 0 || endOffset < aOffset) { 80 return NS_ERROR_ILLEGAL_VALUE; 81 } 82 83 const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1); 84 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 89 // top-up). 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); 95 uint32_t read = 0; 96 nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read); 97 if (NS_FAILED(rv)) { 98 ILOG("ReadAt(%" PRIu32 "@%" PRId64 99 ") uncached read before cache -> %s, %" PRIu32, 100 aCount, aOffset, ResultName(rv).get(), *aBytes); 101 return rv; 102 } 103 *aBytes = read; 104 if (read < toRead) { 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); 109 return NS_OK; 110 } 111 ILOG("ReadAt(%" PRIu32 "@%" PRId64 112 ") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32 113 "@%" PRId64 "...", 114 aCount, aOffset, read, aCount - read, aOffset + read); 115 aOffset += read; 116 aBuffer += read; 117 aCount -= read; 118 // We should have reached the cache. 119 MOZ_ASSERT(aOffset == mCachedOffset); 120 } 121 MOZ_ASSERT(aOffset >= mCachedOffset); 122 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.) 128 if (toCopy != 0) { 129 memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy); 130 *aBytes += toCopy; 131 aCount -= toCopy; 132 if (aCount == 0) { 133 // All done! 134 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32 135 ") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32, 136 aCount, aOffset, toCopy, mCachedBytes, mCachedOffset, *aBytes); 137 return NS_OK; 138 } 139 aOffset += toCopy; 140 aBuffer += toCopy; 141 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32 142 " from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32 143 "@%" PRId64 "...", 144 aCount + toCopy, aOffset - toCopy, toCopy, mCachedBytes, 145 mCachedOffset, aCount, aOffset); 146 } 147 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); 154 } 155 156 // We were not in the last block (but we may just have crossed the line now) 157 MOZ_ASSERT(aOffset <= lastBlockOffset); 158 // Continue below... 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. 163 mCachedBytes = 0; 164 return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes); 165 } 166 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. 169 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); 176 uint32_t read = 0; 177 nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read); 178 if (NS_FAILED(rv)) { 179 ILOG("ReadAt(%" PRIu32 "@%" PRId64 180 ") uncached read before last block failed -> %s, %" PRIu32, 181 aCount, aOffset, ResultName(rv).get(), *aBytes); 182 return rv; 183 } 184 if (read == 0) { 185 ILOG("ReadAt(%" PRIu32 "@%" PRId64 186 ") uncached read 0 before last block -> OK, %" PRIu32, 187 aCount, aOffset, *aBytes); 188 return NS_OK; 189 } 190 *aBytes += read; 191 if (read < toRead) { 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); 196 return NS_OK; 197 } 198 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32 199 " before last block, remaining: %" PRIu32 "@%" PRId64 "...", 200 aCount, aOffset, read, aCount - read, aOffset + read); 201 aOffset += read; 202 aBuffer += read; 203 aCount -= read; 204 } 205 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. 210 mCachedBytes = 0; 211 return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes); 212 } 213 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); 220 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); 238 uint32_t read = 0; 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)) { 243 if (read == 0) { 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 249 // that as success. 250 return NS_OK; 251 } 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 256 "...", 257 aCount, aOffset, aCount, toRead, aOffset, read); 258 mCachedBytes += read; 259 } else { 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; 266 mCachedBytes = read; 267 } 268 // Copy relevant part into output. 269 uint32_t toCopy = std::min(aCount, read); 270 memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy); 271 *aBytes += toCopy; 272 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64 273 " -> OK, %" PRIu32, 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. 277 return NS_OK; 278 } 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. 286 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.) 291 } else { 292 // We were filling the cache from scratch, invalidate cache. 293 mCachedBytes = 0; 294 } 295 } else { 296 ILOG("ReadAt(%" PRIu32 "@%" PRId64 297 ") - no cached data, will fallback to blocking read...", 298 aCount, aOffset); 299 } 300 } else { 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!"); 304 } 305 uint32_t read = 0; 306 nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read); 307 if (NS_SUCCEEDED(rv)) { 308 *aBytes += read; 309 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32 310 " bytes -> %s, %" PRIu32, 311 aCount, aOffset, read, ResultName(rv).get(), *aBytes); 312 } else { 313 ILOG("ReadAt(%" PRIu32 "@%" PRId64 314 ") - fallback uncached read failed -> %s, %" PRIu32, 315 aCount, aOffset, ResultName(rv).get(), *aBytes); 316 } 317 return rv; 318 } 319 320 nsresult MediaResourceIndex::UncachedReadAt(int64_t aOffset, char* aBuffer, 321 uint32_t aCount, 322 uint32_t* aBytes) const { 323 if (aOffset < 0) { 324 return NS_ERROR_ILLEGAL_VALUE; 325 } 326 if (aCount == 0) { 327 *aBytes = 0; 328 return NS_OK; 329 } 330 return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes); 331 } 332 333 nsresult MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset, 334 char* aBuffer, 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; 341 } 342 if (count == 0) { 343 *aBytes = 0; 344 return NS_OK; 345 } 346 return mResource->ReadAt(aOffset, aBuffer, count, aBytes); 347 } 348 349 nsresult MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset) { 350 switch (aWhence) { 351 case SEEK_SET: 352 break; 353 case SEEK_CUR: 354 aOffset += mOffset; 355 break; 356 case SEEK_END: { 357 int64_t length = mResource->GetLength(); 358 if (length == -1 || length - aOffset < 0) { 359 return NS_ERROR_FAILURE; 360 } 361 aOffset = mResource->GetLength() - aOffset; 362 } break; 363 default: 364 return NS_ERROR_FAILURE; 365 } 366 367 if (aOffset < 0) { 368 return NS_ERROR_ILLEGAL_VALUE; 369 } 370 mOffset = aOffset; 371 372 return NS_OK; 373 } 374 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); 381 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); 386 387 bytes->SetLength(bytesRead); 388 return bytes.forget(); 389 } 390 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(); 400 } 401 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(); } 409 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); 413 return index; 414 } 415 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)); 419 return offset; 420 } 421 422 } // namespace mozilla 423 424 // avoid redefined macro in unified build 425 #undef ILOG