// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_BASE_VIDEO_ENCODER_H_
#define MEDIA_BASE_VIDEO_ENCODER_H_

#include <cstdint>
#include <optional>
#include <vector>

#include "base/containers/heap_array.h"
#include "base/functional/callback.h"
#include "base/task/bind_post_task.h"
#include "base/time/time.h"
#include "media/base/bitrate.h"
#include "media/base/encoder_status.h"
#include "media/base/media_export.h"
#include "media/base/svc_scalability_mode.h"
#include "media/base/video_codecs.h"
#include "media/base/video_types.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"

namespace media {

struct VideoEncoderInfo;
class VideoFrame;

// Returns the drop frame threshold for media::VideoEncoder used in WebCodecs.
MEDIA_EXPORT uint8_t GetDefaultVideoEncoderDropFrameThreshold();

MEDIA_EXPORT uint32_t GetDefaultVideoEncodeBitrate(gfx::Size frame_size,
                                                   uint32_t framerate);

MEDIA_EXPORT int GetNumberOfThreadsForSoftwareEncoding(gfx::Size frame_size);

// Converts AV1/VP9 qindex (0-255) to the quantizer parameter (0-63).
MEDIA_EXPORT uint8_t QIndexToQuantizer(VideoCodec codec, uint8_t q_index);

// Converts AV1/VP9 quantizer parameter (0-63) to qindex (0-255) range.
MEDIA_EXPORT uint8_t QuantizerToQIndex(VideoCodec codec, uint8_t quantizer);

// Encoded video frame, its data and metadata.
struct MEDIA_EXPORT VideoEncoderOutput {
  VideoEncoderOutput();
  VideoEncoderOutput(VideoEncoderOutput&&);
  ~VideoEncoderOutput();

  // Feel free take these buffers out and use underlying memory as is without
  // copying.
  base::HeapArray<uint8_t> data;
  base::HeapArray<uint8_t> alpha_data;

  base::TimeDelta timestamp;
  bool key_frame = false;
  int temporal_id = 0;
  gfx::ColorSpace color_space;

  // Some platforms may adjust the encoding size to meet hardware requirements.
  // If not set, the encoded size is the same as configured.
  std::optional<gfx::Size> encoded_size;
};

class MEDIA_EXPORT VideoEncoder {
 public:
  // TODO: Move this to a new file if there are more codec specific options.
  struct MEDIA_EXPORT AvcOptions {
    bool produce_annexb = false;
  };

  struct MEDIA_EXPORT HevcOptions {
    bool produce_annexb = false;
  };

  enum class LatencyMode { Realtime, Quality };
  enum class ContentHint { Camera, Screen };

  struct MEDIA_EXPORT Options {
    Options();
    Options(const Options&);
    ~Options();

    std::string ToString();

    std::optional<Bitrate> bitrate;
    std::optional<double> framerate;

    gfx::Size frame_size;

    std::optional<int> keyframe_interval = 10000;

    LatencyMode latency_mode = LatencyMode::Realtime;

    std::optional<SVCScalabilityMode> scalability_mode;

    std::optional<ContentHint> content_hint;

    // Controls encoded pixel format.
    std::optional<VideoChromaSampling> subsampling;

    // Controls encoded bit depth.
    std::optional<uint8_t> bit_depth;

    // Only used for H264 encoding.
    AvcOptions avc;

    // Only used for HEVC encoding.
    HevcOptions hevc;

    // Allow clients to choose reference pattern for frames.
    bool manual_reference_buffer_control = false;
  };

  struct MEDIA_EXPORT EncodeOptions {
    explicit EncodeOptions(bool key_frame);
    EncodeOptions();
    EncodeOptions(const EncodeOptions&);
    ~EncodeOptions();
    bool key_frame = false;
    // Per-frame codec-specific quantizer value.
    // Should only be used when encoder configured with kExternal bitrate mode.
    std::optional<int> quantizer;

    // Ids of the encoder's buffers (past frames) that this frame can reference
    // for inter-frame prediction.
    // Valid values: [0..VideoEncoderInfo::number_of_manual_reference_buffers)
    absl::InlinedVector<uint8_t, 4> reference_buffers;

    // Id of the encoder's buffer where this frame should be kept after
    // encoding. This frames can later be referenced via `reference_buffers`.
    // If empty, this frame can't be used for inter-frame prediction by future
    // frames.
    // Valid values: [0..VideoEncoderInfo::number_of_manual_reference_buffers)
    std::optional<uint8_t> update_buffer;
  };

  // A sequence of codec specific bytes, commonly known as extradata.
  // If available, it should be given to the decoder as part of the
  // decoder config.
  using CodecDescription = std::vector<uint8_t>;

  // Provides the VideoEncoder client with information about the specific
  // encoder implementation.
  using EncoderInfoCB =
      base::RepeatingCallback<void(const VideoEncoderInfo& encoder_info)>;

  // Callback for VideoEncoder to report an encoded video frame whenever it
  // becomes available.
  using OutputCB =
      base::RepeatingCallback<void(VideoEncoderOutput output,
                                   std::optional<CodecDescription>)>;

  // Callback to report success and errors in encoder calls.
  using EncoderStatusCB = base::OnceCallback<void(EncoderStatus error)>;

  struct MEDIA_EXPORT PendingEncode {
    PendingEncode();
    PendingEncode(PendingEncode&&);
    ~PendingEncode();
    EncoderStatusCB done_callback;
    scoped_refptr<VideoFrame> frame;
    EncodeOptions options;
  };

  VideoEncoder();
  VideoEncoder(const VideoEncoder&) = delete;
  VideoEncoder& operator=(const VideoEncoder&) = delete;
  virtual ~VideoEncoder();

  // Initializes a VideoEncoder with the given |options|, executing the
  // |done_cb| upon completion. |output_cb| is called for each encoded frame
  // produced by the coder.
  //
  // Note:
  // 1) Can't be called more than once for the same instance of the encoder.
  // 2) No VideoEncoder calls should be made before |done_cb| is executed.
  virtual void Initialize(VideoCodecProfile profile,
                          const Options& options,
                          EncoderInfoCB info_cb,
                          OutputCB output_cb,
                          EncoderStatusCB done_cb) = 0;

  // Requests a |frame| to be encoded. The status of the encoder and the frame
  // are returned via the provided callback |done_cb|.
  //
  // |done_cb| will not be called from within this method, and that it will be
  // called even if Encode() is never called again.

  // After the frame, or several frames, are encoded the encoder calls
  // |output_cb| specified in Initialize() for available VideoEncoderOutput.
  // |output_cb| may be called before or after |done_cb|,
  // including before Encode() returns.
  // Encode() does not expect EOS frames, use Flush() to finalize the stream
  // and harvest the outputs.
  virtual void Encode(scoped_refptr<VideoFrame> frame,
                      const EncodeOptions& options,
                      EncoderStatusCB done_cb) = 0;

  // Adjust encoder options and the output callback for future frames, executing
  // the |done_cb| upon completion.
  //
  // Note:
  // 1. Not all options can be changed on the fly.
  // 2. ChangeOptions() should be called after calling Flush() and waiting
  // for it to finish.
  virtual void ChangeOptions(const Options& options,
                             OutputCB output_cb,
                             EncoderStatusCB done_cb) = 0;

  // Requests all outputs for already encoded frames to be
  // produced via |output_cb| and calls |dene_cb| after that.
  virtual void Flush(EncoderStatusCB done_cb) = 0;

  // Normally VideoEncoder implementations aren't supposed to call
  // EncoderInfoCB, OutputCB, and EncoderStatusCB directly from inside any of
  // VideoEncoder's methods.  This method tells VideoEncoder that all callbacks
  // can be called directly from within its methods. It saves extra thread hops
  // if it's known that all callbacks already point to a task runner different
  // from the current one.
  virtual void DisablePostedCallbacks();

 protected:
  template <typename Callback>
  Callback BindCallbackToCurrentLoopIfNeeded(Callback callback) {
    return post_callbacks_
               ? base::BindPostTaskToCurrentDefault(std::move(callback))
               : std::move(callback);
  }

 private:
  bool post_callbacks_ = true;
};

}  // namespace media

#endif  // MEDIA_BASE_VIDEO_ENCODER_H_
