/* * 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 , 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")); 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); 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()); 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); NS_ENSURE_SUCCESS(rv, 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; } if (aKey == CAMERA_PARAM_PICTURE_FILEFORMAT) { // Picture format -- need to keep it for the TakePicture() callback. mFileFormat = aValue; } 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_PICTURESIZE: 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(bool aCancelExistingCall) { MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); RETURN_IF_NO_CAMERA_HW(); if (aCancelExistingCall) { if (mCameraHw.get()) { mCameraHw->CancelAutoFocus(); } } if (mCameraHw->AutoFocus() != 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_PICTURESIZE, 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::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor, const StartRecordingOptions* aOptions) { 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); } else { rv = SetupRecording(fd, 0, 0, 0); } NS_ENSURE_SUCCESS(rv, rv); if (mRecorder->start() != OK) { DOM_CAMERA_LOGE("mRecorder->start() failed\n"); // important: we MUST destroy the recorder if start() fails! mRecorder = nullptr; 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); // notify DeviceStorage that the new video file is closed and ready return NS_DispatchToMainThread(new RecordingComplete(mVideoFile), NS_DISPATCH_NORMAL); } 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); } 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 = aSize; uint32_t minSizeDelta = UINT32_MAX; uint32_t delta; if (!aSize.width && !aSize.height) { // no size specified, take the first supported size best = previewSizes[0]; } 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 (uint32_t i = 0; i < previewSizes.Length(); i++) { Size size = previewSizes[i]; uint32_t delta = abs((long int)(size.width * size.height - targetArea)); if (delta < minSizeDelta) { minSizeDelta = delta; best = size; } } } else if (!aSize.width) { // width not specified, find closest height match for (uint32_t i = 0; i < previewSizes.Length(); i++) { Size size = previewSizes[i]; delta = abs((long int)(size.height - aSize.height)); if (delta < minSizeDelta) { minSizeDelta = delta; best = size; } } } else if (!aSize.height) { // height not specified, find closest width match for (uint32_t i = 0; i < previewSizes.Length(); i++) { Size size = previewSizes[i]; delta = abs((long int)(size.width - aSize.width)); if (delta < minSizeDelta) { minSizeDelta = delta; best = size; } } } // Some camera drivers will ignore our preview size if it's larger // that the currently set video recording size, so we need to set // both here just in case. mParams.Set(CAMERA_PARAM_PREVIEWSIZE, best); mParams.Set(CAMERA_PARAM_VIDEOSIZE, best); mCurrentConfiguration.mPreviewSize = best; return PushParameters(); } 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); mRecorderProfile = GetGonkRecorderProfileManager().get()->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 = SetAndPush(CAMERA_PARAM_PREVIEWSIZE, size); if (NS_FAILED(rv)) { DOM_CAMERA_LOGE("Failed to set video mode preview size (0x%x)\n", rv); return rv; } rv = SetAndPush(CAMERA_PARAM_VIDEOSIZE, size); if (NS_FAILED(rv)) { DOM_CAMERA_LOGE("Failed to set video mode video size (0x%x)\n", rv); return rv; } rv = SetAndPush(CAMERA_PARAM_PREVIEWFRAMERATE, fps); if (NS_FAILED(rv)) { DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv); return rv; } mPreviewFps = fps; } mLastRecorderSize = size; 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 if (mRecorder) { DOM_CAMERA_LOGI("Stopping existing video recorder\n"); mRecorder->stop(); mRecorder = nullptr; OnRecorderStateChange(CameraControlListener::kRecorderStopped); } // 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::GraphicBufferLocked* aBuffer) { nsRefPtr frame = mImageContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR); GrallocImage* videoImage = static_cast(frame.get()); GrallocImage::GrallocData data; data.mGraphicBuffer = static_cast(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 OnNewPreviewFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* 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