diff options
Diffstat (limited to 'dom/media/MediaResource.cpp')
-rw-r--r-- | dom/media/MediaResource.cpp | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/dom/media/MediaResource.cpp b/dom/media/MediaResource.cpp new file mode 100644 index 0000000000..feb0b130bf --- /dev/null +++ b/dom/media/MediaResource.cpp @@ -0,0 +1,425 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#include "MediaResource.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/SchedulerGroup.h" + +using mozilla::media::TimeUnit; + +#undef ILOG + +mozilla::LazyLogModule gMediaResourceIndexLog("MediaResourceIndex"); +// Debug logging macro with object pointer and class name. +#define ILOG(msg, ...) \ + DDMOZ_LOG(gMediaResourceIndexLog, mozilla::LogLevel::Debug, msg, \ + ##__VA_ARGS__) + +namespace mozilla { + +static const uint32_t kMediaResourceIndexCacheSize = 8192; +static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize), + "kMediaResourceIndexCacheSize cache size must be a power of 2"); + +MediaResourceIndex::MediaResourceIndex(MediaResource* aResource) + : mResource(aResource), + mOffset(0), + mCacheBlockSize( + aResource->ShouldCacheReads() ? kMediaResourceIndexCacheSize : 0), + mCachedOffset(0), + mCachedBytes(0), + mCachedBlock(MakeUnique<char[]>(mCacheBlockSize)) { + DDLINKCHILD("resource", aResource); +} + +nsresult MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, + uint32_t* aBytes) { + NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); + + // We purposefuly don't check that we may attempt to read past + // mResource->GetLength() as the resource's length may change over time. + + nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes); + if (NS_FAILED(rv)) { + return rv; + } + mOffset += *aBytes; + if (mOffset < 0) { + // Very unlikely overflow; just return to position 0. + mOffset = 0; + } + return NS_OK; +} + +static nsCString ResultName(nsresult aResult) { + nsCString name; + GetErrorName(aResult, name); + return name; +} + +nsresult MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) { + if (mCacheBlockSize == 0) { + return UncachedReadAt(aOffset, aBuffer, aCount, aBytes); + } + + *aBytes = 0; + + if (aCount == 0) { + return NS_OK; + } + + const int64_t endOffset = aOffset + aCount; + if (aOffset < 0 || endOffset < aOffset) { + return NS_ERROR_ILLEGAL_VALUE; + } + + const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1); + + if (mCachedBytes != 0 && mCachedOffset + mCachedBytes >= aOffset && + mCachedOffset < endOffset) { + // There is data in the cache that is not completely before aOffset and not + // completely after endOffset, so it could be usable (with potential + // top-up). + if (aOffset < mCachedOffset) { + // We need to read before the cached data. + const uint32_t toRead = uint32_t(mCachedOffset - aOffset); + MOZ_ASSERT(toRead > 0); + MOZ_ASSERT(toRead < aCount); + uint32_t read = 0; + nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read); + if (NS_FAILED(rv)) { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") uncached read before cache -> %s, %" PRIu32, + aCount, aOffset, ResultName(rv).get(), *aBytes); + return rv; + } + *aBytes = read; + if (read < toRead) { + // Could not read everything we wanted, we're done. + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") uncached read before cache, incomplete -> OK, %" PRIu32, + aCount, aOffset, *aBytes); + return NS_OK; + } + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32 + "@%" PRId64 "...", + aCount, aOffset, read, aCount - read, aOffset + read); + aOffset += read; + aBuffer += read; + aCount -= read; + // We should have reached the cache. + MOZ_ASSERT(aOffset == mCachedOffset); + } + MOZ_ASSERT(aOffset >= mCachedOffset); + + // We've reached our cache. + const uint32_t toCopy = + std::min(aCount, uint32_t(mCachedOffset + mCachedBytes - aOffset)); + // Note that we could in fact be just after the last byte of the cache, in + // which case we can't actually read from it! (But we will top-up next.) + if (toCopy != 0) { + memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy); + *aBytes += toCopy; + aCount -= toCopy; + if (aCount == 0) { + // All done! + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32 + ") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32, + aCount, aOffset, toCopy, mCachedBytes, mCachedOffset, *aBytes); + return NS_OK; + } + aOffset += toCopy; + aBuffer += toCopy; + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32 + " from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32 + "@%" PRId64 "...", + aCount + toCopy, aOffset - toCopy, toCopy, mCachedBytes, + mCachedOffset, aCount, aOffset); + } + + if (aOffset - 1 >= lastBlockOffset) { + // We were already reading cached data from the last block, we need more + // from it -> try to top-up, read what we can, and we'll be done. + MOZ_ASSERT(aOffset == mCachedOffset + mCachedBytes); + MOZ_ASSERT(endOffset <= lastBlockOffset + mCacheBlockSize); + return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes); + } + + // We were not in the last block (but we may just have crossed the line now) + MOZ_ASSERT(aOffset <= lastBlockOffset); + // Continue below... + } else if (aOffset >= lastBlockOffset) { + // There was nothing we could get from the cache. + // But we're already in the last block -> Cache or read what we can. + // Make sure to invalidate the cache first. + mCachedBytes = 0; + return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes); + } + + // If we're here, either there was nothing usable in the cache, or we've just + // read what was in the cache but there's still more to read. + + if (aOffset < lastBlockOffset) { + // We need to read before the last block. + // Start with an uncached read up to the last block. + const uint32_t toRead = uint32_t(lastBlockOffset - aOffset); + MOZ_ASSERT(toRead > 0); + MOZ_ASSERT(toRead < aCount); + uint32_t read = 0; + nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read); + if (NS_FAILED(rv)) { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") uncached read before last block failed -> %s, %" PRIu32, + aCount, aOffset, ResultName(rv).get(), *aBytes); + return rv; + } + if (read == 0) { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") uncached read 0 before last block -> OK, %" PRIu32, + aCount, aOffset, *aBytes); + return NS_OK; + } + *aBytes += read; + if (read < toRead) { + // Could not read everything we wanted, we're done. + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") uncached read before last block, incomplete -> OK, %" PRIu32, + aCount, aOffset, *aBytes); + return NS_OK; + } + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32 + " before last block, remaining: %" PRIu32 "@%" PRId64 "...", + aCount, aOffset, read, aCount - read, aOffset + read); + aOffset += read; + aBuffer += read; + aCount -= read; + } + + // We should just have reached the start of the last block. + MOZ_ASSERT(aOffset == lastBlockOffset); + MOZ_ASSERT(aCount <= mCacheBlockSize); + // Make sure to invalidate the cache first. + mCachedBytes = 0; + return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes); +} + +nsresult MediaResourceIndex::CacheOrReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) { + // We should be here because there is more data to read. + MOZ_ASSERT(aCount > 0); + // We should be in the last block, so we shouldn't try to read past it. + MOZ_ASSERT(IndexInCache(aOffset) + aCount <= mCacheBlockSize); + + const int64_t length = GetLength(); + // If length is unknown (-1), look at resource-cached data. + // If length is known and equal or greater than requested, also look at + // resource-cached data. + // Otherwise, if length is known but same, or less than(!?), requested, don't + // attempt to access resource-cached data, as we're not expecting it to ever + // be greater than the length. + if (length < 0 || length >= aOffset + aCount) { + // Is there cached data covering at least the requested range? + const int64_t cachedDataEnd = mResource->GetCachedDataEnd(aOffset); + if (cachedDataEnd >= aOffset + aCount) { + // Try to read as much resource-cached data as can fill our local cache. + // Assume we can read as much as is cached without blocking. + const uint32_t cacheIndex = IndexInCache(aOffset); + const uint32_t toRead = uint32_t(std::min( + cachedDataEnd - aOffset, int64_t(mCacheBlockSize - cacheIndex))); + MOZ_ASSERT(toRead >= aCount); + uint32_t read = 0; + // We would like `toRead` if possible, but ok with at least `aCount`. + nsresult rv = UncachedRangedReadAt(aOffset, &mCachedBlock[cacheIndex], + aCount, toRead - aCount, &read); + if (NS_SUCCEEDED(rv)) { + if (read == 0) { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32 + "..%" PRIu32 "@%" PRId64 + ") to top-up succeeded but read nothing -> OK anyway", + aCount, aOffset, aCount, toRead, aOffset); + // Couldn't actually read anything, but didn't error out, so count + // that as success. + return NS_OK; + } + if (mCachedOffset + mCachedBytes == aOffset) { + // We were topping-up the cache, just update its size. + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32 + "..%" PRIu32 "@%" PRId64 ") to top-up succeeded to read %" PRIu32 + "...", + aCount, aOffset, aCount, toRead, aOffset, read); + mCachedBytes += read; + } else { + // We were filling the cache from scratch, save new cache information. + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32 + "..%" PRIu32 "@%" PRId64 + ") to fill cache succeeded to read %" PRIu32 "...", + aCount, aOffset, aCount, toRead, aOffset, read); + mCachedOffset = aOffset; + mCachedBytes = read; + } + // Copy relevant part into output. + uint32_t toCopy = std::min(aCount, read); + memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy); + *aBytes += toCopy; + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64 + " -> OK, %" PRIu32, + aCount, aOffset, toCopy, aOffset, *aBytes); + // We may not have read all that was requested, but we got everything + // we could get, so we're done. + return NS_OK; + } + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32 + "..%" PRIu32 "@%" PRId64 + ") failed: %s, will fallback to blocking read...", + aCount, aOffset, aCount, toRead, aOffset, ResultName(rv).get()); + // Failure during reading. Note that this may be due to the cache + // changing between `GetCachedDataEnd` and `ReadAt`, so it's not + // totally unexpected, just hopefully rare; but we do need to handle it. + + // Invalidate part of cache that may have been partially overridden. + if (mCachedOffset + mCachedBytes == aOffset) { + // We were topping-up the cache, just keep the old untouched data. + // (i.e., nothing to do here.) + } else { + // We were filling the cache from scratch, invalidate cache. + mCachedBytes = 0; + } + } else { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") - no cached data, will fallback to blocking read...", + aCount, aOffset); + } + } else { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - length is %" PRId64 + " (%s), will fallback to blocking read as the caller requested...", + aCount, aOffset, length, length < 0 ? "unknown" : "too short!"); + } + uint32_t read = 0; + nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read); + if (NS_SUCCEEDED(rv)) { + *aBytes += read; + ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32 + " bytes -> %s, %" PRIu32, + aCount, aOffset, read, ResultName(rv).get(), *aBytes); + } else { + ILOG("ReadAt(%" PRIu32 "@%" PRId64 + ") - fallback uncached read failed -> %s, %" PRIu32, + aCount, aOffset, ResultName(rv).get(), *aBytes); + } + return rv; +} + +nsresult MediaResourceIndex::UncachedReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, + uint32_t* aBytes) const { + if (aOffset < 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (aCount == 0) { + *aBytes = 0; + return NS_OK; + } + return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes); +} + +nsresult MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset, + char* aBuffer, + uint32_t aRequestedCount, + uint32_t aExtraCount, + uint32_t* aBytes) const { + uint32_t count = aRequestedCount + aExtraCount; + if (aOffset < 0 || count < aRequestedCount) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (count == 0) { + *aBytes = 0; + return NS_OK; + } + return mResource->ReadAt(aOffset, aBuffer, count, aBytes); +} + +nsresult MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset) { + switch (aWhence) { + case SEEK_SET: + break; + case SEEK_CUR: + aOffset += mOffset; + break; + case SEEK_END: { + int64_t length = mResource->GetLength(); + if (length == -1 || length - aOffset < 0) { + return NS_ERROR_FAILURE; + } + aOffset = mResource->GetLength() - aOffset; + } break; + default: + return NS_ERROR_FAILURE; + } + + if (aOffset < 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + mOffset = aOffset; + + return NS_OK; +} + +already_AddRefed<MediaByteBuffer> MediaResourceIndex::MediaReadAt( + int64_t aOffset, uint32_t aCount) const { + NS_ENSURE_TRUE(aOffset >= 0, nullptr); + RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer(); + bool ok = bytes->SetLength(aCount, fallible); + NS_ENSURE_TRUE(ok, nullptr); + + uint32_t bytesRead = 0; + nsresult rv = mResource->ReadAt( + aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead); + NS_ENSURE_SUCCESS(rv, nullptr); + + bytes->SetLength(bytesRead); + return bytes.forget(); +} + +already_AddRefed<MediaByteBuffer> MediaResourceIndex::CachedMediaReadAt( + int64_t aOffset, uint32_t aCount) const { + RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer(); + bool ok = bytes->SetLength(aCount, fallible); + NS_ENSURE_TRUE(ok, nullptr); + char* curr = reinterpret_cast<char*>(bytes->Elements()); + nsresult rv = mResource->ReadFromCache(curr, aOffset, aCount); + NS_ENSURE_SUCCESS(rv, nullptr); + return bytes.forget(); +} + +// Get the length of the stream in bytes. Returns -1 if not known. +// This can change over time; after a seek operation, a misbehaving +// server may give us a resource of a different length to what it had +// reported previously --- or it may just lie in its Content-Length +// header and give us more or less data than it reported. We will adjust +// the result of GetLength to reflect the data that's actually arriving. +int64_t MediaResourceIndex::GetLength() const { return mResource->GetLength(); } + +uint32_t MediaResourceIndex::IndexInCache(int64_t aOffsetInFile) const { + const uint32_t index = uint32_t(aOffsetInFile) & (mCacheBlockSize - 1); + MOZ_ASSERT(index == aOffsetInFile % mCacheBlockSize); + return index; +} + +int64_t MediaResourceIndex::CacheOffsetContaining(int64_t aOffsetInFile) const { + const int64_t offset = aOffsetInFile & ~(int64_t(mCacheBlockSize) - 1); + MOZ_ASSERT(offset == aOffsetInFile - IndexInCache(aOffsetInFile)); + return offset; +} + +} // namespace mozilla + +// avoid redefined macro in unified build +#undef ILOG |