diff --git a/dom/file/MutableBlobStorage.h b/dom/file/MutableBlobStorage.h index e539d7a78bba..db7c746f7e9e 100644 --- a/dom/file/MutableBlobStorage.h +++ b/dom/file/MutableBlobStorage.h @@ -11,6 +11,7 @@ #include "prio.h" class nsIEventTarget; +class nsIRunnable; namespace mozilla { diff --git a/dom/media/EncodedBufferCache.cpp b/dom/media/EncodedBufferCache.cpp deleted file mode 100644 index 2d47f48e92da..000000000000 --- a/dom/media/EncodedBufferCache.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* -*- 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 "EncodedBufferCache.h" -#include "prio.h" -#include "nsAnonymousTemporaryFile.h" -#include "mozilla/Monitor.h" -#include "mozilla/dom/ContentChild.h" -#include "mozilla/dom/File.h" -#include "nsThreadUtils.h" -#include "nsXULAppAPI.h" - -namespace mozilla { - -void -EncodedBufferCache::AppendBuffer(nsTArray & aBuf) -{ - MOZ_ASSERT(!NS_IsMainThread()); - - MutexAutoLock lock(mMutex); - mDataSize += aBuf.Length(); - - mEncodedBuffers.AppendElement()->SwapElements(aBuf); - - if (!mTempFileEnabled && mDataSize > mMaxMemoryStorage) { - nsresult rv; - PRFileDesc* tempFD = nullptr; - { - // Release the mMutex because of the sync dispatch to the main thread. - MutexAutoUnlock unlock(mMutex); - if (XRE_IsParentProcess()) { - // In case we are in the parent process, do a synchronous I/O here to open a - // temporary file. - rv = NS_OpenAnonymousTemporaryFile(&tempFD); - } else { - // In case we are in the child process, we don't have access to open a file - // directly due to sandbox restrictions, so we need to ask the parent process - // to do that for us. In order to initiate the IPC, we need to first go to - // the main thread. This is done by dispatching a runnable to the main thread. - // From there, we start an asynchronous IPC, and we block the current thread - // using a monitor while this async work is in progress. When we receive the - // resulting file descriptor from the parent process, we notify the monitor - // and unblock the current thread and continue. - typedef dom::ContentChild::AnonymousTemporaryFileCallback - AnonymousTemporaryFileCallback; - bool done = false; - Monitor monitor("EncodeBufferCache::AppendBuffer"); - RefPtr cc = dom::ContentChild::GetSingleton(); - nsCOMPtr runnable = - NewRunnableMethod( - "dom::ContentChild::AsyncOpenAnonymousTemporaryFile", - cc, - &dom::ContentChild::AsyncOpenAnonymousTemporaryFile, - [&](PRFileDesc* aFile) { - rv = aFile ? NS_OK : NS_ERROR_FAILURE; - tempFD = aFile; - MonitorAutoLock lock(monitor); - done = true; - lock.Notify(); - }); - MonitorAutoLock lock(monitor); - rv = NS_DispatchToMainThread(runnable); - if (NS_SUCCEEDED(rv)) { - while (!done) { - lock.Wait(); - } - } - } - } - if (!NS_FAILED(rv)) { - // Check the mDataSize again since we release the mMutex before. - if (mDataSize > mMaxMemoryStorage) { - mFD = tempFD; - mTempFileEnabled = true; - } else { - // Close the tempFD because the data had been taken during the - // MutexAutoUnlock. - PR_Close(tempFD); - } - } - } - - if (mTempFileEnabled) { - // has created temporary file, write buffer in it - for (uint32_t i = 0; i < mEncodedBuffers.Length(); i++) { - int32_t amount = PR_Write(mFD, mEncodedBuffers.ElementAt(i).Elements(), mEncodedBuffers.ElementAt(i).Length()); - if (amount < 0 || size_t(amount) < mEncodedBuffers.ElementAt(i).Length()) { - NS_WARNING("Failed to write media cache block!"); - } - } - mEncodedBuffers.Clear(); - } - -} - -already_AddRefed -EncodedBufferCache::ExtractBlob(nsISupports* aParent, - const nsAString &aContentType) -{ - MutexAutoLock lock(mMutex); - RefPtr blob; - if (mTempFileEnabled) { - // generate new temporary file to write - blob = dom::Blob::CreateTemporaryBlob(aParent, mFD, 0, mDataSize, - aContentType); - // fallback to memory blob - mTempFileEnabled = false; - mDataSize = 0; - mFD = nullptr; - } else { - void* blobData = malloc(mDataSize); - NS_ASSERTION(blobData, "out of memory!!"); - - if (blobData) { - for (uint32_t i = 0, offset = 0; i < mEncodedBuffers.Length(); i++) { - memcpy((uint8_t*)blobData + offset, mEncodedBuffers.ElementAt(i).Elements(), - mEncodedBuffers.ElementAt(i).Length()); - offset += mEncodedBuffers.ElementAt(i).Length(); - } - blob = dom::Blob::CreateMemoryBlob(aParent, blobData, mDataSize, - aContentType); - mEncodedBuffers.Clear(); - } else - return nullptr; - } - mDataSize = 0; - return blob.forget(); -} - -size_t -EncodedBufferCache::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) -{ - MutexAutoLock lock(mMutex); - return mEncodedBuffers.ShallowSizeOfExcludingThis(aMallocSizeOf); -} - -} // namespace mozilla diff --git a/dom/media/EncodedBufferCache.h b/dom/media/EncodedBufferCache.h deleted file mode 100644 index bfbeb0d9d8bd..000000000000 --- a/dom/media/EncodedBufferCache.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- 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 EncodedBufferCache_h_ -#define EncodedBufferCache_h_ - -#include "nsCOMPtr.h" -#include "nsTArray.h" -#include "mozilla/Mutex.h" - -struct PRFileDesc; - -namespace mozilla { - -namespace dom { -class Blob; -} // namespace dom - -/** - * Data is moved into a temporary file when it grows beyond - * the maximal size passed in the Init function. - * The AppendBuffer and ExtractBlob methods are thread-safe and can be called on - * different threads at the same time. - */ -class EncodedBufferCache -{ -public: - explicit EncodedBufferCache(uint32_t aMaxMemoryStorage) - : mFD(nullptr), - mMutex("EncodedBufferCache.Data.Mutex"), - mDataSize(0), - mMaxMemoryStorage(aMaxMemoryStorage), - mTempFileEnabled(false) { } - ~EncodedBufferCache() - { - } - // Append buffers in cache, check if the queue is too large then switch to write buffer to file system - // aBuf will append to mEncodedBuffers or temporary File, aBuf also be cleared - void AppendBuffer(nsTArray & aBuf); - // Read all buffer from memory or file System, also Remove the temporary file or clean the buffers in memory. - already_AddRefed ExtractBlob(nsISupports* aParent, const nsAString &aContentType); - // Returns the heap size in bytes of our internal buffers. - // Note that this intentionally ignores the data in the temp file. - size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); - -private: - //array for storing the encoded data. - nsTArray > mEncodedBuffers; - // File handle for the temporary file - PRFileDesc* mFD; - // Used to protect the mEncodedBuffer for avoiding AppendBuffer/Consume on different thread at the same time. - Mutex mMutex; - // the current buffer size can be read - uint64_t mDataSize; - // The maximal buffer allowed in memory - uint32_t mMaxMemoryStorage; - // indicate the buffer is stored on temporary file or not - bool mTempFileEnabled; -}; - -} // namespace mozilla - -#endif diff --git a/dom/media/MediaRecorder.cpp b/dom/media/MediaRecorder.cpp index b4142b702da7..b4864ab70d39 100644 --- a/dom/media/MediaRecorder.cpp +++ b/dom/media/MediaRecorder.cpp @@ -9,7 +9,6 @@ #include "AudioNodeEngine.h" #include "AudioNodeStream.h" #include "DOMMediaStream.h" -#include "EncodedBufferCache.h" #include "GeckoProfiler.h" #include "MediaDecoder.h" #include "MediaEncoder.h" @@ -19,6 +18,7 @@ #include "mozilla/dom/BlobEvent.h" #include "mozilla/dom/File.h" #include "mozilla/dom/MediaRecorderErrorEvent.h" +#include "mozilla/dom/MutableBlobStorage.h" #include "mozilla/dom/VideoStreamTrack.h" #include "mozilla/media/MediaUtils.h" #include "mozilla/MemoryReporting.h" @@ -201,11 +201,17 @@ class MediaRecorder::Session: public PrincipalChangeObserver, // Main thread task. // Create a blob event and send back to client. class PushBlobRunnable : public Runnable + , public MutableBlobStorageCallback { public: - explicit PushBlobRunnable(Session* aSession) + NS_DECL_ISUPPORTS_INHERITED + + // aDestroyRunnable can be null. If it's not, it will be dispatched after + // the PushBlobRunnable::Run(). + PushBlobRunnable(Session* aSession, Runnable* aDestroyRunnable) : Runnable("dom::MediaRecorder::Session::PushBlobRunnable") , mSession(aSession) + , mDestroyRunnable(aDestroyRunnable) { } NS_IMETHOD Run() override @@ -213,21 +219,72 @@ class MediaRecorder::Session: public PrincipalChangeObserver, LOG(LogLevel::Debug, ("Session.PushBlobRunnable s=(%p)", mSession.get())); MOZ_ASSERT(NS_IsMainThread()); + mSession->GetBlobWhenReady(this); + return NS_OK; + } + + void + BlobStoreCompleted(MutableBlobStorage* aBlobStorage, Blob* aBlob, + nsresult aRv) override + { RefPtr recorder = mSession->mRecorder; if (!recorder) { - return NS_OK; + return; } - nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession->GetEncodedData()); + if (NS_FAILED(aRv)) { + recorder->NotifyError(aRv); + return; + } + + nsresult rv = recorder->CreateAndDispatchBlobEvent(aBlob); if (NS_FAILED(rv)) { - recorder->NotifyError(rv); + recorder->NotifyError(aRv); + } + + if (mDestroyRunnable && + NS_FAILED(NS_DispatchToMainThread(mDestroyRunnable.forget()))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread failed"); + } + } + + private: + ~PushBlobRunnable() = default; + + RefPtr mSession; + + // The generation of the blob is async. In order to avoid dispatching the + // DestroyRunnable before pushing the blob event, we store the runnable + // here. + RefPtr mDestroyRunnable; + }; + + class StoreEncodedBufferRunnable final : public Runnable + { + RefPtr mSession; + nsTArray> mBuffer; + + public: + StoreEncodedBufferRunnable(Session* aSession, + nsTArray>&& aBuffer) + : Runnable("StoreEncodedBufferRunnable") + , mSession(aSession) + , mBuffer(Move(aBuffer)) + {} + + NS_IMETHOD + Run() override + { + mSession->MaybeCreateMutableBlobStorage(); + for (uint32_t i = 0; i < mBuffer.Length(); i++) { + if (!mBuffer[i].IsEmpty()) { + mSession->mMutableBlobStorage->Append(mBuffer[i].Elements(), + mBuffer[i].Length()); + } } return NS_OK; } - - private: - RefPtr mSession; }; // Notify encoder error, run in main thread task. (Bug 1095381) @@ -433,9 +490,8 @@ public: { MOZ_ASSERT(NS_IsMainThread()); - uint32_t maxMem = Preferences::GetUint("media.recorder.max_memory", - MAX_ALLOW_MEMORY_BUFFER); - mEncodedBufferCache = new EncodedBufferCache(maxMem); + mMaxMemory = Preferences::GetUint("media.recorder.max_memory", + MAX_ALLOW_MEMORY_BUFFER); mLastBlobTimeStamp = TimeStamp::Now(); } @@ -551,7 +607,7 @@ public: LOG(LogLevel::Debug, ("Session.RequestData")); MOZ_ASSERT(NS_IsMainThread()); - if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) { + if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, nullptr)))) { MOZ_ASSERT(false, "RequestData NS_DispatchToMainThread failed"); return NS_ERROR_FAILURE; } @@ -559,19 +615,35 @@ public: return NS_OK; } - already_AddRefed GetEncodedData() + void + MaybeCreateMutableBlobStorage() + { + if (!mMutableBlobStorage) { + mMutableBlobStorage = + new MutableBlobStorage(MutableBlobStorage::eCouldBeInTemporaryFile, + nullptr, mMaxMemory); + } + } + + void + GetBlobWhenReady(MutableBlobStorageCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); - return mEncodedBufferCache->ExtractBlob(mRecorder->GetParentObject(), - mMimeType); + + MaybeCreateMutableBlobStorage(); + mMutableBlobStorage->GetBlobWhenReady(mRecorder->GetParentObject(), + NS_ConvertUTF16toUTF8(mMimeType), + aCallback); + mMutableBlobStorage = nullptr; } RefPtr SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { MOZ_ASSERT(NS_IsMainThread()); - size_t encodedBufferSize = - mEncodedBufferCache->SizeOfExcludingThis(aMallocSizeOf); + size_t encodedBufferSize = mMutableBlobStorage + ? mMutableBlobStorage->SizeOfCurrentMemoryBuffer() + : 0; if (!mEncoder) { return SizeOfPromise::CreateAndResolve(encodedBufferSize, __func__); @@ -593,11 +665,11 @@ private: MOZ_ASSERT(mShutdownPromise); LOG(LogLevel::Debug, ("Session.~Session (%p)", this)); } - // Pull encoded media data from MediaEncoder and put into EncodedBufferCache. + // Pull encoded media data from MediaEncoder and put into MutableBlobStorage. // Destroy this session object in the end of this function. // If the bool aForceFlush is true, we will force to dispatch a // PushBlobRunnable to main thread. - void Extract(bool aForceFlush) + void Extract(bool aForceFlush, Runnable* aDestroyRunnable) { MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); @@ -615,11 +687,8 @@ private: } // Append pulled data into cache buffer. - for (uint32_t i = 0; i < encodedBuf.Length(); i++) { - if (!encodedBuf[i].IsEmpty()) { - mEncodedBufferCache->AppendBuffer(encodedBuf[i]); - } - } + NS_DispatchToMainThread(new StoreEncodedBufferRunnable(this, + Move(encodedBuf))); // Whether push encoded data back to onDataAvailable automatically or we // need a flush. @@ -630,11 +699,15 @@ private: pushBlob = true; } if (pushBlob) { - if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) { + if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, aDestroyRunnable)))) { MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed"); } else { mLastBlobTimeStamp = TimeStamp::Now(); } + } else if (aDestroyRunnable) { + if (NS_FAILED(NS_DispatchToMainThread(aDestroyRunnable))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed"); + } } } @@ -894,15 +967,20 @@ private: &MediaRecorder::NotifyError, rv)); } + + RefPtr destroyRunnable = new DestroyRunnable(this); + if (rv != NS_ERROR_DOM_SECURITY_ERR) { // Don't push a blob if there was a security error. - if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) { + if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, destroyRunnable)))) { MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed"); } + } else { + if (NS_FAILED(NS_DispatchToMainThread(destroyRunnable))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed"); + } } - if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(this)))) { - MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed"); - } + mNeedSessionEndTask = false; } @@ -919,11 +997,8 @@ private: } // Append pulled data into cache buffer. - for (uint32_t i = 0; i < encodedBuf.Length(); i++) { - if (!encodedBuf[i].IsEmpty()) { - mEncodedBufferCache->AppendBuffer(encodedBuf[i]); - } - } + NS_DispatchToMainThread(new StoreEncodedBufferRunnable(this, + Move(encodedBuf))); } void MediaEncoderDataAvailable() @@ -936,7 +1011,7 @@ private: mIsStartEventFired = true; } - Extract(false); + Extract(false, nullptr); } void MediaEncoderError() @@ -953,14 +1028,11 @@ private: MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); MOZ_ASSERT(mEncoder->IsShutdown()); - // Forces the last blob even if it's not time for it yet. - Extract(true); + // For the stop event. Let's the creation of the blob to dispatch this runnable. + RefPtr destroyRunnable = new DestroyRunnable(this); - // For the stop event. - if (NS_FAILED(NS_DispatchToMainThread( - new DestroyRunnable(this)))) { - MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed"); - } + // Forces the last blob even if it's not time for it yet. + Extract(true, destroyRunnable); // Clean up. mEncoderListener->Forget(); @@ -1065,15 +1137,17 @@ private: // Set in Shutdown() and resolved when shutdown is complete. RefPtr mShutdownPromise; // A buffer to cache encoded media data. - nsAutoPtr mEncodedBufferCache; + RefPtr mMutableBlobStorage; + // Max memory to use for the MutableBlobStorage. + uint64_t mMaxMemory; // Current session mimeType nsString mMimeType; // Timestamp of the last fired dataavailable event. TimeStamp mLastBlobTimeStamp; - // The interval of passing encoded data from EncodedBufferCache to onDataAvailable - // handler. "mTimeSlice < 0" means Session object does not push encoded data to - // onDataAvailable, instead, it passive wait the client side pull encoded data - // by calling requestData API. + // The interval of passing encoded data from MutableBlobStorage to + // onDataAvailable handler. "mTimeSlice < 0" means Session object does not + // push encoded data to onDataAvailable, instead, it passive wait the client + // side pull encoded data by calling requestData API. const int32_t mTimeSlice; // Indicate this session's stop has been called. bool mStopIssued; @@ -1085,6 +1159,8 @@ private: bool mNeedSessionEndTask; }; +NS_IMPL_ISUPPORTS_INHERITED0(MediaRecorder::Session::PushBlobRunnable, Runnable) + MediaRecorder::~MediaRecorder() { LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this)); @@ -1448,16 +1524,14 @@ MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) } nsresult -MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed&& aBlob) +MediaRecorder::CreateAndDispatchBlobEvent(Blob* aBlob) { MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); BlobEventInit init; init.mBubbles = false; init.mCancelable = false; - - nsCOMPtr blob = aBlob; - init.mData = static_cast(blob.get()); + init.mData = aBlob; RefPtr event = BlobEvent::Constructor(this, diff --git a/dom/media/MediaRecorder.h b/dom/media/MediaRecorder.h index afa786523ebc..f6fad146a002 100644 --- a/dom/media/MediaRecorder.h +++ b/dom/media/MediaRecorder.h @@ -26,16 +26,17 @@ class GlobalObject; namespace dom { class AudioNode; +class Blob; class DOMException; /** * Implementation of https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html * The MediaRecorder accepts a mediaStream as input source passed from UA. When recorder starts, * a MediaEncoder will be created and accept the mediaStream as input source. - * Encoder will get the raw data by track data changes, encode it by selected MIME Type, then store the encoded in EncodedBufferCache object. + * Encoder will get the raw data by track data changes, encode it by selected MIME Type, then store the encoded in a MutableBlobStorage object. * The encoded data will be extracted on every timeslice passed from Start function call or by RequestData function. * Thread model: - * When the recorder starts, it creates a "Media Encoder" thread to read data from MediaEncoder object and store buffer in EncodedBufferCache object. + * When the recorder starts, it creates a "Media Encoder" thread to read data from MediaEncoder object and store buffer in MutableBlobStorage object. * Also extract the encoded data and create blobs on every timeslice passed from start function or RequestData function called by UA. */ @@ -72,7 +73,7 @@ public: void Pause(ErrorResult& aResult); // Resume a paused recording. void Resume(ErrorResult& aResult); - // Extract encoded data Blob from EncodedBufferCache. + // Extract encoded data Blob from MutableBlobStorage. void RequestData(ErrorResult& aResult); // Return the The DOMMediaStream passed from UA. DOMMediaStream* Stream() const { return mDOMStream; } @@ -121,7 +122,7 @@ protected: MediaRecorder& operator = (const MediaRecorder& x) = delete; // Create dataavailable event with Blob data and it runs in main thread - nsresult CreateAndDispatchBlobEvent(already_AddRefed&& aBlob); + nsresult CreateAndDispatchBlobEvent(Blob* aBlob); // Creating a simple event to notify UA simple event. void DispatchSimpleEvent(const nsAString & aStr); // Creating a error event with message. diff --git a/dom/media/moz.build b/dom/media/moz.build index b0d735289bc6..ebe54eb96afe 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -104,7 +104,6 @@ EXPORTS += [ 'DecoderDoctorDiagnostics.h', 'DecoderTraits.h', 'DOMMediaStream.h', - 'EncodedBufferCache.h', 'FileBlockCache.h', 'FrameStatistics.h', 'Intervals.h', @@ -218,7 +217,6 @@ UNIFIED_SOURCES += [ 'CubebUtils.cpp', 'DecoderDoctorDiagnostics.cpp', 'DOMMediaStream.cpp', - 'EncodedBufferCache.cpp', 'FileBlockCache.cpp', 'FileMediaResource.cpp', 'GetUserMediaRequest.cpp',