Bug 1098660 - fix the (complex) requirements for video mode preview size, r=aosmond

This commit is contained in:
Mike Habicher 2014-11-14 20:56:59 -05:00
parent 17f00e6af6
commit 9c15130c47
5 changed files with 273 additions and 106 deletions

View File

@ -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<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
DOM_CAMERA_LOGI("New hardware state is '%s'\n", state[aNewState]);

View File

@ -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<Size> 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<Size> 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<Size> 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<Size> 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<Size>& supportedSizes,
const nsTArray<Size>& 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<Size>::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<Size>::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<long int>(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<Size>::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<long int>(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<Size>::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<long int>(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<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.
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<int>(fps));
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv);
return rv;

View File

@ -98,6 +98,8 @@ protected:
using CameraControlImpl::OnConfigurationChange;
using CameraControlImpl::OnUserError;
typedef nsTArray<Size>::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<Size>& supportedSizes, Size& best);

View File

@ -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;

View File

@ -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,