From 9c15130c47897d13cf51453330f4afdaa37dcfcb Mon Sep 17 00:00:00 2001 From: Mike Habicher Date: Fri, 14 Nov 2014 20:56:59 -0500 Subject: [PATCH] Bug 1098660 - fix the (complex) requirements for video mode preview size, r=aosmond --- dom/camera/CameraControlImpl.cpp | 2 +- dom/camera/GonkCameraControl.cpp | 367 ++++++++++++++++++++-------- dom/camera/GonkCameraControl.h | 7 +- dom/camera/GonkCameraParameters.cpp | 2 + dom/camera/ICameraControl.h | 1 + 5 files changed, 273 insertions(+), 106 deletions(-) diff --git a/dom/camera/CameraControlImpl.cpp b/dom/camera/CameraControlImpl.cpp index 7e63473bd74..bec91b9b2ae 100644 --- a/dom/camera/CameraControlImpl.cpp +++ b/dom/camera/CameraControlImpl.cpp @@ -84,7 +84,7 @@ CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aN } #ifdef PR_LOGGING - const char* state[] = { "open", "closed", "failed" }; + const char* state[] = { "closed", "open", "failed" }; MOZ_ASSERT(aNewState >= 0); if (static_cast(aNewState) < sizeof(state) / sizeof(state[0])) { DOM_CAMERA_LOGI("New hardware state is '%s'\n", state[aNewState]); diff --git a/dom/camera/GonkCameraControl.cpp b/dom/camera/GonkCameraControl.cpp index 9fd95e68d2b..b4f24d99a27 100644 --- a/dom/camera/GonkCameraControl.cpp +++ b/dom/camera/GonkCameraControl.cpp @@ -313,22 +313,114 @@ nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig) return StartPreviewImpl(); } +nsresult +nsGonkCameraControl::MaybeAdjustVideoSize() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported); + + const Size& preview = mCurrentConfiguration.mPreviewSize; + + // Some camera drivers will ignore our preview size if it's larger + // than the currently set video recording size, so in picture mode, we + // give preview size priority, and bump up the video size just in case. + // This is done on a best-effort basis. + + if (preview.width <= mLastRecorderSize.width && + preview.height <= mLastRecorderSize.height) { + DOM_CAMERA_LOGI("Video size %ux%u is suitable for preview size %ux%u\n", + mLastRecorderSize.width, mLastRecorderSize.height, + preview.width, preview.height); + return NS_OK; + } + + nsTArray sizes; + nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + const uint32_t previewArea = preview.width * preview.height; + uint32_t bestDelta = UINT32_MAX; + bool foundBest = false; + SizeIndex best; + + for (SizeIndex i = 0; i < sizes.Length(); ++i) { + const Size& s = sizes[i]; + if (s.width < preview.width || s.height < preview.height) { + continue; + } + + const uint32_t area = s.width * s.height; + const uint32_t delta = area - previewArea; + if (delta < bestDelta) { + bestDelta = delta; + best = i; + foundBest = true; + } + } + + if (!foundBest) { + // If no candidate was found, the driver will be fine with a video size + // smaller than the chosen preview size. + DOM_CAMERA_LOGI("No video size candidate for preview size %ux%u (0x%x)\n", + preview.width, preview.height, rv); + return NS_OK; + } + + DOM_CAMERA_LOGI("Adjusting video size upwards to %ux%u\n", + sizes[best].width, sizes[best].height); + rv = Set(CAMERA_PARAM_VIDEOSIZE, sizes[best]); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGW("Failed to adjust video size for preview size %ux%u (0x%x)\n", + preview.width, preview.height, rv); + return rv; + } + + mLastRecorderSize = preview; + return NS_OK; +} + nsresult nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig) { DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); - nsresult rv = SetPreviewSize(aConfig.mPreviewSize); + nsTArray sizes; + nsresult 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 (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(); + } + 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, - mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height, - mPreviewFps); + aConfig.mPreviewSize.width, aConfig.mPreviewSize.height, + preview.width, preview.height, + mPreviewFps); return NS_OK; } @@ -695,11 +787,10 @@ nsGonkCameraControl::SetThumbnailSizeImpl(const Size& aSize) int area = supportedSizes[i].width * supportedSizes[i].height; int delta = abs(area - targetArea); - if (area != 0 - && delta < smallestDelta - && supportedSizes[i].width * mLastPictureSize.height / - supportedSizes[i].height == mLastPictureSize.width - ) { + if (area != 0 && + delta < smallestDelta && + supportedSizes[i].width * mLastPictureSize.height == + mLastPictureSize.width * supportedSizes[i].height) { smallestDelta = delta; smallestDeltaIndex = i; } @@ -1249,71 +1340,9 @@ nsGonkCameraControl::OnTakePictureError() NS_ERROR_FAILURE); } -nsresult -nsGonkCameraControl::SetPreviewSize(const Size& aSize) -{ - MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); - - nsTArray previewSizes; - nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, previewSizes); - if (NS_FAILED(rv)) { - DOM_CAMERA_LOGE("Camera failed to return any preview sizes (0x%x)\n", rv); - return rv; - } - - Size best; - rv = GetSupportedSize(aSize, previewSizes, best); - if (NS_FAILED(rv)) { - DOM_CAMERA_LOGE("Failed to find a supported preview size, requested size %dx%d", - aSize.width, aSize.height); - return rv; - } - - if (mSeparateVideoAndPreviewSizesSupported) { - // Some camera drivers will ignore our preview size if it's larger - // than the currently set video recording size, so we need to set - // the video size here as well, just in case. - if (best.width > mLastRecorderSize.width || best.height > mLastRecorderSize.height) { - SetVideoSize(best); - } - } else { - mLastRecorderSize = best; - } - mCurrentConfiguration.mPreviewSize = best; - return Set(CAMERA_PARAM_PREVIEWSIZE, best); -} - -nsresult -nsGonkCameraControl::SetVideoSize(const Size& aSize) -{ - MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); - - if (!mSeparateVideoAndPreviewSizesSupported) { - DOM_CAMERA_LOGE("Camera does not support setting separate video size\n"); - return NS_ERROR_NOT_AVAILABLE; - } - - nsTArray videoSizes; - nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, videoSizes); - if (NS_FAILED(rv)) { - DOM_CAMERA_LOGE("Camera failed to return any video sizes (0x%x)\n", rv); - return rv; - } - - Size best; - rv = GetSupportedSize(aSize, videoSizes, best); - if (NS_FAILED(rv)) { - DOM_CAMERA_LOGE("Failed to find a supported video size, requested size %dx%d", - aSize.width, aSize.height); - return rv; - } - mLastRecorderSize = best; - return Set(CAMERA_PARAM_VIDEOSIZE, best); -} - nsresult nsGonkCameraControl::GetSupportedSize(const Size& aSize, - const nsTArray& supportedSizes, + const nsTArray& aSupportedSizes, Size& best) { nsresult rv = NS_ERROR_INVALID_ARG; @@ -1323,24 +1352,25 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize, if (!aSize.width && !aSize.height) { // no size specified, take the first supported size - best = supportedSizes[0]; + best = aSupportedSizes[0]; return NS_OK; } else if (aSize.width && aSize.height) { // both height and width specified, find the supported size closest to // the requested size, looking for an exact match first - for (nsTArray::index_type i = 0; i < supportedSizes.Length(); i++) { - Size size = supportedSizes[i]; + for (SizeIndex i = 0; i < aSupportedSizes.Length(); ++i) { + Size size = aSupportedSizes[i]; if (size.width == aSize.width && size.height == aSize.height) { best = size; return NS_OK; } } - // no exact matches--look for a match closest in area - uint32_t targetArea = aSize.width * aSize.height; - for (nsTArray::index_type i = 0; i < supportedSizes.Length(); i++) { - Size size = supportedSizes[i]; - uint32_t delta = abs((long int)(size.width * size.height - targetArea)); + // no exact match on dimensions--look for a match closest in area + const uint32_t targetArea = aSize.width * aSize.height; + for (SizeIndex i = 0; i < aSupportedSizes.Length(); i++) { + Size size = aSupportedSizes[i]; + uint32_t delta = + abs(static_cast(size.width * size.height - targetArea)); if (delta < minSizeDelta) { minSizeDelta = delta; best = size; @@ -1349,9 +1379,9 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize, } } else if (!aSize.width) { // width not specified, find closest height match - for (nsTArray::index_type i = 0; i < supportedSizes.Length(); i++) { - Size size = supportedSizes[i]; - delta = abs((long int)(size.height - aSize.height)); + for (SizeIndex i = 0; i < aSupportedSizes.Length(); i++) { + Size size = aSupportedSizes[i]; + delta = abs(static_cast(size.height - aSize.height)); if (delta < minSizeDelta) { minSizeDelta = delta; best = size; @@ -1360,9 +1390,9 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize, } } else if (!aSize.height) { // height not specified, find closest width match - for (nsTArray::index_type i = 0; i < supportedSizes.Length(); i++) { - Size size = supportedSizes[i]; - delta = abs((long int)(size.width - aSize.width)); + for (SizeIndex i = 0; i < aSupportedSizes.Length(); i++) { + Size size = aSupportedSizes[i]; + delta = abs(static_cast(size.width - aSize.width)); if (delta < minSizeDelta) { minSizeDelta = delta; best = size; @@ -1370,9 +1400,147 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize, } } } + return rv; } +nsresult +nsGonkCameraControl::SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize) +{ + 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); + + Size oldSize; + nsresult rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize); + 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; + } + + mCurrentConfiguration.mPreviewSize = aPreviewSize; + mLastRecorderSize = aVideoSize; + + return NS_OK; +} + +nsresult +nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported); + + nsTArray 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. + + SizeIndex bestSizeMatch; + SizeIndex bestSizeMatchWithAspectRatio; + bool foundSizeMatch = false; + bool foundSizeMatchWithAspectRatio = false; + + uint32_t bestAreaDelta = UINT32_MAX; + uint32_t bestAreaDeltaWithAspect = UINT32_MAX; + + for (SizeIndex i = 0; i < sizes.Length(); ++i) { + const Size& s = sizes[i]; + const uint32_t area = s.width * s.height; + if (area > preferredArea) { + continue; + } + + const uint32_t delta = preferredArea - area; + if (s.width * video.height == video.width * s.height) { + if (delta == 0) { + // exact match, including aspect ratio--we can stop now + bestSizeMatchWithAspectRatio = i; + foundSizeMatchWithAspectRatio = true; + break; + } else if (delta < bestAreaDeltaWithAspect) { + // aspect ratio match + bestAreaDeltaWithAspect = delta; + bestSizeMatchWithAspectRatio = i; + foundSizeMatchWithAspectRatio = true; + } + } else if (delta < bestAreaDelta) { + bestAreaDelta = delta; + bestSizeMatch = i; + foundSizeMatch = true; + } + } + + if (foundSizeMatchWithAspectRatio) { + preview = 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]; + } else { + DOM_CAMERA_LOGE("Unable to find a preview size for video size %ux%u\n", + video.width, video.height); + return NS_ERROR_INVALID_ARG; + } + + return SetVideoAndPreviewSize(preview, video); +} + nsresult nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig) { @@ -1392,12 +1560,11 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig) return NS_ERROR_INVALID_ARG; } - mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile; const RecorderProfile::Video& video(profile->GetVideo()); const Size& size = video.GetSize(); - int fps = video.GetFramesPerSecond(); - if (fps <= 0 || size.width <= 0 || size.height <= 0) { - DOM_CAMERA_LOGE("Can't configure video with fps=%d, width=%d, height=%d\n", + const uint32_t fps = video.GetFramesPerSecond(); + if (fps == 0 || fps > INT_MAX || size.width == 0 || size.height == 0) { + DOM_CAMERA_LOGE("Can't configure video with fps=%u, width=%u, height=%u\n", fps, size.width, size.height); return NS_ERROR_FAILURE; } @@ -1410,32 +1577,26 @@ 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 = SetVideoSize(size); + // stream and and a potentially high(er) resolution stream for encoding. + rv = SelectVideoAndPreviewSize(aConfig, size); if (NS_FAILED(rv)) { - DOM_CAMERA_LOGE("Failed to set video mode video size (0x%x)\n", rv); - return rv; - } - - // The video size must be set first, before the preview size, because - // some platforms have a dependency between the two. - rv = SetPreviewSize(aConfig.mPreviewSize); - if (NS_FAILED(rv)) { - DOM_CAMERA_LOGE("Failed to set video mode preview size (0x%x)\n", rv); + DOM_CAMERA_LOGE("Failed to set video and preview sizes (0x%x)\n", rv); return rv; } } else { // The camera only supports a single video stream: in this case, we set // the preview size to be the desired video recording size, and ignore // the specified preview size. - rv = SetPreviewSize(size); + rv = Set(CAMERA_PARAM_PREVIEWSIZE, size); if (NS_FAILED(rv)) { DOM_CAMERA_LOGE("Failed to set video mode preview size (0x%x)\n", rv); return rv; } + + mCurrentConfiguration.mPreviewSize = size; } - rv = Set(CAMERA_PARAM_PREVIEWFRAMERATE, fps); + rv = Set(CAMERA_PARAM_PREVIEWFRAMERATE, static_cast(fps)); if (NS_FAILED(rv)) { DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv); return rv; diff --git a/dom/camera/GonkCameraControl.h b/dom/camera/GonkCameraControl.h index b7508080374..0fe82967ab9 100644 --- a/dom/camera/GonkCameraControl.h +++ b/dom/camera/GonkCameraControl.h @@ -98,6 +98,8 @@ protected: using CameraControlImpl::OnConfigurationChange; using CameraControlImpl::OnUserError; + typedef nsTArray::index_type SizeIndex; + virtual void BeginBatchParameterSet() MOZ_OVERRIDE; virtual void EndBatchParameterSet() MOZ_OVERRIDE; @@ -130,8 +132,9 @@ protected: nsresult SetupRecording(int aFd, int aRotation, uint64_t aMaxFileSizeBytes, uint64_t aMaxVideoLengthMs); nsresult SetupRecordingFlash(bool aAutoEnableLowLightTorch); - nsresult SetPreviewSize(const Size& aSize); - nsresult SetVideoSize(const Size& aSize); + nsresult SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize); + nsresult SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize); + nsresult MaybeAdjustVideoSize(); nsresult PausePreview(); nsresult GetSupportedSize(const Size& aSize, const nsTArray& supportedSizes, Size& best); diff --git a/dom/camera/GonkCameraParameters.cpp b/dom/camera/GonkCameraParameters.cpp index 07e9cb848bb..848a15aef3f 100644 --- a/dom/camera/GonkCameraParameters.cpp +++ b/dom/camera/GonkCameraParameters.cpp @@ -117,6 +117,8 @@ GonkCameraParameters::Parameters::GetTextKey(uint32_t aKey) return KEY_RECORDING_HINT; case CAMERA_PARAM_PICTURE_QUALITY: return KEY_JPEG_QUALITY; + case CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO: + return KEY_PREFERRED_PREVIEW_SIZE_FOR_VIDEO; case CAMERA_PARAM_SUPPORTED_PREVIEWSIZES: return KEY_SUPPORTED_PREVIEW_SIZES; diff --git a/dom/camera/ICameraControl.h b/dom/camera/ICameraControl.h index 1b1407c34c2..da1d9a36e2e 100644 --- a/dom/camera/ICameraControl.h +++ b/dom/camera/ICameraControl.h @@ -54,6 +54,7 @@ enum { CAMERA_PARAM_LUMINANCE, CAMERA_PARAM_SCENEMODE_HDR_RETURNNORMALPICTURE, CAMERA_PARAM_RECORDINGHINT, + CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, // supported features CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,