Bug 1371882 - Implement MemoryBlockCache - r?cpearce
Memory-backed block cache.
At initialization, allocates memory needed to store the expected content
length.
If MediaCache attempts to write/move beyond the expected size, we grow the
buffer accordingly, as we cannot fully trust HTTP headers. (Future patch will
ensure we put a limit to this growth.)
MozReview-Commit-ID: GHxYMGXYrwI
new file mode 100644
--- /dev/null
+++ b/dom/media/MemoryBlockCache.cpp
@@ -0,0 +1,127 @@
+/* -*- 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 "MemoryBlockCache.h"
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+#undef LOG
+LazyLogModule gMemoryBlockCacheLog("MemoryBlockCache");
+#define LOG(x, ...) \
+ MOZ_LOG(gMemoryBlockCacheLog, LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
+
+MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
+ // Buffer whole blocks.
+ : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
+ , mMutex("MemoryBlockCache")
+{
+}
+
+MemoryBlockCache::~MemoryBlockCache()
+{
+ MOZ_ASSERT(mBuffer.IsEmpty());
+}
+
+bool
+MemoryBlockCache::EnsureBufferCanContain(size_t aContentLength)
+{
+ mMutex.AssertCurrentThreadOwns();
+ if (aContentLength == 0) {
+ return true;
+ }
+ size_t desiredLength = ((aContentLength - 1) / BLOCK_SIZE + 1) * BLOCK_SIZE;
+ if (mBuffer.Length() >= desiredLength) {
+ // Already large enough.
+ return true;
+ }
+ // Need larger buffer, attempt to re-allocate.
+ return mBuffer.SetLength(desiredLength, mozilla::fallible);
+}
+
+nsresult
+MemoryBlockCache::Init()
+{
+ LOG("@%p Init()", this);
+ MutexAutoLock lock(mMutex);
+ // Attempt to pre-allocate buffer for expected content length.
+ return EnsureBufferCanContain(mInitialContentLength) ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+void
+MemoryBlockCache::Close()
+{
+ LOG("@%p Close()", this);
+ MutexAutoLock lock(mMutex);
+ mBuffer.SetLength(0);
+}
+
+nsresult
+MemoryBlockCache::WriteBlock(uint32_t aBlockIndex,
+ Span<const uint8_t> aData1,
+ Span<const uint8_t> aData2)
+{
+ MutexAutoLock lock(mMutex);
+
+ size_t offset = BlockIndexToOffset(aBlockIndex);
+ if (!EnsureBufferCanContain(offset + aData1.Length() + aData2.Length())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ memcpy(mBuffer.Elements() + offset, aData1.Elements(), aData1.Length());
+ if (aData2.Length() > 0) {
+ memcpy(mBuffer.Elements() + offset + aData1.Length(),
+ aData2.Elements(),
+ aData2.Length());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+MemoryBlockCache::Read(int64_t aOffset,
+ uint8_t* aData,
+ int32_t aLength,
+ int32_t* aBytes)
+{
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(aOffset >= 0);
+ if (aOffset + aLength > int64_t(mBuffer.Length())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ memcpy(aData, mBuffer.Elements() + aOffset, aLength);
+ *aBytes = aLength;
+
+ return NS_OK;
+}
+
+nsresult
+MemoryBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
+{
+ MutexAutoLock lock(mMutex);
+
+ size_t sourceOffset = BlockIndexToOffset(aSourceBlockIndex);
+ size_t destOffset = BlockIndexToOffset(aDestBlockIndex);
+ if (sourceOffset + BLOCK_SIZE > mBuffer.Length() ||
+ !EnsureBufferCanContain(destOffset + BLOCK_SIZE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ memcpy(mBuffer.Elements() + destOffset,
+ mBuffer.Elements() + sourceOffset,
+ BLOCK_SIZE);
+
+ return NS_OK;
+}
+
+} // End namespace mozilla.
+
+// avoid redefined macro in unified build
+#undef LOG
copy from dom/media/MediaBlockCacheBase.h
copy to dom/media/MemoryBlockCache.h
--- a/dom/media/MediaBlockCacheBase.h
+++ b/dom/media/MemoryBlockCache.h
@@ -1,77 +1,84 @@
/* -*- 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/. */
-#ifndef MEDIA_BLOCK_CACHE_BASE_H_
-#define MEDIA_BLOCK_CACHE_BASE_H_
+#ifndef MEMORY_BLOCK_CACHE_H_
+#define MEMORY_BLOCK_CACHE_H_
-#include "MediaCache.h"
-#include "mozilla/Span.h"
+#include "MediaBlockCacheBase.h"
+#include "mozilla/Mutex.h"
namespace mozilla {
// Manages block management for the media cache. Data comes in over the network
// via callbacks on the main thread, however we don't want to write the
// incoming data to the media cache on the main thread, as this could block
// causing UI jank.
//
-// So MediaBlockCacheBase provides an abstraction for a temporary memory buffer or file accessible
+// So MediaBlockCacheBase provides an abstraction for a temporary memory buffer
// as an array of blocks, which supports a block move operation, and
-// allows synchronous reading and writing from any thread, with writes being
-// buffered as needed so as not to block.
+// allows synchronous reading and writing from any thread.
//
// Writes and cache block moves (which require reading) may be deferred to
// their own non-main thread. This object also ensures that data which has
// been scheduled to be written, but hasn't actually *been* written, is read
// as if it had, i.e. pending writes are cached in readable memory until
// they're flushed to file.
//
// To improve efficiency, writes can only be done at block granularity,
// whereas reads can be done with byte granularity.
-//
-// Note it's also recommended not to read from the media cache from the main
-// thread to prevent jank.
-class MediaBlockCacheBase
+class MemoryBlockCache : public MediaBlockCacheBase
{
public:
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaBlockCacheBase)
-
- static_assert(
- MediaCacheStream::BLOCK_SIZE <
- static_cast<decltype(MediaCacheStream::BLOCK_SIZE)>(INT32_MAX),
- "MediaCacheStream::BLOCK_SIZE should fit in 31 bits");
- static const int32_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
+ explicit MemoryBlockCache(int64_t aContentLength);
protected:
- virtual ~MediaBlockCacheBase() {}
+ virtual ~MemoryBlockCache();
public:
- virtual nsresult Init() = 0;
+ // Allocate initial buffer.
+ virtual nsresult Init() override;
- // Closes writer, shuts down thread.
- virtual void Close() = 0;
+ // Empty buffer.
+ virtual void Close() override;
- // Can be called on any thread. This defers to a non-main thread.
+ // Can be called on any thread.
virtual nsresult WriteBlock(uint32_t aBlockIndex,
Span<const uint8_t> aData1,
- Span<const uint8_t> aData2) = 0;
+ Span<const uint8_t> aData2) override;
- // Synchronously reads data from file. May read from file or memory
- // depending on whether written blocks have been flushed to file yet.
- // Not recommended to be called from the main thread, as can cause jank.
+ // Synchronously reads data from buffer.
virtual nsresult Read(int64_t aOffset,
uint8_t* aData,
int32_t aLength,
- int32_t* aBytes) = 0;
+ int32_t* aBytes) override;
+
+ // Moves a block. Can be called on any thread.
+ virtual nsresult MoveBlock(int32_t aSourceBlockIndex,
+ int32_t aDestBlockIndex) override;
+
+private:
+ static size_t BlockIndexToOffset(uint32_t aBlockIndex)
+ {
+ return static_cast<size_t>(aBlockIndex) * BLOCK_SIZE;
+ }
- // Moves a block asynchronously. Can be called on any thread.
- // This defers file I/O to a non-main thread.
- virtual nsresult MoveBlock(int32_t aSourceBlockIndex,
- int32_t aDestBlockIndex) = 0;
+ // Ensure the buffer has at least a multiple of BLOCK_SIZE that can contain
+ // aContentLength bytes. Buffer size can only grow.
+ // Returns false if allocation failed.
+ bool EnsureBufferCanContain(size_t aContentLength);
+
+ // Initial content length.
+ const size_t mInitialContentLength;
+
+ // Mutex which controls access to all members below.
+ Mutex mMutex;
+
+ nsTArray<uint8_t> mBuffer;
};
} // End namespace mozilla.
-#endif /* MEDIA_BLOCK_CACHE_BASE_H_ */
+#endif /* MEMORY_BLOCK_CACHE_H_ */
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -130,16 +130,17 @@ EXPORTS += [
'MediaShutdownManager.h',
'MediaStatistics.h',
'MediaStreamGraph.h',
'MediaStreamListener.h',
'MediaStreamVideoSink.h',
'MediaTimer.h',
'MediaTrack.h',
'MediaTrackList.h',
+ 'MemoryBlockCache.h',
'MP3Decoder.h',
'MP3Demuxer.h',
'MP3FrameParser.h',
'nsIDocumentActivity.h',
'PrincipalChangeObserver.h',
'QueueObject.h',
'SeekJob.h',
'SeekTarget.h',
@@ -237,16 +238,17 @@ UNIFIED_SOURCES += [
'MediaStreamError.cpp',
'MediaStreamGraph.cpp',
'MediaStreamListener.cpp',
'MediaStreamTrack.cpp',
'MediaStreamVideoSink.cpp',
'MediaTimer.cpp',
'MediaTrack.cpp',
'MediaTrackList.cpp',
+ 'MemoryBlockCache.cpp',
'MP3Decoder.cpp',
'MP3Demuxer.cpp',
'MP3FrameParser.cpp',
'QueueObject.cpp',
'SeekJob.cpp',
'StreamTracks.cpp',
'TextTrack.cpp',
'TextTrackCue.cpp',