From 42582d10da2ab984bee711af77b7864ab5f2b339 Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Tue, 16 Jun 2015 20:35:00 -0400 Subject: [PATCH] Bug 1175656 - Implement generation of recording posters in Gecko. r=dhylands,bz --- dom/camera/CameraControlImpl.cpp | 13 ++ dom/camera/CameraControlImpl.h | 5 + dom/camera/CameraControlListener.h | 7 + dom/camera/DOMCameraControl.cpp | 142 +++++++++++++++++++-- dom/camera/DOMCameraControl.h | 4 + dom/camera/DOMCameraControlListener.cpp | 24 ++++ dom/camera/DOMCameraControlListener.h | 1 + dom/camera/GonkCameraControl.cpp | 163 +++++++++++++++++++++++- dom/camera/GonkCameraControl.h | 7 + dom/camera/ICameraControl.h | 1 + dom/camera/moz.build | 1 + dom/webidl/CameraControl.webidl | 6 + 12 files changed, 356 insertions(+), 18 deletions(-) diff --git a/dom/camera/CameraControlImpl.cpp b/dom/camera/CameraControlImpl.cpp index a603189825a..b05eaf19e1d 100644 --- a/dom/camera/CameraControlImpl.cpp +++ b/dom/camera/CameraControlImpl.cpp @@ -177,6 +177,19 @@ CameraControlImpl::OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, } } +void +CameraControlImpl::OnPoster(dom::BlobImpl* aBlobImpl) +{ + // This callback can run on threads other than the Main Thread and + // the Camera Thread. + RwLockAutoEnterRead lock(mListenerLock); + + for (uint32_t i = 0; i < mListeners.Length(); ++i) { + CameraControlListener* l = mListeners[i]; + l->OnPoster(aBlobImpl); + } +} + void CameraControlImpl::OnShutter() { diff --git a/dom/camera/CameraControlImpl.h b/dom/camera/CameraControlImpl.h index 2a6fc1d358d..371c5a346c1 100644 --- a/dom/camera/CameraControlImpl.h +++ b/dom/camera/CameraControlImpl.h @@ -20,6 +20,10 @@ namespace mozilla { +namespace dom { + class BlobImpl; +} + namespace layers { class Image; } @@ -57,6 +61,7 @@ protected: void OnAutoFocusComplete(bool aAutoFocusSucceeded); void OnFacesDetected(const nsTArray& aFaces); void OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType); + void OnPoster(dom::BlobImpl* aBlobImpl); void OnRateLimitPreview(bool aLimit); bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight); diff --git a/dom/camera/CameraControlListener.h b/dom/camera/CameraControlListener.h index e489c1181da..bd066f962f2 100644 --- a/dom/camera/CameraControlListener.h +++ b/dom/camera/CameraControlListener.h @@ -10,6 +10,10 @@ namespace mozilla { +namespace dom { + class BlobImpl; +} + namespace layers { class Image; } @@ -60,6 +64,8 @@ public: { kRecorderStopped, kRecorderStarted, + kPosterCreated, + kPosterFailed, #ifdef MOZ_B2G_CAMERA kFileSizeLimitReached, kVideoLengthLimitReached, @@ -91,6 +97,7 @@ public: virtual void OnAutoFocusMoving(bool aIsMoving) { } virtual void OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { } virtual void OnFacesDetected(const nsTArray& aFaces) { } + virtual void OnPoster(dom::BlobImpl* aBlobImpl) { } enum UserContext { diff --git a/dom/camera/DOMCameraControl.cpp b/dom/camera/DOMCameraControl.cpp index 8ad8584cbe9..9253777f71b 100755 --- a/dom/camera/DOMCameraControl.cpp +++ b/dom/camera/DOMCameraControl.cpp @@ -82,6 +82,19 @@ nsDOMCameraControl::HasSupport(JSContext* aCx, JSObject* aGlobal) return Navigator::HasCameraSupport(aCx, aGlobal); } +static nsresult +RegisterStorageRequestEvents(DOMRequest* aRequest, nsIDOMEventListener* aListener) +{ + EventListenerManager* elm = aRequest->GetOrCreateListenerManager(); + if (NS_WARN_IF(!elm)) { + return NS_ERROR_UNEXPECTED; + } + + elm->AddEventListener(NS_LITERAL_STRING("success"), aListener, false, false); + elm->AddEventListener(NS_LITERAL_STRING("error"), aListener, false, false); + return NS_OK; +} + class mozilla::StartRecordingHelper : public nsIDOMEventListener { public: @@ -90,6 +103,7 @@ public: explicit StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl) : mDOMCameraControl(aDOMCameraControl) + , mState(false) { MOZ_COUNT_CTOR(StartRecordingHelper); } @@ -98,10 +112,12 @@ protected: virtual ~StartRecordingHelper() { MOZ_COUNT_DTOR(StartRecordingHelper); + mDOMCameraControl->OnCreatedFileDescriptor(mState); } protected: nsRefPtr mDOMCameraControl; + bool mState; }; NS_IMETHODIMP @@ -109,13 +125,50 @@ StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent) { nsString eventType; aEvent->GetType(eventType); - - mDOMCameraControl->OnCreatedFileDescriptor(eventType.EqualsLiteral("success")); + mState = eventType.EqualsLiteral("success"); return NS_OK; } NS_IMPL_ISUPPORTS(mozilla::StartRecordingHelper, nsIDOMEventListener) +class mozilla::RecorderPosterHelper : public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + explicit RecorderPosterHelper(nsDOMCameraControl* aDOMCameraControl) + : mDOMCameraControl(aDOMCameraControl) + , mState(CameraControlListener::kPosterFailed) + { + MOZ_COUNT_CTOR(RecorderPosterHelper); + } + +protected: + virtual ~RecorderPosterHelper() + { + MOZ_COUNT_DTOR(RecorderPosterHelper); + mDOMCameraControl->OnRecorderStateChange(mState, 0, 0); + } + +protected: + nsRefPtr mDOMCameraControl; + CameraControlListener::RecorderState mState; +}; + +NS_IMETHODIMP +RecorderPosterHelper::HandleEvent(nsIDOMEvent* aEvent) +{ + nsString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("success")) { + mState = CameraControlListener::kPosterCreated; + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(mozilla::RecorderPosterHelper, nsIDOMEventListener) + nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration() : CameraConfiguration() , mMaxFocusAreas(0) @@ -202,6 +255,7 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, , mWindow(aWindow) , mPreviewState(CameraControlListener::kPreviewStopped) , mRecording(false) + , mRecordingStoppedDeferred(false) , mSetInitialConfig(false) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); @@ -743,7 +797,18 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions, return nullptr; } - if (mStartRecordingPromise || mRecording) { + // Must supply both the poster path and storage area or neither + if (aOptions.mPosterFilepath.IsEmpty() == + static_cast(aOptions.mPosterStorageArea.get())) { + promise->MaybeReject(NS_ERROR_ILLEGAL_VALUE); + return promise.forget(); + } + + // If we are trying to start recording, already recording or are still + // waiting for a poster to be created/fail, we need to wait + if (mStartRecordingPromise || mRecording || + mRecordingStoppedDeferred || + !mOptions.mPosterFilepath.IsEmpty()) { promise->MaybeReject(NS_ERROR_IN_PROGRESS); return promise.forget(); } @@ -762,20 +827,16 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions, return nullptr; } - mStartRecordingPromise = promise; - mOptions = aOptions; - - EventListenerManager* elm = request->GetOrCreateListenerManager(); - if (!elm) { + nsCOMPtr listener = new StartRecordingHelper(this); + aRv = RegisterStorageRequestEvents(request, listener); + if (aRv.Failed()) { NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown")); - aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } + mStartRecordingPromise = promise; + mOptions = aOptions; mRecording = true; - nsCOMPtr listener = new StartRecordingHelper(this); - elm->AddEventListener(NS_LITERAL_STRING("success"), listener, false, false); - elm->AddEventListener(NS_LITERAL_STRING("error"), listener, false, false); return promise.forget(); } @@ -790,6 +851,8 @@ nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded) // Race condition where StopRecording comes in before we issue // the start recording request to Gonk rv = NS_ERROR_ABORT; + mOptions.mPosterFilepath.Truncate(); + mOptions.mPosterStorageArea = nullptr; } else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) { ICameraControl::StartRecordingOptions o; @@ -797,6 +860,7 @@ nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded) o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes; o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs; o.autoEnableLowLightTorch = mOptions.mAutoEnableLowLightTorch; + o.createPoster = !mOptions.mPosterFilepath.IsEmpty(); rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o); if (NS_SUCCEEDED(rv)) { return; @@ -1254,6 +1318,38 @@ nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aSt DispatchPreviewStateEvent(aState); } +void +nsDOMCameraControl::OnPoster(BlobImpl* aPoster) +{ + DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mOptions.mPosterFilepath.IsEmpty()); + + // Destructor will trigger an error notification if any step fails + nsRefPtr listener = new RecorderPosterHelper(this); + if (NS_WARN_IF(!aPoster)) { + return; + } + + nsRefPtr blob = Blob::Create(GetParentObject(), aPoster); + if (NS_WARN_IF(!blob)) { + return; + } + + if (NS_WARN_IF(!mOptions.mPosterStorageArea)) { + return; + } + + ErrorResult rv; + nsRefPtr request = + mOptions.mPosterStorageArea->AddNamed(blob, mOptions.mPosterFilepath, rv); + if (NS_WARN_IF(rv.Failed())) { + return; + } + + RegisterStorageRequestEvents(request, listener); +} + void nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aArg, int32_t aTrackNum) @@ -1278,10 +1374,27 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a break; case CameraControlListener::kRecorderStopped: + if (!mOptions.mPosterFilepath.IsEmpty()) { + mRecordingStoppedDeferred = true; + return; + } + NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown")); state = NS_LITERAL_STRING("Stopped"); break; + case CameraControlListener::kPosterCreated: + state = NS_LITERAL_STRING("PosterCreated"); + mOptions.mPosterFilepath.Truncate(); + mOptions.mPosterStorageArea = nullptr; + break; + + case CameraControlListener::kPosterFailed: + state = NS_LITERAL_STRING("PosterFailed"); + mOptions.mPosterFilepath.Truncate(); + mOptions.mPosterStorageArea = nullptr; + break; + #ifdef MOZ_B2G_CAMERA case CameraControlListener::kFileSizeLimitReached: state = NS_LITERAL_STRING("FileSizeLimitReached"); @@ -1314,6 +1427,11 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a } DispatchStateEvent(NS_LITERAL_STRING("recorderstatechange"), state); + + if (mRecordingStoppedDeferred && mOptions.mPosterFilepath.IsEmpty()) { + mRecordingStoppedDeferred = false; + OnRecorderStateChange(CameraControlListener::kRecorderStopped, 0, 0); + } } void diff --git a/dom/camera/DOMCameraControl.h b/dom/camera/DOMCameraControl.h index e4ab14c7186..c70a6d06f0b 100644 --- a/dom/camera/DOMCameraControl.h +++ b/dom/camera/DOMCameraControl.h @@ -39,6 +39,7 @@ namespace dom { } class ErrorResult; class StartRecordingHelper; +class RecorderPosterHelper; #define NS_DOM_CAMERA_CONTROL_CID \ { 0x3700c096, 0xf920, 0x438d, \ @@ -163,6 +164,7 @@ protected: friend class DOMCameraControlListener; friend class mozilla::StartRecordingHelper; + friend class mozilla::RecorderPosterHelper; void OnCreatedFileDescriptor(bool aSucceeded); @@ -170,6 +172,7 @@ protected: void OnAutoFocusMoving(bool aIsMoving); void OnTakePictureComplete(nsIDOMBlob* aPicture); void OnFacesDetected(const nsTArray& aFaces); + void OnPoster(dom::BlobImpl* aPoster); void OnGetCameraComplete(); void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState, nsresult aReason); @@ -225,6 +228,7 @@ protected: nsRefPtr mDSFileDescriptor; DOMCameraControlListener::PreviewState mPreviewState; bool mRecording; + bool mRecordingStoppedDeferred; bool mSetInitialConfig; #ifdef MOZ_WIDGET_GONK diff --git a/dom/camera/DOMCameraControlListener.cpp b/dom/camera/DOMCameraControlListener.cpp index 1e106e4bbee..684b6b76965 100644 --- a/dom/camera/DOMCameraControlListener.cpp +++ b/dom/camera/DOMCameraControlListener.cpp @@ -410,3 +410,27 @@ DOMCameraControlListener::OnUserError(UserContext aContext, nsresult aError) NS_DispatchToMainThread(new Callback(mDOMCameraControl, aContext, aError)); } + +void +DOMCameraControlListener::OnPoster(BlobImpl* aBlobImpl) +{ + class Callback : public DOMCallback + { + public: + Callback(nsMainThreadPtrHandle aDOMCameraControl, BlobImpl* aBlobImpl) + : DOMCallback(aDOMCameraControl) + , mBlobImpl(aBlobImpl) + { } + + void + RunCallback(nsDOMCameraControl* aDOMCameraControl) override + { + aDOMCameraControl->OnPoster(mBlobImpl); + } + + protected: + nsRefPtr mBlobImpl; + }; + + NS_DispatchToMainThread(new Callback(mDOMCameraControl, aBlobImpl)); +} diff --git a/dom/camera/DOMCameraControlListener.h b/dom/camera/DOMCameraControlListener.h index c1a1be8120e..0684b416fcf 100644 --- a/dom/camera/DOMCameraControlListener.h +++ b/dom/camera/DOMCameraControlListener.h @@ -31,6 +31,7 @@ public: virtual void OnRateLimitPreview(bool aLimit) override; virtual bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) override; virtual void OnUserError(UserContext aContext, nsresult aError) override; + virtual void OnPoster(dom::BlobImpl* aBlobImpl) override; protected: virtual ~DOMCameraControlListener(); diff --git a/dom/camera/GonkCameraControl.cpp b/dom/camera/GonkCameraControl.cpp index 7b31e0ab3a5..0d6fd365237 100644 --- a/dom/camera/GonkCameraControl.cpp +++ b/dom/camera/GonkCameraControl.cpp @@ -27,7 +27,12 @@ #include #include #include "GrallocImages.h" +#include "imgIEncoder.h" +#include "libyuv.h" +#include "nsNetUtil.h" // for NS_ReadInputStreamToBuffer #endif +#include "nsNetCID.h" // for NS_STREAMTRANSPORTSERVICE_CONTRACTID +#include "nsAutoPtr.h" // for nsAutoArrayPtr #include "nsCOMPtr.h" #include "nsMemory.h" #include "nsThread.h" @@ -38,7 +43,6 @@ #include "mozilla/ipc/FileDescriptorUtils.h" #include "nsAlgorithm.h" #include "nsPrintfCString.h" -#include "AutoRwLock.h" #include "GonkCameraHwMgr.h" #include "GonkRecorderProfiles.h" #include "CameraCommon.h" @@ -46,6 +50,7 @@ #include "DeviceStorageFileDescriptor.h" using namespace mozilla; +using namespace mozilla::dom; using namespace mozilla::layers; using namespace mozilla::gfx; using namespace mozilla::ipc; @@ -79,6 +84,7 @@ nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId) #endif , mRecorderMonitor("GonkCameraControl::mRecorder.Monitor") , mVideoFile(nullptr) + , mCapturePoster(false) , mAutoFocusPending(false) , mAutoFocusCompleteExpired(0) , mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor") @@ -1252,6 +1258,7 @@ nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescri #endif OnRecorderStateChange(CameraControlListener::kRecorderStarted); + mCapturePoster = aOptions->createPoster; return NS_OK; } @@ -1296,6 +1303,9 @@ nsGonkCameraControl::StopRecordingImpl() return NS_OK; } #endif + if (mCapturePoster.exchange(false)) { + OnPoster(nullptr, 0); + } OnRecorderStateChange(CameraControlListener::kRecorderStopped); { @@ -2018,11 +2028,13 @@ nsGonkCameraControl::SetupRecording(int aFd, int aRotation, NS_ERROR_INVALID_ARG); // adjust rotation by camera sensor offset - int r = aRotation; - r += mCameraHw->GetSensorOrientation(); - r = RationalizeRotation(r); - DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n", r, aRotation); - snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", r); + mVideoRotation = aRotation; + mVideoRotation += mCameraHw->GetSensorOrientation(); + mVideoRotation = RationalizeRotation(mVideoRotation); + DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n", + mVideoRotation, aRotation); + snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", + mVideoRotation); CHECK_SETARG_RETURN(mRecorder->setParameters(String8(buffer)), NS_ERROR_INVALID_ARG); @@ -2165,6 +2177,137 @@ nsGonkCameraControl::OnRateLimitPreview(bool aLimit) CameraControlImpl::OnRateLimitPreview(aLimit); } +void +nsGonkCameraControl::CreatePoster(Image* aImage, uint32_t aWidth, uint32_t aHeight, int32_t aRotation) +{ + class PosterRunnable : public nsRunnable { + public: + PosterRunnable(nsGonkCameraControl* aTarget, Image* aImage, + uint32_t aWidth, uint32_t aHeight, int32_t aRotation) + : mTarget(aTarget) + , mImage(aImage) + , mWidth(aWidth) + , mHeight(aHeight) + , mRotation(aRotation) + , mDst(nullptr) + , mDstLength(0) + { } + + virtual ~PosterRunnable() + { + mTarget->OnPoster(mDst, mDstLength); + } + + NS_IMETHODIMP Run() override + { +#ifdef MOZ_WIDGET_GONK + // NV21 (yuv420sp) is 12 bits / pixel + size_t srcLength = (mWidth * mHeight * 3 + 1) / 2; + + // ARGB is 32 bits / pixel + size_t tmpLength = mWidth * mHeight * sizeof(uint32_t); + nsAutoArrayPtr tmp; + tmp = new uint8_t[tmpLength]; + + GrallocImage* nativeImage = static_cast(mImage.get()); + android::sp graphicBuffer = nativeImage->GetGraphicBuffer(); + + void* graphicSrc = nullptr; + graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &graphicSrc); + + uint32_t stride = mWidth * 4; + int err = libyuv::ConvertToARGB(static_cast(graphicSrc), + srcLength, tmp, stride, 0, 0, + mWidth, mHeight, mWidth, mHeight, + libyuv::kRotate0, libyuv::FOURCC_NV21); + + graphicBuffer->unlock(); + graphicSrc = nullptr; + graphicBuffer.clear(); + nativeImage = nullptr; + mImage = nullptr; + + if (NS_WARN_IF(err < 0)) { + DOM_CAMERA_LOGE("CreatePoster: to ARGB failed (%d)\n", err); + return NS_OK; + } + + nsCOMPtr encoder = + do_CreateInstance("@mozilla.org/image/encoder;2?type=image/jpeg"); + if (NS_WARN_IF(!encoder)) { + DOM_CAMERA_LOGE("CreatePoster: no JPEG encoder\n"); + return NS_OK; + } + + nsString opt; + nsresult rv = encoder->InitFromData(tmp, tmpLength, mWidth, + mHeight, stride, + imgIEncoder::INPUT_FORMAT_HOSTARGB, + opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + DOM_CAMERA_LOGE("CreatePoster: encoder init failed (0x%x)\n", + rv); + return NS_OK; + } + + nsCOMPtr stream = do_QueryInterface(encoder); + if (NS_WARN_IF(!stream)) { + DOM_CAMERA_LOGE("CreatePoster: to input stream failed\n"); + return NS_OK; + } + + uint64_t length = 0; + rv = stream->Available(&length); + if (NS_WARN_IF(NS_FAILED(rv))) { + DOM_CAMERA_LOGE("CreatePoster: get length failed (0x%x)\n", + rv); + return NS_OK; + } + + rv = NS_ReadInputStreamToBuffer(stream, &mDst, length); + if (NS_WARN_IF(NS_FAILED(rv))) { + DOM_CAMERA_LOGE("CreatePoster: read failed (0x%x)\n", rv); + mDst = nullptr; + return NS_OK; + } + + mDstLength = length; +#endif + return NS_OK; + } + + private: + nsRefPtr mTarget; + nsRefPtr mImage; + int32_t mWidth; + int32_t mHeight; + int32_t mRotation; + void* mDst; + size_t mDstLength; + }; + + nsCOMPtr event = new PosterRunnable(this, aImage, + aWidth, + aHeight, + aRotation); + + nsCOMPtr target + = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + target->Dispatch(event, NS_DISPATCH_NORMAL); +} + +void +nsGonkCameraControl::OnPoster(void* aData, uint32_t aLength) +{ + nsRefPtr blobImpl; + if (aData) { + blobImpl = new BlobImplMemory(aData, aLength, NS_LITERAL_STRING("image/jpeg")); + } + CameraControlImpl::OnPoster(blobImpl); +} + void nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer) { @@ -2179,6 +2322,14 @@ nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer) mCurrentConfiguration.mPreviewSize.height); videoImage->SetData(data); + if (mCapturePoster.exchange(false)) { + CreatePoster(frame, + mCurrentConfiguration.mPreviewSize.width, + mCurrentConfiguration.mPreviewSize.height, + mVideoRotation); + return; + } + OnNewPreviewFrame(frame, mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height); #endif diff --git a/dom/camera/GonkCameraControl.h b/dom/camera/GonkCameraControl.h index 809b5864e7f..2715bfcf09a 100644 --- a/dom/camera/GonkCameraControl.h +++ b/dom/camera/GonkCameraControl.h @@ -47,6 +47,7 @@ namespace mozilla { namespace layers { class TextureClient; class ImageContainer; + class Image; } class nsGonkCameraControl : public CameraControlImpl @@ -60,6 +61,7 @@ public: void OnTakePictureComplete(uint8_t* aData, uint32_t aLength); void OnTakePictureError(); void OnRateLimitPreview(bool aLimit); + void OnPoster(void* aData, uint32_t aLength); void OnNewPreviewFrame(layers::TextureClient* aBuffer); #ifdef MOZ_WIDGET_GONK void OnRecorderEvent(int msg, int ext1, int ext2); @@ -149,6 +151,8 @@ protected: nsresult PausePreview(); nsresult GetSupportedSize(const Size& aSize, const nsTArray& supportedSizes, Size& best); + void CreatePoster(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight, int32_t aRotation); + nsresult LoadRecorderProfiles(); static PLDHashOperator Enumerate(const nsAString& aProfileName, RecorderProfile* aProfile, @@ -198,6 +202,9 @@ protected: nsRefPtr mVideoFile; nsString mFileFormat; + Atomic mCapturePoster; + int32_t mVideoRotation; + bool mAutoFocusPending; nsCOMPtr mAutoFocusCompleteTimer; int32_t mAutoFocusCompleteExpired; diff --git a/dom/camera/ICameraControl.h b/dom/camera/ICameraControl.h index c3c9fdfe81f..1b8d70b28e4 100644 --- a/dom/camera/ICameraControl.h +++ b/dom/camera/ICameraControl.h @@ -138,6 +138,7 @@ public: uint64_t maxFileSizeBytes; uint64_t maxVideoLengthMs; bool autoEnableLowLightTorch; + bool createPoster; }; struct Configuration { diff --git a/dom/camera/moz.build b/dom/camera/moz.build index e2648cf21ee..87725412697 100644 --- a/dom/camera/moz.build +++ b/dom/camera/moz.build @@ -65,6 +65,7 @@ FAIL_ON_WARNINGS = True LOCAL_INCLUDES += [ '../base', + '/media/libyuv/include', ] include('/ipc/chromium/chromium-config.mozbuild') diff --git a/dom/webidl/CameraControl.webidl b/dom/webidl/CameraControl.webidl index 772c9af6d48..b06591770c1 100644 --- a/dom/webidl/CameraControl.webidl +++ b/dom/webidl/CameraControl.webidl @@ -109,6 +109,12 @@ dictionary CameraStartRecordingOptions will be left as-is on stopRecording(). If the camera does not support this setting, it will be ignored. */ boolean autoEnableLowLightTorch = false; + + /* If given, a poster JPG will be created from the recording and saved + at the given path. PosterCreated or PosterFailed recording state + changes will indicate whether or not it was created. */ + DOMString posterFilepath = ""; + DeviceStorage? posterStorageArea = null; }; /*