diff -r 000000000000 -r 6474c204b198 dom/camera/GonkCameraControl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/camera/GonkCameraControl.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1720 @@ +/* + * Copyright (C) 2012-2014 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 "GonkCameraControl.h" +#include +#include +#include +#include +#include +#include +#include "base/basictypes.h" +#include "camera/CameraParameters.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsThread.h" +#include +#include "mozilla/FileUtils.h" +#include "mozilla/Services.h" +#include "mozilla/unused.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "nsAlgorithm.h" +#include +#include "nsPrintfCString.h" +#include "nsIObserverService.h" +#include "nsIVolume.h" +#include "nsIVolumeService.h" +#include "AutoRwLock.h" +#include "GonkCameraHwMgr.h" +#include "GonkRecorderProfiles.h" +#include "CameraCommon.h" +#include "GonkCameraParameters.h" +#include "DeviceStorageFileDescriptor.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::ipc; +using namespace android; + +#define RETURN_IF_NO_CAMERA_HW() \ + do { \ + if (!mCameraHw.get()) { \ + DOM_CAMERA_LOGE("%s:%d : mCameraHw is null\n", __func__, __LINE__); \ + return NS_ERROR_NOT_AVAILABLE; \ + } \ + } while(0) + +// Construct nsGonkCameraControl on the main thread. +nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId) + : CameraControlImpl(aCameraId) + , mLastPictureSize({0, 0}) + , mLastThumbnailSize({0, 0}) + , mPreviewFps(30) + , mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102 + , mFlashSupported(false) + , mLuminanceSupported(false) + , mAutoFlashModeOverridden(false) + , mDeferConfigUpdate(0) + , mMediaProfiles(nullptr) + , mRecorder(nullptr) + , mProfileManager(nullptr) + , mRecorderProfile(nullptr) + , mVideoFile(nullptr) + , mReentrantMonitor("GonkCameraControl::OnTakePictureMonitor") +{ + // Constructor runs on the main thread... + DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); + mImageContainer = LayerManager::CreateImageContainer(); +} + +nsresult +nsGonkCameraControl::StartImpl(const Configuration* aInitialConfig) +{ + /** + * For initialization, we try to return the camera control to the upper + * upper layer (i.e. the DOM) as quickly as possible. To do this, the + * camera is initialized in the following stages: + * + * 0. Initialize() initializes the hardware; + * 1. SetConfigurationInternal() does the minimal configuration + * required so that we can start the preview -and- report a valid + * configuration to the upper layer; + * 2. OnHardwareStateChange() reports that the hardware is ready, + * which the upper (e.g. DOM) layer can (and does) use to return + * the camera control object; + * 3. StartPreviewImpl() starts the flow of preview frames from the + * camera hardware. + * + * The intent of the above flow is to let the Main Thread do as much work + * up-front as possible without waiting for blocking Camera Thread calls + * to complete. + */ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + nsresult rv = Initialize(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aInitialConfig) { + rv = SetConfigurationInternal(*aInitialConfig); + if (NS_WARN_IF(NS_FAILED(rv))) { + // The initial configuration failed, close up the hardware + StopImpl(); + return rv; + } + } + + OnHardwareStateChange(CameraControlListener::kHardwareOpen); + return StartPreviewImpl(); +} + +nsresult +nsGonkCameraControl::Initialize() +{ + mCameraHw = GonkCameraHardware::Connect(this, mCameraId); + if (!mCameraHw.get()) { + DOM_CAMERA_LOGE("Failed to connect to camera %d (this=%p)\n", mCameraId, this); + return NS_ERROR_FAILURE; + } + + DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get()); + + // Initialize our camera configuration database. + PullParametersImpl(); + + // Set preferred preview frame format. + mParams.Set(CAMERA_PARAM_PREVIEWFORMAT, NS_LITERAL_STRING("yuv420sp")); + // Turn off any normal pictures returned by the HDR scene mode + mParams.Set(CAMERA_PARAM_SCENEMODE_HDR_RETURNNORMALPICTURE, false); + PushParametersImpl(); + + // Grab any other settings we'll need later. + mParams.Get(CAMERA_PARAM_PICTURE_FILEFORMAT, mFileFormat); + mParams.Get(CAMERA_PARAM_THUMBNAILSIZE, mLastThumbnailSize); + + // The emulator's camera returns -1 for these values; bump them up to 0 + int areas; + mParams.Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas); + mCurrentConfiguration.mMaxMeteringAreas = areas != -1 ? areas : 0; + mParams.Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas); + mCurrentConfiguration.mMaxFocusAreas = areas != -1 ? areas : 0; + + mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mLastPictureSize); + mParams.Get(CAMERA_PARAM_PREVIEWSIZE, mCurrentConfiguration.mPreviewSize); + mParams.Get(CAMERA_PARAM_VIDEOSIZE, mLastRecorderSize); + + nsString luminance; // check for support + mParams.Get(CAMERA_PARAM_LUMINANCE, luminance); + mLuminanceSupported = !luminance.IsEmpty(); + + nsString flashMode; + mParams.Get(CAMERA_PARAM_FLASHMODE, flashMode); + mFlashSupported = !flashMode.IsEmpty(); + + 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); + DOM_CAMERA_LOGI(" - default thumbnail size: %u x %u\n", + mLastThumbnailSize.width, mLastThumbnailSize.height); + DOM_CAMERA_LOGI(" - default preview size: %u x %u\n", + mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height); + DOM_CAMERA_LOGI(" - default video recorder size: %u x %u\n", + mLastRecorderSize.width, mLastRecorderSize.height); + DOM_CAMERA_LOGI(" - default picture file format: %s\n", + NS_ConvertUTF16toUTF8(mFileFormat).get()); + DOM_CAMERA_LOGI(" - luminance reporting: %ssupported\n", + mLuminanceSupported ? "" : "NOT "); + if (mFlashSupported) { + DOM_CAMERA_LOGI(" - flash: supported, default mode '%s'\n", + NS_ConvertUTF16toUTF8(flashMode).get()); + } else { + DOM_CAMERA_LOGI(" - flash: NOT supported\n"); + } + + return NS_OK; +} + +nsGonkCameraControl::~nsGonkCameraControl() +{ + DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraHw = %p\n", __func__, __LINE__, this, mCameraHw.get()); + + StopImpl(); + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); +} + +nsresult +nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig) +{ + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + + nsresult rv; + + switch (aConfig.mMode) { + case kPictureMode: + rv = SetPictureConfiguration(aConfig); + break; + + case kVideoMode: + rv = SetVideoConfiguration(aConfig); + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode in SetConfigurationInternal()"); + } + + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentConfiguration.mMode = aConfig.mMode; + mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile; + if (aConfig.mMode == kVideoMode) { + mCurrentConfiguration.mPreviewSize = mLastRecorderSize; + } + + OnConfigurationChange(); + return NS_OK; +} + +nsresult +nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig) +{ + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + // Stop any currently running preview + nsresult rv = PausePreview(); + if (NS_FAILED(rv)) { + // warn, but plow ahead + NS_WARNING("PausePreview() in SetConfigurationImpl() failed"); + } + + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + rv = SetConfigurationInternal(aConfig); + if (NS_WARN_IF(NS_FAILED(rv))) { + StopPreviewImpl(); + return rv; + } + + // Restart the preview + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + return StartPreviewImpl(); +} + +nsresult +nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig) +{ + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + + // remove any existing recorder profile + mRecorderProfile = nullptr; + + nsresult rv = SetPreviewSize(aConfig.mPreviewSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = PushParameters(); + 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, + mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height, + mPreviewFps); + + return NS_OK; +} + +nsresult +nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig) +{ + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + + nsresult rv = SetupVideoMode(aConfig.mRecorderProfile); + NS_ENSURE_SUCCESS(rv, rv); + + DOM_CAMERA_LOGI("video mode preview: profile '%s', got %ux%u (%u fps)\n", + NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get(), + mLastRecorderSize.width, mLastRecorderSize.height, + mPreviewFps); + + return rv; +} + +// Parameter management. +nsresult +nsGonkCameraControl::PushParameters() +{ + uint32_t dcu = mDeferConfigUpdate; + if (dcu > 0) { + DOM_CAMERA_LOGI("Defering config update (nest level %u)\n", dcu); + return NS_OK; + } + + /** + * 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_GetCurrentThread() != mCameraThread) { + DOM_CAMERA_LOGT("%s:%d - dispatching to Camera Thread\n", __func__, __LINE__); + nsCOMPtr pushParametersTask = + NS_NewRunnableMethod(this, &nsGonkCameraControl::PushParametersImpl); + return mCameraThread->Dispatch(pushParametersTask, NS_DISPATCH_NORMAL); + } + + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + return PushParametersImpl(); +} + +void +nsGonkCameraControl::BeginBatchParameterSet() +{ + uint32_t dcu = ++mDeferConfigUpdate; + if (dcu == 0) { + NS_WARNING("Overflow weirdness incrementing mDeferConfigUpdate!"); + MOZ_CRASH(); + } + DOM_CAMERA_LOGI("Begin deferring camera configuration updates (nest level %u)\n", dcu); +} + +void +nsGonkCameraControl::EndBatchParameterSet() +{ + uint32_t dcu = mDeferConfigUpdate--; + if (dcu == 0) { + NS_WARNING("Underflow badness decrementing mDeferConfigUpdate!"); + MOZ_CRASH(); + } + DOM_CAMERA_LOGI("End deferring camera configuration updates (nest level %u)\n", dcu); + + if (dcu == 1) { + PushParameters(); + } +} + +template nsresult +nsGonkCameraControl::SetAndPush(uint32_t aKey, const T& aValue) +{ + nsresult rv = mParams.Set(aKey, aValue); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Camera parameter aKey=%d failed to set (0x%x)\n", aKey, rv); + return rv; + } + return PushParameters(); +} + +// Array-of-Size parameter accessor. +nsresult +nsGonkCameraControl::Get(uint32_t aKey, nsTArray& aSizes) +{ + if (aKey == CAMERA_PARAM_SUPPORTED_VIDEOSIZES) { + nsresult rv = mParams.Get(aKey, aSizes); + if (aSizes.Length() != 0) { + return rv; + } + DOM_CAMERA_LOGI("Camera doesn't support video independent of the preview\n"); + aKey = CAMERA_PARAM_SUPPORTED_PREVIEWSIZES; + } + + return mParams.Get(aKey, aSizes); +} + +// Array-of-doubles parameter accessor. +nsresult +nsGonkCameraControl::Get(uint32_t aKey, nsTArray& aValues) +{ + return mParams.Get(aKey, aValues); +} + +// Array-of-nsString parameter accessor. +nsresult +nsGonkCameraControl::Get(uint32_t aKey, nsTArray& aValues) +{ + return mParams.Get(aKey, aValues); +} + +// nsString-valued parameter accessors +nsresult +nsGonkCameraControl::Set(uint32_t aKey, const nsAString& aValue) +{ + nsresult rv = mParams.Set(aKey, aValue); + if (NS_FAILED(rv)) { + return rv; + } + + switch (aKey) { + case CAMERA_PARAM_PICTURE_FILEFORMAT: + // Picture format -- need to keep it for the TakePicture() callback. + mFileFormat = aValue; + break; + + case CAMERA_PARAM_FLASHMODE: + // Explicit flash mode changes always win and stick. + mAutoFlashModeOverridden = false; + break; + } + + return PushParameters(); +} + +nsresult +nsGonkCameraControl::Get(uint32_t aKey, nsAString& aRet) +{ + return mParams.Get(aKey, aRet); +} + +// Double-valued parameter accessors +nsresult +nsGonkCameraControl::Set(uint32_t aKey, double aValue) +{ + return SetAndPush(aKey, aValue); +} + +nsresult +nsGonkCameraControl::Get(uint32_t aKey, double& aRet) +{ + return mParams.Get(aKey, aRet); +} + +// Signed-64-bit parameter accessors. +nsresult +nsGonkCameraControl::Set(uint32_t aKey, int64_t aValue) +{ + return SetAndPush(aKey, aValue); +} + +nsresult +nsGonkCameraControl::Get(uint32_t aKey, int64_t& aRet) +{ + return mParams.Get(aKey, aRet); +} + +// Weighted-region parameter accessors. +nsresult +nsGonkCameraControl::Set(uint32_t aKey, const nsTArray& aRegions) +{ + return SetAndPush(aKey, aRegions); +} + +nsresult +nsGonkCameraControl::Get(uint32_t aKey, nsTArray& aRegions) +{ + return mParams.Get(aKey, aRegions); +} + +// Singleton-size parameter accessors. +nsresult +nsGonkCameraControl::Set(uint32_t aKey, const Size& aSize) +{ + switch (aKey) { + case CAMERA_PARAM_PICTURE_SIZE: + DOM_CAMERA_LOGI("setting picture size to %ux%u\n", aSize.width, aSize.height); + return SetPictureSize(aSize); + + case CAMERA_PARAM_THUMBNAILSIZE: + DOM_CAMERA_LOGI("setting thumbnail size to %ux%u\n", aSize.width, aSize.height); + return SetThumbnailSize(aSize); + + default: + return SetAndPush(aKey, aSize); + } +} + +nsresult +nsGonkCameraControl::Get(uint32_t aKey, Size& aSize) +{ + return mParams.Get(aKey, aSize); +} + +// Signed int parameter accessors. +nsresult +nsGonkCameraControl::Set(uint32_t aKey, int aValue) +{ + if (aKey == CAMERA_PARAM_PICTURE_ROTATION) { + RETURN_IF_NO_CAMERA_HW(); + aValue = RationalizeRotation(aValue + mCameraHw->GetSensorOrientation()); + } + return SetAndPush(aKey, aValue); +} + +nsresult +nsGonkCameraControl::Get(uint32_t aKey, int& aRet) +{ + if (aKey == CAMERA_PARAM_SENSORANGLE) { + RETURN_IF_NO_CAMERA_HW(); + aRet = mCameraHw->GetSensorOrientation(); + return NS_OK; + } + + return mParams.Get(aKey, aRet); +} + +// GPS location parameter accessors. +nsresult +nsGonkCameraControl::SetLocation(const Position& aLocation) +{ + return SetAndPush(CAMERA_PARAM_PICTURE_LOCATION, aLocation); +} + +nsresult +nsGonkCameraControl::StartPreviewImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + RETURN_IF_NO_CAMERA_HW(); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (mPreviewState == CameraControlListener::kPreviewStarted) { + DOM_CAMERA_LOGW("Camera preview already started, nothing to do\n"); + return NS_OK; + } + + DOM_CAMERA_LOGI("Starting preview (this=%p)\n", this); + + if (mCameraHw->StartPreview() != OK) { + DOM_CAMERA_LOGE("Failed to start camera preview\n"); + return NS_ERROR_FAILURE; + } + + OnPreviewStateChange(CameraControlListener::kPreviewStarted); + return NS_OK; +} + +nsresult +nsGonkCameraControl::StopPreviewImpl() +{ + RETURN_IF_NO_CAMERA_HW(); + + DOM_CAMERA_LOGI("Stopping preview (this=%p)\n", this); + + mCameraHw->StopPreview(); + OnPreviewStateChange(CameraControlListener::kPreviewStopped); + return NS_OK; +} + +nsresult +nsGonkCameraControl::PausePreview() +{ + RETURN_IF_NO_CAMERA_HW(); + + DOM_CAMERA_LOGI("Pausing preview (this=%p)\n", this); + + mCameraHw->StopPreview(); + OnPreviewStateChange(CameraControlListener::kPreviewPaused); + return NS_OK; +} + +nsresult +nsGonkCameraControl::AutoFocusImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + RETURN_IF_NO_CAMERA_HW(); + + DOM_CAMERA_LOGI("Starting auto focus\n"); + + if (mCameraHw->AutoFocus() != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::StartFaceDetectionImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + RETURN_IF_NO_CAMERA_HW(); + + DOM_CAMERA_LOGI("Starting face detection\n"); + + if (mCameraHw->StartFaceDetection() != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::StopFaceDetectionImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + RETURN_IF_NO_CAMERA_HW(); + + DOM_CAMERA_LOGI("Stopping face detection\n"); + + if (mCameraHw->StopFaceDetection() != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::SetThumbnailSizeImpl(const Size& aSize) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + /** + * We keep a copy of the specified size so that if the picture size + * changes, we can choose a new thumbnail size close to what was asked for + * last time. + */ + mLastThumbnailSize = aSize; + + /** + * If either of width or height is zero, set the other to zero as well. + * This should disable inclusion of a thumbnail in the final picture. + */ + if (!aSize.width || !aSize.height) { + DOM_CAMERA_LOGW("Requested thumbnail size %ux%u, disabling thumbnail\n", + aSize.width, aSize.height); + Size size = { 0, 0 }; + return SetAndPush(CAMERA_PARAM_THUMBNAILSIZE, size); + } + + /** + * Choose the supported thumbnail size that is closest to the specified size. + * Some drivers will fail to take a picture if the thumbnail does not have + * the same aspect ratio as the set picture size, so we need to enforce that + * too. + */ + int smallestDelta = INT_MAX; + uint32_t smallestDeltaIndex = UINT32_MAX; + int targetArea = aSize.width * aSize.height; + + nsAutoTArray supportedSizes; + Get(CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, supportedSizes); + + for (uint32_t i = 0; i < supportedSizes.Length(); ++i) { + 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 + ) { + smallestDelta = delta; + smallestDeltaIndex = i; + } + } + + if (smallestDeltaIndex == UINT32_MAX) { + DOM_CAMERA_LOGW("Unable to find a thumbnail size close to %ux%u, disabling thumbnail\n", + aSize.width, aSize.height); + // If we are unable to find a thumbnail size with a suitable aspect ratio, + // just disable the thumbnail altogether. + Size size = { 0, 0 }; + return SetAndPush(CAMERA_PARAM_THUMBNAILSIZE, size); + } + + Size size = supportedSizes[smallestDeltaIndex]; + DOM_CAMERA_LOGI("camera-param set thumbnail-size = %ux%u (requested %ux%u)\n", + size.width, size.height, aSize.width, aSize.height); + if (size.width > INT32_MAX || size.height > INT32_MAX) { + DOM_CAMERA_LOGE("Supported thumbnail size is too big, no change\n"); + return NS_ERROR_FAILURE; + } + + return SetAndPush(CAMERA_PARAM_THUMBNAILSIZE, size); +} + +nsresult +nsGonkCameraControl::SetThumbnailSize(const Size& aSize) +{ + class SetThumbnailSize : public nsRunnable + { + public: + SetThumbnailSize(nsGonkCameraControl* aCameraControl, const Size& aSize) + : mCameraControl(aCameraControl) + , mSize(aSize) + { + MOZ_COUNT_CTOR(SetThumbnailSize); + } + ~SetThumbnailSize() { MOZ_COUNT_DTOR(SetThumbnailSize); } + + NS_IMETHODIMP + Run() MOZ_OVERRIDE + { + nsresult rv = mCameraControl->SetThumbnailSizeImpl(mSize); + if (NS_FAILED(rv)) { + mCameraControl->OnError(CameraControlListener::kInUnspecified, + CameraControlListener::kErrorSetThumbnailSizeFailed); + } + return NS_OK; + } + + protected: + nsRefPtr mCameraControl; + Size mSize; + }; + + if (NS_GetCurrentThread() == mCameraThread) { + return SetThumbnailSizeImpl(aSize); + } + + return mCameraThread->Dispatch(new SetThumbnailSize(this, aSize), NS_DISPATCH_NORMAL); +} + +nsresult +nsGonkCameraControl::UpdateThumbnailSize() +{ + return SetThumbnailSize(mLastThumbnailSize); +} + +nsresult +nsGonkCameraControl::SetPictureSizeImpl(const Size& aSize) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + /** + * 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 (!aSize.width || !aSize.height) { + DOM_CAMERA_LOGW("Ignoring requested picture size of %ux%u\n", aSize.width, aSize.height); + return NS_ERROR_INVALID_ARG; + } + + if (aSize.width == mLastPictureSize.width && aSize.height == mLastPictureSize.height) { + DOM_CAMERA_LOGI("Requested picture size %ux%u unchanged\n", aSize.width, aSize.height); + return NS_OK; + } + + /** + * Choose the supported picture size that is closest in area to the + * specified size. Some drivers will fail to take a picture if the + * thumbnail size is not the same aspect ratio, so we update that + * as well to a size closest to the last user-requested one. + */ + int smallestDelta = INT_MAX; + uint32_t smallestDeltaIndex = UINT32_MAX; + int targetArea = aSize.width * aSize.height; + + nsAutoTArray supportedSizes; + Get(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes); + + for (uint32_t i = 0; i < supportedSizes.Length(); ++i) { + int area = supportedSizes[i].width * supportedSizes[i].height; + int delta = abs(area - targetArea); + + if (area != 0 && delta < smallestDelta) { + smallestDelta = delta; + smallestDeltaIndex = i; + } + } + + if (smallestDeltaIndex == UINT32_MAX) { + DOM_CAMERA_LOGW("Unable to find a picture size close to %ux%u\n", + aSize.width, aSize.height); + return NS_ERROR_INVALID_ARG; + } + + Size size = supportedSizes[smallestDeltaIndex]; + DOM_CAMERA_LOGI("camera-param set picture-size = %ux%u (requested %ux%u)\n", + size.width, size.height, aSize.width, aSize.height); + if (size.width > INT32_MAX || size.height > INT32_MAX) { + DOM_CAMERA_LOGE("Supported picture size is too big, no change\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv = mParams.Set(CAMERA_PARAM_PICTURE_SIZE, size); + if (NS_FAILED(rv)) { + return rv; + } + + mLastPictureSize = size; + + // Finally, update the thumbnail size in case the picture + // aspect ratio changed. + return UpdateThumbnailSize(); +} + +int32_t +nsGonkCameraControl::RationalizeRotation(int32_t aRotation) +{ + int32_t r = aRotation; + + // The result of this operation is an angle from 0..270 degrees, + // in steps of 90 degrees. Angles are rounded to the nearest + // magnitude, so 45 will be rounded to 90, and -45 will be rounded + // to -90 (not 0). + if (r >= 0) { + r += 45; + } else { + r -= 45; + } + r /= 90; + r %= 4; + r *= 90; + if (r < 0) { + r += 360; + } + + return r; +} + +nsresult +nsGonkCameraControl::SetPictureSize(const Size& aSize) +{ + class SetPictureSize : public nsRunnable + { + public: + SetPictureSize(nsGonkCameraControl* aCameraControl, const Size& aSize) + : mCameraControl(aCameraControl) + , mSize(aSize) + { + MOZ_COUNT_CTOR(SetPictureSize); + } + ~SetPictureSize() { MOZ_COUNT_DTOR(SetPictureSize); } + + NS_IMETHODIMP + Run() MOZ_OVERRIDE + { + nsresult rv = mCameraControl->SetPictureSizeImpl(mSize); + if (NS_FAILED(rv)) { + mCameraControl->OnError(CameraControlListener::kInUnspecified, + CameraControlListener::kErrorSetPictureSizeFailed); + } + return NS_OK; + } + + protected: + nsRefPtr mCameraControl; + Size mSize; + }; + + if (NS_GetCurrentThread() == mCameraThread) { + return SetPictureSizeImpl(aSize); + } + + return mCameraThread->Dispatch(new SetPictureSize(this, aSize), NS_DISPATCH_NORMAL); +} + +nsresult +nsGonkCameraControl::TakePictureImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + RETURN_IF_NO_CAMERA_HW(); + + if (mCameraHw->TakePicture() != OK) { + return NS_ERROR_FAILURE; + } + + // In Gonk, taking a picture implicitly stops the preview stream, + // so we need to reflect that here. + OnPreviewStateChange(CameraControlListener::kPreviewPaused); + return NS_OK; +} + +nsresult +nsGonkCameraControl::PushParametersImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + DOM_CAMERA_LOGI("Pushing camera parameters\n"); + RETURN_IF_NO_CAMERA_HW(); + + if (mCameraHw->PushParameters(mParams) != OK) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsGonkCameraControl::PullParametersImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + DOM_CAMERA_LOGI("Pulling camera parameters\n"); + RETURN_IF_NO_CAMERA_HW(); + + return mCameraHw->PullParameters(mParams); +} + +nsresult +nsGonkCameraControl::SetupRecordingFlash(bool aAutoEnableLowLightTorch) +{ + mAutoFlashModeOverridden = false; + + if (!aAutoEnableLowLightTorch || !mLuminanceSupported || !mFlashSupported) { + return NS_OK; + } + + DOM_CAMERA_LOGI("Luminance reporting and flash supported\n"); + + nsresult rv = PullParametersImpl(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString luminance; + rv = mParams.Get(CAMERA_PARAM_LUMINANCE, luminance); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If we failed to get the luminance, assume it's "high" + return NS_OK; + } + + nsString flashMode; + rv = mParams.Get(CAMERA_PARAM_FLASHMODE, flashMode); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If we failed to get the current flash mode, swallow the error + return NS_OK; + } + + if (luminance.EqualsASCII("low") && flashMode.EqualsASCII("auto")) { + DOM_CAMERA_LOGI("Low luminance detected, turning on flash\n"); + rv = SetAndPush(CAMERA_PARAM_FLASHMODE, NS_LITERAL_STRING("torch")); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If we failed to turn on the flash, swallow the error + return NS_OK; + } + + mAutoFlashModeOverridden = true; + } + + return NS_OK; +} + +nsresult +nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor, + const StartRecordingOptions* aOptions) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + NS_ENSURE_TRUE(mRecorderProfile, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_FALSE(mRecorder, NS_ERROR_FAILURE); + + /** + * Get the base path from device storage and append the app-specified + * filename to it. The filename may include a relative subpath + * (e.g.) "DCIM/IMG_0001.jpg". + * + * The camera app needs to provide the file extension '.3gp' for now. + * See bug 795202. + */ + NS_ENSURE_TRUE(aFileDescriptor, NS_ERROR_FAILURE); + nsAutoString fullPath; + mVideoFile = aFileDescriptor->mDSFile; + mVideoFile->GetFullPath(fullPath); + DOM_CAMERA_LOGI("Video filename is '%s'\n", + NS_LossyConvertUTF16toASCII(fullPath).get()); + + if (!mVideoFile->IsSafePath()) { + DOM_CAMERA_LOGE("Invalid video file name\n"); + return NS_ERROR_INVALID_ARG; + } + + // SetupRecording creates a dup of the file descriptor, so we need to + // close the file descriptor when we leave this function. Also note, that + // since we're already off the main thread, we don't need to dispatch this. + // We just let the CloseFileRunnable destructor do the work. + nsRefPtr closer; + if (aFileDescriptor->mFileDescriptor.IsValid()) { + closer = new CloseFileRunnable(aFileDescriptor->mFileDescriptor); + } + nsresult rv; + int fd = aFileDescriptor->mFileDescriptor.PlatformHandle(); + if (aOptions) { + rv = SetupRecording(fd, aOptions->rotation, aOptions->maxFileSizeBytes, + aOptions->maxVideoLengthMs); + if (NS_SUCCEEDED(rv)) { + rv = SetupRecordingFlash(aOptions->autoEnableLowLightTorch); + } + } else { + rv = SetupRecording(fd, 0, 0, 0); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mRecorder->start() != OK) { + DOM_CAMERA_LOGE("mRecorder->start() failed\n"); + // important: we MUST destroy the recorder if start() fails! + mRecorder = nullptr; + // put the flash back to the 'auto' state + if (mAutoFlashModeOverridden) { + SetAndPush(CAMERA_PARAM_FLASHMODE, NS_LITERAL_STRING("auto")); + } + return NS_ERROR_FAILURE; + } + + OnRecorderStateChange(CameraControlListener::kRecorderStarted); + return NS_OK; +} + +nsresult +nsGonkCameraControl::StopRecordingImpl() +{ + class RecordingComplete : public nsRunnable + { + public: + RecordingComplete(DeviceStorageFile* aFile) + : mFile(aFile) + { } + + ~RecordingComplete() { } + + NS_IMETHODIMP + Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(mFile, "file-watcher-notify", NS_LITERAL_STRING("modified").get()); + return NS_OK; + } + + private: + nsRefPtr mFile; + }; + + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + // nothing to do if we have no mRecorder + NS_ENSURE_TRUE(mRecorder, NS_OK); + + mRecorder->stop(); + mRecorder = nullptr; + OnRecorderStateChange(CameraControlListener::kRecorderStopped); + + if (mAutoFlashModeOverridden) { + SetAndPush(CAMERA_PARAM_FLASHMODE, NS_LITERAL_STRING("auto")); + } + + // notify DeviceStorage that the new video file is closed and ready + return NS_DispatchToMainThread(new RecordingComplete(mVideoFile), NS_DISPATCH_NORMAL); +} + +nsresult +nsGonkCameraControl::ResumeContinuousFocusImpl() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + RETURN_IF_NO_CAMERA_HW(); + + DOM_CAMERA_LOGI("Resuming continuous autofocus\n"); + + // see + // http://developer.android.com/reference/android/hardware/Camera.Parameters.html#FOCUS_MODE_CONTINUOUS_PICTURE + if (NS_WARN_IF(mCameraHw->CancelAutoFocus() != OK)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +nsGonkCameraControl::OnAutoFocusComplete(bool aSuccess) +{ + class AutoFocusComplete : public nsRunnable + { + public: + AutoFocusComplete(nsGonkCameraControl* aCameraControl, bool aSuccess) + : mCameraControl(aCameraControl) + , mSuccess(aSuccess) + { } + + NS_IMETHODIMP + Run() MOZ_OVERRIDE + { + mCameraControl->OnAutoFocusComplete(mSuccess); + return NS_OK; + } + + protected: + nsRefPtr mCameraControl; + bool mSuccess; + }; + + if (NS_GetCurrentThread() == mCameraThread) { + /** + * Auto focusing can change some of the camera's parameters, so + * we need to pull a new set before notifying any clients. + */ + PullParametersImpl(); + CameraControlImpl::OnAutoFocusComplete(aSuccess); + return; + } + + /** + * Because the callback needs to call PullParametersImpl(), + * we need to dispatch this callback through the Camera Thread. + */ + mCameraThread->Dispatch(new AutoFocusComplete(this, aSuccess), NS_DISPATCH_NORMAL); +} + +bool +FeatureDetected(int32_t feature[]) +{ + /** + * For information on what constitutes a valid feature, see: + * http://androidxref.com/4.0.4/xref/system/core/include/system/camera.h#202 + * + * Although the comments explicitly state that undetected features are + * indicated using the value -2000, we conservatively include anything + * outside the explicitly valid range of [-1000, 1000] as undetected + * as well. + */ + const int32_t kLowerFeatureBound = -1000; + const int32_t kUpperFeatureBound = 1000; + return (feature[0] >= kLowerFeatureBound && feature[0] <= kUpperFeatureBound) || + (feature[1] >= kLowerFeatureBound && feature[1] <= kUpperFeatureBound); +} + +void +nsGonkCameraControl::OnFacesDetected(camera_frame_metadata_t* aMetaData) +{ + NS_ENSURE_TRUE_VOID(aMetaData); + + nsTArray faces; + uint32_t numFaces = aMetaData->number_of_faces; + DOM_CAMERA_LOGI("Camera detected %d face(s)", numFaces); + + faces.SetCapacity(numFaces); + + for (uint32_t i = 0; i < numFaces; ++i) { + Face* f = faces.AppendElement(); + + f->id = aMetaData->faces[i].id; + f->score = aMetaData->faces[i].score; + if (f->score > 100) { + f->score = 100; + } + f->bound.left = aMetaData->faces[i].rect[0]; + f->bound.top = aMetaData->faces[i].rect[1]; + f->bound.right = aMetaData->faces[i].rect[2]; + f->bound.bottom = aMetaData->faces[i].rect[3]; + DOM_CAMERA_LOGI("Camera face[%u] appended: id=%d, score=%d, bound=(%d, %d)-(%d, %d)\n", + i, f->id, f->score, f->bound.left, f->bound.top, f->bound.right, f->bound.bottom); + + f->hasLeftEye = FeatureDetected(aMetaData->faces[i].left_eye); + if (f->hasLeftEye) { + f->leftEye.x = aMetaData->faces[i].left_eye[0]; + f->leftEye.y = aMetaData->faces[i].left_eye[1]; + DOM_CAMERA_LOGI(" Left eye detected at (%d, %d)\n", + f->leftEye.x, f->leftEye.y); + } else { + DOM_CAMERA_LOGI(" No left eye detected\n"); + } + + f->hasRightEye = FeatureDetected(aMetaData->faces[i].right_eye); + if (f->hasRightEye) { + f->rightEye.x = aMetaData->faces[i].right_eye[0]; + f->rightEye.y = aMetaData->faces[i].right_eye[1]; + DOM_CAMERA_LOGI(" Right eye detected at (%d, %d)\n", + f->rightEye.x, f->rightEye.y); + } else { + DOM_CAMERA_LOGI(" No right eye detected\n"); + } + + f->hasMouth = FeatureDetected(aMetaData->faces[i].mouth); + if (f->hasMouth) { + f->mouth.x = aMetaData->faces[i].mouth[0]; + f->mouth.y = aMetaData->faces[i].mouth[1]; + DOM_CAMERA_LOGI(" Mouth detected at (%d, %d)\n", f->mouth.x, f->mouth.y); + } else { + DOM_CAMERA_LOGI(" No mouth detected\n"); + } + } + + CameraControlImpl::OnFacesDetected(faces); +} + +void +nsGonkCameraControl::OnTakePictureComplete(uint8_t* aData, uint32_t aLength) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + uint8_t* data = new uint8_t[aLength]; + + memcpy(data, aData, aLength); + + nsString s(NS_LITERAL_STRING("image/")); + s.Append(mFileFormat); + DOM_CAMERA_LOGI("Got picture, type '%s', %u bytes\n", NS_ConvertUTF16toUTF8(s).get(), aLength); + OnTakePictureComplete(data, aLength, s); + + if (mResumePreviewAfterTakingPicture) { + nsresult rv = StartPreview(); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Failed to restart camera preview (%x)\n", rv); + OnPreviewStateChange(CameraControlListener::kPreviewStopped); + } + } + + DOM_CAMERA_LOGI("nsGonkCameraControl::OnTakePictureComplete() done\n"); +} + +void +nsGonkCameraControl::OnTakePictureError() +{ + CameraControlImpl::OnError(CameraControlListener::kInTakePicture, + CameraControlListener::kErrorApiFailed); +} + +nsresult +nsGonkCameraControl::SetPreviewSize(const Size& aSize) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + nsTArray previewSizes; + nsresult rv = mParams.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; + } + + // 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); + } + mCurrentConfiguration.mPreviewSize = best; + return mParams.Set(CAMERA_PARAM_PREVIEWSIZE, best); +} + +nsresult +nsGonkCameraControl::SetVideoSize(const Size& aSize) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); + + nsTArray videoSizes; + nsresult rv = mParams.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 mParams.Set(CAMERA_PARAM_VIDEOSIZE, best); +} + +nsresult +nsGonkCameraControl::GetSupportedSize(const Size& aSize, + const nsTArray& supportedSizes, + Size& best) +{ + nsresult rv = NS_ERROR_INVALID_ARG; + best = aSize; + uint32_t minSizeDelta = UINT32_MAX; + uint32_t delta; + + if (!aSize.width && !aSize.height) { + // no size specified, take the first supported size + best = supportedSizes[0]; + rv = NS_OK; + } else if (aSize.width && aSize.height) { + // both height and width specified, find the supported size closest to requested size + 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)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + best = size; + rv = NS_OK; + } + } + } 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)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + best = size; + rv = NS_OK; + } + } + } 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)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + best = size; + rv = NS_OK; + } + } + } + return rv; +} + +nsresult +nsGonkCameraControl::SetupVideoMode(const nsAString& aProfile) +{ + DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + + // read preferences for camcorder + mMediaProfiles = MediaProfiles::getInstance(); + + nsAutoCString profile = NS_ConvertUTF16toUTF8(aProfile); + // XXXkhuey are we leaking? + mRecorderProfile = GetGonkRecorderProfileManager().take()->Get(profile.get()); + if (!mRecorderProfile) { + DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n", profile.get()); + return NS_ERROR_INVALID_ARG; + } + + const GonkRecorderVideoProfile* video = mRecorderProfile->GetGonkVideoProfile(); + int width = video->GetWidth(); + int height = video->GetHeight(); + int fps = video->GetFramerate(); + if (fps == -1 || width < 0 || height < 0) { + DOM_CAMERA_LOGE("Can't configure preview with fps=%d, width=%d, height=%d\n", + fps, width, height); + return NS_ERROR_FAILURE; + } + + PullParametersImpl(); + + Size size; + size.width = static_cast(width); + size.height = static_cast(height); + + { + ICameraControlParameterSetAutoEnter set(this); + + // The camera interface allows for hardware to provide two video + // streams, a low resolution preview and a potentially high resolution + // stream for encoding. For now we don't use this and set preview and video + // size to the same thing. + nsresult rv = SetVideoSize(size); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Failed to set video mode video size (0x%x)\n", rv); + return rv; + } + + rv = SetPreviewSize(size); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Failed to set video mode preview size (0x%x)\n", rv); + return rv; + } + + rv = mParams.Set(CAMERA_PARAM_PREVIEWFRAMERATE, fps); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv); + return rv; + } + + rv = PushParameters(); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Failed to set video mode settings (0x%x)\n", rv); + return rv; + } + + mPreviewFps = fps; + } + return NS_OK; +} + +class GonkRecorderListener : public IMediaRecorderClient +{ +public: + GonkRecorderListener(nsGonkCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { + DOM_CAMERA_LOGT("%s:%d : this=%p, aCameraControl=%p\n", + __func__, __LINE__, this, mCameraControl.get()); + } + + void notify(int msg, int ext1, int ext2) + { + if (mCameraControl) { + mCameraControl->OnRecorderEvent(msg, ext1, ext2); + } + } + + IBinder* onAsBinder() + { + DOM_CAMERA_LOGE("onAsBinder() called, should NEVER get called!\n"); + return nullptr; + } + +protected: + ~GonkRecorderListener() { } + nsRefPtr mCameraControl; +}; + +void +nsGonkCameraControl::OnRecorderEvent(int msg, int ext1, int ext2) +{ + /** + * Refer to base/include/media/mediarecorder.h for a complete list + * of error and info message codes. There are duplicate values + * within the status/error code space, as determined by code inspection: + * + * +------- msg + * | +----- ext1 + * | | +--- ext2 + * V V V + * 1 MEDIA_RECORDER_EVENT_ERROR + * 1 MEDIA_RECORDER_ERROR_UNKNOWN + * [3] ERROR_MALFORMED + * 100 mediaplayer.h::MEDIA_ERROR_SERVER_DIED + * 0 + * 2 MEDIA_RECORDER_EVENT_INFO + * 800 MEDIA_RECORDER_INFO_MAX_DURATION_REACHED + * 0 + * 801 MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED + * 0 + * 1000 MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS[1b] + * [3] UNKNOWN_ERROR, etc. + * 100 MEDIA_ERROR[4] + * 100 mediaplayer.h::MEDIA_ERROR_SERVER_DIED + * 0 + * 100 MEDIA_RECORDER_TRACK_EVENT_ERROR + * 100 MEDIA_RECORDER_TRACK_ERROR_GENERAL[1a] + * [3] UNKNOWN_ERROR, etc. + * 200 MEDIA_RECORDER_ERROR_VIDEO_NO_SYNC_FRAME[2] + * ? + * 101 MEDIA_RECORDER_TRACK_EVENT_INFO + * 1000 MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS[1a] + * [3] UNKNOWN_ERROR, etc. + * N see mediarecorder.h::media_recorder_info_type[5] + * + * 1. a) High 4 bits are the track number, the next 12 bits are reserved, + * and the final 16 bits are the actual error code (above). + * b) But not in this case. + * 2. Never actually used in AOSP code? + * 3. Specific error codes are from utils/Errors.h and/or + * include/media/stagefright/MediaErrors.h. + * 4. Only in frameworks/base/media/libmedia/mediaplayer.cpp. + * 5. These are mostly informational and we can ignore them; note that + * although the MEDIA_RECORDER_INFO_MAX_DURATION_REACHED and + * MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED values are defined in this + * enum, they are used with different ext1 codes. /o\ + */ + int trackNum = CameraControlListener::kNoTrackNumber; + + switch (msg) { + // Recorder-related events + case MEDIA_RECORDER_EVENT_INFO: + switch (ext1) { + case MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: + DOM_CAMERA_LOGI("recorder-event : info: maximum file size reached\n"); + OnRecorderStateChange(CameraControlListener::kFileSizeLimitReached, ext2, trackNum); + return; + + case MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: + DOM_CAMERA_LOGI("recorder-event : info: maximum video duration reached\n"); + OnRecorderStateChange(CameraControlListener::kVideoLengthLimitReached, ext2, trackNum); + return; + + case MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS: + DOM_CAMERA_LOGI("recorder-event : info: track completed\n"); + OnRecorderStateChange(CameraControlListener::kTrackCompleted, ext2, trackNum); + return; + } + break; + + case MEDIA_RECORDER_EVENT_ERROR: + switch (ext1) { + case MEDIA_RECORDER_ERROR_UNKNOWN: + DOM_CAMERA_LOGE("recorder-event : recorder-error: %d (0x%08x)\n", ext2, ext2); + OnRecorderStateChange(CameraControlListener::kMediaRecorderFailed, ext2, trackNum); + return; + + case MEDIA_ERROR_SERVER_DIED: + DOM_CAMERA_LOGE("recorder-event : recorder-error: server died\n"); + OnRecorderStateChange(CameraControlListener::kMediaServerFailed, ext2, trackNum); + return; + } + break; + + // Track-related events, see note 1(a) above. + case MEDIA_RECORDER_TRACK_EVENT_INFO: + trackNum = (ext1 & 0xF0000000) >> 28; + ext1 &= 0xFFFF; + switch (ext1) { + case MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS: + if (ext2 == OK) { + DOM_CAMERA_LOGI("recorder-event : track-complete: track %d, %d (0x%08x)\n", trackNum, ext2, ext2); + OnRecorderStateChange(CameraControlListener::kTrackCompleted, ext2, trackNum); + return; + } + DOM_CAMERA_LOGE("recorder-event : track-error: track %d, %d (0x%08x)\n", trackNum, ext2, ext2); + OnRecorderStateChange(CameraControlListener::kTrackFailed, ext2, trackNum); + return; + + case MEDIA_RECORDER_TRACK_INFO_PROGRESS_IN_TIME: + DOM_CAMERA_LOGI("recorder-event : track-info: progress in time: %d ms\n", ext2); + return; + } + break; + + case MEDIA_RECORDER_TRACK_EVENT_ERROR: + trackNum = (ext1 & 0xF0000000) >> 28; + ext1 &= 0xFFFF; + DOM_CAMERA_LOGE("recorder-event : track-error: track %d, %d (0x%08x)\n", trackNum, ext2, ext2); + OnRecorderStateChange(CameraControlListener::kTrackFailed, ext2, trackNum); + return; + } + + // All unhandled cases wind up here + DOM_CAMERA_LOGW("recorder-event : unhandled: msg=%d, ext1=%d, ext2=%d\n", msg, ext1, ext2); +} + +nsresult +nsGonkCameraControl::SetupRecording(int aFd, int aRotation, + int64_t aMaxFileSizeBytes, + int64_t aMaxVideoLengthMs) +{ + RETURN_IF_NO_CAMERA_HW(); + + // choosing a size big enough to hold the params + const size_t SIZE = 256; + char buffer[SIZE]; + + mRecorder = new GonkRecorder(); + CHECK_SETARG(mRecorder->init()); + + nsresult rv = mRecorderProfile->ConfigureRecorder(mRecorder); + NS_ENSURE_SUCCESS(rv, rv); + + CHECK_SETARG(mRecorder->setCamera(mCameraHw)); + + DOM_CAMERA_LOGI("maxVideoLengthMs=%lld\n", aMaxVideoLengthMs); + if (aMaxVideoLengthMs == 0) { + aMaxVideoLengthMs = -1; + } + snprintf(buffer, SIZE, "max-duration=%lld", aMaxVideoLengthMs); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + + DOM_CAMERA_LOGI("maxFileSizeBytes=%lld\n", aMaxFileSizeBytes); + if (aMaxFileSizeBytes == 0) { + aMaxFileSizeBytes = -1; + } + snprintf(buffer, SIZE, "max-filesize=%lld", aMaxFileSizeBytes); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + + // adjust rotation by camera sensor offset + int r = aRotation; + r += mCameraHw->GetSensorOrientation(); + r = RationalizeRotation(r); + DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n", r, aRotation); + snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", r); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + + CHECK_SETARG(mRecorder->setListener(new GonkRecorderListener(this))); + + // recording API needs file descriptor of output file + CHECK_SETARG(mRecorder->setOutputFile(aFd, 0, 0)); + CHECK_SETARG(mRecorder->prepare()); + + return NS_OK; +} + +nsresult +nsGonkCameraControl::StopImpl() +{ + DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); + + // if we're recording, stop recording + StopRecordingImpl(); + + // stop the preview + StopPreviewImpl(); + + // release the hardware handle + if (mCameraHw.get()){ + mCameraHw->Close(); + mCameraHw.clear(); + } + + OnHardwareStateChange(CameraControlListener::kHardwareClosed); + return NS_OK; +} + +already_AddRefed +nsGonkCameraControl::GetGonkRecorderProfileManager() +{ + if (!mProfileManager) { + nsTArray sizes; + nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes); + NS_ENSURE_SUCCESS(rv, nullptr); + + mProfileManager = new GonkRecorderProfileManager(mCameraId); + mProfileManager->SetSupportedResolutions(sizes); + } + + nsRefPtr profileMgr = mProfileManager; + return profileMgr.forget(); +} + +already_AddRefed +nsGonkCameraControl::GetRecorderProfileManagerImpl() +{ + nsRefPtr profileMgr = GetGonkRecorderProfileManager(); + return profileMgr.forget(); +} + +void +nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer) +{ + nsRefPtr frame = mImageContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR); + + GrallocImage* videoImage = static_cast(frame.get()); + + GrallocImage::GrallocData data; + data.mGraphicBuffer = aBuffer; + data.mPicSize = IntSize(mCurrentConfiguration.mPreviewSize.width, + mCurrentConfiguration.mPreviewSize.height); + videoImage->SetData(data); + + OnNewPreviewFrame(frame, mCurrentConfiguration.mPreviewSize.width, + mCurrentConfiguration.mPreviewSize.height); +} + +void +nsGonkCameraControl::OnError(CameraControlListener::CameraErrorContext aWhere, + CameraControlListener::CameraError aError) +{ + if (aError == CameraControlListener::kErrorServiceFailed) { + OnPreviewStateChange(CameraControlListener::kPreviewStopped); + OnHardwareStateChange(CameraControlListener::kHardwareClosed); + } + + CameraControlImpl::OnError(aWhere, aError); +} + +// Gonk callback handlers. +namespace mozilla { + +void +OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength) +{ + gc->OnTakePictureComplete(aData, aLength); +} + +void +OnTakePictureError(nsGonkCameraControl* gc) +{ + gc->OnTakePictureError(); +} + +void +OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess) +{ + gc->OnAutoFocusComplete(aSuccess); +} + +void +OnAutoFocusMoving(nsGonkCameraControl* gc, bool aIsMoving) +{ + gc->OnAutoFocusMoving(aIsMoving); +} + +void +OnFacesDetected(nsGonkCameraControl* gc, camera_frame_metadata_t* aMetaData) +{ + gc->OnFacesDetected(aMetaData); +} + +void +OnNewPreviewFrame(nsGonkCameraControl* gc, layers::TextureClient* aBuffer) +{ + gc->OnNewPreviewFrame(aBuffer); +} + +void +OnShutter(nsGonkCameraControl* gc) +{ + gc->OnShutter(); +} + +void +OnClosed(nsGonkCameraControl* gc) +{ + gc->OnClosed(); +} + +void +OnError(nsGonkCameraControl* gc, CameraControlListener::CameraError aError, + int32_t aArg1, int32_t aArg2) +{ +#ifdef PR_LOGGING + DOM_CAMERA_LOGE("OnError : aError=%d, aArg1=%d, aArg2=%d\n", aError, aArg1, aArg2); +#else + unused << aArg1; + unused << aArg2; +#endif + gc->OnError(CameraControlListener::kInUnspecified, aError); +} + +} // namespace mozilla