diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index e1e25402bc7..4a0cdd2d8b0 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -165,6 +165,7 @@ #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif +@BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_alarm.xpt diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index bf417b0ff03..b74c323d3a2 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -174,6 +174,7 @@ #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif +@BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_alarm.xpt diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index 7b713036a6e..5e0ad3919e1 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -273,6 +273,7 @@ MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@ MOZ_B2G_RIL = @MOZ_B2G_RIL@ MOZ_B2G_BT = @MOZ_B2G_BT@ +MOZ_B2G_CAMERA = @MOZ_B2G_CAMERA@ MOZ_SYS_MSG = @MOZ_SYS_MSG@ diff --git a/configure.in b/configure.in index c190251a20e..5214ac23ac5 100644 --- a/configure.in +++ b/configure.in @@ -192,7 +192,7 @@ if test -n "$gonkdir" ; then ;; esac - CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice" + CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice -I$gonkdir/frameworks/base/services/camera" CFLAGS="-mandroid -fno-short-enums -fno-exceptions $CFLAGS" CXXFLAGS="-mandroid -fno-short-enums -fno-exceptions $CXXFLAGS $STLPORT_CPPFLAGS" LIBS="$LIBS $STLPORT_LIBS" @@ -7351,6 +7351,18 @@ if test -n "$MOZ_SYS_MSG"; then fi AC_SUBST(MOZ_SYS_MSG) +dnl ======================================================== +dnl = Enable Camera Interface for B2G (Gonk usually) +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(b2g-camera, +[ --enable-b2g-camera Set compile flags necessary for compiling camera API for B2G ], + MOZ_B2G_CAMERA=1, + MOZ_B2G_CAMERA= ) +if test -n "$MOZ_B2G_CAMERA"; then + AC_DEFINE(MOZ_B2G_CAMERA) +fi +AC_SUBST(MOZ_B2G_CAMERA) + dnl ======================================================== dnl = Support for demangling undefined symbols dnl ======================================================== diff --git a/dom/Makefile.in b/dom/Makefile.in index 9247ef3225e..3d98a8f8ff3 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -70,6 +70,7 @@ DIRS += \ ipc \ identity \ workers \ + camera \ $(NULL) ifdef MOZ_B2G_RIL diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 2565b9f29a0..cae943bd6c1 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -50,6 +50,8 @@ #include "nsIDOMBluetoothManager.h" #include "BluetoothManager.h" #endif +#include "nsIDOMCameraManager.h" +#include "DOMCameraManager.h" #include "nsIDOMGlobalPropertyInitializer.h" @@ -112,6 +114,7 @@ NS_INTERFACE_MAP_BEGIN(Navigator) #ifdef MOZ_B2G_BT NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorBluetooth) #endif + NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorCamera) NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorSystemMessages) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Navigator) NS_INTERFACE_MAP_END @@ -181,6 +184,8 @@ Navigator::Invalidate() } #endif + mCameraManager = nullptr; + #ifdef MOZ_SYS_MSG if (mMessagesManager) { mMessagesManager = nullptr; @@ -1320,6 +1325,30 @@ Navigator::MozSetMessageHandler(const nsAString& aType, #endif } +//***************************************************************************** +// nsNavigator::nsIDOMNavigatorCamera +//***************************************************************************** + +NS_IMETHODIMP +Navigator::GetMozCameras(nsIDOMCameraManager** aCameraManager) +{ + if (!mCameraManager) { + nsCOMPtr win = do_QueryReferent(mWindow); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + if (!win->GetOuterWindow() || win->GetOuterWindow()->GetCurrentInnerWindow() != win) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCameraManager = nsDOMCameraManager::Create(win->WindowID()); + } + + nsRefPtr cameraManager = mCameraManager; + cameraManager.forget(aCameraManager); + + return NS_OK; +} + size_t Navigator::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { @@ -1344,12 +1373,19 @@ Navigator::SetWindow(nsPIDOMWindow *aInnerWindow) void Navigator::OnNavigation() { - // Inform MediaManager in case there are live streams or pending callbacks. -#ifdef MOZ_MEDIA_NAVIGATOR - MediaManager *manager = MediaManager::Get(); nsCOMPtr win = do_QueryReferent(mWindow); - return manager->OnNavigation(win->WindowID()); + if (!win) { + return; + } + +#ifdef MOZ_MEDIA_NAVIGATOR + // Inform MediaManager in case there are live streams or pending callbacks. + MediaManager *manager = MediaManager::Get(); + manager->OnNavigation(win->WindowID()); #endif + if (mCameraManager) { + mCameraManager->OnNavigation(win->WindowID()); + } } } // namespace dom diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index cc810bfb891..508d78c8c51 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -41,6 +41,9 @@ class nsIDOMMozVoicemail; #include "nsIDOMNavigatorSystemMessages.h" +#include "nsIDOMNavigatorCamera.h" +#include "DOMCameraManager.h" + //***************************************************************************** // Navigator: Script "navigator" object //***************************************************************************** @@ -82,6 +85,7 @@ class Navigator : public nsIDOMNavigator #ifdef MOZ_B2G_BT , public nsIDOMNavigatorBluetooth #endif + , public nsIDOMNavigatorCamera , public nsIDOMNavigatorSystemMessages { public: @@ -134,6 +138,7 @@ public: // Helper to initialize mMessagesManager. nsresult EnsureMessagesManager(); #endif + NS_DECL_NSIDOMNAVIGATORCAMERA private: bool IsSmsAllowed() const; @@ -155,6 +160,7 @@ private: #ifdef MOZ_B2G_BT nsCOMPtr mBluetooth; #endif + nsRefPtr mCameraManager; nsCOMPtr mMessagesManager; nsWeakPtr mWindow; }; diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 63c01c3f13c..a4fabe7fe4b 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -522,6 +522,10 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #include "mozilla/dom/Activity.h" +#include "DOMCameraManager.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" + #include "DOMError.h" #include "DOMRequest.h" #include "nsIOpenWindowEventDetail.h" @@ -1668,6 +1672,13 @@ static nsDOMClassInfoData sClassInfoData[] = { DOM_DEFAULT_SCRIPTABLE_FLAGS) #endif + NS_DEFINE_CLASSINFO_DATA(CameraManager, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(CameraControl, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) @@ -2466,6 +2477,7 @@ nsDOMClassInfo::Init() #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorBluetooth) #endif + DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorCamera) DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorSystemMessages) DOM_CLASSINFO_MAP_END @@ -4457,6 +4469,18 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_END #endif + DOM_CLASSINFO_MAP_BEGIN(CameraManager, nsIDOMCameraManager) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMCameraManager) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(CameraControl, nsICameraControl) + DOM_CLASSINFO_MAP_ENTRY(nsICameraControl) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities) + DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities) + DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(DOMError, nsIDOMDOMError) DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMError) DOM_CLASSINFO_MAP_END diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 78bf3fb6942..f416c57f321 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -526,6 +526,10 @@ DOMCI_CLASS(BluetoothDevice) DOMCI_CLASS(BluetoothDeviceEvent) #endif +DOMCI_CLASS(CameraManager) +DOMCI_CLASS(CameraControl) +DOMCI_CLASS(CameraCapabilities) + DOMCI_CLASS(DOMError) DOMCI_CLASS(DOMRequest) DOMCI_CLASS(OpenWindowEventDetail) diff --git a/dom/camera/CameraCapabilities.h b/dom/camera/CameraCapabilities.h new file mode 100644 index 00000000000..c2e529065e8 --- /dev/null +++ b/dom/camera/CameraCapabilities.h @@ -0,0 +1,44 @@ +/* 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 DOM_CAMERA_NSCAMERACAPABILITIES_H +#define DOM_CAMERA_NSCAMERACAPABILITIES_H + +#include "CameraControl.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +typedef nsresult (*ParseItemAndAddFunc)(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd); + +class nsCameraCapabilities : public nsICameraCapabilities +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAMERACAPABILITIES + + nsCameraCapabilities(nsCameraControl* aCamera); + + nsresult ParameterListToNewArray( + JSContext* cx, + JSObject** aArray, + PRUint32 aKey, + ParseItemAndAddFunc aParseItemAndAdd + ); + nsresult StringListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey); + nsresult DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey); + +private: + nsCameraCapabilities(const nsCameraCapabilities&) MOZ_DELETE; + nsCameraCapabilities& operator=(const nsCameraCapabilities&) MOZ_DELETE; + +protected: + /* additional members */ + ~nsCameraCapabilities(); + nsCOMPtr mCamera; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_NSCAMERACAPABILITIES_H diff --git a/dom/camera/CameraCommon.h b/dom/camera/CameraCommon.h new file mode 100644 index 00000000000..c9493d0911e --- /dev/null +++ b/dom/camera/CameraCommon.h @@ -0,0 +1,68 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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 DOM_CAMERA_CAMERACOMMON_H +#define DOM_CAMERA_CAMERACOMMON_H + +#ifndef __func__ +#ifdef __FUNCTION__ +#define __func__ __FUNCTION__ +#else +#define __func__ __FILE__ +#endif +#endif + +#ifndef NAN +#define NAN std::numeric_limits::quiet_NaN() +#endif + +#include "nsThreadUtils.h" +#include "nsIDOMCameraManager.h" + +#define DOM_CAMERA_LOG( l, ... ) \ + do { \ + if ( DOM_CAMERA_LOG_LEVEL >= (l) ) { \ + printf_stderr (__VA_ARGS__); \ + } \ + } while (0) + +#define DOM_CAMERA_LOGA( ... ) DOM_CAMERA_LOG( 0, __VA_ARGS__ ) + +enum { + DOM_CAMERA_LOG_NOTHING, + DOM_CAMERA_LOG_ERROR, + DOM_CAMERA_LOG_WARNING, + DOM_CAMERA_LOG_INFO +}; + +#define DOM_CAMERA_LOGI( ... ) DOM_CAMERA_LOG( DOM_CAMERA_LOG_INFO, __VA_ARGS__ ) +#define DOM_CAMERA_LOGW( ... ) DOM_CAMERA_LOG( DOM_CAMERA_LOG_WARNING, __VA_ARGS__ ) +#define DOM_CAMERA_LOGE( ... ) DOM_CAMERA_LOG( DOM_CAMERA_LOG_ERROR, __VA_ARGS__ ) + +class CameraErrorResult : public nsRunnable +{ +public: + CameraErrorResult(nsICameraErrorCallback* onError, const nsString& aErrorMsg) + : mOnErrorCb(onError) + , mErrorMsg(aErrorMsg) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnErrorCb) { + mOnErrorCb->HandleEvent(mErrorMsg); + } + return NS_OK; + } + +protected: + nsCOMPtr mOnErrorCb; + const nsString mErrorMsg; +}; + +#endif // DOM_CAMERA_CAMERACOMMON_H diff --git a/dom/camera/CameraControl.cpp b/dom/camera/CameraControl.cpp new file mode 100644 index 00000000000..02a09dd1f82 --- /dev/null +++ b/dom/camera/CameraControl.cpp @@ -0,0 +1,486 @@ +/* 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 "nsCOMPtr.h" +#include "nsDOMClassInfo.h" +#include "jsapi.h" +#include "nsThread.h" +#include "DOMCameraManager.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" +#include "CameraControl.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; +using namespace dom; + +DOMCI_DATA(CameraControl, nsICameraControl) + +NS_INTERFACE_MAP_BEGIN(nsCameraControl) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICameraControl) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraControl) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsCameraControl) +NS_IMPL_RELEASE(nsCameraControl) + +// Helpers for string properties. +nsresult +nsCameraControl::SetHelper(PRUint32 aKey, const nsAString& aValue) +{ + SetParameter(aKey, NS_ConvertUTF16toUTF8(aValue).get()); + return NS_OK; +} + +nsresult +nsCameraControl::GetHelper(PRUint32 aKey, nsAString& aValue) +{ + const char* value = GetParameterConstChar(aKey); + if (!value) { + return NS_ERROR_FAILURE; + } + + aValue.AssignASCII(value); + return NS_OK; +} + +// Helpers for doubles. +nsresult +nsCameraControl::SetHelper(PRUint32 aKey, double aValue) +{ + SetParameter(aKey, aValue); + return NS_OK; +} + +nsresult +nsCameraControl::GetHelper(PRUint32 aKey, double* aValue) +{ + MOZ_ASSERT(aValue); + *aValue = GetParameterDouble(aKey); + return NS_OK; +} + +// Helper for weighted regions. +nsresult +nsCameraControl::SetHelper(JSContext* aCx, PRUint32 aKey, const JS::Value& aValue, PRUint32 aLimit) +{ + if (aLimit == 0) { + DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__); + return NS_OK; + } + + if (!aValue.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + uint32_t length = 0; + + JSObject* regions = &aValue.toObject(); + if (!JS_GetArrayLength(aCx, regions, &length)) { + return NS_ERROR_FAILURE; + } + + DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit); + if (length > aLimit) { + length = aLimit; + } + + nsTArray regionArray; + regionArray.SetCapacity(length); + + for (PRUint32 i = 0; i < length; ++i) { + JS::Value v; + + if (!JS_GetElement(aCx, regions, i, &v)) { + return NS_ERROR_FAILURE; + } + + CameraRegion* r = regionArray.AppendElement(); + /** + * These are the default values. We can remove these when the xpidl + * dictionary parser gains the ability to grok default values. + */ + r->top = -1000; + r->left = -1000; + r->bottom = 1000; + r->right = 1000; + r->weight = 1000; + + nsresult rv = r->Init(aCx, &v); + NS_ENSURE_SUCCESS(rv, rv); + + DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%d\n", + i, + r->top, + r->left, + r->bottom, + r->right, + r->weight + ); + } + SetParameter(aKey, regionArray); + return NS_OK; +} + +nsresult +nsCameraControl::GetHelper(JSContext* aCx, PRUint32 aKey, JS::Value* aValue) +{ + nsTArray regionArray; + + GetParameter(aKey, regionArray); + + JSObject* array = JS_NewArrayObject(aCx, 0, nullptr); + if (!array) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PRUint32 length = regionArray.Length(); + DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length); + + for (PRUint32 i = 0; i < length; ++i) { + CameraRegion* r = ®ionArray[i]; + JS::Value v; + + JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DOM_CAMERA_LOGI("top=%d\n", r->top); + v = INT_TO_JSVAL(r->top); + if (!JS_SetProperty(aCx, o, "top", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("left=%d\n", r->left); + v = INT_TO_JSVAL(r->left); + if (!JS_SetProperty(aCx, o, "left", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("bottom=%d\n", r->bottom); + v = INT_TO_JSVAL(r->bottom); + if (!JS_SetProperty(aCx, o, "bottom", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("right=%d\n", r->right); + v = INT_TO_JSVAL(r->right); + if (!JS_SetProperty(aCx, o, "right", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("weight=%d\n", r->weight); + v = INT_TO_JSVAL(r->weight); + if (!JS_SetProperty(aCx, o, "weight", &v)) { + return NS_ERROR_FAILURE; + } + + v = OBJECT_TO_JSVAL(o); + if (!JS_SetElement(aCx, array, i, &v)) { + return NS_ERROR_FAILURE; + } + } + + *aValue = JS::ObjectValue(*array); + return NS_OK; +} + +/* readonly attribute nsICameraCapabilities capabilities; */ +NS_IMETHODIMP +nsCameraControl::GetCapabilities(nsICameraCapabilities** aCapabilities) +{ + if (!mCapabilities) { + mCapabilities = new nsCameraCapabilities(this); + } + + nsCOMPtr capabilities = mCapabilities; + capabilities.forget(aCapabilities); + return NS_OK; +} + +/* attribute DOMString effect; */ +NS_IMETHODIMP +nsCameraControl::GetEffect(nsAString& aEffect) +{ + return GetHelper(CAMERA_PARAM_EFFECT, aEffect); +} +NS_IMETHODIMP +nsCameraControl::SetEffect(const nsAString& aEffect) +{ + return SetHelper(CAMERA_PARAM_EFFECT, aEffect); +} + +/* attribute DOMString whiteBalanceMode; */ +NS_IMETHODIMP +nsCameraControl::GetWhiteBalanceMode(nsAString& aWhiteBalanceMode) +{ + return GetHelper(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); +} +NS_IMETHODIMP +nsCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode) +{ + return SetHelper(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); +} + +/* attribute DOMString sceneMode; */ +NS_IMETHODIMP +nsCameraControl::GetSceneMode(nsAString& aSceneMode) +{ + return GetHelper(CAMERA_PARAM_SCENEMODE, aSceneMode); +} +NS_IMETHODIMP +nsCameraControl::SetSceneMode(const nsAString& aSceneMode) +{ + return SetHelper(CAMERA_PARAM_SCENEMODE, aSceneMode); +} + +/* attribute DOMString flashMode; */ +NS_IMETHODIMP +nsCameraControl::GetFlashMode(nsAString& aFlashMode) +{ + return GetHelper(CAMERA_PARAM_FLASHMODE, aFlashMode); +} +NS_IMETHODIMP +nsCameraControl::SetFlashMode(const nsAString& aFlashMode) +{ + return SetHelper(CAMERA_PARAM_FLASHMODE, aFlashMode); +} + +/* attribute DOMString focusMode; */ +NS_IMETHODIMP +nsCameraControl::GetFocusMode(nsAString& aFocusMode) +{ + return GetHelper(CAMERA_PARAM_FOCUSMODE, aFocusMode); +} +NS_IMETHODIMP +nsCameraControl::SetFocusMode(const nsAString& aFocusMode) +{ + return SetHelper(CAMERA_PARAM_FOCUSMODE, aFocusMode); +} + +/* attribute double zoom; */ +NS_IMETHODIMP +nsCameraControl::GetZoom(double* aZoom) +{ + return GetHelper(CAMERA_PARAM_ZOOM, aZoom); +} +NS_IMETHODIMP +nsCameraControl::SetZoom(double aZoom) +{ + return SetHelper(CAMERA_PARAM_ZOOM, aZoom); +} + +/* attribute jsval meteringAreas; */ +NS_IMETHODIMP +nsCameraControl::GetMeteringAreas(JSContext* cx, JS::Value* aMeteringAreas) +{ + return GetHelper(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas); +} +NS_IMETHODIMP +nsCameraControl::SetMeteringAreas(JSContext* cx, const JS::Value& aMeteringAreas) +{ + return SetHelper(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas, mMaxMeteringAreas); +} + +/* attribute jsval focusAreas; */ +NS_IMETHODIMP +nsCameraControl::GetFocusAreas(JSContext* cx, JS::Value* aFocusAreas) +{ + return GetHelper(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas); +} +NS_IMETHODIMP +nsCameraControl::SetFocusAreas(JSContext* cx, const JS::Value& aFocusAreas) +{ + return SetHelper(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas, mMaxFocusAreas); +} + +/* readonly attribute double focalLength; */ +NS_IMETHODIMP +nsCameraControl::GetFocalLength(double* aFocalLength) +{ + return GetHelper(CAMERA_PARAM_FOCALLENGTH, aFocalLength); +} + +/* readonly attribute double focusDistanceNear; */ +NS_IMETHODIMP +nsCameraControl::GetFocusDistanceNear(double* aFocusDistanceNear) +{ + return GetHelper(CAMERA_PARAM_FOCUSDISTANCENEAR, aFocusDistanceNear); +} + +/* readonly attribute double focusDistanceOptimum; */ +NS_IMETHODIMP +nsCameraControl::GetFocusDistanceOptimum(double* aFocusDistanceOptimum) +{ + return GetHelper(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, aFocusDistanceOptimum); +} + +/* readonly attribute double focusDistanceFar; */ +NS_IMETHODIMP +nsCameraControl::GetFocusDistanceFar(double* aFocusDistanceFar) +{ + return GetHelper(CAMERA_PARAM_FOCUSDISTANCEFAR, aFocusDistanceFar); +} + +/* void setExposureCompensation (const JS::Value& aCompensation, JSContext* cx); */ +NS_IMETHODIMP +nsCameraControl::SetExposureCompensation(const JS::Value& aCompensation, JSContext* cx) +{ + if (aCompensation.isNullOrUndefined()) { + // use NaN to switch the camera back into auto mode + return SetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN); + } + + double compensation; + if (!JS_ValueToNumber(cx, aCompensation, &compensation)) { + return NS_ERROR_INVALID_ARG; + } + + return SetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation); +} + +/* readonly attribute double exposureCompensation; */ +NS_IMETHODIMP +nsCameraControl::GetExposureCompensation(double* aExposureCompensation) +{ + return GetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, aExposureCompensation); +} + +/* attribute nsICameraShutterCallback onShutter; */ +NS_IMETHODIMP +nsCameraControl::GetOnShutter(nsICameraShutterCallback** aOnShutter) +{ + *aOnShutter = mOnShutterCb; + return NS_OK; +} +NS_IMETHODIMP +nsCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter) +{ + mOnShutterCb = aOnShutter; + return NS_OK; +} + +/* void startRecording (in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsCameraControl::StartRecording(const JS::Value& aOptions, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraSize size; + nsresult rv = size.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr startRecordingTask = new StartRecordingTask(this, size, onSuccess, onError); + mCameraThread->Dispatch(startRecordingTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* void stopRecording (); */ +NS_IMETHODIMP +nsCameraControl::StopRecording() +{ + nsCOMPtr stopRecordingTask = new StopRecordingTask(this); + mCameraThread->Dispatch(stopRecordingTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* [implicit_jscontext] void getPreviewStream (in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsCameraControl::GetPreviewStream(const JS::Value& aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraSize size; + nsresult rv = size.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr getPreviewStreamTask = new GetPreviewStreamTask(this, size, onSuccess, onError); + mCameraThread->Dispatch(getPreviewStreamTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* void autoFocus (in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + nsCOMPtr autoFocusTask = new AutoFocusTask(this, onSuccess, onError); + mCameraThread->Dispatch(autoFocusTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* void takePicture (in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP nsCameraControl::TakePicture(const JS::Value& aOptions, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraPictureOptions options; + CameraSize size; + CameraPosition pos; + + nsresult rv = options.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + rv = size.Init(cx, &options.pictureSize); + NS_ENSURE_SUCCESS(rv, rv); + + /** + * Default values, until the dictionary parser can handle them. + * NaN indicates no value provided. + */ + pos.latitude = NAN; + pos.longitude = NAN; + pos.altitude = NAN; + pos.timestamp = NAN; + rv = pos.Init(cx, &options.position); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr takePictureTask = new TakePictureTask(this, size, options.rotation, options.fileFormat, pos, onSuccess, onError); + mCameraThread->Dispatch(takePictureTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +nsCameraControl::AutoFocusComplete(bool aSuccess) +{ + /** + * Auto focusing can change some of the camera's parameters, so + * we need to pull a new set before sending the result to the + * main thread. + */ + PullParametersImpl(nullptr); + + nsCOMPtr autoFocusResult = new AutoFocusResult(aSuccess, mAutoFocusOnSuccessCb); + + nsresult rv = NS_DispatchToMainThread(autoFocusResult); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch autoFocus() onSuccess callback to main thread!"); + } +} + +void +nsCameraControl::TakePictureComplete(PRUint8* aData, PRUint32 aLength) +{ + PRUint8* data = new PRUint8[aLength]; + + memcpy(data, aData, aLength); + + /** + * TODO: pick up the actual specified picture format for the MIME type; + * for now, assume we'll be using JPEGs. + */ + nsIDOMBlob* blob = new nsDOMMemoryFile(static_cast(data), static_cast(aLength), NS_LITERAL_STRING("image/jpeg")); + nsCOMPtr takePictureResult = new TakePictureResult(blob, mTakePictureOnSuccessCb); + + nsresult rv = NS_DispatchToMainThread(takePictureResult); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch takePicture() onSuccess callback to main thread!"); + } +} diff --git a/dom/camera/CameraControl.h b/dom/camera/CameraControl.h new file mode 100644 index 00000000000..8c5f6234bf3 --- /dev/null +++ b/dom/camera/CameraControl.h @@ -0,0 +1,438 @@ +/* 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 DOM_CAMERA_NSCAMERACONTROL_H +#define DOM_CAMERA_NSCAMERACONTROL_H + +#include "prtypes.h" +#include "nsCOMPtr.h" +#include "nsThread.h" +#include "nsDOMFile.h" +#include "DictionaryHelpers.h" +#include "CameraPreview.h" +#include "nsIDOMCameraManager.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +namespace mozilla { + +using namespace dom; + +class GetPreviewStreamTask; +class AutoFocusTask; +class TakePictureTask; +class StartRecordingTask; +class StopRecordingTask; +class SetParameterTask; +class GetParameterTask; +class PushParametersTask; +class PullParametersTask; + +// Main camera control. +class nsCameraControl : public nsICameraControl +{ + friend class GetPreviewStreamTask; + friend class AutoFocusTask; + friend class TakePictureTask; + friend class StartRecordingTask; + friend class StopRecordingTask; + friend class SetParameterTask; + friend class GetParameterTask; + friend class PushParametersTask; + friend class PullParametersTask; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAMERACONTROL + + enum { + CAMERA_PARAM_EFFECT, + CAMERA_PARAM_WHITEBALANCE, + CAMERA_PARAM_SCENEMODE, + CAMERA_PARAM_FLASHMODE, + CAMERA_PARAM_FOCUSMODE, + CAMERA_PARAM_ZOOM, + CAMERA_PARAM_METERINGAREAS, + CAMERA_PARAM_FOCUSAREAS, + CAMERA_PARAM_FOCALLENGTH, + CAMERA_PARAM_FOCUSDISTANCENEAR, + CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, + CAMERA_PARAM_FOCUSDISTANCEFAR, + CAMERA_PARAM_EXPOSURECOMPENSATION, + + CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, + CAMERA_PARAM_SUPPORTED_VIDEOSIZES, + CAMERA_PARAM_SUPPORTED_PICTURESIZES, + CAMERA_PARAM_SUPPORTED_PICTUREFORMATS, + CAMERA_PARAM_SUPPORTED_WHITEBALANCES, + CAMERA_PARAM_SUPPORTED_SCENEMODES, + CAMERA_PARAM_SUPPORTED_EFFECTS, + CAMERA_PARAM_SUPPORTED_FLASHMODES, + CAMERA_PARAM_SUPPORTED_FOCUSMODES, + CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, + CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, + CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, + CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION, + CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, + CAMERA_PARAM_SUPPORTED_ZOOM, + CAMERA_PARAM_SUPPORTED_ZOOMRATIOS + }; + virtual const char* GetParameter(const char* aKey) = 0; + virtual const char* GetParameterConstChar(PRUint32 aKey) = 0; + virtual double GetParameterDouble(PRUint32 aKey) = 0; + virtual void GetParameter(PRUint32 aKey, nsTArray& aRegions) = 0; + virtual void SetParameter(const char* aKey, const char* aValue) = 0; + virtual void SetParameter(PRUint32 aKey, const char* aValue) = 0; + virtual void SetParameter(PRUint32 aKey, double aValue) = 0; + virtual void SetParameter(PRUint32 aKey, const nsTArray& aRegions) = 0; + virtual void PushParameters() = 0; + + nsCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread) + : mCameraId(aCameraId) + , mCameraThread(aCameraThread) + , mCapabilities(nullptr) + , mPreview(nullptr) + , mFileFormat() + , mMaxMeteringAreas(0) + , mMaxFocusAreas(0) + , mAutoFocusOnSuccessCb(nullptr) + , mAutoFocusOnErrorCb(nullptr) + , mTakePictureOnSuccessCb(nullptr) + , mTakePictureOnErrorCb(nullptr) + , mStartRecordingOnSuccessCb(nullptr) + , mStartRecordingOnErrorCb(nullptr) + , mOnShutterCb(nullptr) + { } + + void TakePictureComplete(PRUint8 *aData, PRUint32 aLength); + void AutoFocusComplete(bool aSuccess); + +protected: + virtual ~nsCameraControl() { } + + nsresult SetHelper(PRUint32 aKey, const nsAString& aValue); + nsresult GetHelper(PRUint32 aKey, nsAString& aValue); + nsresult SetHelper(PRUint32 aKey, double aValue); + nsresult GetHelper(PRUint32 aKey, double* aValue); + nsresult SetHelper(JSContext* aCx, PRUint32 aKey, const JS::Value& aValue, PRUint32 aLimit); + nsresult GetHelper(JSContext* aCx, PRUint32 aKey, JS::Value* aValue); + + virtual nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) = 0; + virtual nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus) = 0; + virtual nsresult TakePictureImpl(TakePictureTask* aTakePicture) = 0; + virtual nsresult StartRecordingImpl(StartRecordingTask* aStartRecording) = 0; + virtual nsresult StopRecordingImpl(StopRecordingTask* aStopRecording) = 0; + virtual nsresult PushParametersImpl(PushParametersTask* aPushParameters) = 0; + virtual nsresult PullParametersImpl(PullParametersTask* aPullParameters) = 0; + +private: + nsCameraControl(const nsCameraControl&) MOZ_DELETE; + nsCameraControl& operator=(const nsCameraControl&) MOZ_DELETE; + +protected: + /* additional members */ + PRUint32 mCameraId; + nsCOMPtr mCameraThread; + nsCOMPtr mCapabilities; + PRUint32 mPreviewWidth; + PRUint32 mPreviewHeight; + nsCOMPtr mPreview; + nsString mFileFormat; + PRUint32 mMaxMeteringAreas; + PRUint32 mMaxFocusAreas; + + nsCOMPtr mAutoFocusOnSuccessCb; + nsCOMPtr mAutoFocusOnErrorCb; + nsCOMPtr mTakePictureOnSuccessCb; + nsCOMPtr mTakePictureOnErrorCb; + nsCOMPtr mStartRecordingOnSuccessCb; + nsCOMPtr mStartRecordingOnErrorCb; + nsCOMPtr mOnShutterCb; +}; + +// Return the resulting preview stream to JS. Runs on the main thread. +class GetPreviewStreamResult : public nsRunnable +{ +public: + GetPreviewStreamResult(nsIDOMMediaStream* aStream, nsICameraPreviewStreamCallback* onSuccess) + : mStream(aStream) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mStream); + } + return NS_OK; + } + +protected: + nsCOMPtr mStream; + nsCOMPtr mOnSuccessCb; +}; + +// Get the desired preview stream. +class GetPreviewStreamTask : public nsRunnable +{ +public: + GetPreviewStreamTask(nsCameraControl* aCameraControl, CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError) + : mSize(aSize) + , mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + nsresult rv = mCameraControl->GetPreviewStreamImpl(this); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + CameraSize mSize; + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Return the autofocus status to JS. Runs on the main thread. +class AutoFocusResult : public nsRunnable +{ +public: + AutoFocusResult(bool aSuccess, nsICameraAutoFocusCallback* onSuccess) + : mSuccess(aSuccess) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mSuccess); + } + return NS_OK; + } + +protected: + bool mSuccess; + nsCOMPtr mOnSuccessCb; +}; + +// Autofocus the camera. +class AutoFocusTask : public nsRunnable +{ +public: + AutoFocusTask(nsCameraControl* aCameraControl, nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError) + : mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->AutoFocusImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Return the captured picture to JS. Runs on the main thread. +class TakePictureResult : public nsRunnable +{ +public: + TakePictureResult(nsIDOMBlob* aImage, nsICameraTakePictureCallback* onSuccess) + : mImage(aImage) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mImage); + } + return NS_OK; + } + +protected: + nsCOMPtr mImage; + nsCOMPtr mOnSuccessCb; +}; + +// Capture a still image with the camera. +class TakePictureTask : public nsRunnable +{ +public: + TakePictureTask(nsCameraControl* aCameraControl, CameraSize aSize, PRInt32 aRotation, const nsAString& aFileFormat, CameraPosition aPosition, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError) + : mCameraControl(aCameraControl) + , mSize(aSize) + , mRotation(aRotation) + , mFileFormat(aFileFormat) + , mPosition(aPosition) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->TakePictureImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + nsCOMPtr mCameraControl; + CameraSize mSize; + PRInt32 mRotation; + nsString mFileFormat; + CameraPosition mPosition; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Return the captured video to JS. Runs on the main thread. +class StartRecordingResult : public nsRunnable +{ +public: + StartRecordingResult(nsIDOMMediaStream* aStream, nsICameraStartRecordingCallback* onSuccess) + : mStream(aStream) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mStream); + } + return NS_OK; + } + +protected: + nsCOMPtr mStream; + nsCOMPtr mOnSuccessCb; +}; + +// Start video recording. +class StartRecordingTask : public nsRunnable +{ +public: + StartRecordingTask(nsCameraControl* aCameraControl, CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) + : mSize(aSize) + , mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->StartRecordingImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + CameraSize mSize; + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Stop video recording. +class StopRecordingTask : public nsRunnable +{ +public: + StopRecordingTask(nsCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->StopRecordingImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nsCOMPtr mCameraControl; +}; + +// Pushes all camera parameters to the camera. +class PushParametersTask : public nsRunnable +{ +public: + PushParametersTask(nsCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->PushParametersImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nsCOMPtr mCameraControl; +}; + +// Get all camera parameters from the camera. +class PullParametersTask : public nsRunnable +{ +public: + PullParametersTask(nsCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->PullParametersImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nsCOMPtr mCameraControl; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_NSCAMERACONTROL_H diff --git a/dom/camera/CameraPreview.cpp b/dom/camera/CameraPreview.cpp new file mode 100644 index 00000000000..bfebf27e25c --- /dev/null +++ b/dom/camera/CameraPreview.cpp @@ -0,0 +1,98 @@ +/* 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 "CameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS1(CameraPreview, CameraPreview) + +class CameraPreviewListener : public MediaStreamListener +{ +public: + CameraPreviewListener(CameraPreview* aPreview) : + mPreview(aPreview) + { + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + } + + ~CameraPreviewListener() + { + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + } + + void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming) + { + const char* state; + + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + switch (aConsuming) { + case NOT_CONSUMED: + state = "not consuming"; + break; + + case CONSUMED: + state = "consuming"; + break; + + default: + state = "unknown"; + break; + } + + DOM_CAMERA_LOGA("camera viewfinder is %s\n", state); + + switch (aConsuming) { + case NOT_CONSUMED: + mPreview->Stop(); + break; + + case CONSUMED: + mPreview->Start(); + break; + } + } + +protected: + nsCOMPtr mPreview; +}; + +CameraPreview::CameraPreview(PRUint32 aWidth, PRUint32 aHeight) + : nsDOMMediaStream() + , mWidth(aWidth) + , mHeight(aHeight) + , mFramesPerSecond(0) + , mFrameCount(0) +{ + DOM_CAMERA_LOGI("%s:%d : mWidth=%d, mHeight=%d : this=%p\n", __func__, __LINE__, mWidth, mHeight, this); + + mImageContainer = LayerManager::CreateImageContainer(); + MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); + mStream = gm->CreateInputStream(this); + mInput = GetStream()->AsSourceStream(); + mInput->AddListener(new CameraPreviewListener(this)); +} + +void +CameraPreview::SetFrameRate(PRUint32 aFramesPerSecond) +{ + mFramesPerSecond = aFramesPerSecond; + mInput->AddTrack(TRACK_VIDEO, mFramesPerSecond, 0, new VideoSegment()); + mInput->AdvanceKnownTracksTime(MEDIA_TIME_MAX); +} + +CameraPreview::~CameraPreview() +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + /** + * We _must_ remember to call RemoveListener on this before destroying this, + * else the media framework will trigger a double-free. + */ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); +} diff --git a/dom/camera/CameraPreview.h b/dom/camera/CameraPreview.h new file mode 100644 index 00000000000..f68481ffc16 --- /dev/null +++ b/dom/camera/CameraPreview.h @@ -0,0 +1,58 @@ +/* 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 DOM_CAMERA_CAMERAPREVIEW_H +#define DOM_CAMERA_CAMERAPREVIEW_H + +#include "MediaStreamGraph.h" +#include "StreamBuffer.h" +#include "nsDOMMediaStream.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; +using namespace mozilla::layers; + +namespace mozilla { + +class CameraPreview : public nsDOMMediaStream + , public MediaStreamListener +{ +public: + NS_DECL_ISUPPORTS + + CameraPreview(PRUint32 aWidth, PRUint32 aHeight); + + void SetFrameRate(PRUint32 aFramesPerSecond); + + NS_IMETHODIMP + GetCurrentTime(double* aCurrentTime) { + return nsDOMMediaStream::GetCurrentTime(aCurrentTime); + } + + virtual void Start() = 0; + virtual void Stop() = 0; + +protected: + virtual ~CameraPreview(); + + PRUint32 mWidth; + PRUint32 mHeight; + PRUint32 mFramesPerSecond; + SourceMediaStream* mInput; + nsRefPtr mImageContainer; + VideoSegment mVideoSegment; + PRUint32 mFrameCount; + + enum { TRACK_VIDEO = 1 }; + +private: + CameraPreview(const CameraPreview&) MOZ_DELETE; + CameraPreview& operator=(const CameraPreview&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_CAMERAPREVIEW_H diff --git a/dom/camera/DOMCameraManager.cpp b/dom/camera/DOMCameraManager.cpp new file mode 100644 index 00000000000..3075c7ca667 --- /dev/null +++ b/dom/camera/DOMCameraManager.cpp @@ -0,0 +1,89 @@ +/* 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 "CameraControl.h" +#include "DOMCameraManager.h" +#include "nsDOMClassInfo.h" +#include "DictionaryHelpers.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +DOMCI_DATA(CameraManager, nsIDOMCameraManager) + +NS_INTERFACE_MAP_BEGIN(nsDOMCameraManager) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDOMCameraManager) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraManager) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsDOMCameraManager) +NS_IMPL_RELEASE(nsDOMCameraManager) + +/** + * nsDOMCameraManager::GetListOfCameras + * is implementation-specific, and can be found in (e.g.) + * GonkCameraManager.cpp and FallbackCameraManager.cpp. + */ + +nsDOMCameraManager::nsDOMCameraManager(PRUint64 aWindowId) + : mWindowId(aWindowId) +{ + /* member initializers and constructor code */ + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); +} + +nsDOMCameraManager::~nsDOMCameraManager() +{ + /* destructor code */ + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); +} + +void +nsDOMCameraManager::OnNavigation(PRUint64 aWindowId) +{ + // TODO: implement -- see getUserMedia() implementation +} + +// static creator +already_AddRefed +nsDOMCameraManager::Create(PRUint64 aWindowId) +{ + // TODO: check for permissions here to access cameras + + nsRefPtr cameraManager = new nsDOMCameraManager(aWindowId); + return cameraManager.forget(); +} + +/* [implicit_jscontext] void getCamera ([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsDOMCameraManager::GetCamera(const JS::Value& aOptions, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + PRUint32 cameraId = 0; // back (or forward-facing) camera by default + CameraSelector selector; + + nsresult rv = selector.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + if (selector.camera.EqualsASCII("front")) { + cameraId = 1; + } + + // reuse the same camera thread to conserve resources + if (!mCameraThread) { + rv = NS_NewThread(getter_AddRefs(mCameraThread)); + NS_ENSURE_SUCCESS(rv, rv); + } + + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + nsCOMPtr getCameraTask = new GetCameraTask(cameraId, onSuccess, onError, mCameraThread); + mCameraThread->Dispatch(getCameraTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} diff --git a/dom/camera/DOMCameraManager.h b/dom/camera/DOMCameraManager.h new file mode 100644 index 00000000000..86699c23bc3 --- /dev/null +++ b/dom/camera/DOMCameraManager.h @@ -0,0 +1,82 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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 DOM_CAMERA_DOMCAMERAMANAGER_H +#define DOM_CAMERA_DOMCAMERAMANAGER_H + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nsIDOMCameraManager.h" + +class nsDOMCameraManager : public nsIDOMCameraManager +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMCAMERAMANAGER + + static already_AddRefed Create(PRUint64 aWindowId); + + void OnNavigation(PRUint64 aWindowId); + +private: + nsDOMCameraManager(); + nsDOMCameraManager(PRUint64 aWindowId); + nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE; + nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE; + ~nsDOMCameraManager(); + +protected: + PRUint64 mWindowId; + nsCOMPtr mCameraThread; +}; + + +class GetCameraTask : public nsRunnable +{ +public: + GetCameraTask(PRUint32 aCameraId, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsIThread* aCameraThread) + : mCameraId(aCameraId) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + , mCameraThread(aCameraThread) + { } + + NS_IMETHOD Run(); + +protected: + PRUint32 mCameraId; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; + nsCOMPtr mCameraThread; +}; + +class GetCameraResult : public nsRunnable +{ +public: + GetCameraResult(nsICameraControl* aCameraControl, nsICameraGetCameraCallback* onSuccess) + : mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO: window management stuff + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mCameraControl); + } + return NS_OK; + } + +protected: + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; +}; + +#endif // DOM_CAMERA_DOMCAMERAMANAGER_H diff --git a/dom/camera/FallbackCameraCapabilities.cpp b/dom/camera/FallbackCameraCapabilities.cpp new file mode 100644 index 00000000000..c638a976bc6 --- /dev/null +++ b/dom/camera/FallbackCameraCapabilities.cpp @@ -0,0 +1,140 @@ +/* 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 "nsDOMClassInfoID.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +DOMCI_DATA(CameraCapabilities, nsICameraCapabilities) + +NS_INTERFACE_MAP_BEGIN(nsCameraCapabilities) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsCameraCapabilities) +NS_IMPL_RELEASE(nsCameraCapabilities) + +nsCameraCapabilities::nsCameraCapabilities(nsCameraControl* aCamera) + : mCamera(aCamera) +{ + /* member initializers and constructor code */ + DOM_CAMERA_LOGI("%s:%d : FALLBACK CAMERA CAPABILITIES\n", __func__, __LINE__); +} + +nsCameraCapabilities::~nsCameraCapabilities() +{ + /* destructor code */ +} + +/* [implicit_jscontext] readonly attribute jsval previewSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval pictureSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval fileFormats; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval whiteBalanceModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval sceneModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval effects; */ +NS_IMETHODIMP +nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval flashModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval focusModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute long maxFocusAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute double minExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute double maxExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute double stepExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute long maxMeteringAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAreas) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval zoomRatios; */ +NS_IMETHODIMP +nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval videoSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/camera/FallbackCameraControl.cpp b/dom/camera/FallbackCameraControl.cpp new file mode 100644 index 00000000000..ca8c016c4ff --- /dev/null +++ b/dom/camera/FallbackCameraControl.cpp @@ -0,0 +1,147 @@ +/* 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 "nsDOMClassInfoID.h" +#include "DOMCameraManager.h" +#include "CameraControl.h" + +using namespace mozilla; + +/** + * Fallback camera control subclass. Can be used as a template for the + * definition of new camera support classes. + */ +class nsFallbackCameraControl : public nsCameraControl +{ +public: + nsFallbackCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread); + + const char* GetParameter(const char* aKey); + const char* GetParameterConstChar(PRUint32 aKey); + double GetParameterDouble(PRUint32 aKey); + void GetParameter(PRUint32 aKey, nsTArray& aRegions); + void SetParameter(const char* aKey, const char* aValue); + void SetParameter(PRUint32 aKey, const char* aValue); + void SetParameter(PRUint32 aKey, double aValue); + void SetParameter(PRUint32 aKey, const nsTArray& aRegions); + void PushParameters(); + +protected: + ~nsFallbackCameraControl(); + + nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream); + nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus); + nsresult TakePictureImpl(TakePictureTask* aTakePicture); + nsresult StartRecordingImpl(StartRecordingTask* aStartRecording); + nsresult StopRecordingImpl(StopRecordingTask* aStopRecording); + nsresult PushParametersImpl(PushParametersTask* aPushParameters); + nsresult PullParametersImpl(PullParametersTask* aPullParameters); + +private: + nsFallbackCameraControl(const nsFallbackCameraControl&) MOZ_DELETE; + nsFallbackCameraControl& operator=(const nsFallbackCameraControl&) MOZ_DELETE; +}; + +/** + * Stub implemetations of the fallback camera control. + * + * None of these should ever get called--they exist to keep the linker happy, + * and may be used as templates for new camera support classes. + */ +nsFallbackCameraControl::nsFallbackCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread) + : nsCameraControl(aCameraId, aCameraThread) +{ } + +nsFallbackCameraControl::~nsFallbackCameraControl() +{ } + +const char* +nsFallbackCameraControl::GetParameter(const char* aKey) +{ + return nullptr; +} + +const char* +nsFallbackCameraControl::GetParameterConstChar(PRUint32 aKey) +{ + return nullptr; +} + +double +nsFallbackCameraControl::GetParameterDouble(PRUint32 aKey) +{ + return NAN; +} + +void +nsFallbackCameraControl::GetParameter(PRUint32 aKey, nsTArray& aRegions) +{ +} + +void +nsFallbackCameraControl::SetParameter(const char* aKey, const char* aValue) +{ +} + +void +nsFallbackCameraControl::SetParameter(PRUint32 aKey, const char* aValue) +{ +} + +void +nsFallbackCameraControl::SetParameter(PRUint32 aKey, double aValue) +{ +} + +void +nsFallbackCameraControl::SetParameter(PRUint32 aKey, const nsTArray& aRegions) +{ +} + +void +nsFallbackCameraControl::PushParameters() +{ +} + +nsresult +nsFallbackCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::TakePictureImpl(TakePictureTask* aTakePicture) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::PushParametersImpl(PushParametersTask* aPushParameters) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::PullParametersImpl(PullParametersTask* aPullParameters) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/camera/FallbackCameraManager.cpp b/dom/camera/FallbackCameraManager.cpp new file mode 100644 index 00000000000..964f251585a --- /dev/null +++ b/dom/camera/FallbackCameraManager.cpp @@ -0,0 +1,22 @@ +/* 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 "DOMCameraManager.h" + +// From nsDOMCameraManager. + +/* [implicit_jscontext] jsval getListOfCameras (); */ +NS_IMETHODIMP +nsDOMCameraManager::GetListOfCameras(JSContext* cx, JS::Value* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +using namespace mozilla; + +NS_IMETHODIMP +GetCameraTask::Run() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/camera/GonkCameraCapabilities.cpp b/dom/camera/GonkCameraCapabilities.cpp new file mode 100644 index 00000000000..b2420ea6d23 --- /dev/null +++ b/dom/camera/GonkCameraCapabilities.cpp @@ -0,0 +1,352 @@ +/* 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 +#include +#include "nsDOMClassInfo.h" +#include "jsapi.h" +#include "camera/CameraParameters.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace android; +using namespace mozilla; + +DOMCI_DATA(CameraCapabilities, nsICameraCapabilities) + +NS_INTERFACE_MAP_BEGIN(nsCameraCapabilities) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsCameraCapabilities) +NS_IMPL_RELEASE(nsCameraCapabilities) + + +nsCameraCapabilities::nsCameraCapabilities(nsCameraControl* aCamera) + : mCamera(aCamera) +{ + // member initializers and constructor code + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); +} + +nsCameraCapabilities::~nsCameraCapabilities() +{ + // destructor code + DOM_CAMERA_LOGI("%s:%d : this=%p, mCamera=%p\n", __func__, __LINE__, this, mCamera.get()); +} + +static nsresult +ParseZoomRatioItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd) +{ + if (!*aEnd) { + // make 'aEnd' follow the same semantics as strchr(). + aEnd = nullptr; + } + + double d = strtod(aStart, aEnd); + jsval v; + + d /= 100; + if (!JS_NewNumberValue(aCx, d, &v)) { + return NS_ERROR_FAILURE; + } + if (!JS_SetElement(aCx, aArray, aIndex, &v)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static nsresult +ParseStringItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd) +{ + JSString* s; + + if (*aEnd) { + s = JS_NewStringCopyN(aCx, aStart, *aEnd - aStart); + } else { + s = JS_NewStringCopyZ(aCx, aStart); + } + if (!s) { + return NS_ERROR_OUT_OF_MEMORY; + } + + jsval v = STRING_TO_JSVAL(s); + if (!JS_SetElement(aCx, aArray, aIndex, &v)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static nsresult +ParseDimensionItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd) +{ + char* x; + + if (!*aEnd) { + // make 'aEnd' follow the same semantics as strchr(). + aEnd = nullptr; + } + + jsval w = INT_TO_JSVAL(strtol(aStart, &x, 10)); + jsval h = INT_TO_JSVAL(strtol(x + 1, aEnd, 10)); + + JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!JS_SetProperty(aCx, o, "width", &w)) { + return NS_ERROR_FAILURE; + } + if (!JS_SetProperty(aCx, o, "height", &h)) { + return NS_ERROR_FAILURE; + } + + jsval v = OBJECT_TO_JSVAL(o); + if (!JS_SetElement(aCx, aArray, aIndex, &v)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsCameraCapabilities::ParameterListToNewArray(JSContext* aCx, JSObject** aArray, const char* aKey, ParseItemAndAddFunc aParseItemAndAdd) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(aKey); + if (!value) { + // in case we get nonsense data back + *aArray = nullptr; + return NS_OK; + } + + *aArray = JS_NewArrayObject(aCx, 0, nullptr); + if (!*aArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + const char* p = value; + PRUint32 index = 0; + nsresult rv; + char* q; + + while (p) { + q = strchr(p, ','); + if (q != p) { // skip consecutive delimiters, just in case + rv = aParseItemAndAdd(aCx, *aArray, index, p, &q); + NS_ENSURE_SUCCESS(rv, rv); + ++index; + } + p = q; + if (p) { + ++p; + } + } + + return JS_FreezeObject(aCx, *aArray) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +nsCameraCapabilities::StringListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey) +{ + JSObject* array; + + nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseStringItemAndAdd); + NS_ENSURE_SUCCESS(rv, rv); + + *aArray = OBJECT_TO_JSVAL(array); + return NS_OK; +} + +nsresult +nsCameraCapabilities::DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey) +{ + JSObject* array; + nsresult rv; + + rv = ParameterListToNewArray(aCx, &array, aKey, ParseDimensionItemAndAdd); + NS_ENSURE_SUCCESS(rv, rv); + + *aArray = OBJECT_TO_JSVAL(array); + return NS_OK; +} + +/* readonly attribute jsval previewSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes) +{ + return DimensionListToNewObject(cx, aPreviewSizes, CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES); +} + +/* readonly attribute jsval pictureSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes) +{ + return DimensionListToNewObject(cx, aPictureSizes, CameraParameters::KEY_SUPPORTED_PICTURE_SIZES); +} + +/* readonly attribute jsval fileFormats; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats) +{ + return StringListToNewObject(cx, aFileFormats, CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS); +} + +/* readonly attribute jsval whiteBalanceModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes) +{ + return StringListToNewObject(cx, aWhiteBalanceModes, CameraParameters::KEY_SUPPORTED_WHITE_BALANCE); +} + +/* readonly attribute jsval sceneModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes) +{ + return StringListToNewObject(cx, aSceneModes, CameraParameters::KEY_SUPPORTED_SCENE_MODES); +} + +/* readonly attribute jsval effects; */ +NS_IMETHODIMP +nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects) +{ + return StringListToNewObject(cx, aEffects, CameraParameters::KEY_SUPPORTED_EFFECTS); +} + +/* readonly attribute jsval flashModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes) +{ + return StringListToNewObject(cx, aFlashModes, CameraParameters::KEY_SUPPORTED_FLASH_MODES); +} + +/* readonly attribute jsval focusModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes) +{ + return StringListToNewObject(cx, aFocusModes, CameraParameters::KEY_SUPPORTED_FOCUS_MODES); +} + +/* readonly attribute long maxFocusAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS); + if (!value) { + // in case we get nonsense data back + *aMaxFocusAreas = 0; + return NS_OK; + } + + *aMaxFocusAreas = atoi(value); + return NS_OK; +} + +/* readonly attribute double minExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION); + if (!value) { + // in case we get nonsense data back + *aMinExposureCompensation = 0; + return NS_OK; + } + + *aMinExposureCompensation = atof(value); + return NS_OK; +} + +/* readonly attribute double maxExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION); + if (!value) { + // in case we get nonsense data back + *aMaxExposureCompensation = 0; + return NS_OK; + } + + *aMaxExposureCompensation = atof(value); + return NS_OK; +} + +/* readonly attribute double stepExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP); + if (!value) { + // in case we get nonsense data back + *aStepExposureCompensation = 0; + return NS_OK; + } + + *aStepExposureCompensation = atof(value); + return NS_OK; +} + +/* readonly attribute long maxMeteringAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAreas) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_METERING_AREAS); + if (!value) { + // in case we get nonsense data back + *aMaxMeteringAreas = 0; + return NS_OK; + } + + *aMaxMeteringAreas = atoi(value); + return NS_OK; +} + +/* readonly attribute jsval zoomRatios; */ +NS_IMETHODIMP +nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_ZOOM_SUPPORTED); + if (!value || strcmp(value, CameraParameters::TRUE) != 0) { + // if zoom is not supported, return a null object + *aZoomRatios = JSVAL_NULL; + return NS_OK; + } + + JSObject* array; + + nsresult rv = ParameterListToNewArray(cx, &array, CameraParameters::KEY_ZOOM_RATIOS, ParseZoomRatioItemAndAdd); + NS_ENSURE_SUCCESS(rv, rv); + + *aZoomRatios = OBJECT_TO_JSVAL(array); + return NS_OK; +} + +/* readonly attribute jsval videoSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes) +{ + return DimensionListToNewObject(cx, aVideoSizes, CameraParameters::KEY_SUPPORTED_VIDEO_SIZES); +} diff --git a/dom/camera/GonkCameraControl.cpp b/dom/camera/GonkCameraControl.cpp new file mode 100644 index 00000000000..06e8c594dc2 --- /dev/null +++ b/dom/camera/GonkCameraControl.cpp @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "libcameraservice/CameraHardwareInterface.h" +#include "camera/CameraParameters.h" +#include "nsCOMPtr.h" +#include "nsDOMClassInfo.h" +#include "nsMemory.h" +#include "jsapi.h" +#include "nsThread.h" +#include "nsPrintfCString.h" +#include "DOMCameraManager.h" +#include "GonkCameraHwMgr.h" +#include "CameraCapabilities.h" +#include "GonkCameraControl.h" +#include "GonkCameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +static const char* getKeyText(PRUint32 aKey) +{ + switch (aKey) { + case nsCameraControl::CAMERA_PARAM_EFFECT: + return CameraParameters::KEY_EFFECT; + case nsCameraControl::CAMERA_PARAM_WHITEBALANCE: + return CameraParameters::KEY_WHITE_BALANCE; + case nsCameraControl::CAMERA_PARAM_SCENEMODE: + return CameraParameters::KEY_SCENE_MODE; + case nsCameraControl::CAMERA_PARAM_FLASHMODE: + return CameraParameters::KEY_FLASH_MODE; + case nsCameraControl::CAMERA_PARAM_FOCUSMODE: + return CameraParameters::KEY_FOCUS_MODE; + case nsCameraControl::CAMERA_PARAM_ZOOM: + return CameraParameters::KEY_ZOOM; + case nsCameraControl::CAMERA_PARAM_METERINGAREAS: + return CameraParameters::KEY_METERING_AREAS; + case nsCameraControl::CAMERA_PARAM_FOCUSAREAS: + return CameraParameters::KEY_FOCUS_AREAS; + case nsCameraControl::CAMERA_PARAM_FOCALLENGTH: + return CameraParameters::KEY_FOCAL_LENGTH; + case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCENEAR: + return CameraParameters::KEY_FOCUS_DISTANCES; + case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCEOPTIMUM: + return CameraParameters::KEY_FOCUS_DISTANCES; + case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCEFAR: + return CameraParameters::KEY_FOCUS_DISTANCES; + case nsCameraControl::CAMERA_PARAM_EXPOSURECOMPENSATION: + return CameraParameters::KEY_EXPOSURE_COMPENSATION; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_PREVIEWSIZES: + return CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_VIDEOSIZES: + return CameraParameters::KEY_SUPPORTED_VIDEO_SIZES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTURESIZES: + return CameraParameters::KEY_SUPPORTED_PICTURE_SIZES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTUREFORMATS: + return CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_WHITEBALANCES: + return CameraParameters::KEY_SUPPORTED_WHITE_BALANCE; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_SCENEMODES: + return CameraParameters::KEY_SUPPORTED_SCENE_MODES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_EFFECTS: + return CameraParameters::KEY_SUPPORTED_EFFECTS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_FLASHMODES: + return CameraParameters::KEY_SUPPORTED_FLASH_MODES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_FOCUSMODES: + return CameraParameters::KEY_SUPPORTED_FOCUS_MODES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS: + return CameraParameters::KEY_MAX_NUM_FOCUS_AREAS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS: + return CameraParameters::KEY_MAX_NUM_METERING_AREAS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION: + return CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION: + return CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP: + return CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOM: + return CameraParameters::KEY_ZOOM_SUPPORTED; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOMRATIOS: + return CameraParameters::KEY_ZOOM_RATIOS; + default: + return nullptr; + } +} + +// Gonk-specific CameraControl implementation. + +nsGonkCameraControl::nsGonkCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread) + : nsCameraControl(aCameraId, aCameraThread) + , mHwHandle(0) + , mExposureCompensationMin(0.0) + , mExposureCompensationStep(0.0) + , mDeferConfigUpdate(false) +{ + // Constructor runs on the camera thread--see DOMCameraManager.cpp::GetCameraImpl(). + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + mHwHandle = GonkCameraHardware::GetHandle(this, mCameraId); + DOM_CAMERA_LOGI("%s:%d : this = %p, mHwHandle = %d\n", __func__, __LINE__, this, mHwHandle); + + // Initialize our camera configuration database. + mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock"); + PullParametersImpl(nullptr); + + // Grab any settings we'll need later. + mExposureCompensationMin = mParams.getFloat(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION); + mExposureCompensationStep = mParams.getFloat(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP); + mMaxMeteringAreas = mParams.getInt(CameraParameters::KEY_MAX_NUM_METERING_AREAS); + mMaxFocusAreas = mParams.getInt(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS); + + DOM_CAMERA_LOGI("minimum exposure compensation = %f\n", mExposureCompensationMin); + DOM_CAMERA_LOGI("exposure compensation step = %f\n", mExposureCompensationStep); + DOM_CAMERA_LOGI("maximum metering areas = %d\n", mMaxMeteringAreas); + DOM_CAMERA_LOGI("maximum focus areas = %d\n", mMaxFocusAreas); +} + +nsGonkCameraControl::~nsGonkCameraControl() +{ + DOM_CAMERA_LOGI("%s:%d : this = %p, mHwHandle = %d\n", __func__, __LINE__, this, mHwHandle); + GonkCameraHardware::ReleaseHandle(mHwHandle); + if (mRwLock) { + PRRWLock* lock = mRwLock; + mRwLock = nullptr; + PR_DestroyRWLock(lock); + } + + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); +} + +class RwAutoLockRead +{ +public: + RwAutoLockRead(PRRWLock* aRwLock) + : mRwLock(aRwLock) + { + PR_RWLock_Rlock(mRwLock); + } + + ~RwAutoLockRead() + { + PR_RWLock_Unlock(mRwLock); + } + +protected: + PRRWLock* mRwLock; +}; + +class RwAutoLockWrite +{ +public: + RwAutoLockWrite(PRRWLock* aRwLock) + : mRwLock(aRwLock) + { + PR_RWLock_Wlock(mRwLock); + } + + ~RwAutoLockWrite() + { + PR_RWLock_Unlock(mRwLock); + } + +protected: + PRRWLock* mRwLock; +}; + +const char* +nsGonkCameraControl::GetParameter(const char* aKey) +{ + RwAutoLockRead lock(mRwLock); + return mParams.get(aKey); +} + +const char* +nsGonkCameraControl::GetParameterConstChar(PRUint32 aKey) +{ + const char* key = getKeyText(aKey); + if (!key) { + return nullptr; + } + + RwAutoLockRead lock(mRwLock); + return mParams.get(key); +} + +double +nsGonkCameraControl::GetParameterDouble(PRUint32 aKey) +{ + double val; + int index = 0; + double focusDistance[3]; + const char* s; + + const char* key = getKeyText(aKey); + if (!key) { + // return 1x when zooming is not supported + return aKey == CAMERA_PARAM_ZOOM ? 1.0 : 0.0; + } + + RwAutoLockRead lock(mRwLock); + switch (aKey) { + case CAMERA_PARAM_ZOOM: + val = mParams.getInt(key); + return val / 100; + + /** + * The gonk camera parameters API only exposes one focus distance property + * that contains "Near,Optimum,Far" distances, in metres, where 'Far' may + * be 'Infinity'. + */ + case CAMERA_PARAM_FOCUSDISTANCEFAR: + ++index; + // intentional fallthrough + + case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM: + ++index; + // intentional fallthrough + + case CAMERA_PARAM_FOCUSDISTANCENEAR: + s = mParams.get(key); + if (sscanf(s, "%lf,%lf,%lf", &focusDistance[0], &focusDistance[1], &focusDistance[2]) == 3) { + return focusDistance[index]; + } + return 0.0; + + case CAMERA_PARAM_EXPOSURECOMPENSATION: + index = mParams.getInt(key); + if (!index) { + // NaN indicates automatic exposure compensation + return NAN; + } + val = (index - 1) * mExposureCompensationStep + mExposureCompensationMin; + DOM_CAMERA_LOGI("index = %d --> compensation = %f\n", index, val); + return val; + + default: + return mParams.getFloat(key); + } +} + +void +nsGonkCameraControl::GetParameter(PRUint32 aKey, nsTArray& aRegions) +{ + aRegions.Clear(); + + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + RwAutoLockRead lock(mRwLock); + + const char* value = mParams.get(key); + DOM_CAMERA_LOGI("key='%s' --> value='%s'\n", key, value); + if (!value) { + return; + } + + const char* p = value; + PRUint32 count = 1; + + // count the number of regions in the string + while ((p = strstr(p, "),("))) { + ++count; + p += 3; + } + + aRegions.SetCapacity(count); + CameraRegion* r; + + // parse all of the region sets + PRUint32 i; + for (i = 0, p = value; p && i < count; ++i, p = strchr(p + 1, '(')) { + r = aRegions.AppendElement(); + if (sscanf(p, "(%d,%d,%d,%d,%u)", &r->top, &r->left, &r->bottom, &r->right, &r->weight) != 5) { + DOM_CAMERA_LOGE("%s:%d : region tuple has bad format: '%s'\n", __func__, __LINE__, p); + goto GetParameter_error; + } + } + + return; + +GetParameter_error: + aRegions.Clear(); +} + +void +nsGonkCameraControl::PushParameters() +{ + if (!mDeferConfigUpdate) { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + /** + * If we're already on the camera thread, call PushParametersImpl() + * directly, so that it executes synchronously. Some callers + * require this so that changes take effect immediately before + * we can proceed. + */ + if (NS_IsMainThread()) { + nsCOMPtr pushParametersTask = new PushParametersTask(this); + mCameraThread->Dispatch(pushParametersTask, NS_DISPATCH_NORMAL); + } else { + PushParametersImpl(nullptr); + } + } +} + +void +nsGonkCameraControl::SetParameter(const char* aKey, const char* aValue) +{ + { + RwAutoLockWrite lock(mRwLock); + mParams.set(aKey, aValue); + } + PushParameters(); +} + +void +nsGonkCameraControl::SetParameter(PRUint32 aKey, const char* aValue) +{ + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + { + RwAutoLockWrite lock(mRwLock); + mParams.set(key, aValue); + } + PushParameters(); +} + +void +nsGonkCameraControl::SetParameter(PRUint32 aKey, double aValue) +{ + PRUint32 index; + + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + { + RwAutoLockWrite lock(mRwLock); + if (aKey == CAMERA_PARAM_EXPOSURECOMPENSATION) { + /** + * Convert from real value to a Gonk index, round + * to the nearest step; index is 1-based. + */ + index = (aValue - mExposureCompensationMin + mExposureCompensationStep / 2) / mExposureCompensationStep + 1; + DOM_CAMERA_LOGI("compensation = %f --> index = %d\n", aValue, index); + mParams.set(key, index); + } else { + mParams.setFloat(key, aValue); + } + } + PushParameters(); +} + +void +nsGonkCameraControl::SetParameter(PRUint32 aKey, const nsTArray& aRegions) +{ + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + PRUint32 length = aRegions.Length(); + + if (!length) { + // This tells the camera driver to revert to automatic regioning. + mParams.set(key, "(0,0,0,0,0)"); + PushParameters(); + return; + } + + nsCString s; + + for (PRUint32 i = 0; i < length; ++i) { + const CameraRegion* r = &aRegions[i]; + s.AppendPrintf("(%d,%d,%d,%d,%d),", r->top, r->left, r->bottom, r->right, r->weight); + } + + // remove the trailing comma + s.Trim(",", false, true, true); + + DOM_CAMERA_LOGI("camera region string '%s'\n", s.get()); + + { + RwAutoLockWrite lock(mRwLock); + mParams.set(key, s.get()); + } + PushParameters(); +} + +nsresult +nsGonkCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) +{ + nsCOMPtr preview = mPreview; + nsresult rv; + + if (!preview) { + preview = new GonkCameraPreview(mHwHandle, aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height); + if (!preview) { + if (aGetPreviewStream->mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(aGetPreviewStream->mOnErrorCb, NS_LITERAL_STRING("OUT_OF_MEMORY"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_ERROR_OUT_OF_MEMORY; + } + } + + mPreview = preview; + return NS_DispatchToMainThread(new GetPreviewStreamResult(preview.get(), aGetPreviewStream->mOnSuccessCb)); +} + +nsresult +nsGonkCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus) +{ + nsCOMPtr cb = mAutoFocusOnSuccessCb; + if (cb) { + /** + * We already have a callback, so someone has already + * called autoFocus() -- cancel it. + */ + mAutoFocusOnSuccessCb = nullptr; + nsCOMPtr ecb = mAutoFocusOnErrorCb; + mAutoFocusOnErrorCb = nullptr; + if (ecb) { + nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED"))); + NS_ENSURE_SUCCESS(rv, rv); + } + + GonkCameraHardware::CancelAutoFocus(mHwHandle); + } + + mAutoFocusOnSuccessCb = aAutoFocus->mOnSuccessCb; + mAutoFocusOnErrorCb = aAutoFocus->mOnErrorCb; + + if (GonkCameraHardware::AutoFocus(mHwHandle) != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::TakePictureImpl(TakePictureTask* aTakePicture) +{ + nsCOMPtr cb = mTakePictureOnSuccessCb; + if (cb) { + /** + * We already have a callback, so someone has already + * called TakePicture() -- cancel it. + */ + mTakePictureOnSuccessCb = nullptr; + nsCOMPtr ecb = mTakePictureOnErrorCb; + mTakePictureOnErrorCb = nullptr; + if (ecb) { + nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED"))); + NS_ENSURE_SUCCESS(rv, rv); + } + + GonkCameraHardware::CancelTakePicture(mHwHandle); + } + + mTakePictureOnSuccessCb = aTakePicture->mOnSuccessCb; + mTakePictureOnErrorCb = aTakePicture->mOnErrorCb; + + // batch-update camera configuration + mDeferConfigUpdate = true; + + /** + * height and width: some drivers are less friendly about getting one of + * these set to zero, so if either is not specified, ignore both and go + * with current or default settings. + */ + if (aTakePicture->mSize.width && aTakePicture->mSize.height) { + nsCString s; + s.AppendPrintf("%dx%d", aTakePicture->mSize.width, aTakePicture->mSize.height); + DOM_CAMERA_LOGI("setting picture size to '%s'\n", s.get()); + SetParameter(CameraParameters::KEY_PICTURE_SIZE, s.get()); + } + + // Picture format -- need to keep it for the callback. + mFileFormat = aTakePicture->mFileFormat; + SetParameter(CameraParameters::KEY_PICTURE_FORMAT, NS_ConvertUTF16toUTF8(mFileFormat).get()); + + // Convert 'rotation' to a positive value from 0..270 degrees, in steps of 90. + PRUint32 r = static_cast(aTakePicture->mRotation); + r %= 360; + r += 45; + r /= 90; + r *= 90; + DOM_CAMERA_LOGI("setting picture rotation to %d degrees (mapped from %d)\n", r, aTakePicture->mRotation); + SetParameter(CameraParameters::KEY_ROTATION, nsPrintfCString("%u", r).get()); + + // Add any specified positional information -- don't care if these fail. + if (!isnan(aTakePicture->mPosition.latitude)) { + DOM_CAMERA_LOGI("setting picture latitude to %lf\n", aTakePicture->mPosition.latitude); + SetParameter(CameraParameters::KEY_GPS_LATITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.latitude).get()); + } + if (!isnan(aTakePicture->mPosition.longitude)) { + DOM_CAMERA_LOGI("setting picture longitude to %lf\n", aTakePicture->mPosition.longitude); + SetParameter(CameraParameters::KEY_GPS_LONGITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.longitude).get()); + } + if (!isnan(aTakePicture->mPosition.altitude)) { + DOM_CAMERA_LOGI("setting picture altitude to %lf\n", aTakePicture->mPosition.altitude); + SetParameter(CameraParameters::KEY_GPS_ALTITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.altitude).get()); + } + if (!isnan(aTakePicture->mPosition.timestamp)) { + DOM_CAMERA_LOGI("setting picture timestamp to %lf\n", aTakePicture->mPosition.timestamp); + SetParameter(CameraParameters::KEY_GPS_TIMESTAMP, nsPrintfCString("%lf", aTakePicture->mPosition.timestamp).get()); + } + + mDeferConfigUpdate = false; + PushParameters(); + + if (GonkCameraHardware::TakePicture(mHwHandle) != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::PushParametersImpl(PushParametersTask* aPushParameters) +{ + RwAutoLockRead lock(mRwLock); + if (GonkCameraHardware::PushParameters(mHwHandle, mParams) != OK) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsGonkCameraControl::PullParametersImpl(PullParametersTask* aPullParameters) +{ + RwAutoLockWrite lock(mRwLock); + GonkCameraHardware::PullParameters(mHwHandle, mParams); + return NS_OK; +} + +nsresult +nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsGonkCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +nsGonkCameraControl::ReceiveFrame(PRUint8* aData, PRUint32 aLength) +{ + nsCOMPtr preview = mPreview; + + if (preview) { + GonkCameraPreview* p = static_cast(preview.get()); + MOZ_ASSERT(p); + p->ReceiveFrame(aData, aLength); + } +} + +// Gonk callback handlers. +namespace mozilla { + +void +ReceiveImage(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength) +{ + gc->TakePictureComplete(aData, aLength); +} + +void +AutoFocusComplete(nsGonkCameraControl* gc, bool success) +{ + gc->AutoFocusComplete(success); +} + +void +ReceiveFrame(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength) +{ + gc->ReceiveFrame(aData, aLength); +} + +} // namespace mozilla diff --git a/dom/camera/GonkCameraControl.h b/dom/camera/GonkCameraControl.h new file mode 100644 index 00000000000..a579de0de88 --- /dev/null +++ b/dom/camera/GonkCameraControl.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKCAMERACONTROL_H +#define DOM_CAMERA_GONKCAMERACONTROL_H + +#include "prtypes.h" +#include "prrwlock.h" +#include "CameraControl.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +namespace mozilla { + +class nsGonkCameraControl : public nsCameraControl +{ +public: + nsGonkCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread); + + const char* GetParameter(const char* aKey); + const char* GetParameterConstChar(PRUint32 aKey); + double GetParameterDouble(PRUint32 aKey); + void GetParameter(PRUint32 aKey, nsTArray& aRegions); + void SetParameter(const char* aKey, const char* aValue); + void SetParameter(PRUint32 aKey, const char* aValue); + void SetParameter(PRUint32 aKey, double aValue); + void SetParameter(PRUint32 aKey, const nsTArray& aRegions); + void PushParameters(); + + void ReceiveFrame(PRUint8 *aData, PRUint32 aLength); + +protected: + ~nsGonkCameraControl(); + + nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream); + nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus); + nsresult TakePictureImpl(TakePictureTask* aTakePicture); + nsresult StartRecordingImpl(StartRecordingTask* aStartRecording); + nsresult StopRecordingImpl(StopRecordingTask* aStopRecording); + nsresult PushParametersImpl(PushParametersTask* aPushParameters); + nsresult PullParametersImpl(PullParametersTask* aPullParameters); + + PRUint32 mHwHandle; + double mExposureCompensationMin; + double mExposureCompensationStep; + bool mDeferConfigUpdate; + PRRWLock* mRwLock; + android::CameraParameters mParams; + +private: + nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE; + nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE; +}; + +// camera driver callbacks +void ReceiveImage(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength); +void AutoFocusComplete(nsGonkCameraControl* gc, bool success); +void ReceiveFrame(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength); + +} // namespace mozilla + +#endif // DOM_CAMERA_GONKCAMERACONTROL_H diff --git a/dom/camera/GonkCameraHwMgr.cpp b/dom/camera/GonkCameraHwMgr.cpp new file mode 100644 index 00000000000..83313a61801 --- /dev/null +++ b/dom/camera/GonkCameraHwMgr.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "nsDebug.h" +#include "GonkCameraHwMgr.h" +#include "GonkNativeWindow.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +#if GIHM_TIMING_RECEIVEFRAME +#define INCLUDE_TIME_H 1 +#endif +#if GIHM_TIMING_OVERALL +#define INCLUDE_TIME_H 1 +#endif + +#if INCLUDE_TIME_H +#include + +static __inline void timespecSubtract(struct timespec* a, struct timespec* b) +{ + // a = b - a + if (b->tv_nsec < a->tv_nsec) { + b->tv_nsec += 1000000000; + b->tv_sec -= 1; + } + a->tv_nsec = b->tv_nsec - a->tv_nsec; + a->tv_sec = b->tv_sec - a->tv_sec; +} +#endif + +GonkCameraHardware::GonkCameraHardware(GonkCamera* aTarget, PRUint32 aCamera) + : mCamera(aCamera) + , mFps(30) + , mPreviewFormat(PREVIEW_FORMAT_UNKNOWN) + , mClosing(false) + , mMonitor("GonkCameraHardware.Monitor") + , mNumFrames(0) + , mTarget(aTarget) + , mInitialized(false) +{ + DOM_CAMERA_LOGI( "%s: this = %p (aTarget = %p)\n", __func__, (void* )this, (void* )aTarget ); + init(); +} + +// Android data callback +void +GonkCameraHardware::DataCallback(int32_t aMsgType, const sp &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser) +{ + GonkCameraHardware* hw = GetHardware((PRUint32)aUser); + if (!hw) { + DOM_CAMERA_LOGW("%s:aUser = %d resolved to no camera hw\n", __func__, (PRUint32)aUser); + return; + } + if (hw->mClosing) { + return; + } + + GonkCamera* camera = hw->mTarget; + if (camera) { + switch (aMsgType) { + case CAMERA_MSG_PREVIEW_FRAME: + ReceiveFrame(camera, (PRUint8*)aDataPtr->pointer(), aDataPtr->size()); + break; + + case CAMERA_MSG_COMPRESSED_IMAGE: + ReceiveImage(camera, (PRUint8*)aDataPtr->pointer(), aDataPtr->size()); + break; + + default: + DOM_CAMERA_LOGE("Unhandled data callback event %d\n", aMsgType); + break; + } + } else { + DOM_CAMERA_LOGW("%s: hw = %p (camera = NULL)\n", __func__, hw); + } +} + +// Android notify callback +void +GonkCameraHardware::NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser) +{ + bool bSuccess; + GonkCameraHardware* hw = GetHardware((PRUint32)aUser); + if (!hw) { + DOM_CAMERA_LOGW("%s:aUser = %d resolved to no camera hw\n", __func__, (PRUint32)aUser); + return; + } + if (hw->mClosing) { + return; + } + + GonkCamera* camera = hw->mTarget; + if (!camera) { + return; + } + + switch (aMsgType) { + case CAMERA_MSG_FOCUS: + if (ext1) { + DOM_CAMERA_LOGI("Autofocus complete"); + bSuccess = true; + } else { + DOM_CAMERA_LOGW("Autofocus failed"); + bSuccess = false; + } + AutoFocusComplete(camera, bSuccess); + break; + + case CAMERA_MSG_SHUTTER: + DOM_CAMERA_LOGW("Shutter event not handled yet\n"); + break; + + default: + DOM_CAMERA_LOGE("Unhandled notify callback event %d\n", aMsgType); + break; + } +} + +void +GonkCameraHardware::init() +{ + DOM_CAMERA_LOGI("%s: this = %p\n", __func__, (void* )this); + + if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t**)&mModule) < 0) { + return; + } + char cameraDeviceName[4]; + snprintf(cameraDeviceName, sizeof(cameraDeviceName), "%d", mCamera); + mHardware = new CameraHardwareInterface(cameraDeviceName); + if (mHardware->initialize(&mModule->common) != OK) { + mHardware.clear(); + return; + } + + mWindow = new android::GonkNativeWindow(); + + if (sHwHandle == 0) { + sHwHandle = 1; // don't use 0 + } + mHardware->setCallbacks(GonkCameraHardware::NotifyCallback, GonkCameraHardware::DataCallback, NULL, (void*)sHwHandle); + + // initialize the local camera parameter database + mParams = mHardware->getParameters(); + + mHardware->setPreviewWindow(mWindow); + + mInitialized = true; +} + +GonkCameraHardware::~GonkCameraHardware() +{ + DOM_CAMERA_LOGI( "%s:%d : this = %p\n", __func__, __LINE__, (void*)this ); + sHw = nullptr; +} + +GonkCameraHardware* GonkCameraHardware::sHw = nullptr; +PRUint32 GonkCameraHardware::sHwHandle = 0; + +void +GonkCameraHardware::ReleaseHandle(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + DOM_CAMERA_LOGI("%s: aHwHandle = %d, hw = %p (sHwHandle = %d)\n", __func__, aHwHandle, (void*)hw, sHwHandle); + if (!hw) { + return; + } + + DOM_CAMERA_LOGI("%s: before: sHwHandle = %d\n", __func__, sHwHandle); + sHwHandle += 1; // invalidate old handles before deleting + hw->mClosing = true; + hw->mHardware->disableMsgType(CAMERA_MSG_ALL_MSGS); + hw->mHardware->stopPreview(); + hw->mHardware->release(); + DOM_CAMERA_LOGI("%s: after: sHwHandle = %d\n", __func__, sHwHandle); + delete hw; // destroy the camera hardware instance +} + +PRUint32 +GonkCameraHardware::GetHandle(GonkCamera* aTarget, PRUint32 aCamera) +{ + ReleaseHandle(sHwHandle); + + sHw = new GonkCameraHardware(aTarget, aCamera); + + if (sHw->IsInitialized()) { + return sHwHandle; + } + + DOM_CAMERA_LOGE("failed to initialize camera hardware\n"); + delete sHw; + sHw = nullptr; + return 0; +} + +PRUint32 +GonkCameraHardware::GetFps(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return 0; + } + + return hw->mFps; +} + +void +GonkCameraHardware::GetPreviewSize(PRUint32 aHwHandle, PRUint32* aWidth, PRUint32* aHeight) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + *aWidth = hw->mWidth; + *aHeight = hw->mHeight; + } else { + *aWidth = 0; + *aHeight = 0; + } +} + +void +GonkCameraHardware::SetPreviewSize(PRUint32 aWidth, PRUint32 aHeight) +{ + Vector previewSizes; + PRUint32 bestWidth = aWidth; + PRUint32 bestHeight = aHeight; + PRUint32 minSizeDelta = PR_UINT32_MAX; + PRUint32 delta; + Size size; + + mParams.getSupportedPreviewSizes(previewSizes); + + if (!aWidth && !aHeight) { + // no size specified, take the first supported size + size = previewSizes[0]; + bestWidth = size.width; + bestHeight = size.height; + } else if (aWidth && aHeight) { + // both height and width specified, find the supported size closest to requested size + for (PRUint32 i = 0; i < previewSizes.size(); i++) { + Size size = previewSizes[i]; + PRUint32 delta = abs((long int)(size.width * size.height - aWidth * aHeight)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + bestWidth = size.width; + bestHeight = size.height; + } + } + } else if (!aWidth) { + // width not specified, find closest height match + for (PRUint32 i = 0; i < previewSizes.size(); i++) { + size = previewSizes[i]; + delta = abs((long int)(size.height - aHeight)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + bestWidth = size.width; + bestHeight = size.height; + } + } + } else if (!aHeight) { + // height not specified, find closest width match + for (PRUint32 i = 0; i < previewSizes.size(); i++) { + size = previewSizes[i]; + delta = abs((long int)(size.width - aWidth)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + bestWidth = size.width; + bestHeight = size.height; + } + } + } + + mWidth = bestWidth; + mHeight = bestHeight; + mParams.setPreviewSize(mWidth, mHeight); +} + +void +GonkCameraHardware::SetPreviewSize(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->SetPreviewSize(aWidth, aHeight); + } +} + +int +GonkCameraHardware::AutoFocus(PRUint32 aHwHandle) +{ + DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle); + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + hw->mHardware->enableMsgType(CAMERA_MSG_FOCUS); + return hw->mHardware->autoFocus(); +} + +void +GonkCameraHardware::CancelAutoFocus(PRUint32 aHwHandle) +{ + DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle); + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->cancelAutoFocus(); + } +} + +int +GonkCameraHardware::TakePicture(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + hw->mHardware->enableMsgType(CAMERA_MSG_COMPRESSED_IMAGE); + return hw->mHardware->takePicture(); +} + +void +GonkCameraHardware::CancelTakePicture(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->cancelPicture(); + } +} + +int +GonkCameraHardware::PushParameters(PRUint32 aHwHandle, const CameraParameters& aParams) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + return hw->mHardware->setParameters(aParams); +} + +void +GonkCameraHardware::PullParameters(PRUint32 aHwHandle, CameraParameters& aParams) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + aParams = hw->mHardware->getParameters(); + } +} + +int +GonkCameraHardware::StartPreview() +{ + const char* format; + + mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + + DOM_CAMERA_LOGI("Preview formats: %s\n", mParams.get(mParams.KEY_SUPPORTED_PREVIEW_FORMATS)); + + // try to set preferred image format and frame rate + const char* const PREVIEW_FORMAT = "yuv420p"; + const char* const BAD_PREVIEW_FORMAT = "yuv420sp"; + mParams.setPreviewFormat(PREVIEW_FORMAT); + mParams.setPreviewFrameRate(mFps); + mHardware->setParameters(mParams); + + // check that our settings stuck + mParams = mHardware->getParameters(); + format = mParams.getPreviewFormat(); + if (strcmp(format, PREVIEW_FORMAT) == 0) { + mPreviewFormat = PREVIEW_FORMAT_YUV420P; /* \o/ */ + } else if (strcmp(format, BAD_PREVIEW_FORMAT) == 0) { + mPreviewFormat = PREVIEW_FORMAT_YUV420SP; + DOM_CAMERA_LOGA("Camera ignored our request for '%s' preview, will have to convert (from %d)\n", PREVIEW_FORMAT, mPreviewFormat); + } else { + mPreviewFormat = PREVIEW_FORMAT_UNKNOWN; + DOM_CAMERA_LOGE("Camera ignored our request for '%s' preview, returned UNSUPPORTED format '%s'\n", PREVIEW_FORMAT, format); + } + + // Check the frame rate and log if the camera ignored our setting + PRUint32 fps = mParams.getPreviewFrameRate(); + if (fps != mFps) { + DOM_CAMERA_LOGA("We asked for %d fps but camera returned %d fps, using it", mFps, fps); + mFps = fps; + } + + return mHardware->startPreview(); +} + +int +GonkCameraHardware::StartPreview(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + DOM_CAMERA_LOGI("%s:%d : aHwHandle = %d, hw = %p\n", __func__, __LINE__, aHwHandle, hw); + if (!hw) { + return DEAD_OBJECT; + } + + return hw->StartPreview(); +} + +void +GonkCameraHardware::StopPreview(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->stopPreview(); + } +} + +PRUint32 +GonkCameraHardware::GetPreviewFormat(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return PREVIEW_FORMAT_UNKNOWN; + } + + return hw->mPreviewFormat; +} diff --git a/dom/camera/GonkCameraHwMgr.h b/dom/camera/GonkCameraHwMgr.h new file mode 100644 index 00000000000..5b85813363e --- /dev/null +++ b/dom/camera/GonkCameraHwMgr.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKCAMERAHWMGR_H +#define DOM_CAMERA_GONKCAMERAHWMGR_H + +#include "libcameraservice/CameraHardwareInterface.h" +#include "binder/IMemory.h" +#include "mozilla/ReentrantMonitor.h" + +#include "GonkCameraControl.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +// config +#define GIHM_TIMING_RECEIVEFRAME 0 +#define GIHM_TIMING_OVERALL 1 + +using namespace mozilla; +using namespace android; + +namespace mozilla { + +typedef class nsGonkCameraControl GonkCamera; + +class GonkCameraHardware +{ +protected: + GonkCameraHardware(GonkCamera* aTarget, PRUint32 aCamera); + ~GonkCameraHardware(); + void init(); + + static void DataCallback(int32_t aMsgType, const sp &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser); + static void NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser); + +public: + static void ReleaseHandle(PRUint32 aHwHandle); + static PRUint32 GetHandle(GonkCamera* aTarget, PRUint32 aCamera); + static PRUint32 GetFps(PRUint32 aHwHandle); + static void GetPreviewSize(PRUint32 aHwHandle, PRUint32* aWidth, PRUint32* aHeight); + static void SetPreviewSize(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight); + static int AutoFocus(PRUint32 aHwHandle); + static void CancelAutoFocus(PRUint32 aHwHandle); + static int TakePicture(PRUint32 aHwHandle); + static void CancelTakePicture(PRUint32 aHwHandle); + static int StartPreview(PRUint32 aHwHandle); + static void StopPreview(PRUint32 aHwHandle); + static int PushParameters(PRUint32 aHwHandle, const CameraParameters& aParams); + static void PullParameters(PRUint32 aHwHandle, CameraParameters& aParams); + + enum { + PREVIEW_FORMAT_UNKNOWN, + PREVIEW_FORMAT_YUV420P, + PREVIEW_FORMAT_YUV420SP + }; + // GetPreviewFormat() MUST be called only after StartPreview(). + static PRUint32 GetPreviewFormat(PRUint32 aHwHandle); + +protected: + static GonkCameraHardware* sHw; + static PRUint32 sHwHandle; + + static GonkCameraHardware* GetHardware(PRUint32 aHwHandle) + { + if (aHwHandle == sHwHandle) { + /** + * In the initial case, sHw will be null and sHwHandle will be 0, + * so even if this function is called with aHwHandle = 0, the + * result will still be null. + */ + return sHw; + } + return nullptr; + } + + // Instance wrappers to make member function access easier. + void SetPreviewSize(PRUint32 aWidth, PRUint32 aHeight); + int StartPreview(); + + PRUint32 mCamera; + PRUint32 mWidth; + PRUint32 mHeight; + PRUint32 mFps; + PRUint32 mPreviewFormat; + bool mClosing; + mozilla::ReentrantMonitor mMonitor; + PRUint32 mNumFrames; + sp mHardware; + GonkCamera* mTarget; + camera_module_t* mModule; + sp mWindow; + CameraParameters mParams; +#if GIHM_TIMING_OVERALL + struct timespec mStart; + struct timespec mAutoFocusStart; +#endif + bool mInitialized; + + bool IsInitialized() + { + return mInitialized; + } + +private: + GonkCameraHardware(const GonkCameraHardware&) MOZ_DELETE; + GonkCameraHardware& operator=(const GonkCameraHardware&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // GONK_IMPL_HW_MGR_H diff --git a/dom/camera/GonkCameraManager.cpp b/dom/camera/GonkCameraManager.cpp new file mode 100644 index 00000000000..6911f4c8b77 --- /dev/null +++ b/dom/camera/GonkCameraManager.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jsapi.h" +#include "libcameraservice/CameraHardwareInterface.h" +#include "GonkCameraControl.h" +#include "DOMCameraManager.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +// From nsDOMCameraManager, but gonk-specific! + +/* [implicit_jscontext] jsval getListOfCameras (); */ +NS_IMETHODIMP +nsDOMCameraManager::GetListOfCameras(JSContext* cx, JS::Value* _retval) +{ + JSObject* a = JS_NewArrayObject(cx, 0, nullptr); + camera_module_t* module; + PRUint32 index = 0; + PRUint32 count; + + if (!a) { + DOM_CAMERA_LOGE("getListOfCameras : Could not create array object"); + return NS_ERROR_OUT_OF_MEMORY; + } + if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t**)&module) < 0) { + DOM_CAMERA_LOGE("getListOfCameras : Could not load camera HAL module"); + return NS_ERROR_NOT_AVAILABLE; + } + + count = module->get_number_of_cameras(); + DOM_CAMERA_LOGI("getListOfCameras : get_number_of_cameras() returned %d\n", count); + while (count--) { + struct camera_info info; + int rv = module->get_camera_info(count, &info); + if (rv != 0) { + DOM_CAMERA_LOGE("getListOfCameras : get_camera_info(%d) failed: %d\n", count, rv); + continue; + } + + JSString* v; + jsval jv; + + switch (info.facing) { + case CAMERA_FACING_BACK: + v = JS_NewStringCopyZ(cx, "back"); + index = 0; + break; + + case CAMERA_FACING_FRONT: + v = JS_NewStringCopyZ(cx, "front"); + index = 1; + break; + + default: + // TODO: handle extra cameras in getCamera(). + { + static PRUint32 extraIndex = 2; + nsCString s; + s.AppendPrintf("extra-camera-%d", count); + v = JS_NewStringCopyZ(cx, s.get()); + index = extraIndex++; + } + break; + } + if (!v) { + DOM_CAMERA_LOGE("getListOfCameras : out of memory populating camera list"); + delete a; + return NS_ERROR_NOT_AVAILABLE; + } + jv = STRING_TO_JSVAL(v); + if (!JS_SetElement(cx, a, index, &jv)) { + DOM_CAMERA_LOGE("getListOfCameras : failed building list of cameras"); + delete a; + return NS_ERROR_NOT_AVAILABLE; + } + } + + *_retval = OBJECT_TO_JSVAL(a); + return NS_OK; +} + +using namespace mozilla; + +NS_IMETHODIMP +GetCameraTask::Run() +{ + nsCOMPtr cameraControl = new nsGonkCameraControl(mCameraId, mCameraThread); + + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + return NS_DispatchToMainThread(new GetCameraResult(cameraControl, mOnSuccessCb)); +} diff --git a/dom/camera/GonkCameraPreview.cpp b/dom/camera/GonkCameraPreview.cpp new file mode 100644 index 00000000000..81960237b3e --- /dev/null +++ b/dom/camera/GonkCameraPreview.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "VideoUtils.h" +#include "GonkCameraHwMgr.h" +#include "GonkCameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 2 +#include "CameraCommon.h" + +using namespace mozilla; + +/** + * This big macro takes two 32-bit input blocks of interlaced u and + * v data (from a yuv420sp frame) in 's0' and 's1', and deinterlaces + * them into pairs of contiguous 32-bit blocks, the u plane data in + * 'u', and the v plane data in 'v' (i.e. for a yuv420p frame). + * + * yuv420sp: + * [ y-data ][ uv-data ] + * [ uv-data ]: [u0][v0][u1][v1][u2][v2]... + * + * yuv420p: + * [ y-data ][ u-data ][ v-data ] + * [ u-data ]: [u0][u1][u2]... + * [ v-data ]: [v0][v1][v2]... + * + * Doing this in 32-bit blocks is significantly faster than using + * byte-wise operations on ARM. (In some cases, the byte-wise + * de-interlacing can be too slow to keep up with the preview frames + * coming from the driver. + */ +#define DEINTERLACE( u, v, s0, s1 ) \ + u = ( (s0) & 0xFF00UL ) >> 8 | ( (s0) & 0xFF000000UL ) >> 16; \ + u |= ( (s1) & 0xFF00UL ) << 8 | ( (s1) & 0xFF000000UL ); \ + v = ( (s0) & 0xFFUL ) | ( (s0) & 0xFF0000UL ) >> 8; \ + v |= ( (s1) & 0xFFUL ) << 16 | ( (s1) & 0xFF0000UL ) << 8; + +void +GonkCameraPreview::ReceiveFrame(PRUint8 *aData, PRUint32 aLength) +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + if (mInput->HaveEnoughBuffered(TRACK_VIDEO)) { + if (mDiscardedFrameCount == 0) { + DOM_CAMERA_LOGI("mInput has enough data buffered, starting to discard\n"); + } + ++mDiscardedFrameCount; + return; + } else if (mDiscardedFrameCount) { + DOM_CAMERA_LOGI("mInput needs more data again; discarded %d frames in a row\n", mDiscardedFrameCount); + mDiscardedFrameCount = 0; + } + + switch (mFormat) { + case GonkCameraHardware::PREVIEW_FORMAT_YUV420SP: + { + // de-interlace the u and v planes + uint8_t* y = aData; + uint32_t yN = mWidth * mHeight; + + NS_ASSERTION(yN & 0x3 == 0, "Invalid image dimensions!"); + + uint32_t uvN = yN / 4; + uint32_t* src = (uint32_t*)( y + yN ); + uint32_t* d = new uint32_t[ uvN / 2 ]; + uint32_t* u = d; + uint32_t* v = u + uvN / 4; + + // we're handling pairs of 32-bit words, so divide by 8 + NS_ASSERTION(uvN & 0x7 == 0, "Invalid image dimensions!"); + uvN /= 8; + + while (uvN--) { + uint32_t src0 = *src++; + uint32_t src1 = *src++; + + uint32_t u0; + uint32_t v0; + uint32_t u1; + uint32_t v1; + + DEINTERLACE( u0, v0, src0, src1 ); + + src0 = *src++; + src1 = *src++; + + DEINTERLACE( u1, v1, src0, src1 ); + + *u++ = u0; + *u++ = u1; + *v++ = v0; + *v++ = v1; + } + + memcpy(y + yN, d, yN / 2); + delete[] d; + } + break; + + case GonkCameraHardware::PREVIEW_FORMAT_YUV420P: + // no transformating required + break; + + default: + // in a format we don't handle, get out of here + return; + } + + Image::Format format = Image::PLANAR_YCBCR; + nsRefPtr image = mImageContainer->CreateImage(&format, 1); + image->AddRef(); + PlanarYCbCrImage* videoImage = static_cast(image.get()); + + /** + * If you change either lumaBpp or chromaBpp, make sure the + * assertions below still hold. + */ + const PRUint8 lumaBpp = 8; + const PRUint8 chromaBpp = 4; + PlanarYCbCrImage::Data data; + data.mYChannel = aData; + data.mYSize = gfxIntSize(mWidth, mHeight); + + data.mYStride = mWidth * lumaBpp; + NS_ASSERTION(data.mYStride & 0x7 == 0, "Invalid image dimensions!"); + data.mYStride /= 8; + + data.mCbCrStride = mWidth * chromaBpp; + NS_ASSERTION(data.mCbCrStride & 0x7 == 0, "Invalid image dimensions!"); + data.mCbCrStride /= 8; + + data.mCbChannel = aData + mHeight * data.mYStride; + data.mCrChannel = data.mCbChannel + mHeight * data.mCbCrStride / 2; + data.mCbCrSize = gfxIntSize(mWidth / 2, mHeight / 2); + data.mPicX = 0; + data.mPicY = 0; + data.mPicSize = gfxIntSize(mWidth, mHeight); + data.mStereoMode = mozilla::layers::STEREO_MODE_MONO; + videoImage->SetData(data); // copies buffer + + mVideoSegment.AppendFrame(videoImage, 1, gfxIntSize(mWidth, mHeight)); + mInput->AppendToTrack(TRACK_VIDEO, &mVideoSegment); + + mFrameCount += 1; + + if ((mFrameCount % 10) == 0) { + DOM_CAMERA_LOGI("%s:%d : mFrameCount = %d\n", __func__, __LINE__, mFrameCount); + } +} + +void +GonkCameraPreview::Start() +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + /** + * We set and then immediately get the preview size, in case the camera + * driver has decided to ignore our given dimensions. We need to know + * the dimensions the driver is using so that, if needed, we can properly + * de-interlace the yuv420sp format in ReceiveFrame() above. + */ + GonkCameraHardware::SetPreviewSize(mHwHandle, mWidth, mHeight); + GonkCameraHardware::GetPreviewSize(mHwHandle, &mWidth, &mHeight); + SetFrameRate(GonkCameraHardware::GetFps(mHwHandle)); + + if (GonkCameraHardware::StartPreview(mHwHandle) == OK) { + // GetPreviewFormat() must be called after StartPreview(). + mFormat = GonkCameraHardware::GetPreviewFormat(mHwHandle); + DOM_CAMERA_LOGI("preview stream is (actually!) %d x %d (w x h), %d frames per second, format %d\n", mWidth, mHeight, mFramesPerSecond, mFormat); + } else { + DOM_CAMERA_LOGE("%s: failed to start preview\n", __func__); + } +} + +void +GonkCameraPreview::Stop() +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + GonkCameraHardware::StopPreview(mHwHandle); + mInput->EndTrack(TRACK_VIDEO); + mInput->Finish(); +} diff --git a/dom/camera/GonkCameraPreview.h b/dom/camera/GonkCameraPreview.h new file mode 100644 index 00000000000..1999976046f --- /dev/null +++ b/dom/camera/GonkCameraPreview.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKCAMERAPREVIEW_H +#define DOM_CAMERA_GONKCAMERAPREVIEW_H + +#include "CameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +namespace mozilla { + +class GonkCameraPreview : public CameraPreview +{ +public: + GonkCameraPreview(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight) + : CameraPreview(aWidth, aHeight) + , mHwHandle(aHwHandle) + , mDiscardedFrameCount(0) + , mFormat(GonkCameraHardware::PREVIEW_FORMAT_UNKNOWN) + { } + + void ReceiveFrame(PRUint8 *aData, PRUint32 aLength); + + void Start(); + void Stop(); + +protected: + ~GonkCameraPreview() + { + Stop(); + } + + PRUint32 mHwHandle; + PRUint32 mDiscardedFrameCount; + PRUint32 mFormat; + +private: + GonkCameraPreview(const GonkCameraPreview&) MOZ_DELETE; + GonkCameraPreview& operator=(const GonkCameraPreview&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_GONKCAMERAPREVIEW_H diff --git a/dom/camera/GonkNativeWindow.cpp b/dom/camera/GonkNativeWindow.cpp new file mode 100644 index 00000000000..163517f3216 --- /dev/null +++ b/dom/camera/GonkNativeWindow.cpp @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GonkNativeWindow.h" +#include "nsDebug.h" + +// enable debug logging by setting to 1 +#define CNW_DEBUG 0 +#if CNW_DEBUG +#define CNW_LOGD(...) {(void)printf_stderr(__VA_ARGS__);} +#else +#define CNW_LOGD(...) ((void)0) +#endif + +#define CNW_LOGE(...) {(void)printf_stderr(__VA_ARGS__);} + +using namespace android; + +GonkNativeWindow::GonkNativeWindow() +{ + GonkNativeWindow::init(); +} + +GonkNativeWindow::~GonkNativeWindow() +{ + freeAllBuffersLocked(); +} + +void GonkNativeWindow::init() +{ + // Initialize the ANativeWindow function pointers. + ANativeWindow::setSwapInterval = hook_setSwapInterval; + ANativeWindow::dequeueBuffer = hook_dequeueBuffer; + ANativeWindow::cancelBuffer = hook_cancelBuffer; + ANativeWindow::lockBuffer = hook_lockBuffer; + ANativeWindow::queueBuffer = hook_queueBuffer; + ANativeWindow::query = hook_query; + ANativeWindow::perform = hook_perform; + + mDefaultWidth = 0; + mDefaultHeight = 0; + mPixelFormat = 0; + mUsage = 0; + mTimestamp = NATIVE_WINDOW_TIMESTAMP_AUTO; + mBufferCount = MIN_BUFFER_SLOTS; + mFrameCounter = 0; +} + + +int GonkNativeWindow::hook_setSwapInterval(ANativeWindow* window, int interval) +{ + GonkNativeWindow* c = getSelf(window); + return c->setSwapInterval(interval); +} + +int GonkNativeWindow::hook_dequeueBuffer(ANativeWindow* window, + ANativeWindowBuffer** buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->dequeueBuffer(buffer); +} + +int GonkNativeWindow::hook_cancelBuffer(ANativeWindow* window, + ANativeWindowBuffer* buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->cancelBuffer(buffer); +} + +int GonkNativeWindow::hook_lockBuffer(ANativeWindow* window, + ANativeWindowBuffer* buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->lockBuffer(buffer); +} + +int GonkNativeWindow::hook_queueBuffer(ANativeWindow* window, + ANativeWindowBuffer* buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->queueBuffer(buffer); +} + +int GonkNativeWindow::hook_query(const ANativeWindow* window, + int what, int* value) +{ + const GonkNativeWindow* c = getSelf(window); + return c->query(what, value); +} + +int GonkNativeWindow::hook_perform(ANativeWindow* window, int operation, ...) +{ + va_list args; + va_start(args, operation); + GonkNativeWindow* c = getSelf(window); + return c->perform(operation, args); +} + +void GonkNativeWindow::freeBufferLocked(int i) +{ + if (mSlots[i].mGraphicBuffer != NULL) { + mSlots[i].mGraphicBuffer.clear(); + mSlots[i].mGraphicBuffer = NULL; + } + mSlots[i].mBufferState = BufferSlot::FREE; +} + +void GonkNativeWindow::freeAllBuffersLocked() +{ + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + freeBufferLocked(i); + } +} + +int GonkNativeWindow::setBufferCount(int bufferCount) { + CNW_LOGD("setBufferCount: count=%d", bufferCount); + Mutex::Autolock lock(mMutex); + + if (bufferCount > NUM_BUFFER_SLOTS) { + CNW_LOGE("setBufferCount: bufferCount larger than slots available"); + return BAD_VALUE; + } + + // special-case, nothing to do + if (bufferCount == mBufferCount) { + return OK; + } + + if (bufferCount < MIN_BUFFER_SLOTS) { + CNW_LOGE("setBufferCount: requested buffer count (%d) is less than " + "minimum (%d)", bufferCount, MIN_BUFFER_SLOTS); + return BAD_VALUE; + } + + // Error out if the user has dequeued buffers + for (int i=0 ; i mBufferCount) { + // easy, we just have more buffers + mBufferCount = bufferCount; + mDequeueCondition.signal(); + return OK; + } + + // reducing the number of buffers + // here we're guaranteed that the client doesn't have dequeued buffers + // and will release all of its buffer references. + freeAllBuffersLocked(); + mBufferCount = bufferCount; + mDequeueCondition.signal(); + return OK; +} + +int GonkNativeWindow::dequeueBuffer(android_native_buffer_t** buffer) +{ + Mutex::Autolock lock(mMutex); + + int found = -1; + int dequeuedCount = 0; + bool tryAgain = true; + + CNW_LOGD("dequeueBuffer: E"); + while (tryAgain) { + // look for a free buffer to give to the client + found = INVALID_BUFFER_SLOT; + dequeuedCount = 0; + for (int i = 0; i < mBufferCount; i++) { + const int state = mSlots[i].mBufferState; + if (state == BufferSlot::DEQUEUED) { + dequeuedCount++; + } + else if (state == BufferSlot::FREE) { + /* We return the oldest of the free buffers to avoid + * stalling the producer if possible. This is because + * the consumer may still have pending reads of the + * buffers in flight. + */ + bool isOlder = mSlots[i].mFrameNumber < mSlots[found].mFrameNumber; + if (found < 0 || isOlder) { + found = i; + } + } + } + + // we're in synchronous mode and didn't find a buffer, we need to + // wait for some buffers to be consumed + tryAgain = (found == INVALID_BUFFER_SLOT); + if (tryAgain) { + mDequeueCondition.wait(mMutex); + } + } + + if (found == INVALID_BUFFER_SLOT) { + // This should not happen. + CNW_LOGE("dequeueBuffer: no available buffer slots"); + return -EBUSY; + } + + const int buf = found; + + // buffer is now in DEQUEUED + mSlots[buf].mBufferState = BufferSlot::DEQUEUED; + + const sp& gbuf(mSlots[buf].mGraphicBuffer); + + if (gbuf == NULL) { + status_t error; + sp graphicBuffer( new GraphicBuffer( mDefaultWidth, mDefaultHeight, mPixelFormat, mUsage)); + error = graphicBuffer->initCheck(); + if (error != NO_ERROR) { + CNW_LOGE("dequeueBuffer: createGraphicBuffer failed with error %d",error); + return error; + } + mSlots[buf].mGraphicBuffer = graphicBuffer; + } + *buffer = mSlots[buf].mGraphicBuffer.get(); + + CNW_LOGD("dequeueBuffer: returning slot=%d buf=%p ", buf, + mSlots[buf].mGraphicBuffer->handle ); + + CNW_LOGD("dequeueBuffer: X"); + return NO_ERROR; +} + +int GonkNativeWindow::getSlotFromBufferLocked( + android_native_buffer_t* buffer) const +{ + if (buffer == NULL) { + CNW_LOGE("getSlotFromBufferLocked: encountered NULL buffer"); + return BAD_VALUE; + } + + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) { + return i; + } + } + CNW_LOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle); + return BAD_VALUE; +} + +int GonkNativeWindow::queueBuffer(ANativeWindowBuffer* buffer) +{ + Mutex::Autolock lock(mMutex); + CNW_LOGD("queueBuffer: E"); + int buf = getSlotFromBufferLocked(buffer); + + if (buf < 0 || buf >= mBufferCount) { + CNW_LOGE("queueBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + CNW_LOGE("queueBuffer: slot %d is not owned by the client " + "(state=%d)", buf, mSlots[buf].mBufferState); + return -EINVAL; + } + + int64_t timestamp; + if (mTimestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) { + timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + } else { + timestamp = mTimestamp; + } + + // Set the state to FREE as there are no operations on the queued buffer + // And, so that the buffer can be dequeued when needed. + mSlots[buf].mBufferState = BufferSlot::FREE; + mSlots[buf].mTimestamp = timestamp; + mFrameCounter++; + mSlots[buf].mFrameNumber = mFrameCounter; + + mDequeueCondition.signal(); + CNW_LOGD("queueBuffer: X"); + + return OK; +} + +int GonkNativeWindow::lockBuffer(ANativeWindowBuffer* buffer) +{ + CNW_LOGD("GonkNativeWindow::lockBuffer"); + Mutex::Autolock lock(mMutex); + return OK; +} + +int GonkNativeWindow::cancelBuffer(ANativeWindowBuffer* buffer) +{ + Mutex::Autolock lock(mMutex); + int buf = getSlotFromBufferLocked(buffer); + + CNW_LOGD("cancelBuffer: slot=%d", buf); + if (buf < 0 || buf >= mBufferCount) { + CNW_LOGE("cancelBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + CNW_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)", + buf, mSlots[buf].mBufferState); + return -EINVAL; + } + mSlots[buf].mBufferState = BufferSlot::FREE; + mSlots[buf].mFrameNumber = 0; + mDequeueCondition.signal(); + return OK; +} + +int GonkNativeWindow::perform(int operation, va_list args) +{ + switch (operation) { + case NATIVE_WINDOW_CONNECT: + // deprecated. must return NO_ERROR. + return NO_ERROR; + case NATIVE_WINDOW_DISCONNECT: + // deprecated. must return NO_ERROR. + return NO_ERROR; + case NATIVE_WINDOW_SET_USAGE: + return dispatchSetUsage(args); + case NATIVE_WINDOW_SET_BUFFER_COUNT: + return dispatchSetBufferCount(args); + case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: + return dispatchSetBuffersGeometry(args); + case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP: + return dispatchSetBuffersTimestamp(args); + case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: + return dispatchSetBuffersDimensions(args); + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: + return dispatchSetBuffersFormat(args); + case NATIVE_WINDOW_SET_CROP: + case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: + case NATIVE_WINDOW_SET_SCALING_MODE: + case NATIVE_WINDOW_LOCK: + case NATIVE_WINDOW_UNLOCK_AND_POST: + case NATIVE_WINDOW_API_CONNECT: + case NATIVE_WINDOW_API_DISCONNECT: + default: + return INVALID_OPERATION; + } +} + +int GonkNativeWindow::query(int what, int* outValue) const +{ + Mutex::Autolock lock(mMutex); + + int value; + switch (what) { + case NATIVE_WINDOW_WIDTH: + value = mDefaultWidth; + break; + case NATIVE_WINDOW_HEIGHT: + value = mDefaultHeight; + break; + case NATIVE_WINDOW_FORMAT: + value = mPixelFormat; + break; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + value = MIN_UNDEQUEUED_BUFFERS; + break; + default: + return BAD_VALUE; + } + outValue[0] = value; + return NO_ERROR; +} + +int GonkNativeWindow::setSwapInterval(int interval) +{ + return NO_ERROR; +} + +int GonkNativeWindow::dispatchSetUsage(va_list args) +{ + int usage = va_arg(args, int); + return setUsage(usage); +} + +int GonkNativeWindow::dispatchSetBufferCount(va_list args) +{ + size_t bufferCount = va_arg(args, size_t); + return setBufferCount(bufferCount); +} + +int GonkNativeWindow::dispatchSetBuffersGeometry(va_list args) +{ + int w = va_arg(args, int); + int h = va_arg(args, int); + int f = va_arg(args, int); + int err = setBuffersDimensions(w, h); + if (err != 0) { + return err; + } + return setBuffersFormat(f); +} + +int GonkNativeWindow::dispatchSetBuffersDimensions(va_list args) +{ + int w = va_arg(args, int); + int h = va_arg(args, int); + return setBuffersDimensions(w, h); +} + +int GonkNativeWindow::dispatchSetBuffersFormat(va_list args) +{ + int f = va_arg(args, int); + return setBuffersFormat(f); +} + +int GonkNativeWindow::dispatchSetBuffersTimestamp(va_list args) +{ + int64_t timestamp = va_arg(args, int64_t); + return setBuffersTimestamp(timestamp); +} + +int GonkNativeWindow::setUsage(uint32_t reqUsage) +{ + CNW_LOGD("GonkNativeWindow::setUsage"); + Mutex::Autolock lock(mMutex); + mUsage = reqUsage; + return OK; +} + +int GonkNativeWindow::setBuffersDimensions(int w, int h) +{ + CNW_LOGD("GonkNativeWindow::setBuffersDimensions"); + Mutex::Autolock lock(mMutex); + + if (w<0 || h<0) + return BAD_VALUE; + + if ((w && !h) || (!w && h)) + return BAD_VALUE; + + mDefaultWidth = w; + mDefaultHeight = h; + + return OK; +} + +int GonkNativeWindow::setBuffersFormat(int format) +{ + CNW_LOGD("GonkNativeWindow::setBuffersFormat"); + Mutex::Autolock lock(mMutex); + + if (format<0) + return BAD_VALUE; + + mPixelFormat = format; + + return NO_ERROR; +} + +int GonkNativeWindow::setBuffersTimestamp(int64_t timestamp) +{ + CNW_LOGD("GonkNativeWindow::setBuffersTimestamp"); + Mutex::Autolock lock(mMutex); + mTimestamp = timestamp; + return OK; +} diff --git a/dom/camera/GonkNativeWindow.h b/dom/camera/GonkNativeWindow.h new file mode 100644 index 00000000000..310dea55048 --- /dev/null +++ b/dom/camera/GonkNativeWindow.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKNATIVEWINDOW_H +#define DOM_CAMERA_GONKNATIVEWINDOW_H + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +namespace android { + +class GonkNativeWindow : public EGLNativeBase +{ +public: + enum { MIN_UNDEQUEUED_BUFFERS = 2 }; + enum { MIN_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS }; + enum { NUM_BUFFER_SLOTS = 32 }; + + GonkNativeWindow(); + ~GonkNativeWindow(); // this class cannot be overloaded + + // ANativeWindow hooks + static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer); + static int hook_lockBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_perform(ANativeWindow* window, int operation, ...); + static int hook_query(const ANativeWindow* window, int what, int* value); + static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_setSwapInterval(ANativeWindow* window, int interval); + +protected: + virtual int cancelBuffer(ANativeWindowBuffer* buffer); + virtual int dequeueBuffer(ANativeWindowBuffer** buffer); + virtual int lockBuffer(ANativeWindowBuffer* buffer); + virtual int perform(int operation, va_list args); + virtual int query(int what, int* value) const; + virtual int queueBuffer(ANativeWindowBuffer* buffer); + virtual int setSwapInterval(int interval); + + virtual int setBufferCount(int bufferCount); + virtual int setBuffersDimensions(int w, int h); + virtual int setBuffersFormat(int format); + virtual int setBuffersTimestamp(int64_t timestamp); + virtual int setUsage(uint32_t reqUsage); + + // freeBufferLocked frees the resources (both GraphicBuffer and EGLImage) + // for the given slot. + void freeBufferLocked(int index); + + // freeAllBuffersLocked frees the resources (both GraphicBuffer and + // EGLImage) for all slots. + void freeAllBuffersLocked(); + +private: + void init(); + + int dispatchSetBufferCount(va_list args); + int dispatchSetBuffersGeometry(va_list args); + int dispatchSetBuffersDimensions(va_list args); + int dispatchSetBuffersFormat(va_list args); + int dispatchSetBuffersTimestamp(va_list args); + int dispatchSetUsage(va_list args); + + int getSlotFromBufferLocked(android_native_buffer_t* buffer) const; + +private: + enum { INVALID_BUFFER_SLOT = -1 }; + + struct BufferSlot { + + BufferSlot() + : mGraphicBuffer(0), + mBufferState(BufferSlot::FREE), + mTimestamp(0), + mFrameNumber(0){ + } + + // mGraphicBuffer points to the buffer allocated for this slot or is NULL + // if no buffer has been allocated. + sp mGraphicBuffer; + + // BufferState represents the different states in which a buffer slot + // can be. + enum BufferState { + // FREE indicates that the buffer is not currently being used and + // will not be used in the future until it gets dequeued and + // subsequently queued by the client. + FREE = 0, + + // DEQUEUED indicates that the buffer has been dequeued by the + // client, but has not yet been queued or canceled. The buffer is + // considered 'owned' by the client, and the server should not use + // it for anything. + // + // Note that when in synchronous-mode (mSynchronousMode == true), + // the buffer that's currently attached to the texture may be + // dequeued by the client. That means that the current buffer can + // be in either the DEQUEUED or QUEUED state. In asynchronous mode, + // however, the current buffer is always in the QUEUED state. + DEQUEUED = 1, + + // QUEUED indicates that the buffer has been queued by the client, + // and has not since been made available for the client to dequeue. + // Attaching the buffer to the texture does NOT transition the + // buffer away from the QUEUED state. However, in Synchronous mode + // the current buffer may be dequeued by the client under some + // circumstances. See the note about the current buffer in the + // documentation for DEQUEUED. + QUEUED = 2, + }; + + // mBufferState is the current state of this buffer slot. + BufferState mBufferState; + + // mTimestamp is the current timestamp for this buffer slot. This gets + // to set by queueBuffer each time this slot is queued. + int64_t mTimestamp; + + // mFrameNumber is the number of the queued frame for this slot. + uint64_t mFrameNumber; + }; + + // mSlots is the array of buffer slots that must be mirrored on the client + // side. This allows buffer ownership to be transferred between the client + // and server without sending a GraphicBuffer over binder. The entire array + // is initialized to NULL at construction time, and buffers are allocated + // for a slot when requestBuffer is called with that slot's index. + BufferSlot mSlots[NUM_BUFFER_SLOTS]; + + // mDequeueCondition condition used for dequeueBuffer in synchronous mode + mutable Condition mDequeueCondition; + + // mTimestamp is the timestamp that will be used for the next buffer queue + // operation. It defaults to NATIVE_WINDOW_TIMESTAMP_AUTO, which means that + // a timestamp is auto-generated when queueBuffer is called. + int64_t mTimestamp; + + // mDefaultWidth holds the default width of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultWidth; + + // mDefaultHeight holds the default height of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultHeight; + + // mPixelFormat holds the pixel format of allocated buffers. It is used + // in requestBuffers() if a format of zero is specified. + uint32_t mPixelFormat; + + // usage flag + uint32_t mUsage; + + // mBufferCount is the number of buffer slots that the client and server + // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed + // by calling setBufferCount or setBufferCountServer + int mBufferCount; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables. It must be locked whenever the member variables are accessed. + mutable Mutex mMutex; + + // mFrameCounter is the free running counter, incremented for every buffer queued + uint64_t mFrameCounter; +}; + +}; // namespace android + +#endif // DOM_CAMERA_GONKNATIVEWINDOW_H diff --git a/dom/camera/Makefile.in b/dom/camera/Makefile.in new file mode 100644 index 00000000000..a86c3ff7500 --- /dev/null +++ b/dom/camera/Makefile.in @@ -0,0 +1,52 @@ +# 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/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = dom +LIBRARY_NAME = domcamera_s +XPIDL_MODULE = dom_camera +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/dom/dom-config.mk + +CPPSRCS = \ + DOMCameraManager.cpp \ + CameraControl.cpp \ + CameraPreview.cpp \ + $(NULL) + +ifeq ($(MOZ_B2G_CAMERA),1) +CPPSRCS += \ + GonkCameraManager.cpp \ + GonkCameraControl.cpp \ + GonkCameraHwMgr.cpp \ + GonkCameraPreview.cpp \ + GonkNativeWindow.cpp \ + GonkCameraCapabilities.cpp \ + $(NULL) +else +CPPSRCS += \ + FallbackCameraManager.cpp \ + FallbackCameraControl.cpp \ + FallbackCameraCapabilities.cpp \ + $(NULL) +endif + +XPIDLSRCS = \ + nsIDOMNavigatorCamera.idl \ + nsIDOMCameraManager.idl \ + $(NULL) + +EXPORTS = \ + DOMCameraManager.h \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/dom/camera/nsIDOMCameraManager.idl b/dom/camera/nsIDOMCameraManager.idl new file mode 100644 index 00000000000..9863658dd66 --- /dev/null +++ b/dom/camera/nsIDOMCameraManager.idl @@ -0,0 +1,341 @@ +#include "domstubs.idl" + +#include "nsIDOMMediaStream.idl" +#include "nsIDOMDOMRequest.idl" + + +interface nsIDOMBlob; + +/* Used to set the dimensions of a captured picture, + a preview stream, a video capture stream, etc. */ +dictionary CameraSize { + unsigned long width; + unsigned long height; +}; + +/* Camera regions are used to set focus and metering areas; + the coordinates are referenced to the sensor: + (-1000, -1000) is the top left corner + (1000, 1000) is the bottom left corner + The weight of the region can range from 0 to 1000. */ +dictionary CameraRegion { + long top; + long left; + long bottom; + long right; + unsigned long weight; +}; + +/* The position information to record in the image header. + 'NaN' indicates the information is not available. */ +dictionary CameraPosition { + double latitude; + double longitude; + double altitude; + double timestamp; +}; + +/* Select a camera to use. */ +dictionary CameraSelector { + DOMString camera = "back"; +}; + +[scriptable, uuid(64196840-0d03-4b65-a955-790f43a4b810)] +interface nsICameraCapabilities : nsISupports +{ + /* an array of objects with 'height' and 'width' properties + supported for the preview stream */ + [implicit_jscontext] + readonly attribute jsval previewSizes; + + /* an array of objects with 'height' and 'width' properties + supported for picture taking */ + [implicit_jscontext] + readonly attribute jsval pictureSizes; + + /* an array of strings, e.g. [ "jpeg", "rgb565" ] */ + [implicit_jscontext] + readonly attribute jsval fileFormats; + + /* an array of strings, e.g. [ "auto", "fluorescent", etc. ] */ + [implicit_jscontext] + readonly attribute jsval whiteBalanceModes; + + /* an array of strings, e.g. [ "auto", "night", "beach", etc. ] */ + [implicit_jscontext] + readonly attribute jsval sceneModes; + + /* an array of strings, e.g. [ "normal", "sepia", "mono", etc. ] */ + [implicit_jscontext] + readonly attribute jsval effects; + + /* an array of strings, e.g. [ "auto", "off", "on", etc. ] */ + [implicit_jscontext] + readonly attribute jsval flashModes; + + /* an array of strings, e.g. [ "auto", "fixed", "macro", etc. ] */ + [implicit_jscontext] + readonly attribute jsval focusModes; + + /* the maximum number of focus areas supported by the camera */ + [implicit_jscontext] + readonly attribute long maxFocusAreas; + + /* the minimum supported exposure compensation value */ + [implicit_jscontext] + readonly attribute double minExposureCompensation; + + /* the maximum supported exposure compensation value */ + [implicit_jscontext] + readonly attribute double maxExposureCompensation; + + /* exposure compensation minimum step-size */ + [implicit_jscontext] + readonly attribute double stepExposureCompensation; + + /* the maximum number of metering areas supported by the camera */ + [implicit_jscontext] + readonly attribute long maxMeteringAreas; + + /* an array of doubles, e.g. [ 1.0, 1.2, 1.5, 2.0, 3.0, etc. ], + or null if zooming is not supported */ + [implicit_jscontext] + readonly attribute jsval zoomRatios; + + /* an array of objects with 'height' and 'width' properties + supported for video recording */ + [implicit_jscontext] + readonly attribute jsval videoSizes; +}; + +/* + These properties only affect the captured image; + invalid property settings are ignored. +*/ +dictionary CameraPictureOptions +{ + /* an object with a combination of 'height' and 'width' properties + chosen from nsICameraCapabilities.pictureSizes */ + jsval pictureSize; + + /* one of the file formats chosen from + nsICameraCapabilities.fileFormats */ + DOMString fileFormat; + + /* the rotation of the image in degrees, from 0 to 270 in + steps of 90; this doesn't affect the image, only the + rotation recorded in the image header.*/ + long rotation; + + /* an object containing any or all of 'latitude', 'longitude', + 'altitude', and 'timestamp', used to record when and where + the image was taken. e.g. + { + latitude: 43.647118, + longitude: -79.3943, + altitude: 500 + // timestamp not specified, in this case, and + // won't be included in the image header + } + + can be null in the case where position information isn't + available/desired. + + 'altitude' is in metres; 'timestamp' is UTC, in seconds from + January 1, 1970. + */ + jsval position; +}; + +[scriptable, function, uuid(0444a687-4bc9-462c-8246-5423f0fe46a4)] +interface nsICameraPreviewStreamCallback : nsISupports +{ + void handleEvent(in nsIDOMMediaStream stream); +}; + +[scriptable, function, uuid(6baa4ac7-9c25-4c48-9bb0-5193b38b9b0a)] +interface nsICameraAutoFocusCallback : nsISupports +{ + void handleEvent(in boolean success); +}; + +[scriptable, function, uuid(17af779e-cb6f-4ca5-890c-06468ff82e4f)] +interface nsICameraTakePictureCallback : nsISupports +{ + void handleEvent(in nsIDOMBlob picture); +}; + +[scriptable, function, uuid(ac43f123-529c-48d3-84dd-ad206b7aca9b)] +interface nsICameraStartRecordingCallback : nsISupports +{ + void handleEvent(in nsIDOMMediaStream stream); +}; + +[scriptable, function, uuid(fb80db71-e315-42f0-9ea9-dd3dd312ed70)] +interface nsICameraShutterCallback : nsISupports +{ + void handleEvent(); +}; + +[scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)] +interface nsICameraErrorCallback : nsISupports +{ + void handleEvent(in DOMString error); +}; + +/* + attributes here affect the preview, any pictures taken, and/or + any video recorded by the camera. +*/ +[scriptable, uuid(3066c884-d2c3-4477-847d-08ea1c2d188a)] +interface nsICameraControl : nsISupports +{ + readonly attribute nsICameraCapabilities capabilities; + + /* one of the vales chosen from capabilities.effects; + default is "none" */ + attribute DOMString effect; + + /* one of the values chosen from capabilities.whiteBalanceModes; + default is "auto" */ + attribute DOMString whiteBalanceMode; + + /* one of the valus chosen from capabilities.sceneModes; + default is "auto" */ + attribute DOMString sceneMode; + + /* one of the values chosen from capabilities.flashModes; + default is "auto" */ + attribute DOMString flashMode; + + /* one of the values chosen from capabilities.focusModes; + default is "auto", if supported, or "fixed" */ + attribute DOMString focusMode; + + /* one of the values chosen from capabilities.zoomRatios; other + values will be rounded to the nearest supported value; + default is 1.0 */ + attribute double zoom; + + /* an array of one or more objects that define where the + camera will perform light metering, each defining the properties: + { + top: -1000, + left: -1000, + bottom: 1000, + right: 1000, + weight: 1000 + } + + 'top', 'left', 'bottom', and 'right' all range from -1000 at + the top-/leftmost of the sensor to 1000 at the bottom-/rightmost + of the sensor. + + objects missing one or more of these properties will be ignored; + if the array contains more than capabilities.maxMeteringAreas, + extra areas will be ignored. + + this attribute can be set to null to allow the camera to determine + where to perform light metering. */ + [implicit_jscontext] + attribute jsval meteringAreas; + + /* an array of one or more objects that define where the camera will + perform auto-focusing, with the same definition as meteringAreas. + + if the array contains more than capabilities.maxFocusAreas, extra + areas will be ignored. + + this attribute can be set to null to allow the camera to determine + where to focus. */ + [implicit_jscontext] + attribute jsval focusAreas; + + /* focal length in millimetres */ + readonly attribute double focalLength; + + /* the distances in metres to where the image subject appears to be + in focus. 'focusDistanceOptimum' is where the subject will appear + sharpest; the difference between 'focusDistanceFar' and + 'focusDistanceNear' is the image's depth of field. + + 'focusDistanceFar' may be infinity. */ + readonly attribute double focusDistanceNear; + readonly attribute double focusDistanceOptimum; + readonly attribute double focusDistanceFar; + + /* 'compensation' is optional, and if missing, will + set the camera to use automatic exposure compensation. + + acceptable values must range from minExposureCompensation + to maxExposureCompensation in steps of stepExposureCompensation; + invalid values will be rounded to the nearest valid value. */ + [implicit_jscontext] + void setExposureCompensation([optional] in jsval compensation); + readonly attribute double exposureCompensation; + + /* the function to call on the camera's shutter event, to trigger + a shutter sound and/or a visual shutter indicator. */ + attribute nsICameraShutterCallback onShutter; + + /* tell the camera to attempt to focus the image */ + void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* capture an image and return it as a blob to the 'onSuccess' callback; + if the camera supports it, this may be invoked while the camera is + already recording video. + + invoking this function will stop the preview stream, which must be + manually restarted (e.g. by calling .play() on it). */ + [implicit_jscontext] + void takePicture(in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* start recording video; 'aOptions' define the frame size of to + capture, chosen from capabilities.videoSizes, e.g.: + { + width: 640, + height: 480 + } + */ + [implicit_jscontext] + void startRecording(in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* stop precording video. */ + void stopRecording(); + + /* get a media stream to be used as a camera viewfinder; the options + define the desired frame size of the preview, chosen from + capabilities.previewSizes, e.g.: + { + height: 640, + width: 480, + } + */ + [implicit_jscontext] + void getPreviewStream(in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); +}; + +[scriptable, function, uuid(a267afbc-d91c-413a-8de5-0b94aecffa3e)] +interface nsICameraGetCameraCallback : nsISupports +{ + void handleEvent(in nsICameraControl camera); +}; + +[scriptable, uuid(671ee624-0336-441a-a24e-26b5319f14fe)] +interface nsIDOMCameraManager : nsISupports +{ + /* get a camera instance; options will be used to specify which + camera to get from the list returned by getListOfCameras(), e.g.: + { + camera: front + } + */ + [implicit_jscontext] + void getCamera([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* return a JSON array of camera identifiers, e.g. + [ "front", "back" ] + */ + [implicit_jscontext] + jsval getListOfCameras(); +}; diff --git a/dom/camera/nsIDOMNavigatorCamera.idl b/dom/camera/nsIDOMNavigatorCamera.idl new file mode 100644 index 00000000000..587eb506cf4 --- /dev/null +++ b/dom/camera/nsIDOMNavigatorCamera.idl @@ -0,0 +1,15 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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 "nsISupports.idl" + +interface nsIDOMCameraManager; + +[scriptable, uuid(bbb2456a-a6c8-42c8-8f52-6de071097e4b)] +interface nsIDOMNavigatorCamera : nsISupports +{ + readonly attribute nsIDOMCameraManager mozCameras; +}; diff --git a/dom/dom-config.mk b/dom/dom-config.mk index f91f51a1ea0..92b372f88b0 100644 --- a/dom/dom-config.mk +++ b/dom/dom-config.mk @@ -30,6 +30,7 @@ DOM_SRCDIRS = \ layout/style \ layout/xul/base/src \ layout/xul/base/src/tree/src \ + dom/camera \ $(NULL) ifdef MOZ_B2G_RIL diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 63ae12aa6ea..b1f2fe626d6 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -523,7 +523,11 @@ var interfaceNamesInGlobalScope = "ContactTelephone", "ContactEmail", "SVGFitToViewBox", - "SVGAElement" + "SVGAElement", + "NavigatorCamera", + "CameraControl", + "CameraCapabilities", + "CameraManager" ] for (var i in Components.interfaces) { diff --git a/js/xpconnect/src/dictionary_helper_gen.conf b/js/xpconnect/src/dictionary_helper_gen.conf index 4061137b3e1..676320f6e10 100644 --- a/js/xpconnect/src/dictionary_helper_gen.conf +++ b/js/xpconnect/src/dictionary_helper_gen.conf @@ -18,7 +18,12 @@ dictionaries = [ [ 'GeoPositionOptions', 'nsIDOMGeoGeolocation.idl' ], [ 'DOMFileMetadataParameters', 'nsIDOMLockedFile.idl' ], [ 'XMLHttpRequestParameters', 'nsIXMLHttpRequest.idl' ], - [ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ] + [ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ], + [ 'CameraSize', 'nsIDOMCameraManager.idl' ], + [ 'CameraRegion', 'nsIDOMCameraManager.idl' ], + [ 'CameraPosition', 'nsIDOMCameraManager.idl' ], + [ 'CameraSelector', 'nsIDOMCameraManager.idl' ], + [ 'CameraPictureOptions', 'nsIDOMCameraManager.idl' ] ] # include file names diff --git a/layout/build/Makefile.in b/layout/build/Makefile.in index dff4e1193ef..71a50587b3b 100644 --- a/layout/build/Makefile.in +++ b/layout/build/Makefile.in @@ -121,6 +121,8 @@ ifdef MOZ_B2G_BT #{ SHARED_LIBRARY_LIBS += $(DEPTH)/dom/bluetooth/$(LIB_PREFIX)dombluetooth_s.$(LIB_SUFFIX) endif #} +SHARED_LIBRARY_LIBS += $(DEPTH)/dom/camera/$(LIB_PREFIX)domcamera_s.$(LIB_SUFFIX) + ifdef MOZ_B2G_RIL #{ SHARED_LIBRARY_LIBS += \ $(DEPTH)/dom/system/gonk/$(LIB_PREFIX)domsystemgonk_s.$(LIB_SUFFIX) \ @@ -273,4 +275,6 @@ ifdef MOZ_B2G_BT #{ LOCAL_INCLUDES += -I$(topsrcdir)/dom/bluetooth endif #} +LOCAL_INCLUDES += -I$(topsrcdir)/dom/camera + DEFINES += -D_IMPL_NS_LAYOUT diff --git a/mobile/xul/installer/package-manifest.in b/mobile/xul/installer/package-manifest.in index 5e5d9336c31..8cf2a0a8ed8 100644 --- a/mobile/xul/installer/package-manifest.in +++ b/mobile/xul/installer/package-manifest.in @@ -164,6 +164,7 @@ #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif +@BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt