DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (d1c688190228)

VCS Links

Line Code
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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
/* -*- 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 "MediaPrefs.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Logging.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/SystemGroup.h"
#include "mozilla/ErrorNames.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 {

void MediaResource::Destroy() {
  // Ensures we only delete the MediaResource on the main thread.
  if (NS_IsMainThread()) {
    delete this;
    return;
  }
  nsresult rv = SystemGroup::Dispatch(
      TaskCategory::Other,
      NewNonOwningRunnableMethod("MediaResource::Destroy", this,
                                 &MediaResource::Destroy));
  MOZ_ALWAYS_SUCCEEDS(rv);
}

NS_IMPL_ADDREF(MediaResource)
NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())

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