mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Bug 1161913 - Part 2. Request canvas to push out its next drawn frame instead of pulling it. r=mt
--HG-- extra : transplant_source : %FF%3D%E7%BE%A1%EA%BE%5E%00w%07%084%D8%27D%CFp%EB%D6
This commit is contained in:
parent
37ac1b6da7
commit
3f1d386795
@ -10,6 +10,7 @@
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "Layers.h"
|
||||
#include "MediaSegment.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
@ -35,6 +36,7 @@
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsMathUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "ActiveLayerTracker.h"
|
||||
#include "WebGL1Context.h"
|
||||
@ -48,6 +50,136 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class RequestedFrameRefreshObserver : public nsARefreshObserver
|
||||
{
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestedFrameRefreshObserver, override)
|
||||
|
||||
public:
|
||||
RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
|
||||
nsRefreshDriver* aRefreshDriver)
|
||||
: mRegistered(false),
|
||||
mOwningElement(aOwningElement),
|
||||
mRefreshDriver(aRefreshDriver)
|
||||
{
|
||||
MOZ_ASSERT(mOwningElement);
|
||||
}
|
||||
|
||||
static already_AddRefed<DataSourceSurface>
|
||||
CopySurface(const RefPtr<SourceSurface>& aSurface)
|
||||
{
|
||||
RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
|
||||
if (!data) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
|
||||
if (!read.IsMapped()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<DataSourceSurface> copy =
|
||||
Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
|
||||
data->GetFormat(),
|
||||
read.GetStride());
|
||||
if (!copy) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
|
||||
if (!write.IsMapped()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(read.GetStride() == write.GetStride());
|
||||
MOZ_ASSERT(data->GetSize() == copy->GetSize());
|
||||
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
|
||||
|
||||
memcpy(write.GetData(), read.GetData(),
|
||||
write.GetStride() * copy->GetSize().height);
|
||||
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
void WillRefresh(TimeStamp aTime) override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mOwningElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOwningElement->IsWriteOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOwningElement->IsContextCleanForFrameCapture()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mOwningElement->IsFrameCaptureRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<SourceSurface> snapshot = mOwningElement->GetSurfaceSnapshot(nullptr);
|
||||
if (!snapshot) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<DataSourceSurface> copy = CopySurface(snapshot);
|
||||
|
||||
mOwningElement->SetFrameCapture(copy.forget());
|
||||
mOwningElement->MarkContextCleanForFrameCapture();
|
||||
}
|
||||
|
||||
void DetachFromRefreshDriver()
|
||||
{
|
||||
MOZ_ASSERT(mOwningElement);
|
||||
MOZ_ASSERT(mRefreshDriver);
|
||||
|
||||
Unregister();
|
||||
mRefreshDriver = nullptr;
|
||||
}
|
||||
|
||||
void Register()
|
||||
{
|
||||
if (mRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mRefreshDriver);
|
||||
if (mRefreshDriver) {
|
||||
mRefreshDriver->AddRefreshObserver(this, Flush_Display);
|
||||
mRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Unregister()
|
||||
{
|
||||
if (!mRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mRefreshDriver);
|
||||
if (mRefreshDriver) {
|
||||
mRefreshDriver->RemoveRefreshObserver(this, Flush_Display);
|
||||
mRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~RequestedFrameRefreshObserver()
|
||||
{
|
||||
MOZ_ASSERT(!mRefreshDriver);
|
||||
MOZ_ASSERT(!mRegistered);
|
||||
}
|
||||
|
||||
bool mRegistered;
|
||||
HTMLCanvasElement* const mOwningElement;
|
||||
RefPtr<nsRefreshDriver> mRefreshDriver;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas,
|
||||
mContext, mCallback)
|
||||
|
||||
@ -116,6 +248,9 @@ HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& a
|
||||
HTMLCanvasElement::~HTMLCanvasElement()
|
||||
{
|
||||
ResetPrintCallback();
|
||||
if (mRequestedFrameRefreshObserver) {
|
||||
mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
|
||||
@ -419,14 +554,6 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mCurrentContextType != CanvasContextType::Canvas2D) {
|
||||
WebGLContext* gl = static_cast<WebGLContext*>(mCurrentContext.get());
|
||||
if (!gl->IsPreservingDrawingBuffer()) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<CanvasCaptureMediaStream> stream =
|
||||
CanvasCaptureMediaStream::CreateSourceStream(window, this);
|
||||
if (!stream) {
|
||||
@ -443,6 +570,9 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO);
|
||||
RegisterFrameCaptureListener(stream->FrameCaptureListener());
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
@ -1047,6 +1177,85 @@ HTMLCanvasElement::IsContextCleanForFrameCapture()
|
||||
return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
|
||||
{
|
||||
WeakPtr<FrameCaptureListener> listener = aListener;
|
||||
|
||||
if (mRequestedFrameListeners.Contains(listener)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mRequestedFrameListeners.AppendElement(listener);
|
||||
|
||||
if (!mRequestedFrameRefreshObserver) {
|
||||
nsIDocument* doc = OwnerDoc();
|
||||
MOZ_RELEASE_ASSERT(doc);
|
||||
|
||||
nsIPresShell* shell = doc->GetShell();
|
||||
MOZ_RELEASE_ASSERT(shell);
|
||||
|
||||
nsPresContext* context = shell->GetPresContext();
|
||||
MOZ_RELEASE_ASSERT(context);
|
||||
|
||||
context = context->GetRootPresContext();
|
||||
MOZ_RELEASE_ASSERT(context);
|
||||
|
||||
nsRefreshDriver* driver = context->RefreshDriver();
|
||||
MOZ_RELEASE_ASSERT(driver);
|
||||
|
||||
mRequestedFrameRefreshObserver =
|
||||
new RequestedFrameRefreshObserver(this, driver);
|
||||
}
|
||||
|
||||
mRequestedFrameRefreshObserver->Register();
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLCanvasElement::IsFrameCaptureRequested() const
|
||||
{
|
||||
for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
|
||||
if (!listener) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (listener->FrameCaptureRequested()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
|
||||
{
|
||||
RefPtr<SourceSurface> surface = aSurface;
|
||||
|
||||
CairoImage::Data imageData;
|
||||
imageData.mSize = surface->GetSize();
|
||||
imageData.mSourceSurface = surface;
|
||||
|
||||
nsRefPtr<CairoImage> image = new CairoImage();
|
||||
image->SetData(imageData);
|
||||
|
||||
// Loop backwards to allow removing elements in the loop.
|
||||
for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
|
||||
WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i];
|
||||
if (!listener) {
|
||||
// listener was destroyed. Remove it from the list.
|
||||
mRequestedFrameListeners.RemoveElementAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
nsRefPtr<Image> imageRefCopy = image.get();
|
||||
listener->NewFrame(imageRefCopy.forget());
|
||||
}
|
||||
|
||||
if (mRequestedFrameListeners.IsEmpty()) {
|
||||
mRequestedFrameRefreshObserver->Unregister();
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<SourceSurface>
|
||||
HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define mozilla_dom_HTMLCanvasElement_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "nsIDOMHTMLCanvasElement.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsGkAtoms.h"
|
||||
@ -22,6 +23,7 @@ namespace mozilla {
|
||||
|
||||
namespace layers {
|
||||
class CanvasLayer;
|
||||
class Image;
|
||||
class LayerManager;
|
||||
} // namespace layers
|
||||
namespace gfx {
|
||||
@ -34,6 +36,7 @@ class File;
|
||||
class FileCallback;
|
||||
class HTMLCanvasPrintState;
|
||||
class PrintCallback;
|
||||
class RequestedFrameRefreshObserver;
|
||||
|
||||
enum class CanvasContextType : uint8_t {
|
||||
NoContext,
|
||||
@ -42,6 +45,44 @@ enum class CanvasContextType : uint8_t {
|
||||
WebGL2
|
||||
};
|
||||
|
||||
/*
|
||||
* FrameCaptureListener is used by captureStream() as a way of getting video
|
||||
* frames from the canvas. On a refresh driver tick after something has been
|
||||
* drawn to the canvas since the last such tick, all registered
|
||||
* FrameCaptureListeners whose `mFrameCaptureRequested` equals `true`,
|
||||
* will be given a copy of the just-painted canvas.
|
||||
* All FrameCaptureListeners get the same copy.
|
||||
*/
|
||||
class FrameCaptureListener : public SupportsWeakPtr<FrameCaptureListener>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(FrameCaptureListener)
|
||||
|
||||
FrameCaptureListener()
|
||||
: mFrameCaptureRequested(false) {}
|
||||
|
||||
/*
|
||||
* Called when a frame capture is desired on next paint.
|
||||
*/
|
||||
void RequestFrameCapture() { mFrameCaptureRequested = true; }
|
||||
|
||||
/*
|
||||
* Indicates to the canvas whether or not this listener has requested a frame.
|
||||
*/
|
||||
bool FrameCaptureRequested() const { return mFrameCaptureRequested; }
|
||||
|
||||
/*
|
||||
* Interface through which new video frames will be provided while
|
||||
* `mFrameCaptureRequested` is `true`.
|
||||
*/
|
||||
virtual void NewFrame(already_AddRefed<layers::Image> aImage) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~FrameCaptureListener() {}
|
||||
|
||||
bool mFrameCaptureRequested;
|
||||
};
|
||||
|
||||
class HTMLCanvasElement final : public nsGenericHTMLElement,
|
||||
public nsIDOMHTMLCanvasElement
|
||||
{
|
||||
@ -171,6 +212,29 @@ public:
|
||||
|
||||
virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
|
||||
|
||||
/*
|
||||
* Register a FrameCaptureListener with this canvas.
|
||||
* The canvas hooks into the RefreshDriver while there are
|
||||
* FrameCaptureListeners registered.
|
||||
* The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
|
||||
* caller's responsibility to keep them alive. Once a registered
|
||||
* FrameCaptureListener is destroyed it will be automatically deregistered.
|
||||
*/
|
||||
void RegisterFrameCaptureListener(FrameCaptureListener* aListener);
|
||||
|
||||
/*
|
||||
* Returns true when there is at least one registered FrameCaptureListener
|
||||
* that has requested a frame capture.
|
||||
*/
|
||||
bool IsFrameCaptureRequested() const;
|
||||
|
||||
/*
|
||||
* Called by the RefreshDriver hook when a frame has been captured.
|
||||
* Makes a copy of the provided surface and hands it to all
|
||||
* FrameCaptureListeners having requested frame capture.
|
||||
*/
|
||||
void SetFrameCapture(already_AddRefed<gfx::SourceSurface> aSurface);
|
||||
|
||||
virtual bool ParseAttribute(int32_t aNamespaceID,
|
||||
nsIAtom* aAttribute,
|
||||
const nsAString& aValue,
|
||||
@ -253,6 +317,8 @@ protected:
|
||||
nsRefPtr<PrintCallback> mPrintCallback;
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
|
||||
nsRefPtr<HTMLCanvasPrintState> mPrintState;
|
||||
nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
|
||||
nsRefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
|
||||
|
||||
public:
|
||||
// Record whether this canvas should be write-only or not.
|
||||
|
@ -9,9 +9,8 @@
|
||||
#include "ImageContainer.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "mozilla/dom/CanvasCaptureMediaStreamBinding.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
using namespace mozilla::layers;
|
||||
@ -24,31 +23,44 @@ class OutputStreamDriver::StreamListener : public MediaStreamListener
|
||||
{
|
||||
public:
|
||||
explicit StreamListener(OutputStreamDriver* aDriver,
|
||||
TrackID aTrackId,
|
||||
SourceMediaStream* aSourceStream)
|
||||
: mSourceStream(aSourceStream)
|
||||
, mMutex("CanvasCaptureMediaStream::OSD::StreamListener")
|
||||
, mDriver(aDriver)
|
||||
: mEnded(false)
|
||||
, mSourceStream(aSourceStream)
|
||||
, mTrackId(aTrackId)
|
||||
, mMutex("CanvasCaptureMediaStream OutputStreamDriver::StreamListener")
|
||||
, mImage(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(mDriver);
|
||||
MOZ_ASSERT(mSourceStream);
|
||||
}
|
||||
|
||||
void Forget() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
void EndStream() {
|
||||
mEnded = true;
|
||||
}
|
||||
|
||||
void SetImage(const nsRefPtr<layers::Image>& aImage)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mDriver = nullptr;
|
||||
mImage = aImage;
|
||||
}
|
||||
|
||||
virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
|
||||
{
|
||||
// Called on the MediaStreamGraph thread.
|
||||
StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
|
||||
if (delta > 0) {
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mSourceStream);
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mDriver) {
|
||||
mDriver->NotifyPull(aDesiredTime);
|
||||
} else {
|
||||
// The DOM stream is dead, let's end it
|
||||
nsRefPtr<Image> image = mImage;
|
||||
IntSize size = image ? image->GetSize() : IntSize(0, 0);
|
||||
VideoSegment segment;
|
||||
segment.AppendFrame(image.forget(), delta, size);
|
||||
|
||||
mSourceStream->AppendToTrack(mTrackId, &segment);
|
||||
}
|
||||
|
||||
if (mEnded) {
|
||||
mSourceStream->EndAllTrackAndFinish();
|
||||
}
|
||||
}
|
||||
@ -57,224 +69,62 @@ protected:
|
||||
~StreamListener() { }
|
||||
|
||||
private:
|
||||
nsRefPtr<SourceMediaStream> mSourceStream;
|
||||
Atomic<bool> mEnded;
|
||||
const nsRefPtr<SourceMediaStream> mSourceStream;
|
||||
const TrackID mTrackId;
|
||||
|
||||
// The below members are protected by mMutex.
|
||||
Mutex mMutex;
|
||||
// This is a raw pointer to avoid a reference cycle with OutputStreamDriver.
|
||||
// Accessed on main and MediaStreamGraph threads, set on main thread.
|
||||
OutputStreamDriver* mDriver;
|
||||
// The below members are protected by mMutex.
|
||||
nsRefPtr<layers::Image> mImage;
|
||||
};
|
||||
|
||||
OutputStreamDriver::OutputStreamDriver(CanvasCaptureMediaStream* aDOMStream,
|
||||
OutputStreamDriver::OutputStreamDriver(SourceMediaStream* aSourceStream,
|
||||
const TrackID& aTrackId)
|
||||
: mDOMStream(aDOMStream)
|
||||
, mSourceStream(nullptr)
|
||||
, mStarted(false)
|
||||
, mStreamListener(nullptr)
|
||||
, mTrackId(aTrackId)
|
||||
, mMutex("CanvasCaptureMediaStream::OutputStreamDriver")
|
||||
, mImage(nullptr)
|
||||
: FrameCaptureListener()
|
||||
, mSourceStream(aSourceStream)
|
||||
, mStreamListener(new StreamListener(this, aTrackId, aSourceStream))
|
||||
{
|
||||
MOZ_ASSERT(mDOMStream);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mSourceStream);
|
||||
mSourceStream->AddListener(mStreamListener);
|
||||
mSourceStream->AddTrack(aTrackId, 0, new VideoSegment());
|
||||
mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
|
||||
mSourceStream->SetPullEnabled(true);
|
||||
|
||||
// All CanvasCaptureMediaStreams shall at least get one frame.
|
||||
mFrameCaptureRequested = true;
|
||||
}
|
||||
|
||||
OutputStreamDriver::~OutputStreamDriver()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mStreamListener) {
|
||||
// MediaStreamGraph will keep the listener alive until it can finish the
|
||||
// stream on the next NotifyPull().
|
||||
mStreamListener->Forget();
|
||||
mStreamListener->EndStream();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
OutputStreamDriver::Start()
|
||||
{
|
||||
if (mStarted) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mDOMStream);
|
||||
|
||||
mDOMStream->CreateDOMTrack(mTrackId, MediaSegment::VIDEO);
|
||||
|
||||
mSourceStream = mDOMStream->GetStream()->AsSourceStream();
|
||||
MOZ_ASSERT(mSourceStream);
|
||||
|
||||
mStreamListener = new StreamListener(this, mSourceStream);
|
||||
mSourceStream->AddListener(mStreamListener);
|
||||
mSourceStream->AddTrack(mTrackId, 0, new VideoSegment());
|
||||
mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
|
||||
mSourceStream->SetPullEnabled(true);
|
||||
|
||||
// Run StartInternal() in stable state to allow it to directly capture a frame
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &OutputStreamDriver::StartInternal);
|
||||
nsContentUtils::RunInStableState(runnable.forget());
|
||||
|
||||
mStarted = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
OutputStreamDriver::ForgetDOMStream()
|
||||
OutputStreamDriver::SetImage(const nsRefPtr<layers::Image>& aImage)
|
||||
{
|
||||
if (mStreamListener) {
|
||||
mStreamListener->Forget();
|
||||
mStreamListener->SetImage(aImage);
|
||||
}
|
||||
mDOMStream = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
OutputStreamDriver::AppendToTrack(StreamTime aDuration)
|
||||
{
|
||||
MOZ_ASSERT(mSourceStream);
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsRefPtr<Image> image = mImage;
|
||||
IntSize size = image ? image->GetSize() : IntSize(0, 0);
|
||||
VideoSegment segment;
|
||||
segment.AppendFrame(image.forget(), aDuration, size);
|
||||
|
||||
mSourceStream->AppendToTrack(mTrackId, &segment);
|
||||
}
|
||||
|
||||
void
|
||||
OutputStreamDriver::NotifyPull(StreamTime aDesiredTime)
|
||||
{
|
||||
StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
|
||||
if (delta > 0) {
|
||||
// nullptr images are allowed
|
||||
AppendToTrack(delta);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OutputStreamDriver::SetImage(Image* aImage)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mImage = aImage;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class TimerDriver : public OutputStreamDriver
|
||||
, public nsITimerCallback
|
||||
{
|
||||
public:
|
||||
explicit TimerDriver(CanvasCaptureMediaStream* aDOMStream,
|
||||
explicit TimerDriver(SourceMediaStream* aSourceStream,
|
||||
const double& aFPS,
|
||||
const TrackID& aTrackId)
|
||||
: OutputStreamDriver(aDOMStream, aTrackId)
|
||||
: OutputStreamDriver(aSourceStream, aTrackId)
|
||||
, mFPS(aFPS)
|
||||
, mTimer(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ForgetDOMStream() override
|
||||
{
|
||||
if (mTimer) {
|
||||
mTimer->Cancel();
|
||||
mTimer = nullptr;
|
||||
}
|
||||
OutputStreamDriver::ForgetDOMStream();
|
||||
}
|
||||
|
||||
nsresult
|
||||
TakeSnapshot()
|
||||
{
|
||||
// mDOMStream can't be killed while we're on main thread
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(DOMStream());
|
||||
|
||||
if (!DOMStream()->Canvas()) {
|
||||
// DOMStream's canvas pointer was garbage collected. We can abort now.
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
MOZ_ASSERT(DOMStream()->Canvas());
|
||||
|
||||
if (DOMStream()->Canvas()->IsWriteOnly()) {
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
// Pass `nullptr` to force alpha-premult.
|
||||
RefPtr<SourceSurface> snapshot = DOMStream()->Canvas()->GetSurfaceSnapshot(nullptr);
|
||||
if (!snapshot) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
|
||||
if (!data) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<DataSourceSurface> copy;
|
||||
|
||||
{
|
||||
DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
|
||||
if (!read.IsMapped()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
copy = Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
|
||||
data->GetFormat(),
|
||||
read.GetStride());
|
||||
if (!copy) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
|
||||
if (!write.IsMapped()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(read.GetStride() == write.GetStride());
|
||||
MOZ_ASSERT(data->GetSize() == copy->GetSize());
|
||||
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
|
||||
|
||||
memcpy(write.GetData(), read.GetData(),
|
||||
write.GetStride() * copy->GetSize().height);
|
||||
}
|
||||
|
||||
CairoImage::Data imageData;
|
||||
imageData.mSize = copy->GetSize();
|
||||
imageData.mSourceSurface = copy;
|
||||
|
||||
RefPtr<CairoImage> image = new layers::CairoImage();
|
||||
image->SetData(imageData);
|
||||
|
||||
SetImage(image);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Notify(nsITimer* aTimer) override
|
||||
{
|
||||
nsresult rv = TakeSnapshot();
|
||||
if (NS_FAILED(rv)) {
|
||||
aTimer->Cancel();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
virtual void RequestFrame() override
|
||||
{
|
||||
TakeSnapshot();
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
protected:
|
||||
virtual ~TimerDriver() {}
|
||||
|
||||
virtual void StartInternal() override
|
||||
{
|
||||
// Always capture at least one frame.
|
||||
DebugOnly<nsresult> rv = TakeSnapshot();
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
if (mFPS == 0.0) {
|
||||
return;
|
||||
}
|
||||
@ -283,18 +133,45 @@ protected:
|
||||
if (!mTimer) {
|
||||
return;
|
||||
}
|
||||
mTimer->InitWithCallback(this, int(1000 / mFPS), nsITimer::TYPE_REPEATING_SLACK);
|
||||
mTimer->InitWithFuncCallback(&TimerTick, this, int(1000 / mFPS), nsITimer::TYPE_REPEATING_SLACK);
|
||||
}
|
||||
|
||||
static void TimerTick(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aClosure);
|
||||
TimerDriver* driver = static_cast<TimerDriver*>(aClosure);
|
||||
|
||||
driver->RequestFrameCapture();
|
||||
}
|
||||
|
||||
void NewFrame(already_AddRefed<Image> aImage) override
|
||||
{
|
||||
nsRefPtr<Image> image = aImage;
|
||||
|
||||
if (!mFrameCaptureRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFrameCaptureRequested = false;
|
||||
SetImage(image.forget());
|
||||
}
|
||||
|
||||
void Forget() override
|
||||
{
|
||||
if (mTimer) {
|
||||
mTimer->Cancel();
|
||||
mTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~TimerDriver() {}
|
||||
|
||||
private:
|
||||
const double mFPS;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
};
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(TimerDriver, OutputStreamDriver)
|
||||
NS_IMPL_RELEASE_INHERITED(TimerDriver, OutputStreamDriver)
|
||||
NS_IMPL_QUERY_INTERFACE(TimerDriver, nsITimerCallback)
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream, DOMMediaStream,
|
||||
@ -315,7 +192,7 @@ CanvasCaptureMediaStream::CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas)
|
||||
CanvasCaptureMediaStream::~CanvasCaptureMediaStream()
|
||||
{
|
||||
if (mOutputStreamDriver) {
|
||||
mOutputStreamDriver->ForgetDOMStream();
|
||||
mOutputStreamDriver->Forget();
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,8 +205,9 @@ CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGive
|
||||
void
|
||||
CanvasCaptureMediaStream::RequestFrame()
|
||||
{
|
||||
MOZ_ASSERT(mOutputStreamDriver);
|
||||
if (mOutputStreamDriver) {
|
||||
mOutputStreamDriver->RequestFrame();
|
||||
mOutputStreamDriver->RequestFrameCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,15 +218,16 @@ CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
|
||||
if (!aFPS.WasPassed()) {
|
||||
// TODO (Bug 1152298): Implement a real AutoDriver.
|
||||
// We use a 30FPS TimerDriver for now.
|
||||
mOutputStreamDriver = new TimerDriver(this, 30.0, aTrackId);
|
||||
mOutputStreamDriver = new TimerDriver(GetStream()->AsSourceStream(), 30.0, aTrackId);
|
||||
} else if (aFPS.Value() < 0) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// Cap frame rate to 60 FPS for sanity
|
||||
double fps = std::min(60.0, aFPS.Value());
|
||||
mOutputStreamDriver = new TimerDriver(this, fps, aTrackId);
|
||||
mOutputStreamDriver =
|
||||
new TimerDriver(GetStream()->AsSourceStream(), fps, aTrackId);
|
||||
}
|
||||
return mOutputStreamDriver->Start();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<CanvasCaptureMediaStream>
|
||||
@ -363,6 +242,12 @@ CanvasCaptureMediaStream::CreateSourceStream(nsIDOMWindow* aWindow,
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
FrameCaptureListener*
|
||||
CanvasCaptureMediaStream::FrameCaptureListener()
|
||||
{
|
||||
return mOutputStreamDriver;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define mozilla_dom_CanvasCaptureMediaStream_h_
|
||||
|
||||
#include "DOMMediaStream.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "StreamBuffer.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -21,60 +22,78 @@ class Image;
|
||||
namespace dom {
|
||||
class CanvasCaptureMediaStream;
|
||||
class HTMLCanvasElement;
|
||||
class OutputStreamFrameListener;
|
||||
|
||||
class OutputStreamDriver
|
||||
/*
|
||||
* The CanvasCaptureMediaStream is a MediaStream subclass that provides a video
|
||||
* track containing frames from a canvas. See an architectural overview below.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* === Main Thread === __________________________
|
||||
* | |
|
||||
* | CanvasCaptureMediaStream |
|
||||
* |__________________________|
|
||||
* |
|
||||
* | RequestFrame()
|
||||
* v
|
||||
* ________________________
|
||||
* ________ FrameCaptureRequested? | |
|
||||
* | | ------------------------> | OutputStreamDriver |
|
||||
* | Canvas | SetFrameCapture() | (FrameCaptureListener) |
|
||||
* |________| ------------------------> |________________________|
|
||||
* |
|
||||
* | SetImage()
|
||||
* v
|
||||
* ___________________
|
||||
* | StreamListener |
|
||||
* ---------------------------------------| (All image access |----------------
|
||||
* === MediaStreamGraph Thread === | Mutex Guarded) |
|
||||
* |___________________|
|
||||
* ^ |
|
||||
* NotifyPull() | | AppendToTrack()
|
||||
* | v
|
||||
* ___________________________
|
||||
* | |
|
||||
* | MSG / SourceMediaStream |
|
||||
* |___________________________|
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Base class for drivers of the output stream.
|
||||
* It is up to each sub class to implement the NewFrame() callback of
|
||||
* FrameCaptureListener.
|
||||
*/
|
||||
class OutputStreamDriver : public FrameCaptureListener
|
||||
{
|
||||
public:
|
||||
OutputStreamDriver(CanvasCaptureMediaStream* aDOMStream,
|
||||
OutputStreamDriver(SourceMediaStream* aSourceStream,
|
||||
const TrackID& aTrackId);
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OutputStreamDriver);
|
||||
|
||||
nsresult Start();
|
||||
|
||||
virtual void ForgetDOMStream();
|
||||
|
||||
virtual void RequestFrame() { }
|
||||
|
||||
CanvasCaptureMediaStream* DOMStream() const { return mDOMStream; }
|
||||
|
||||
protected:
|
||||
virtual ~OutputStreamDriver();
|
||||
class StreamListener;
|
||||
|
||||
/*
|
||||
* Appends mImage to video track for the desired duration.
|
||||
*/
|
||||
void AppendToTrack(StreamTime aDuration);
|
||||
void NotifyPull(StreamTime aDesiredTime);
|
||||
|
||||
/*
|
||||
* Sub classes can SetImage() to update the image being appended to the
|
||||
* output stream. It will be appended on the next NotifyPull from MSG.
|
||||
*/
|
||||
void SetImage(layers::Image* aImage);
|
||||
void SetImage(const nsRefPtr<layers::Image>& aImage);
|
||||
|
||||
/*
|
||||
* Called in main thread stable state to initialize sub classes.
|
||||
* Makes sure any internal resources this driver is holding that may create
|
||||
* reference cycles are released.
|
||||
*/
|
||||
virtual void StartInternal() = 0;
|
||||
virtual void Forget() {}
|
||||
|
||||
protected:
|
||||
virtual ~OutputStreamDriver();
|
||||
class StreamListener;
|
||||
|
||||
private:
|
||||
// This is a raw pointer to avoid a reference cycle between OutputStreamDriver
|
||||
// and CanvasCaptureMediaStream. ForgetDOMStream() will be called by
|
||||
// ~CanvasCaptureMediaStream() to make sure we don't do anything illegal.
|
||||
CanvasCaptureMediaStream* mDOMStream;
|
||||
nsRefPtr<SourceMediaStream> mSourceStream;
|
||||
bool mStarted;
|
||||
nsRefPtr<StreamListener> mStreamListener;
|
||||
const TrackID mTrackId;
|
||||
|
||||
// The below members are protected by mMutex.
|
||||
Mutex mMutex;
|
||||
nsRefPtr<layers::Image> mImage;
|
||||
};
|
||||
|
||||
class CanvasCaptureMediaStream: public DOMMediaStream
|
||||
class CanvasCaptureMediaStream : public DOMMediaStream
|
||||
{
|
||||
public:
|
||||
explicit CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas);
|
||||
@ -89,6 +108,7 @@ public:
|
||||
// WebIDL
|
||||
HTMLCanvasElement* Canvas() const { return mCanvas; }
|
||||
void RequestFrame();
|
||||
dom::FrameCaptureListener* FrameCaptureListener();
|
||||
|
||||
/**
|
||||
* Create a CanvasCaptureMediaStream whose underlying stream is a SourceMediaStream.
|
||||
|
Loading…
Reference in New Issue
Block a user