Bug 1104913 - Update selection algorithms for preview, picture and video sizes to have sane defaults. r=mikeh, r=bz

This commit is contained in:
Andrew Osmond 2014-12-12 19:51:29 -08:00
parent 6c818493a3
commit 6a9e14e1fb
26 changed files with 522 additions and 188 deletions

View File

@ -20,10 +20,11 @@ using namespace mozilla;
CameraControlImpl::CameraControlImpl()
: mListenerLock(PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock"))
, mPreviewState(CameraControlListener::kPreviewStopped)
, mHardwareState(CameraControlListener::kHardwareClosed)
, mHardwareState(CameraControlListener::kHardwareUninitialized)
, mHardwareStateChangeReason(NS_OK)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mCurrentConfiguration.mMode = ICameraControl::kUnspecifiedMode;
// reuse the same camera thread to conserve resources
nsCOMPtr<nsIThread> ct = do_QueryInterface(sCameraThread);
@ -79,7 +80,7 @@ CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aN
}
#ifdef PR_LOGGING
const char* state[] = { "closed", "open", "failed" };
const char* state[] = { "uninitialized", "closed", "open", "failed" };
MOZ_ASSERT(aNewState >= 0);
if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
DOM_CAMERA_LOGI("New hardware state is '%s' (reason=0x%x)\n",

View File

@ -34,6 +34,7 @@ public:
enum HardwareState
{
kHardwareUninitialized,
kHardwareClosed,
kHardwareOpen,
kHardwareOpenFailed

View File

@ -202,6 +202,7 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
, mGetCameraPromise(aPromise)
, mWindow(aWindow)
, mPreviewState(CameraControlListener::kPreviewStopped)
, mSetInitialConfig(false)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mInput = new CameraPreviewMediaStream(this);
@ -236,8 +237,14 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
}
if (haveInitialConfig) {
config.mPreviewSize.width = aInitialConfig.mPreviewSize.mWidth;
config.mPreviewSize.height = aInitialConfig.mPreviewSize.mHeight;
rv = SelectPreviewSize(aInitialConfig.mPreviewSize, config.mPreviewSize);
if (NS_FAILED(rv)) {
mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
return;
}
config.mPictureSize.width = aInitialConfig.mPictureSize.mWidth;
config.mPictureSize.height = aInitialConfig.mPictureSize.mHeight;
config.mRecorderProfile = aInitialConfig.mRecorderProfile;
}
@ -274,6 +281,9 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
// Start the camera...
if (haveInitialConfig) {
rv = mCameraControl->Start(&config);
if (NS_SUCCEEDED(rv)) {
mSetInitialConfig = true;
}
} else {
rv = mCameraControl->Start();
}
@ -281,6 +291,9 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
} else {
if (haveInitialConfig) {
rv = mCameraControl->SetConfiguration(config);
if (NS_SUCCEEDED(rv)) {
mSetInitialConfig = true;
}
} else {
rv = NS_OK;
}
@ -308,6 +321,46 @@ nsDOMCameraControl::IsWindowStillActive()
return nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID());
}
nsresult
nsDOMCameraControl::SelectPreviewSize(const CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize)
{
if (aRequestedPreviewSize.mWidth && aRequestedPreviewSize.mHeight) {
aSelectedPreviewSize.width = aRequestedPreviewSize.mWidth;
aSelectedPreviewSize.height = aRequestedPreviewSize.mHeight;
} else {
/* Use the window width and height if no preview size is provided.
Note that the width and height are actually reversed from the
camera perspective. */
int32_t width = 0;
int32_t height = 0;
float ratio = 0.0;
nsresult rv;
rv = mWindow->GetDevicePixelRatio(&ratio);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mWindow->GetInnerWidth(&height);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mWindow->GetInnerHeight(&width);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(width > 0);
MOZ_ASSERT(height > 0);
MOZ_ASSERT(ratio > 0.0);
aSelectedPreviewSize.width = std::ceil(width * ratio);
aSelectedPreviewSize.height = std::ceil(height * ratio);
}
return NS_OK;
}
// Setter for weighted regions: { top, bottom, left, right, weight }
nsresult
nsDOMCameraControl::Set(uint32_t aKey, const Optional<Sequence<CameraRegion> >& aValue, uint32_t aLimit)
@ -801,9 +854,14 @@ nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
}
ICameraControl::Configuration config;
aRv = SelectPreviewSize(aConfiguration.mPreviewSize, config.mPreviewSize);
if (aRv.Failed()) {
return nullptr;
}
config.mRecorderProfile = aConfiguration.mRecorderProfile;
config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
config.mPictureSize.width = aConfiguration.mPictureSize.mWidth;
config.mPictureSize.height = aConfiguration.mPictureSize.mHeight;
config.mMode = ICameraControl::kPictureMode;
if (aConfiguration.mMode == CameraMode::Video) {
config.mMode = ICameraControl::kVideoMode;
@ -1041,6 +1099,20 @@ nsDOMCameraControl::DispatchStateEvent(const nsString& aType, const nsString& aS
DispatchTrustedEvent(event);
}
void
nsDOMCameraControl::OnGetCameraComplete()
{
// The hardware is open, so we can return a camera to JS, even if
// the preview hasn't started yet.
nsRefPtr<Promise> promise = mGetCameraPromise.forget();
if (promise) {
CameraGetPromiseData data;
data.mCamera = this;
data.mConfiguration = *mCurrentConfiguration;
promise->MaybeResolve(data);
}
}
// Camera Control event handlers--must only be called from the Main Thread!
void
nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState,
@ -1055,22 +1127,16 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
case CameraControlListener::kHardwareOpen:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
MOZ_ASSERT(aReason == NS_OK);
{
if (!mSetInitialConfig) {
// The hardware is open, so we can return a camera to JS, even if
// the preview hasn't started yet.
nsRefPtr<Promise> promise = mGetCameraPromise.forget();
if (promise) {
CameraGetPromiseData data;
data.mCamera = this;
data.mConfiguration = *mCurrentConfiguration;
promise->MaybeResolve(data);
}
OnGetCameraComplete();
}
break;
case CameraControlListener::kHardwareClosed:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
{
if (!mSetInitialConfig) {
nsRefPtr<Promise> promise = mReleasePromise.forget();
if (promise) {
promise->MaybeResolve(JS::UndefinedHandleValue);
@ -1102,6 +1168,9 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
NS_LITERAL_STRING("close"),
eventInit);
DispatchTrustedEvent(event);
} else {
// The configuration failed and we forced the camera to shutdown.
OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
}
break;
@ -1111,6 +1180,9 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
break;
case CameraControlListener::kHardwareUninitialized:
break;
default:
DOM_CAMERA_LOGE("DOM OnHardwareStateChange: UNKNOWN=%d\n", aState);
MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
@ -1227,9 +1299,17 @@ nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration
mCurrentConfiguration->mMaxMeteringAreas);
DOM_CAMERA_LOGI(" preview size (w x h) : %d x %d\n",
mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
DOM_CAMERA_LOGI(" picture size (w x h) : %d x %d\n",
mCurrentConfiguration->mPictureSize.mWidth, mCurrentConfiguration->mPictureSize.mHeight);
DOM_CAMERA_LOGI(" recorder profile : %s\n",
NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
if (mSetInitialConfig) {
OnGetCameraComplete();
mSetInitialConfig = false;
return;
}
nsRefPtr<Promise> promise = mSetConfigurationPromise.forget();
if (promise) {
promise->MaybeResolve(*aConfiguration);
@ -1241,6 +1321,9 @@ nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration
eventInit.mPreviewSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
mCurrentConfiguration->mPreviewSize.mWidth,
mCurrentConfiguration->mPreviewSize.mHeight);
eventInit.mPictureSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
mCurrentConfiguration->mPictureSize.mWidth,
mCurrentConfiguration->mPictureSize.mHeight);
nsRefPtr<CameraConfigurationEvent> event =
CameraConfigurationEvent::Constructor(this,
@ -1359,6 +1442,16 @@ nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsr
break;
case CameraControlListener::kInSetConfiguration:
if (mSetInitialConfig) {
// If the SetConfiguration() call in the constructor fails, there
// is nothing we can do except release the camera hardware. This
// will trigger a hardware state change, and when the flag that
// got us here is set in that handler, we replace the normal reason
// code with one that indicates the hardware isn't available.
DOM_CAMERA_LOGI("Failed to configure cached camera, stopping\n");
mCameraControl->Stop();
return;
}
promise = mSetConfigurationPromise.forget();
break;

View File

@ -171,6 +171,7 @@ protected:
void OnTakePictureComplete(nsIDOMBlob* aPicture);
void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces);
void OnGetCameraComplete();
void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState, nsresult aReason);
void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
@ -179,6 +180,7 @@ protected:
void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
bool IsWindowStillActive();
nsresult SelectPreviewSize(const dom::CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize);
nsresult NotifyRecordingStatusChange(const nsString& aMsg);
@ -222,6 +224,8 @@ protected:
nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
DOMCameraControlListener::PreviewState mPreviewState;
bool mSetInitialConfig;
#ifdef MOZ_WIDGET_GONK
// cached camera control, to improve start-up time
static StaticRefPtr<ICameraControl> sCachedCameraControl;

View File

@ -215,6 +215,8 @@ DOMCameraControlListener::OnConfigurationChange(const CameraListenerConfiguratio
config->mRecorderProfile = mConfiguration.mRecorderProfile;
config->mPreviewSize.mWidth = mConfiguration.mPreviewSize.width;
config->mPreviewSize.mHeight = mConfiguration.mPreviewSize.height;
config->mPictureSize.mWidth = mConfiguration.mPictureSize.width;
config->mPictureSize.mHeight = mConfiguration.mPictureSize.height;
config->mMaxMeteringAreas = mConfiguration.mMaxMeteringAreas;
config->mMaxFocusAreas = mConfiguration.mMaxFocusAreas;

View File

@ -62,7 +62,6 @@ using namespace android;
// Construct nsGonkCameraControl on the main thread.
nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
: mCameraId(aCameraId)
, mLastPictureSize({0, 0})
, mLastThumbnailSize({0, 0})
, mPreviewFps(30)
, mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
@ -136,10 +135,16 @@ nsGonkCameraControl::StartInternal(const Configuration* aInitialConfig)
}
OnHardwareStateChange(CameraControlListener::kHardwareOpen, NS_OK);
if (aInitialConfig) {
return StartPreviewImpl();
}
if (aInitialConfig) {
rv = StartPreviewInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
OnConfigurationChange();
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
}
return NS_OK;
}
@ -180,7 +185,7 @@ nsGonkCameraControl::Initialize()
mParams.Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
mCurrentConfiguration.mMaxFocusAreas = areas != -1 ? areas : 0;
mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mLastPictureSize);
mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mCurrentConfiguration.mPictureSize);
mParams.Get(CAMERA_PARAM_PREVIEWSIZE, mCurrentConfiguration.mPreviewSize);
nsString luminance; // check for support
@ -197,7 +202,7 @@ nsGonkCameraControl::Initialize()
DOM_CAMERA_LOGI(" - maximum metering areas: %u\n", mCurrentConfiguration.mMaxMeteringAreas);
DOM_CAMERA_LOGI(" - maximum focus areas: %u\n", mCurrentConfiguration.mMaxFocusAreas);
DOM_CAMERA_LOGI(" - default picture size: %u x %u\n",
mLastPictureSize.width, mLastPictureSize.height);
mCurrentConfiguration.mPictureSize.width, mCurrentConfiguration.mPictureSize.height);
DOM_CAMERA_LOGI(" - default picture file format: %s\n",
NS_ConvertUTF16toUTF8(mFileFormat).get());
DOM_CAMERA_LOGI(" - default picture quality: %f\n", quality);
@ -222,6 +227,11 @@ nsGonkCameraControl::Initialize()
mParams.Get(CAMERA_PARAM_VIDEOSIZE, mLastRecorderSize);
DOM_CAMERA_LOGI(" - default video recorder size: %u x %u\n",
mLastRecorderSize.width, mLastRecorderSize.height);
Size preferred;
mParams.Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
DOM_CAMERA_LOGI(" - preferred video preview size: %u x %u\n",
preferred.width, preferred.height);
} else {
mLastRecorderSize = mCurrentConfiguration.mPreviewSize;
}
@ -261,23 +271,66 @@ nsGonkCameraControl::~nsGonkCameraControl()
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
}
nsresult
nsGonkCameraControl::ValidateConfiguration(const Configuration& aConfig, Configuration& aValidatedConfig)
{
nsAutoTArray<Size, 16> supportedSizes;
Get(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes);
nsresult rv = GetSupportedSize(aConfig.mPictureSize, supportedSizes,
aValidatedConfig.mPictureSize);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGW("Unable to find a picture size close to %ux%u\n",
aConfig.mPictureSize.width, aConfig.mPictureSize.height);
return NS_ERROR_INVALID_ARG;
}
rv = LoadRecorderProfiles();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsString profileName = aConfig.mRecorderProfile;
if (profileName.IsEmpty()) {
profileName.AssignASCII("default");
}
RecorderProfile* profile;
if (!mRecorderProfiles.Get(profileName, &profile)) {
DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get());
return NS_ERROR_INVALID_ARG;
}
aValidatedConfig.mMode = aConfig.mMode;
aValidatedConfig.mPreviewSize = aConfig.mPreviewSize;
aValidatedConfig.mRecorderProfile = profile->GetName();
return NS_OK;
}
nsresult
nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
{
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
nsresult rv;
// Ensure sanity of all provided parameters and determine defaults if
// none are provided when given a new configuration
Configuration config;
nsresult rv = ValidateConfiguration(aConfig, config);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
{
ICameraControlParameterSetAutoEnter set(this);
switch (aConfig.mMode) {
switch (config.mMode) {
case kPictureMode:
rv = SetPictureConfiguration(aConfig);
rv = SetPictureConfiguration(config);
break;
case kVideoMode:
rv = SetVideoConfiguration(aConfig);
rv = SetVideoConfiguration(config);
break;
default:
@ -291,16 +344,15 @@ nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
return rv;
}
rv = Set(CAMERA_PARAM_RECORDINGHINT, aConfig.mMode == kVideoMode);
rv = Set(CAMERA_PARAM_RECORDINGHINT, config.mMode == kVideoMode);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set recording hint (0x%x)\n", rv);
}
}
mCurrentConfiguration.mMode = aConfig.mMode;
mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
OnConfigurationChange();
mCurrentConfiguration.mMode = config.mMode;
mCurrentConfiguration.mRecorderProfile = config.mRecorderProfile;
mCurrentConfiguration.mPictureSize = config.mPictureSize;
return NS_OK;
}
@ -335,7 +387,18 @@ nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig)
// Restart the preview
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
return StartPreviewImpl();
rv = StartPreviewInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
StopPreviewImpl();
return rv;
}
// OnConfigurationChange() indicates the success case of this operation.
// It must not be fired until all intermediate steps, including starting
// the preview, have completed successfully.
OnConfigurationChange();
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
return NS_OK;
}
nsresult
@ -412,39 +475,29 @@ nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig)
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
nsTArray<Size> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
Size max({0, 0});
nsresult rv = SelectCaptureAndPreviewSize(aConfig.mPreviewSize,
aConfig.mPictureSize, max,
CAMERA_PARAM_PICTURE_SIZE);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Size preview;
rv = GetSupportedSize(aConfig.mPreviewSize, sizes, preview);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE(
"Failed to find a supported preview size, requested size %ux%u (0x%x)",
aConfig.mPreviewSize.width, aConfig.mPreviewSize.height, rv);
return rv;
}
rv = Set(CAMERA_PARAM_PREVIEWSIZE, preview);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set supported preview size %ux%u (0x%x)",
preview.width, preview.height, rv);
return rv;
}
mCurrentConfiguration.mPreviewSize = preview;
if (mSeparateVideoAndPreviewSizesSupported) {
MaybeAdjustVideoSize();
}
rv = UpdateThumbnailSize();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mParams.Get(CAMERA_PARAM_PREVIEWFRAMERATE, mPreviewFps);
DOM_CAMERA_LOGI("picture mode preview: wanted %ux%u, got %ux%u (%u fps)\n",
aConfig.mPreviewSize.width, aConfig.mPreviewSize.height,
preview.width, preview.height,
mCurrentConfiguration.mPreviewSize.width,
mCurrentConfiguration.mPreviewSize.height,
mPreviewFps);
return NS_OK;
@ -683,7 +736,7 @@ nsGonkCameraControl::SetLocation(const Position& aLocation)
}
nsresult
nsGonkCameraControl::StartPreviewImpl()
nsGonkCameraControl::StartPreviewInternal()
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
RETURN_IF_NO_CAMERA_HW();
@ -702,10 +755,19 @@ nsGonkCameraControl::StartPreviewImpl()
return NS_ERROR_FAILURE;
}
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
return NS_OK;
}
nsresult
nsGonkCameraControl::StartPreviewImpl()
{
nsresult rv = StartPreviewInternal();
if (NS_SUCCEEDED(rv)) {
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
}
return rv;
}
nsresult
nsGonkCameraControl::StopPreviewImpl()
{
@ -814,8 +876,8 @@ nsGonkCameraControl::SetThumbnailSizeImpl(const Size& aSize)
if (area != 0 &&
delta < smallestDelta &&
supportedSizes[i].width * mLastPictureSize.height ==
mLastPictureSize.width * supportedSizes[i].height) {
supportedSizes[i].width * mCurrentConfiguration.mPictureSize.height ==
mCurrentConfiguration.mPictureSize.width * supportedSizes[i].height) {
smallestDelta = delta;
smallestDeltaIndex = i;
}
@ -898,7 +960,8 @@ nsGonkCameraControl::SetPictureSizeImpl(const Size& aSize)
return NS_ERROR_INVALID_ARG;
}
if (aSize.width == mLastPictureSize.width && aSize.height == mLastPictureSize.height) {
if (aSize.width == mCurrentConfiguration.mPictureSize.width &&
aSize.height == mCurrentConfiguration.mPictureSize.height) {
DOM_CAMERA_LOGI("Requested picture size %ux%u unchanged\n", aSize.width, aSize.height);
return NS_OK;
}
@ -926,7 +989,7 @@ nsGonkCameraControl::SetPictureSizeImpl(const Size& aSize)
return rv;
}
mLastPictureSize = best;
mCurrentConfiguration.mPictureSize = best;
// Finally, update the thumbnail size in case the picture aspect ratio changed.
// Some drivers will fail to take a picture if the thumbnail size is not the
@ -1377,6 +1440,11 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize,
uint32_t minSizeDelta = UINT32_MAX;
uint32_t delta;
if (aSupportedSizes.IsEmpty()) {
// no valid sizes
return rv;
}
if (!aSize.width && !aSize.height) {
// no size specified, take the first supported size
best = aSupportedSizes[0];
@ -1432,91 +1500,41 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize,
}
nsresult
nsGonkCameraControl::SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize)
nsGonkCameraControl::SelectCaptureAndPreviewSize(const Size& aPreviewSize,
const Size& aCaptureSize,
const Size& aMaxSize,
uint32_t aCaptureSizeKey)
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
DOM_CAMERA_LOGI("Setting video size to %ux%u, preview size to %ux%u\n",
aVideoSize.width, aVideoSize.height,
aPreviewSize.width, aPreviewSize.height);
// At this point, we know the capture size has been validated and replaced
// if necessary with the best matching supported value.
DOM_CAMERA_LOGI("Select capture size %ux%u, preview size %ux%u, maximum size %ux%u\n",
aCaptureSize.width, aCaptureSize.height,
aPreviewSize.width, aPreviewSize.height,
aMaxSize.width, aMaxSize.height);
Size oldSize;
nsresult rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize);
nsAutoTArray<Size, 16> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(CAMERA_PARAM_PREVIEWSIZE, aPreviewSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(CAMERA_PARAM_VIDEOSIZE, aVideoSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
Set(CAMERA_PARAM_VIDEOSIZE, oldSize); // error, try to restore the original preview size
return rv;
// May optionally apply a ceiling to the preview size. Any supported preview
// size with an area larger than the maximum will be ignored regardless of
// aspect ratio or delta to requested preview size.
uint32_t maxArea = aMaxSize.width * aMaxSize.height;
if (maxArea == 0) {
maxArea = UINT32_MAX;
}
mCurrentConfiguration.mPreviewSize = aPreviewSize;
mLastRecorderSize = aVideoSize;
const uint32_t previewArea = aPreviewSize.width * aPreviewSize.height;
return NS_OK;
}
nsresult
nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize)
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
nsTArray<Size> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Size video;
rv = GetSupportedSize(aVideoSize, sizes, video);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to find a supported video size, requested size %ux%u",
aVideoSize.width, aVideoSize.height);
return rv;
}
rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Size preview;
rv = GetSupportedSize(aConfig.mPreviewSize, sizes, preview);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to find a supported preview size, requested size %ux%u",
aConfig.mPreviewSize.width, aConfig.mPreviewSize.height);
return rv;
}
Size preferred;
rv = Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// If the requested preview size has the same aspect ratio as the
// requested video size, *and* is the same size or smaller than
// the preferred video size, then we're done.
const uint32_t preferredArea = preferred.width * preferred.height;
if (video.width * aConfig.mPreviewSize.height == aConfig.mPreviewSize.width * video.height &&
preview.width * preview.height <= preferredArea) {
// We're done: set the video and preview sizes and return...
return SetVideoAndPreviewSize(preview, video);
}
// Otherwise, if the requested preview size is larger than the preferred
// size, or there is an aspect ratio mismatch, then we need to set the
// preview size to the closest size smaller than the preferred size,
// preferably with the same aspect ratio as the requested video size.
// We should select a preview size with the same aspect ratio as the capture
// size and minimize the delta with the requested preview size. If we are
// unable to find any supported preview sizes which match the aspect ratio
// of the capture size, we fallback to only minimizing the delta with the
// requested preview size.
SizeIndex bestSizeMatch = 0; // initializers to keep warnings away
SizeIndex bestSizeMatchWithAspectRatio = 0;
@ -1528,13 +1546,19 @@ nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, con
for (SizeIndex i = 0; i < sizes.Length(); ++i) {
const Size& s = sizes[i];
const uint32_t area = s.width * s.height;
if (area > preferredArea) {
// preview size must be smaller or equal to the capture size
if (aCaptureSize.width < s.width || aCaptureSize.height < s.height) {
continue;
}
const uint32_t delta = preferredArea - area;
if (s.width * video.height == video.width * s.height) {
const uint32_t area = s.width * s.height;
if (area > maxArea) {
continue;
}
const uint32_t delta = abs(static_cast<long int>(previewArea - area));
if (s.width * aCaptureSize.height == aCaptureSize.width * s.height) {
if (delta == 0) {
// exact match, including aspect ratio--we can stop now
bestSizeMatchWithAspectRatio = i;
@ -1553,19 +1577,41 @@ nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, con
}
}
Size previewSize;
if (foundSizeMatchWithAspectRatio) {
preview = sizes[bestSizeMatchWithAspectRatio];
previewSize = sizes[bestSizeMatchWithAspectRatio];
} else if (foundSizeMatch) {
DOM_CAMERA_LOGW("Unable to match a preview size with aspect ratio of video size %ux%u\n",
video.width, video.height);
preview = sizes[bestSizeMatch];
DOM_CAMERA_LOGW("Unable to match a preview size with aspect ratio of capture size %ux%u\n",
aCaptureSize.width, aCaptureSize.height);
previewSize = sizes[bestSizeMatch];
} else {
DOM_CAMERA_LOGE("Unable to find a preview size for video size %ux%u\n",
video.width, video.height);
DOM_CAMERA_LOGE("Unable to find a preview size for capture size %ux%u\n",
aCaptureSize.width, aCaptureSize.height);
return NS_ERROR_INVALID_ARG;
}
return SetVideoAndPreviewSize(preview, video);
DOM_CAMERA_LOGI("Setting capture size to %ux%u, preview size to %ux%u\n",
aCaptureSize.width, aCaptureSize.height,
previewSize.width, previewSize.height);
Size oldSize;
rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(CAMERA_PARAM_PREVIEWSIZE, previewSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(aCaptureSizeKey, aCaptureSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
Set(CAMERA_PARAM_PREVIEWSIZE, oldSize); // error, try to restore the original preview size
return rv;
}
mCurrentConfiguration.mPreviewSize = previewSize;
return NS_OK;
}
nsresult
@ -1573,13 +1619,6 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
{
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
// The application may cache an old configuration and already have
// a desired recorder profile without checking the capabilities first
nsresult rv = LoadRecorderProfiles();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
RecorderProfile* profile;
if (!mRecorderProfiles.Get(aConfig.mRecorderProfile, &profile)) {
DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
@ -1605,7 +1644,14 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
if (mSeparateVideoAndPreviewSizesSupported) {
// The camera supports two video streams: a low(er) resolution preview
// stream and and a potentially high(er) resolution stream for encoding.
rv = SelectVideoAndPreviewSize(aConfig, size);
Size preferred;
rv = Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = SelectCaptureAndPreviewSize(aConfig.mPreviewSize, size, preferred,
CAMERA_PARAM_VIDEOSIZE);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set video and preview sizes (0x%x)\n", rv);
return rv;
@ -1623,6 +1669,8 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
mCurrentConfiguration.mPreviewSize = size;
}
mLastRecorderSize = size;
rv = Set(CAMERA_PARAM_PREVIEWFRAMERATE, static_cast<int>(fps));
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv);
@ -1903,9 +1951,12 @@ nsGonkCameraControl::LoadRecorderProfiles()
return NS_ERROR_NOT_AVAILABLE;
}
nsTArray<RecorderProfile>::size_type bestIndexMatch = 0;
int bestAreaMatch = 0;
// Limit profiles to those video sizes supported by the camera hardware...
for (nsTArray<RecorderProfile>::size_type i = 0; i < profiles.Length(); ++i) {
int width = profiles[i]->GetVideo().GetSize().width;
int width = profiles[i]->GetVideo().GetSize().width;
int height = profiles[i]->GetVideo().GetSize().height;
if (width < 0 || height < 0) {
DOM_CAMERA_LOGW("Ignoring weird profile '%s' with width and/or height < 0\n",
@ -1916,10 +1967,22 @@ nsGonkCameraControl::LoadRecorderProfiles()
if (static_cast<uint32_t>(width) == sizes[n].width &&
static_cast<uint32_t>(height) == sizes[n].height) {
mRecorderProfiles.Put(profiles[i]->GetName(), profiles[i]);
int area = width * height;
if (area > bestAreaMatch) {
bestIndexMatch = i;
bestAreaMatch = area;
}
break;
}
}
}
// Default profile is the one with the largest area.
if (bestAreaMatch > 0) {
nsAutoString name;
name.AssignASCII("default");
mRecorderProfiles.Put(name, profiles[bestIndexMatch]);
}
}
return NS_OK;

View File

@ -105,10 +105,12 @@ protected:
nsresult Initialize();
nsresult ValidateConfiguration(const Configuration& aConfig, Configuration& aValidatedConfig);
nsresult SetConfigurationInternal(const Configuration& aConfig);
nsresult SetPictureConfiguration(const Configuration& aConfig);
nsresult SetVideoConfiguration(const Configuration& aConfig);
nsresult StartInternal(const Configuration* aInitialConfig);
nsresult StartPreviewInternal();
nsresult StopInternal();
template<class T> nsresult SetAndPush(uint32_t aKey, const T& aValue);
@ -133,8 +135,8 @@ protected:
nsresult SetupRecording(int aFd, int aRotation, uint64_t aMaxFileSizeBytes,
uint64_t aMaxVideoLengthMs);
nsresult SetupRecordingFlash(bool aAutoEnableLowLightTorch);
nsresult SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize);
nsresult SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize);
nsresult SelectCaptureAndPreviewSize(const Size& aPreviewSize, const Size& aCaptureSize,
const Size& aMaxSize, uint32_t aCaptureSizeKey);
nsresult MaybeAdjustVideoSize();
nsresult PausePreview();
nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
@ -158,7 +160,6 @@ protected:
android::sp<android::GonkCameraHardware> mCameraHw;
Size mLastPictureSize;
Size mLastThumbnailSize;
Size mLastRecorderSize;
uint32_t mPreviewFps;

View File

@ -145,6 +145,7 @@ public:
struct Configuration {
Mode mMode;
Size mPreviewSize;
Size mPictureSize;
nsString mRecorderProfile;
};

View File

@ -13,3 +13,5 @@ support-files = camera_common.js
[test_bug1022766.html]
[test_bug1037322.html]
[test_bug1099390.html]
[test_bug1104913.html]
[test_camera_bad_initial_config.html]

View File

@ -15,7 +15,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -15,10 +15,10 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};
@ -57,14 +57,17 @@ var Camera = {
ok(cfg.mode === "unspecified", "Initial mode = " + cfg.mode);
ok(cfg.previewSize.width === 0 && cfg.previewSize.height === 0,
"Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
ok(cfg.recorderProfile === "",
ok(cfg.recorderProfile === "default",
"Initial recorder profile = '" + cfg.recorderProfile + "'");
// Apply our specific configuration
camera.setConfiguration(config).then(setConfig_onSuccess, onError);
}
navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
var cfg = {
mode: 'unspecified',
};
navigator.mozCameras.getCamera(whichCamera, cfg).then(getCamera_onSuccess, onError);
}
}

View File

@ -15,7 +15,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -0,0 +1,81 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for bug 1104913</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="camera_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<video id="viewfinder" width="200" height="200" autoplay></video>
<img src="#" alt="This image is going to load" id="testimage"/>
<script class="testbody" type="text/javascript;version=1.7">
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'qvga',
pictureSize: {
width: 640,
height: 480
},
previewSize: {
width: 320,
height: 240
}
};
function onError(e) {
ok(false, "Error: " + JSON.stringify(e));
}
var Camera = {
cameraObj: null,
get viewfinder() {
return document.getElementById('viewfinder');
},
start: function test_start() {
function getCamera_onSuccess(d) {
var camera = d.camera;
var cfg = d.configuration;
Camera.cameraObj = camera;
Camera.viewfinder.mozSrcObject = camera;
Camera.viewfinder.play();
// Check the default configuration
ok(cfg.mode === config.mode, "Initial mode = " + cfg.mode);
ok(cfg.previewSize.width === config.previewSize.width &&
cfg.previewSize.height === config.previewSize.height,
"Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
ok(cfg.pictureSize.width === config.pictureSize.width &&
cfg.pictureSize.height === config.pictureSize.height,
"Initial picture size = " + cfg.pictureSize.width + "x" + cfg.pictureSize.height);
ok(cfg.recorderProfile === config.recorderProfile,
"Initial recorder profile = '" + cfg.recorderProfile + "'");
SimpleTest.finish();
}
navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
}
}
SimpleTest.waitForExplicitFinish();
window.addEventListener('beforeunload', function() {
Camera.viewfinder.mozSrcObject = null;
if (Camera.cameraObj) {
Camera.cameraObj.release();
Camera.cameraObj = null;
}
});
Camera.start();
</script>
</body>
</html>

View File

@ -17,10 +17,10 @@ const Cr = Components.results;
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};
var options = {
@ -137,7 +137,7 @@ var tests = [
next();
}
var recordingOptions = {
profile: 'cif',
profile: 'high',
rotation: 0
};
camera.startRecording(recordingOptions,

View File

@ -14,10 +14,10 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var options = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};

View File

@ -14,10 +14,10 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var options = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};

View File

@ -14,7 +14,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var options = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for bad initial configuration</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="camera_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<video id="viewfinder" width="200" height="200" autoplay></video>
<img src="#" alt="This image is going to load" id="testimage"/>
<script class="testbody" type="text/javascript;version=1.7">
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'foobar',
};
var Camera = {
cameraObj: null,
get viewfinder() {
return document.getElementById('viewfinder');
},
start: function test_start() {
function getCamera_onSuccess(d) {
ok(false, "Get camera should have failed");
SimpleTest.finish();
}
function getCamera_onError(e) {
ok(true, "Get camera failed as expected: " + JSON.stringify(e));
SimpleTest.finish();
}
navigator.mozCameras.getCamera(whichCamera, config).then(getCamera_onSuccess, getCamera_onError);
}
}
SimpleTest.waitForExplicitFinish();
window.addEventListener('beforeunload', function() {
Camera.viewfinder.mozSrcObject = null;
if (Camera.cameraObj) {
Camera.cameraObj.release();
Camera.cameraObj = null;
}
});
Camera.start();
</script>
</body>
</html>

View File

@ -17,7 +17,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=965421
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=965420
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=940424
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -22,7 +22,7 @@ SimpleTest.waitForExplicitFinish();
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -10,6 +10,7 @@ interface CameraConfigurationEvent : Event
readonly attribute CameraMode mode;
readonly attribute DOMString recorderProfile;
readonly attribute DOMRectReadOnly? previewSize;
readonly attribute DOMRectReadOnly? pictureSize;
};
dictionary CameraConfigurationEventInit : EventInit
@ -17,4 +18,5 @@ dictionary CameraConfigurationEventInit : EventInit
CameraMode mode = "picture";
DOMString recorderProfile = "cif";
DOMRectReadOnly? previewSize = null;
DOMRectReadOnly? pictureSize = null;
};

View File

@ -255,7 +255,10 @@ interface CameraControl : MediaStream
/* the size of the picture to be returned by a call to takePicture();
an object with 'height' and 'width' properties that corresponds to
one of the options returned by capabilities.pictureSizes. */
one of the options returned by capabilities.pictureSizes.
note that unlike when one uses setConfiguration instead to update the
picture size, this will not recalculate the ideal preview size. */
[Throws]
CameraSize getPictureSize();
[Throws]
@ -287,6 +290,7 @@ interface CameraControl : MediaStream
to the display; e.g. if 'sensorAngle' is 270 degrees (or -90 degrees),
then the preview stream needs to be rotated +90 degrees to have the
same orientation as the real world. */
[Constant, Cached]
readonly attribute long sensorAngle;
/* the mode the camera will use to determine the correct exposure of
@ -360,11 +364,8 @@ interface CameraControl : MediaStream
/* the event dispatched when the camera is successfully configured.
event type is CameraConfigurationEvent where:
'mode' is the selected camera mode
'recorderProfile' is the selected profile
'width' contains the preview width
'height' contains the preview height */
event type is CameraConfigurationEvent which has the same members as
CameraConfiguration. */
attribute EventHandler onconfigurationchange;
/* if focusMode is set to either 'continuous-picture' or 'continuous-video',

View File

@ -15,13 +15,34 @@ dictionary CameraSize
unsigned long height = 0;
};
/* Pre-emptive camera configuration options. */
/* Pre-emptive camera configuration options. If 'mode' is set to "unspecified",
the camera will not be configured immediately. If the 'mode' is set to
"video" or "picture", then the camera automatically configures itself and
will be ready for use upon return.
The remaining parameters are optional and are considered hints by the
camera. The application should use the values returned in the
GetCameraCallback configuration because while the camera makes a best effort
to adhere to the requested values, it may need to change them to ensure
optimal behavior.
If not specified, 'pictureSize' and 'recorderProfile' default to the best or
highest resolutions supported by the camera hardware.
To determine 'previewSize', one should generally provide the size of the
element which will contain the preview rather than guess which supported
preview size is the best. If not specified, 'previewSize' defaults to the
inner window size. */
dictionary CameraConfiguration
{
CameraMode mode = "unspecified";
CameraMode mode = "picture";
CameraSize previewSize = null;
DOMString recorderProfile = ""; // one of the profiles reported by
// CameraControl.capabilities.recorderProfiles
CameraSize pictureSize = null;
/* one of the profiles reported by
CameraControl.capabilities.recorderProfiles
*/
DOMString recorderProfile = "default";
};
[Func="nsDOMCameraManager::HasSupport"]