/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CameraControlImpl.h" #include "base/basictypes.h" #include "mozilla/Assertions.h" #include "mozilla/unused.h" #include "nsPrintfCString.h" #include "nsIWeakReferenceUtils.h" #include "CameraCommon.h" #include "nsGlobalWindow.h" #include "DeviceStorageFileDescriptor.h" #include "CameraControlListener.h" using namespace mozilla; /* static */ StaticRefPtr CameraControlImpl::sCameraThread; CameraControlImpl::CameraControlImpl() : mListenerLock("mozilla::camera::CameraControlImpl.Listeners") , mPreviewState(CameraControlListener::kPreviewStopped) , mHardwareState(CameraControlListener::kHardwareUninitialized) , mHardwareStateChangeReason(NS_OK) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); mCurrentConfiguration.mMode = ICameraControl::kUnspecifiedMode; class Delegate : public nsRunnable { public: NS_IMETHOD Run() { char stackBaseGuess; profiler_register_thread("CameraThread", &stackBaseGuess); return NS_OK; } }; // reuse the same camera thread to conserve resources nsCOMPtr ct = do_QueryInterface(sCameraThread); if (ct) { mCameraThread = ct.forget(); } else { nsresult rv = NS_NewNamedThread("CameraThread", getter_AddRefs(mCameraThread)); if (NS_FAILED(rv)) { MOZ_CRASH("Failed to create new Camera Thread"); } mCameraThread->Dispatch(new Delegate(), NS_DISPATCH_NORMAL); sCameraThread = mCameraThread; } } CameraControlImpl::~CameraControlImpl() { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); } void CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aNewState, nsresult aReason) { // This callback can run on threads other than the Main Thread and // the Camera Thread. On Gonk, it may be called from the camera's // local binder thread, should the mediaserver process die. MutexAutoLock lock(mListenerLock); if (aNewState == mHardwareState) { DOM_CAMERA_LOGI("OnHardwareStateChange: state did not change from %d\n", mHardwareState); return; } const char* state[] = { "uninitialized", "closed", "open", "failed" }; MOZ_ASSERT(aNewState >= 0); if (static_cast(aNewState) < sizeof(state) / sizeof(state[0])) { DOM_CAMERA_LOGI("New hardware state is '%s' (reason=0x%x)\n", state[aNewState], aReason); } else { DOM_CAMERA_LOGE("OnHardwareStateChange: got invalid HardwareState value %d\n", aNewState); } mHardwareState = aNewState; mHardwareStateChangeReason = aReason; for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnHardwareStateChange(mHardwareState, mHardwareStateChangeReason); } } void CameraControlImpl::OnConfigurationChange() { MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread); MutexAutoLock lock(mListenerLock); DOM_CAMERA_LOGI("OnConfigurationChange : %zu listeners\n", mListeners.Length()); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnConfigurationChange(mCurrentConfiguration); } } void CameraControlImpl::OnAutoFocusComplete(bool aAutoFocusSucceeded) { // This callback can run on threads other than the Main Thread and // the Camera Thread. On Gonk, it is called from the camera // library's auto focus thread. MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnAutoFocusComplete(aAutoFocusSucceeded); } } void CameraControlImpl::OnAutoFocusMoving(bool aIsMoving) { MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnAutoFocusMoving(aIsMoving); } } void CameraControlImpl::OnFacesDetected(const nsTArray& aFaces) { // This callback can run on threads other than the Main Thread and // the Camera Thread. On Gonk, it is called from the camera // library's face detection thread. MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnFacesDetected(aFaces); } } void CameraControlImpl::OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { // This callback can run on threads other than the Main Thread and // the Camera Thread. On Gonk, it is called from the camera // library's snapshot thread. MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnTakePictureComplete(aData, aLength, aMimeType); } } void CameraControlImpl::OnPoster(dom::BlobImpl* aBlobImpl) { // This callback can run on threads other than the Main Thread and // the Camera Thread. MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnPoster(aBlobImpl); } } void CameraControlImpl::OnShutter() { // This callback can run on threads other than the Main Thread and // the Camera Thread. On Gonk, it is called from the camera driver's // preview thread. MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnShutter(); } } void CameraControlImpl::OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNumber) { // This callback can run on threads other than the Main Thread and // the Camera Thread. On Gonk, it is called from the media encoder // thread. MutexAutoLock lock(mListenerLock); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnRecorderStateChange(aState, aStatus, aTrackNumber); } } void CameraControlImpl::OnPreviewStateChange(CameraControlListener::PreviewState aNewState) { // This callback runs on the Main Thread and the Camera Thread, and // may run on the local binder thread, should the mediaserver // process die. MutexAutoLock lock(mListenerLock); if (aNewState == mPreviewState) { DOM_CAMERA_LOGI("OnPreviewStateChange: state did not change from %d\n", mPreviewState); return; } const char* state[] = { "stopped", "paused", "started" }; MOZ_ASSERT(aNewState >= 0); if (static_cast(aNewState) < sizeof(state) / sizeof(state[0])) { DOM_CAMERA_LOGI("New preview state is '%s'\n", state[aNewState]); } else { DOM_CAMERA_LOGE("OnPreviewStateChange: got unknown PreviewState value %d\n", aNewState); } mPreviewState = aNewState; for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnPreviewStateChange(mPreviewState); } } void CameraControlImpl::OnRateLimitPreview(bool aLimit) { // This function runs on neither the Main Thread nor the Camera Thread. MutexAutoLock lock(mListenerLock); DOM_CAMERA_LOGI("OnRateLimitPreview: %d\n", aLimit); for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnRateLimitPreview(aLimit); } } bool CameraControlImpl::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) { // This function runs on neither the Main Thread nor the Camera Thread. // On Gonk, it is called from the camera driver's preview thread. MutexAutoLock lock(mListenerLock); DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %zu preview frame listener(s)\n", mListeners.Length()); bool consumed = false; for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; consumed = l->OnNewPreviewFrame(aImage, aWidth, aHeight) || consumed; } return consumed; } void CameraControlImpl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError) { // This callback can run on threads other than the Main Thread and // the Camera Thread. MutexAutoLock lock(mListenerLock); const char* context[] = { "StartCamera", "StopCamera", "AutoFocus", "StartFaceDetection", "StopFaceDetection", "TakePicture", "StartRecording", "StopRecording", "PauseRecording", "ResumeRecording", "SetConfiguration", "StartPreview", "StopPreview", "SetPictureSize", "SetThumbnailSize", "ResumeContinuousFocus", "Unspecified" }; if (static_cast(aContext) < sizeof(context) / sizeof(context[0])) { DOM_CAMERA_LOGW("CameraControlImpl::OnUserError : aContext='%s' (%d), aError=0x%x\n", context[aContext], aContext, aError); } else { DOM_CAMERA_LOGE("CameraControlImpl::OnUserError : aContext=%d, aError=0x%x\n", aContext, aError); } for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnUserError(aContext, aError); } } void CameraControlImpl::OnSystemError(CameraControlListener::SystemContext aContext, nsresult aError) { // This callback can run on threads other than the Main Thread and // the Camera Thread. MutexAutoLock lock(mListenerLock); const char* context[] = { "Camera Service" }; if (static_cast(aContext) < sizeof(context) / sizeof(context[0])) { DOM_CAMERA_LOGW("CameraControlImpl::OnSystemError : aContext='%s' (%d), aError=0x%x\n", context[aContext], aContext, aError); } else { DOM_CAMERA_LOGE("CameraControlImpl::OnSystemError : aContext=%d, aError=0x%x\n", aContext, aError); } for (uint32_t i = 0; i < mListeners.Length(); ++i) { CameraControlListener* l = mListeners[i]; l->OnSystemError(aContext, aError); } } // Camera control asynchronous message; these are dispatched from // the Main Thread to the Camera Thread, where they are consumed. class CameraControlImpl::ControlMessage : public nsRunnable { public: ControlMessage(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : mCameraControl(aCameraControl) , mContext(aContext) { } virtual nsresult RunImpl() = 0; NS_IMETHOD Run() override { MOZ_ASSERT(mCameraControl); MOZ_ASSERT(NS_GetCurrentThread() == mCameraControl->mCameraThread); nsresult rv = RunImpl(); if (NS_FAILED(rv)) { nsPrintfCString msg("Camera control API(%d) failed with 0x%x", mContext, rv); NS_WARNING(msg.get()); mCameraControl->OnUserError(mContext, rv); } return NS_OK; } protected: virtual ~ControlMessage() { } nsRefPtr mCameraControl; CameraControlListener::UserContext mContext; }; nsresult CameraControlImpl::Dispatch(ControlMessage* aMessage) { nsresult rv = mCameraThread->Dispatch(aMessage, NS_DISPATCH_NORMAL); if (NS_SUCCEEDED(rv)) { return NS_OK; } nsPrintfCString msg("Failed to dispatch camera control message (0x%x)", rv); NS_WARNING(msg.get()); return NS_ERROR_FAILURE; } nsresult CameraControlImpl::Start(const Configuration* aConfig) { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext, const Configuration* aConfig) : ControlMessage(aCameraControl, aContext) , mHaveInitialConfig(false) { if (aConfig) { mConfig = *aConfig; mHaveInitialConfig = true; } } nsresult RunImpl() override { if (mHaveInitialConfig) { return mCameraControl->StartImpl(&mConfig); } return mCameraControl->StartImpl(); } protected: bool mHaveInitialConfig; Configuration mConfig; }; return Dispatch(new Message(this, CameraControlListener::kInStartCamera, aConfig)); } nsresult CameraControlImpl::SetConfiguration(const Configuration& aConfig) { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext, const Configuration& aConfig) : ControlMessage(aCameraControl, aContext) , mConfig(aConfig) { } nsresult RunImpl() override { return mCameraControl->SetConfigurationImpl(mConfig); } protected: Configuration mConfig; }; return Dispatch(new Message(this, CameraControlListener::kInSetConfiguration, aConfig)); } nsresult CameraControlImpl::AutoFocus() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->AutoFocusImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInAutoFocus)); } nsresult CameraControlImpl::StartFaceDetection() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->StartFaceDetectionImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInStartFaceDetection)); } nsresult CameraControlImpl::StopFaceDetection() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->StopFaceDetectionImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInStopFaceDetection)); } nsresult CameraControlImpl::TakePicture() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->TakePictureImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInTakePicture)); } nsresult CameraControlImpl::StartRecording(DeviceStorageFileDescriptor* aFileDescriptor, const StartRecordingOptions* aOptions) { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext, const StartRecordingOptions* aOptions, DeviceStorageFileDescriptor* aFileDescriptor) : ControlMessage(aCameraControl, aContext) , mOptionsPassed(false) , mFileDescriptor(aFileDescriptor) { if (aOptions) { mOptions = *aOptions; mOptionsPassed = true; } } nsresult RunImpl() override { return mCameraControl->StartRecordingImpl(mFileDescriptor, mOptionsPassed ? &mOptions : nullptr); } protected: StartRecordingOptions mOptions; bool mOptionsPassed; nsRefPtr mFileDescriptor; }; if (!aFileDescriptor) { return NS_ERROR_INVALID_ARG; } return Dispatch(new Message(this, CameraControlListener::kInStartRecording, aOptions, aFileDescriptor)); } nsresult CameraControlImpl::StopRecording() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->StopRecordingImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInStopRecording)); } nsresult CameraControlImpl::PauseRecording() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->PauseRecordingImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInPauseRecording)); } nsresult CameraControlImpl::ResumeRecording() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->ResumeRecordingImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInResumeRecording)); } nsresult CameraControlImpl::StartPreview() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->StartPreviewImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInStartPreview)); } nsresult CameraControlImpl::StopPreview() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->StopPreviewImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInStopPreview)); } nsresult CameraControlImpl::ResumeContinuousFocus() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->ResumeContinuousFocusImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInResumeContinuousFocus)); } nsresult CameraControlImpl::Stop() { class Message : public ControlMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener::UserContext aContext) : ControlMessage(aCameraControl, aContext) { } nsresult RunImpl() override { return mCameraControl->StopImpl(); } }; return Dispatch(new Message(this, CameraControlListener::kInStopCamera)); } class CameraControlImpl::ListenerMessage : public CameraControlImpl::ControlMessage { public: ListenerMessage(CameraControlImpl* aCameraControl, CameraControlListener* aListener) : ControlMessage(aCameraControl, CameraControlListener::kInUnspecified) , mListener(aListener) { } protected: nsRefPtr mListener; }; void CameraControlImpl::AddListenerImpl(already_AddRefed aListener) { MutexAutoLock lock(mListenerLock); CameraControlListener* l = *mListeners.AppendElement() = aListener; DOM_CAMERA_LOGI("Added camera control listener %p\n", l); // Update the newly-added listener's state l->OnConfigurationChange(mCurrentConfiguration); l->OnHardwareStateChange(mHardwareState, mHardwareStateChangeReason); l->OnPreviewStateChange(mPreviewState); } void CameraControlImpl::AddListener(CameraControlListener* aListener) { class Message : public ListenerMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener* aListener) : ListenerMessage(aCameraControl, aListener) { } nsresult RunImpl() override { mCameraControl->AddListenerImpl(mListener.forget()); return NS_OK; } }; if (aListener) { Dispatch(new Message(this, aListener)); } } void CameraControlImpl::RemoveListenerImpl(CameraControlListener* aListener) { MutexAutoLock lock(mListenerLock); nsRefPtr l(aListener); mListeners.RemoveElement(l); DOM_CAMERA_LOGI("Removed camera control listener %p\n", l.get()); // XXXmikeh - do we want to notify the listener that it has been removed? } void CameraControlImpl::RemoveListener(CameraControlListener* aListener) { class Message : public ListenerMessage { public: Message(CameraControlImpl* aCameraControl, CameraControlListener* aListener) : ListenerMessage(aCameraControl, aListener) { } nsresult RunImpl() override { mCameraControl->RemoveListenerImpl(mListener); return NS_OK; } }; if (aListener) { Dispatch(new Message(this, aListener)); } }