/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "OmxPlatformLayer.h"

#include "OmxDataDecoder.h"
#include "OMX_Component.h"
#include "OMX_VideoExt.h"  // For VP8.

#ifdef MOZ_OMX
#  include "PureOmxPlatformLayer.h"
#endif

#include "VPXDecoder.h"

#ifdef LOG
#  undef LOG
#endif

#define LOG(arg, ...)                        \
  MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, \
          ("OmxPlatformLayer -- %s: " arg, __func__, ##__VA_ARGS__))

#define RETURN_IF_ERR(err)     \
  if (err != OMX_ErrorNone) {  \
    LOG("error: 0x%08x", err); \
    return err;                \
  }

// Common OMX decoder configuration code.
namespace mozilla {

// This helper class encapsulates the details of component parameters setting
// for different OMX audio & video codecs.
template <typename ParamType>
class OmxConfig {
 public:
  virtual ~OmxConfig() = default;
  // Subclasses should implement this method to configure the codec.
  virtual OMX_ERRORTYPE Apply(OmxPlatformLayer& aOmx,
                              const ParamType& aParam) = 0;
};

typedef OmxConfig<AudioInfo> OmxAudioConfig;
typedef OmxConfig<VideoInfo> OmxVideoConfig;

template <typename ConfigType>
UniquePtr<ConfigType> ConfigForMime(const nsACString&);

static OMX_ERRORTYPE ConfigAudioOutputPort(OmxPlatformLayer& aOmx,
                                           const AudioInfo& aInfo) {
  OMX_PARAM_PORTDEFINITIONTYPE def;
  InitOmxParameter(&def);
  def.nPortIndex = aOmx.OutputPortIndex();
  OMX_ERRORTYPE err =
      aOmx.GetParameter(OMX_IndexParamPortDefinition, &def, sizeof(def));
  RETURN_IF_ERR(err);

  def.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
  err = aOmx.SetParameter(OMX_IndexParamPortDefinition, &def, sizeof(def));
  RETURN_IF_ERR(err);

  OMX_AUDIO_PARAM_PCMMODETYPE pcmParams;
  InitOmxParameter(&pcmParams);
  pcmParams.nPortIndex = def.nPortIndex;
  err =
      aOmx.GetParameter(OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams));
  RETURN_IF_ERR(err);

  pcmParams.nChannels = aInfo.mChannels;
  pcmParams.eNumData = OMX_NumericalDataSigned;
  pcmParams.bInterleaved = OMX_TRUE;
  pcmParams.nBitPerSample = 16;
  pcmParams.nSamplingRate = aInfo.mRate;
  pcmParams.ePCMMode = OMX_AUDIO_PCMModeLinear;
  err =
      aOmx.SetParameter(OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams));
  RETURN_IF_ERR(err);

  LOG("Config OMX_IndexParamAudioPcm, channel %lu, sample rate %lu",
      pcmParams.nChannels, pcmParams.nSamplingRate);

  return OMX_ErrorNone;
}

class OmxAacConfig : public OmxAudioConfig {
 public:
  OMX_ERRORTYPE Apply(OmxPlatformLayer& aOmx, const AudioInfo& aInfo) override {
    OMX_AUDIO_PARAM_AACPROFILETYPE aacProfile;
    InitOmxParameter(&aacProfile);
    aacProfile.nPortIndex = aOmx.InputPortIndex();
    OMX_ERRORTYPE err = aOmx.GetParameter(OMX_IndexParamAudioAac, &aacProfile,
                                          sizeof(aacProfile));
    RETURN_IF_ERR(err);

    aacProfile.nChannels = aInfo.mChannels;
    aacProfile.nSampleRate = aInfo.mRate;
    aacProfile.eAACProfile =
        static_cast<OMX_AUDIO_AACPROFILETYPE>(aInfo.mProfile);
    err = aOmx.SetParameter(OMX_IndexParamAudioAac, &aacProfile,
                            sizeof(aacProfile));
    RETURN_IF_ERR(err);

    LOG("Config OMX_IndexParamAudioAac, channel %lu, sample rate %lu, profile "
        "%d",
        aacProfile.nChannels, aacProfile.nSampleRate, aacProfile.eAACProfile);

    return ConfigAudioOutputPort(aOmx, aInfo);
  }
};

class OmxMp3Config : public OmxAudioConfig {
 public:
  OMX_ERRORTYPE Apply(OmxPlatformLayer& aOmx, const AudioInfo& aInfo) override {
    OMX_AUDIO_PARAM_MP3TYPE mp3Param;
    InitOmxParameter(&mp3Param);
    mp3Param.nPortIndex = aOmx.InputPortIndex();
    OMX_ERRORTYPE err =
        aOmx.GetParameter(OMX_IndexParamAudioMp3, &mp3Param, sizeof(mp3Param));
    RETURN_IF_ERR(err);

    mp3Param.nChannels = aInfo.mChannels;
    mp3Param.nSampleRate = aInfo.mRate;
    err =
        aOmx.SetParameter(OMX_IndexParamAudioMp3, &mp3Param, sizeof(mp3Param));
    RETURN_IF_ERR(err);

    LOG("Config OMX_IndexParamAudioMp3, channel %lu, sample rate %lu",
        mp3Param.nChannels, mp3Param.nSampleRate);

    return ConfigAudioOutputPort(aOmx, aInfo);
  }
};

enum OmxAmrSampleRate {
  kNarrowBand = 8000,
  kWideBand = 16000,
};

template <OmxAmrSampleRate R>
class OmxAmrConfig : public OmxAudioConfig {
 public:
  OMX_ERRORTYPE Apply(OmxPlatformLayer& aOmx, const AudioInfo& aInfo) override {
    OMX_AUDIO_PARAM_AMRTYPE def;
    InitOmxParameter(&def);
    def.nPortIndex = aOmx.InputPortIndex();
    OMX_ERRORTYPE err =
        aOmx.GetParameter(OMX_IndexParamAudioAmr, &def, sizeof(def));
    RETURN_IF_ERR(err);

    def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
    err = aOmx.SetParameter(OMX_IndexParamAudioAmr, &def, sizeof(def));
    RETURN_IF_ERR(err);

    MOZ_ASSERT(aInfo.mChannels == 1);
    MOZ_ASSERT(aInfo.mRate == R);

    return ConfigAudioOutputPort(aOmx, aInfo);
  }
};

template <>
UniquePtr<OmxAudioConfig> ConfigForMime(const nsACString& aMimeType) {
  UniquePtr<OmxAudioConfig> conf;

  if (OmxPlatformLayer::SupportsMimeType(aMimeType)) {
    if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
      conf.reset(new OmxAacConfig());
    } else if (aMimeType.EqualsLiteral("audio/mp3") ||
               aMimeType.EqualsLiteral("audio/mpeg")) {
      conf.reset(new OmxMp3Config());
    } else if (aMimeType.EqualsLiteral("audio/3gpp")) {
      conf.reset(new OmxAmrConfig<OmxAmrSampleRate::kNarrowBand>());
    } else if (aMimeType.EqualsLiteral("audio/amr-wb")) {
      conf.reset(new OmxAmrConfig<OmxAmrSampleRate::kWideBand>());
    }
  }
  return conf;
}

// There should be a better way to calculate it.
#define MIN_VIDEO_INPUT_BUFFER_SIZE 64 * 1024

class OmxCommonVideoConfig : public OmxVideoConfig {
 public:
  explicit OmxCommonVideoConfig() : OmxVideoConfig() {}

  OMX_ERRORTYPE Apply(OmxPlatformLayer& aOmx, const VideoInfo& aInfo) override {
    OMX_ERRORTYPE err = OMX_ErrorNone;
    OMX_PARAM_PORTDEFINITIONTYPE def;

    // Set up in/out port definition.
    nsTArray<uint32_t> ports;
    aOmx.GetPortIndices(ports);
    for (auto idx : ports) {
      InitOmxParameter(&def);
      def.nPortIndex = idx;
      err = aOmx.GetParameter(OMX_IndexParamPortDefinition, &def, sizeof(def));
      RETURN_IF_ERR(err);

      def.format.video.nFrameWidth = aInfo.mDisplay.width;
      def.format.video.nFrameHeight = aInfo.mDisplay.height;
      def.format.video.nStride = aInfo.mImage.width;
      def.format.video.nSliceHeight = aInfo.mImage.height;

      if (def.eDir == OMX_DirInput) {
        def.format.video.eCompressionFormat = aOmx.CompressionFormat();
        def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
        if (def.nBufferSize < MIN_VIDEO_INPUT_BUFFER_SIZE) {
          def.nBufferSize = aInfo.mImage.width * aInfo.mImage.height;
          LOG("Change input buffer size to %lu", def.nBufferSize);
        }
      } else {
        def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
      }

      err = aOmx.SetParameter(OMX_IndexParamPortDefinition, &def, sizeof(def));
    }
    return err;
  }
};

template <>
UniquePtr<OmxVideoConfig> ConfigForMime(const nsACString& aMimeType) {
  UniquePtr<OmxVideoConfig> conf;

  if (OmxPlatformLayer::SupportsMimeType(aMimeType)) {
    conf.reset(new OmxCommonVideoConfig());
  }
  return conf;
}

OMX_ERRORTYPE
OmxPlatformLayer::Config() {
  MOZ_ASSERT(mInfo);

  OMX_PORT_PARAM_TYPE portParam;
  InitOmxParameter(&portParam);
  if (mInfo->IsAudio()) {
    GetParameter(OMX_IndexParamAudioInit, &portParam, sizeof(portParam));
    mStartPortNumber = portParam.nStartPortNumber;
    UniquePtr<OmxAudioConfig> conf(
        ConfigForMime<OmxAudioConfig>(mInfo->mMimeType));
    MOZ_RELEASE_ASSERT(conf.get());
    return conf->Apply(*this, *(mInfo->GetAsAudioInfo()));
  } else if (mInfo->IsVideo()) {
    GetParameter(OMX_IndexParamVideoInit, &portParam, sizeof(portParam));
    UniquePtr<OmxVideoConfig> conf(
        ConfigForMime<OmxVideoConfig>(mInfo->mMimeType));
    MOZ_RELEASE_ASSERT(conf.get());
    return conf->Apply(*this, *(mInfo->GetAsVideoInfo()));
  } else {
    MOZ_ASSERT_UNREACHABLE("non-AV data (text?) is not supported.");
    return OMX_ErrorNotImplemented;
  }
}

OMX_VIDEO_CODINGTYPE
OmxPlatformLayer::CompressionFormat() {
  MOZ_ASSERT(mInfo);

  if (mInfo->mMimeType.EqualsLiteral("video/avc")) {
    return OMX_VIDEO_CodingAVC;
  } else if (mInfo->mMimeType.EqualsLiteral("video/mp4v-es") ||
             mInfo->mMimeType.EqualsLiteral("video/mp4")) {
    return OMX_VIDEO_CodingMPEG4;
  } else if (mInfo->mMimeType.EqualsLiteral("video/3gpp")) {
    return OMX_VIDEO_CodingH263;
  } else if (VPXDecoder::IsVP8(mInfo->mMimeType)) {
    return static_cast<OMX_VIDEO_CODINGTYPE>(OMX_VIDEO_CodingVP8);
  } else {
    MOZ_ASSERT_UNREACHABLE("Unsupported compression format");
    return OMX_VIDEO_CodingUnused;
  }
}

// Implementations for different platforms will be defined in their own files.
#if defined(MOZ_OMX)

bool OmxPlatformLayer::SupportsMimeType(const nsACString& aMimeType) {
  return PureOmxPlatformLayer::SupportsMimeType(aMimeType);
}

OmxPlatformLayer* OmxPlatformLayer::Create(
    OmxDataDecoder* aDataDecoder, OmxPromiseLayer* aPromiseLayer,
    TaskQueue* aTaskQueue, layers::ImageContainer* aImageContainer) {
  return new PureOmxPlatformLayer(aDataDecoder, aPromiseLayer, aTaskQueue,
                                  aImageContainer);
}

#else  // For platforms without OMX IL support.

bool OmxPlatformLayer::SupportsMimeType(const nsACString& aMimeType) {
  return false;
}

OmxPlatformLayer* OmxPlatformLayer::Create(
    OmxDataDecoder* aDataDecoder, OmxPromiseLayer* aPromiseLayer,
    TaskQueue* aTaskQueue, layers::ImageContainer* aImageContainer) {
  return nullptr;
}

#endif
}  // namespace mozilla
