/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* 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/. */

#ifndef OMXCodecWrapper_h_
#define OMXCodecWrapper_h_

#include <gui/Surface.h>
#include <utils/RefBase.h>
#include <stagefright/foundation/ABuffer.h>
#include <stagefright/foundation/AMessage.h>
#include <stagefright/MediaCodec.h>

#include "AudioSegment.h"
#include "GonkNativeWindow.h"
#include "mozilla/media/MediaSystemResourceClient.h"
#include "mozilla/RefPtr.h"

#include <speex/speex_resampler.h>

namespace android {

// Wrapper class for managing HW codec reservations
class OMXCodecReservation : public RefBase
{
public:
  OMXCodecReservation(bool aEncoder) : mOwned(false)
  {
    mType = aEncoder ? mozilla::MediaSystemResourceType::VIDEO_ENCODER :
            mozilla::MediaSystemResourceType::VIDEO_DECODER;
  }

  virtual ~OMXCodecReservation()
  {
    ReleaseOMXCodec();
  }

  /** Reserve the Encode or Decode resource for this instance */
  virtual bool ReserveOMXCodec();

  /** Release the Encode or Decode resource for this instance */
  virtual void ReleaseOMXCodec();

private:
  mozilla::MediaSystemResourceType mType;
  bool mOwned;  // We already own this resource

  RefPtr<mozilla::MediaSystemResourceClient> mClient;
};


class OMXAudioEncoder;
class OMXVideoEncoder;

/**
 * This class (and its subclasses) wraps the video and audio codec from
 * MediaCodec API in libstagefright. Currently only AVC/H.264 video encoder and
 * AAC audio encoder are supported.
 *
 * OMXCodecWrapper has static creator functions that returns actual codec
 * instances for different types of codec supported and serves as superclass to
 * provide a function to read encoded data as byte array from codec. Two
 * subclasses, OMXAudioEncoder and OMXVideoEncoder, respectively provides
 * functions for encoding data from audio and video track.
 *
 * A typical usage is as follows:
 * - Call one of the creator function Create...() to get either a
 *   OMXAudioEncoder or OMXVideoEncoder object.
 * - Configure codec by providing characteristics of input raw data, such as
 *   video frame width and height, using Configure().
 * - Send raw data (and notify end of stream) with Encode().
 * - Get encoded data through GetNextEncodedFrame().
 * - Repeat previous 2 steps until end of stream.
 * - Destroy the object.
 *
 * The lifecycle of underlying OMX codec is binded with construction and
 * destruction of OMXCodecWrapper and subclass objects. For some types of
 * codecs, such as HW accelerated AVC/H.264 encoder, there can be only one
 * instance system-wise at a time, attempting to create another instance will
 * fail.
 */
class OMXCodecWrapper
{
public:
  // Codec types.
  enum CodecType {
    AAC_ENC, // AAC encoder.
    AMR_NB_ENC, // AMR_NB encoder.
    AVC_ENC, // AVC/H.264 encoder.
    EVRC_ENC, // EVRC encoder
    TYPE_COUNT
  };

  // Input and output flags.
  enum {
    // For Encode() and Encode, it indicates the end of input stream;
    // For GetNextEncodedFrame(), it indicates the end of output
    // stream.
    BUFFER_EOS = MediaCodec::BUFFER_FLAG_EOS,
    // For GetNextEncodedFrame(). It indicates the output buffer is an I-frame.
    BUFFER_SYNC_FRAME = MediaCodec::BUFFER_FLAG_SYNCFRAME,
    // For GetNextEncodedFrame(). It indicates that the output buffer contains
    // codec specific configuration info. (SPS & PPS for AVC/H.264;
    // DecoderSpecificInfo for AAC)
    BUFFER_CODEC_CONFIG = MediaCodec::BUFFER_FLAG_CODECCONFIG,
  };

  // Hard-coded values for AAC DecoderConfigDescriptor in libstagefright.
  // See MPEG4Writer::Track::writeMp4aEsdsBox()
  // Exposed for the need of MP4 container writer.
  enum {
    kAACBitrate = 96000,      // kbps
    kAACFrameSize = 768,      // bytes
    kAACFrameDuration = 1024, // How many samples per AAC frame.
  };

  /** Create a AAC audio encoder. Returns nullptr when failed. */
  static OMXAudioEncoder* CreateAACEncoder();

  /** Create a AMR audio encoder. Returns nullptr when failed. */
  static OMXAudioEncoder* CreateAMRNBEncoder();

  /** Create a EVRC audio encoder. Returns nullptr when failed. */
  static OMXAudioEncoder* CreateEVRCEncoder();

  /** Create a AVC/H.264 video encoder. Returns nullptr when failed. */
  static OMXVideoEncoder* CreateAVCEncoder();

  virtual ~OMXCodecWrapper();

  /**
   * Get the next available encoded data from MediaCodec. The data will be
   * copied into aOutputBuf array, with its timestamp (in microseconds) in
   * aOutputTimestamp.
   * Wait at most aTimeout microseconds to dequeue a output buffer.
   */
  nsresult GetNextEncodedFrame(nsTArray<uint8_t>* aOutputBuf,
                               int64_t* aOutputTimestamp, int* aOutputFlags,
                               int64_t aTimeOut);
  /*
   * Get the codec type
   */
  int GetCodecType() { return mCodecType; }
protected:
  /**
   * See whether the object has been initialized successfully and is ready to
   * use.
   */
  virtual bool IsValid() { return mCodec != nullptr; }

  /**
   * Construct codec specific configuration blob with given data aData generated
   * by media codec and append it into aOutputBuf. Needed by MP4 container
   * writer for generating decoder config box, or WebRTC for generating RTP
   * packets. Returns OK if succeed.
   */
  virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
                                       ABuffer* aData) = 0;

  /**
   * Append encoded frame data generated by media codec (stored in aData and
   * is aSize bytes long) into aOutputBuf. Subclasses can override this function
   * to process the data for specific container writer.
   */
  virtual void AppendFrame(nsTArray<uint8_t>* aOutputBuf,
                           const uint8_t* aData, size_t aSize)
  {
    aOutputBuf->AppendElements(aData, aSize);
  }

private:
  // Hide these. User should always use creator functions to get a media codec.
  OMXCodecWrapper() = delete;
  OMXCodecWrapper(const OMXCodecWrapper&) = delete;
  OMXCodecWrapper& operator=(const OMXCodecWrapper&) = delete;

  /**
   * Create a media codec of given type. It will be a AVC/H.264 video encoder if
   * aCodecType is CODEC_AVC_ENC, or AAC audio encoder if aCodecType is
   * CODEC_AAC_ENC.
   */
  OMXCodecWrapper(CodecType aCodecType);

  // For subclasses to access hidden constructor and implementation details.
  friend class OMXAudioEncoder;
  friend class OMXVideoEncoder;

  /**
   * Start the media codec.
   */
  status_t Start();

  /**
   * Stop the media codec.
   */
  status_t Stop();

  // The actual codec instance provided by libstagefright.
  sp<MediaCodec> mCodec;

  // A dedicate message loop with its own thread used by MediaCodec.
  sp<ALooper> mLooper;

  Vector<sp<ABuffer> > mInputBufs;  // MediaCodec buffers to hold input data.
  Vector<sp<ABuffer> > mOutputBufs; // MediaCodec buffers to hold output data.

  int mCodecType;
  bool mStarted; // Has MediaCodec been started?
  bool mAMRCSDProvided;
  bool mEVRCCSDProvided;
};

/**
 * Audio encoder.
 */
class OMXAudioEncoder final : public OMXCodecWrapper
{
public:
  /**
   * Configure audio codec parameters and start media codec. It must be called
   * before calling Encode() and GetNextEncodedFrame().
   * aReSamplingRate = 0 means no resampler required
   */
  nsresult Configure(int aChannelCount, int aInputSampleRate, int aEncodedSampleRate);

  /**
   * Encode 16-bit PCM audio samples stored in aSegment. To notify end of
   * stream, set aInputFlags to BUFFER_EOS. Since encoder has limited buffers,
   * this function might not be able to encode all chunks in one call, however
   * it will remove chunks it consumes from aSegment.
   * aSendEOS is the output to tell the caller EOS signal sent into MediaCodec
   * because the signal might not be sent due to the dequeueInputBuffer timeout.
   * And the value of aSendEOS won't be set to any default value, only set to
   * true when EOS signal sent into MediaCodec.
   */
  nsresult Encode(mozilla::AudioSegment& aSegment, int aInputFlags = 0,
                  bool* aSendEOS = nullptr);

  ~OMXAudioEncoder();
protected:
  virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
                                       ABuffer* aData) override;
private:
  // Hide these. User should always use creator functions to get a media codec.
  OMXAudioEncoder() = delete;
  OMXAudioEncoder(const OMXAudioEncoder&) = delete;
  OMXAudioEncoder& operator=(const OMXAudioEncoder&) = delete;

  /**
   * Create a audio codec. It will be a AAC encoder if aCodecType is
   * CODEC_AAC_ENC.
   */
  OMXAudioEncoder(CodecType aCodecType)
    : OMXCodecWrapper(aCodecType)
    , mResampler(nullptr)
    , mChannels(0)
    , mResamplingRatio(0)
    , mTimestamp(0)
    , mSampleDuration(0) {}

  // For creator function to access hidden constructor.
  friend class OMXCodecWrapper;
  friend class InputBufferHelper;

  /**
   * If the input sample rate does not divide 48kHz evenly, the input data are
   * resampled.
   */
  SpeexResamplerState* mResampler;
  // Number of audio channels.
  size_t mChannels;

  float mResamplingRatio;
  // The total duration of audio samples that have been encoded in microseconds.
  int64_t mTimestamp;
  // Time per audio sample in microseconds.
  int64_t mSampleDuration;
};

/**
 * Video encoder.
 */
class OMXVideoEncoder final : public OMXCodecWrapper
{
public:
  // Types of output blob format.
  enum BlobFormat {
    AVC_MP4, // MP4 file config descripter (defined in ISO/IEC 14496-15 5.2.4.1.1)
    AVC_NAL  // NAL (Network Abstract Layer) (defined in ITU-T H.264 7.4.1)
  };

  /**
   * Configure video codec parameters and start media codec. It must be called
   * before calling Encode() and GetNextEncodedFrame().
   * aBlobFormat specifies output blob format provided by encoder. It can be
   * AVC_MP4 or AVC_NAL.
   * Configure sets up most format value to values appropriate for camera use.
   * ConfigureDirect lets the caller determine all the defaults.
   */
  nsresult Configure(int aWidth, int aHeight, int aFrameRate,
                     BlobFormat aBlobFormat = BlobFormat::AVC_MP4);
  nsresult ConfigureDirect(sp<AMessage>& aFormat,
                           BlobFormat aBlobFormat = BlobFormat::AVC_MP4);

  /**
   * Encode a aWidth pixels wide and aHeight pixels tall video frame of
   * semi-planar YUV420 format stored in the buffer of aImage. aTimestamp gives
   * the frame timestamp/presentation time (in microseconds). To notify end of
   * stream, set aInputFlags to BUFFER_EOS.
   * aSendEOS is the output to tell the caller EOS signal sent into MediaCodec
   * because the signal might not be sent due to the dequeueInputBuffer timeout.
   * And the value of aSendEOS won't be set to any default value, only set to
   * true when EOS signal sent into MediaCodec.
   */
  nsresult Encode(const mozilla::layers::Image* aImage, int aWidth, int aHeight,
                  int64_t aTimestamp, int aInputFlags = 0,
                  bool* aSendEOS = nullptr);

#if ANDROID_VERSION >= 18
  /** Set encoding bitrate (in kbps). */
  nsresult SetBitrate(int32_t aKbps);
#endif

  /**
   * Ask codec to generate an instantaneous decoding refresh (IDR) frame
   * (defined in ISO/IEC 14496-10).
   */
  nsresult RequestIDRFrame();

protected:
  virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
                                       ABuffer* aData) override;

  // If configured to output MP4 format blob, AVC/H.264 encoder has to replace
  // NAL unit start code with the unit length as specified in
  // ISO/IEC 14496-15 5.2.3.
  virtual void AppendFrame(nsTArray<uint8_t>* aOutputBuf,
                           const uint8_t* aData, size_t aSize) override;

private:
  // Hide these. User should always use creator functions to get a media codec.
  OMXVideoEncoder() = delete;
  OMXVideoEncoder(const OMXVideoEncoder&) = delete;
  OMXVideoEncoder& operator=(const OMXVideoEncoder&) = delete;

  /**
   * Create a video codec. It will be a AVC/H.264 encoder if aCodecType is
   * CODEC_AVC_ENC.
   */
  OMXVideoEncoder(CodecType aCodecType)
    : OMXCodecWrapper(aCodecType)
    , mWidth(0)
    , mHeight(0)
    , mBlobFormat(BlobFormat::AVC_MP4)
  {}

  // For creator function to access hidden constructor.
  friend class OMXCodecWrapper;

  int mWidth;
  int mHeight;
  BlobFormat mBlobFormat;
};

} // namespace android

#endif // OMXCodecWrapper_h_
