dom/camera/CameraControlImpl.cpp
author Chris H-C <chutten@mozilla.com>
Mon, 04 Jul 2016 11:16:05 -0400
changeset 312997 df28918fe2361f0b54ca9ce4773a29c4c0675d06
parent 310949 564549c354b038a465c0b3fc245da3cab8753eab
permissions -rw-r--r--
bug 1218576 - Support remote accumulation via JS histograms. r=gfritzsche The JS histograms, too, need to dispatch their accumulations from child to parent. JSHistograms_Add now only supports histograms that are in gHistogramsMap or that were created in the parent process. After bug 1288745, maybe we'll be able to change this to be less convoluted. MozReview-Commit-ID: 3qTH89YKbGP

/* 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<nsIThread> 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 Runnable
  {
  public:
    NS_IMETHOD
    Run() override
    {
      char stackBaseGuess;
      profiler_register_thread("CameraThread", &stackBaseGuess);
      return NS_OK;
    }
  };

  // reuse the same camera thread to conserve resources
  nsCOMPtr<nsIThread> 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<unsigned int>(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<Face>& 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<unsigned int>(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<size_t>(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<size_t>(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 Runnable
{
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() { }

  RefPtr<CameraControlImpl> 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;
    RefPtr<DeviceStorageFileDescriptor> 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:
  RefPtr<CameraControlListener> mListener;
};

void
CameraControlImpl::AddListenerImpl(already_AddRefed<CameraControlListener> 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);

  RefPtr<CameraControlListener> 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));
  }
}