/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#if !defined(MediaData_h)
#define MediaData_h

#include "nsSize.h"
#include "mozilla/gfx/Rect.h"
#include "nsRect.h"
#include "AudioSampleFormat.h"
#include "nsIMemoryReporter.h"
#include "SharedBuffer.h"
#include "nsRefPtr.h"
#include "nsTArray.h"

namespace mozilla {

namespace layers {
class Image;
class ImageContainer;
}

class LargeDataBuffer;
class DataBuffer;

// Container that holds media samples.
class MediaData {
public:

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaData)

  enum Type {
    AUDIO_DATA = 0,
    VIDEO_DATA,
    RAW_DATA
  };

  MediaData(Type aType,
            int64_t aOffset,
            int64_t aTimestamp,
            int64_t aDuration)
    : mType(aType)
    , mOffset(aOffset)
    , mTime(aTimestamp)
    , mTimecode(aTimestamp)
    , mDuration(aDuration)
    , mKeyframe(false)
    , mDiscontinuity(false)
  {}

  // Type of contained data.
  const Type mType;

  // Approximate byte offset where this data was demuxed from its media.
  int64_t mOffset;

  // Start time of sample, in microseconds.
  int64_t mTime;

  // Codec specific internal time code. For Ogg based codecs this is the
  // granulepos.
  int64_t mTimecode;

  // Duration of sample, in microseconds.
  int64_t mDuration;

  bool mKeyframe;

  // True if this is the first sample after a gap or discontinuity in
  // the stream. This is true for the first sample in a stream after a seek.
  bool mDiscontinuity;

  int64_t GetEndTime() const { return mTime + mDuration; }

protected:
  explicit MediaData(Type aType)
    : mType(aType)
    , mOffset(0)
    , mTime(0)
    , mTimecode(0)
    , mDuration(0)
    , mKeyframe(false)
    , mDiscontinuity(false)
  {}

  virtual ~MediaData() {}

};

// Holds chunk a decoded audio frames.
class AudioData : public MediaData {
public:

  AudioData(int64_t aOffset,
            int64_t aTime,
            int64_t aDuration,
            uint32_t aFrames,
            AudioDataValue* aData,
            uint32_t aChannels,
            uint32_t aRate)
    : MediaData(AUDIO_DATA, aOffset, aTime, aDuration)
    , mFrames(aFrames)
    , mChannels(aChannels)
    , mRate(aRate)
    , mAudioData(aData) {}

  // Creates a new VideoData identical to aOther, but with a different
  // specified timestamp and duration. All data from aOther is copied
  // into the new AudioData but the audio data which is transferred.
  // After such call, the original aOther is unusable.
  static already_AddRefed<AudioData>
  TransferAndUpdateTimestampAndDuration(AudioData* aOther,
                                        int64_t aTimestamp,
                                        int64_t aDuration);

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;

  // If mAudioBuffer is null, creates it from mAudioData.
  void EnsureAudioBuffer();

  const uint32_t mFrames;
  const uint32_t mChannels;
  const uint32_t mRate;
  // At least one of mAudioBuffer/mAudioData must be non-null.
  // mChannels channels, each with mFrames frames
  nsRefPtr<SharedBuffer> mAudioBuffer;
  // mFrames frames, each with mChannels values
  nsAutoArrayPtr<AudioDataValue> mAudioData;

protected:
  ~AudioData() {}
};

namespace layers {
class TextureClient;
class PlanarYCbCrImage;
}

class VideoInfo;

// Holds a decoded video frame, in YCbCr format. These are queued in the reader.
class VideoData : public MediaData {
public:
  typedef gfx::IntRect IntRect;
  typedef gfx::IntSize IntSize;
  typedef layers::ImageContainer ImageContainer;
  typedef layers::Image Image;
  typedef layers::PlanarYCbCrImage PlanarYCbCrImage;

  // YCbCr data obtained from decoding the video. The index's are:
  //   0 = Y
  //   1 = Cb
  //   2 = Cr
  struct YCbCrBuffer {
    struct Plane {
      uint8_t* mData;
      uint32_t mWidth;
      uint32_t mHeight;
      uint32_t mStride;
      uint32_t mOffset;
      uint32_t mSkip;
    };

    Plane mPlanes[3];
  };

  // Constructs a VideoData object. If aImage is nullptr, creates a new Image
  // holding a copy of the YCbCr data passed in aBuffer. If aImage is not
  // nullptr, it's stored as the underlying video image and aBuffer is assumed
  // to point to memory within aImage so no copy is made. aTimecode is a codec
  // specific number representing the timestamp of the frame of video data.
  // Returns nsnull if an error occurs. This may indicate that memory couldn't
  // be allocated to create the VideoData object, or it may indicate some
  // problem with the input data (e.g. negative stride).
  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
                                            ImageContainer* aContainer,
                                            Image* aImage,
                                            int64_t aOffset,
                                            int64_t aTime,
                                            int64_t aDuration,
                                            const YCbCrBuffer &aBuffer,
                                            bool aKeyframe,
                                            int64_t aTimecode,
                                            const IntRect& aPicture);

  // Variant that always makes a copy of aBuffer
  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
                                            ImageContainer* aContainer,
                                            int64_t aOffset,
                                            int64_t aTime,
                                            int64_t aDuration,
                                            const YCbCrBuffer &aBuffer,
                                            bool aKeyframe,
                                            int64_t aTimecode,
                                            const IntRect& aPicture);

  // Variant to create a VideoData instance given an existing aImage
  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
                                            Image* aImage,
                                            int64_t aOffset,
                                            int64_t aTime,
                                            int64_t aDuration,
                                            const YCbCrBuffer &aBuffer,
                                            bool aKeyframe,
                                            int64_t aTimecode,
                                            const IntRect& aPicture);

  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
                                            ImageContainer* aContainer,
                                            int64_t aOffset,
                                            int64_t aTime,
                                            int64_t aDuration,
                                            layers::TextureClient* aBuffer,
                                            bool aKeyframe,
                                            int64_t aTimecode,
                                            const IntRect& aPicture);

  static already_AddRefed<VideoData> CreateFromImage(const VideoInfo& aInfo,
                                                     ImageContainer* aContainer,
                                                     int64_t aOffset,
                                                     int64_t aTime,
                                                     int64_t aDuration,
                                                     const nsRefPtr<Image>& aImage,
                                                     bool aKeyframe,
                                                     int64_t aTimecode,
                                                     const IntRect& aPicture);

  // Creates a new VideoData identical to aOther, but with a different
  // specified duration. All data from aOther is copied into the new
  // VideoData. The new VideoData's mImage field holds a reference to
  // aOther's mImage, i.e. the Image is not copied. This function is useful
  // in reader backends that can't determine the duration of a VideoData
  // until the next frame is decoded, i.e. it's a way to change the const
  // duration field on a VideoData.
  static already_AddRefed<VideoData> ShallowCopyUpdateDuration(const VideoData* aOther,
                                                               int64_t aDuration);

  // Creates a new VideoData identical to aOther, but with a different
  // specified timestamp. All data from aOther is copied into the new
  // VideoData, as ShallowCopyUpdateDuration() does.
  static already_AddRefed<VideoData> ShallowCopyUpdateTimestamp(const VideoData* aOther,
                                                                int64_t aTimestamp);

  // Creates a new VideoData identical to aOther, but with a different
  // specified timestamp and duration. All data from aOther is copied
  // into the new VideoData, as ShallowCopyUpdateDuration() does.
  static already_AddRefed<VideoData>
  ShallowCopyUpdateTimestampAndDuration(const VideoData* aOther, int64_t aTimestamp,
                                        int64_t aDuration);

  // Initialize PlanarYCbCrImage. Only When aCopyData is true,
  // video data is copied to PlanarYCbCrImage.
  static void SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
                                  const VideoInfo& aInfo,
                                  const YCbCrBuffer &aBuffer,
                                  const IntRect& aPicture,
                                  bool aCopyData);

  // Constructs a duplicate VideoData object. This intrinsically tells the
  // player that it does not need to update the displayed frame when this
  // frame is played; this frame is identical to the previous.
  static already_AddRefed<VideoData> CreateDuplicate(int64_t aOffset,
                                                     int64_t aTime,
                                                     int64_t aDuration,
                                                     int64_t aTimecode)
  {
    nsRefPtr<VideoData> rv = new VideoData(aOffset, aTime, aDuration, aTimecode);
    return rv.forget();
  }

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;

  // Dimensions at which to display the video frame. The picture region
  // will be scaled to this size. This is should be the picture region's
  // dimensions scaled with respect to its aspect ratio.
  const IntSize mDisplay;

  // This frame's image.
  nsRefPtr<Image> mImage;

  // When true, denotes that this frame is identical to the frame that
  // came before; it's a duplicate. mBuffer will be empty.
  const bool mDuplicate;

  VideoData(int64_t aOffset,
            int64_t aTime,
            int64_t aDuration,
            int64_t aTimecode);

  VideoData(int64_t aOffset,
            int64_t aTime,
            int64_t aDuration,
            bool aKeyframe,
            int64_t aTimecode,
            IntSize aDisplay);

protected:
  ~VideoData();
};

class CryptoTrack
{
public:
  CryptoTrack() : mValid(false) {}
  bool mValid;
  int32_t mMode;
  int32_t mIVSize;
  nsTArray<uint8_t> mKeyId;
};

class CryptoSample : public CryptoTrack
{
public:
  nsTArray<uint16_t> mPlainSizes;
  nsTArray<uint32_t> mEncryptedSizes;
  nsTArray<uint8_t> mIV;
  nsTArray<nsCString> mSessionIds;
};


// MediaRawData is a MediaData container used to store demuxed, still compressed
// samples.
// Use MediaRawData::CreateWriter() to obtain a MediaRawDataWriter object that
// provides methods to modify and manipulate the data.
// Memory allocations are fallibles. Methods return a boolean indicating if
// memory allocations were successful. Return values should always be checked.
// MediaRawData::mData will be nullptr if no memory has been allocated or if
// an error occurred during construction.
// Existing data is only ever modified if new memory allocation has succeeded
// and preserved if not.
//
// The memory referenced by mData will always be 32 bytes aligned and the
// underlying buffer will always have a size such that 32 bytes blocks can be
// used to read the content, regardless of the mSize value. Buffer is zeroed
// on creation.
//
// Typical usage: create new MediaRawData; create the associated
// MediaRawDataWriter, call SetSize() to allocate memory, write to mData,
// up to mSize bytes.

class MediaRawData;

class MediaRawDataWriter
{
public:
  // Pointer to data or null if not-yet allocated
  uint8_t* mData;
  // Writeable size of buffer.
  size_t mSize;
  // Writeable reference to MediaRawData::mCryptoInternal
  CryptoSample& mCrypto;

  // Data manipulation methods. mData and mSize may be updated accordingly.

  // Set size of buffer, allocating memory as required.
  // If size is increased, new buffer area is filled with 0.
  bool SetSize(size_t aSize);
  // Add aData at the beginning of buffer.
  bool Prepend(const uint8_t* aData, size_t aSize);
  // Replace current content with aData.
  bool Replace(const uint8_t* aData, size_t aSize);
  // Clear the memory buffer. Will set mData and mSize to 0.
  void Clear();

private:
  friend class MediaRawData;
  explicit MediaRawDataWriter(MediaRawData* aMediaRawData);
  bool EnsureSize(size_t aSize);
  MediaRawData* mTarget;
  nsRefPtr<LargeDataBuffer> mBuffer;
};

class MediaRawData : public MediaData {
public:
  MediaRawData();
  MediaRawData(const uint8_t* aData, size_t mSize);

  // Pointer to data or null if not-yet allocated
  const uint8_t* mData;
  // Size of buffer.
  size_t mSize;

  const CryptoSample& mCrypto;
  nsRefPtr<DataBuffer> mExtraData;

  // Return a deep copy or nullptr if out of memory.
  virtual already_AddRefed<MediaRawData> Clone() const;
  // Create a MediaRawDataWriter for this MediaRawData. The caller must
  // delete the writer once done. The writer is not thread-safe.
  virtual MediaRawDataWriter* CreateWriter();
  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;

protected:
  ~MediaRawData();

private:
  friend class MediaRawDataWriter;
  // Ensure that the backend buffer can hold aSize data. Will update mData.
  // Will enforce that the start of allocated data is always 32 bytes
  // aligned and that it has sufficient end padding to allow for 32 bytes block
  // read as required by some data decoders.
  // Returns false if memory couldn't be allocated.
  bool EnsureCapacity(size_t aSize);
  nsRefPtr<LargeDataBuffer> mBuffer;
  CryptoSample mCryptoInternal;
  uint32_t mPadding;
  MediaRawData(const MediaRawData&); // Not implemented
};

  // LargeDataBuffer is a ref counted fallible TArray.
  // It is designed to share potentially big byte arrays.
class LargeDataBuffer : public FallibleTArray<uint8_t> {
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LargeDataBuffer);
  LargeDataBuffer() = default;
  explicit LargeDataBuffer(size_t aCapacity) : FallibleTArray<uint8_t>(aCapacity) {}

private:
  ~LargeDataBuffer() {}
};

  // DataBuffer is a ref counted infallible TArray.
class DataBuffer : public nsTArray<uint8_t> {
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataBuffer);

private:
  ~DataBuffer() {}
};

} // namespace mozilla

#endif // MediaData_h
