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

#include "cast/standalone_receiver/decoder.h"

#include <libavcodec/version.h>

#include <algorithm>
#include <sstream>
#include <thread>

#include "util/osp_logging.h"
#include "util/trace_logging.h"

namespace openscreen {
namespace cast {

Decoder::Buffer::Buffer() {
  Resize(0);
}

Decoder::Buffer::~Buffer() = default;

void Decoder::Buffer::Resize(int new_size) {
  const int padded_size = new_size + AV_INPUT_BUFFER_PADDING_SIZE;
  if (static_cast<int>(buffer_.size()) == padded_size) {
    return;
  }
  buffer_.resize(padded_size);
  // libavcodec requires zero-padding the region at the end, as some decoders
  // will treat this as a stop marker.
  memset(buffer_.data() + new_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
}

absl::Span<const uint8_t> Decoder::Buffer::GetSpan() const {
  return absl::Span<const uint8_t>(
      buffer_.data(), buffer_.size() - AV_INPUT_BUFFER_PADDING_SIZE);
}

absl::Span<uint8_t> Decoder::Buffer::GetSpan() {
  return absl::Span<uint8_t>(buffer_.data(),
                             buffer_.size() - AV_INPUT_BUFFER_PADDING_SIZE);
}

Decoder::Client::Client() = default;
Decoder::Client::~Client() = default;

Decoder::Decoder(const std::string& codec_name) : codec_name_(codec_name) {
#if LIBAVCODEC_VERSION_MAJOR < 59
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  avcodec_register_all();
#pragma GCC diagnostic pop
#endif  // LIBAVCODEC_VERSION_MAJOR < 59
}

Decoder::~Decoder() = default;

void Decoder::Decode(FrameId frame_id, const Decoder::Buffer& buffer) {
  TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
  if (!codec_ && !Initialize()) {
    return;
  }

  // Parse the buffer for the required metadata and the packet to send to the
  // decoder.
  const absl::Span<const uint8_t> input = buffer.GetSpan();
  const int bytes_consumed = av_parser_parse2(
      parser_.get(), context_.get(), &packet_->data, &packet_->size,
      input.data(), input.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
  if (bytes_consumed < 0) {
    OnError("av_parser_parse2", bytes_consumed, frame_id);
    return;
  }
  if (!packet_->data) {
    OnError("av_parser_parse2 found no packet", AVERROR_BUFFER_TOO_SMALL,
            frame_id);
    return;
  }

  // Send the packet to the decoder.
  const int send_packet_result =
      avcodec_send_packet(context_.get(), packet_.get());
  if (send_packet_result < 0) {
    // The result should not be EAGAIN because this code always pulls out all
    // the decoded frames after feeding-in each AVPacket.
    OSP_DCHECK_NE(send_packet_result, AVERROR(EAGAIN));
    OnError("avcodec_send_packet", send_packet_result, frame_id);
    return;
  }
  frames_decoding_.push_back(frame_id);

  // Receive zero or more frames from the decoder.
  for (;;) {
    const int receive_frame_result =
        avcodec_receive_frame(context_.get(), decoded_frame_.get());
    if (receive_frame_result == AVERROR(EAGAIN)) {
      break;  // Decoder needs more input to produce another frame.
    }
    const FrameId decoded_frame_id = DidReceiveFrameFromDecoder();
    if (receive_frame_result < 0) {
      OnError("avcodec_receive_frame", receive_frame_result, decoded_frame_id);
      return;
    }
    if (client_) {
      client_->OnFrameDecoded(decoded_frame_id, *decoded_frame_);
    }
    av_frame_unref(decoded_frame_.get());
  }
}

bool Decoder::Initialize() {
  TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
  // NOTE: The codec_name values found in OFFER messages, such as "vp8" or
  // "h264" or "opus" are valid input strings to FFMPEG's look-up function, so
  // no translation is required here.
  codec_ = avcodec_find_decoder_by_name(codec_name_.c_str());
  if (!codec_) {
    HandleInitializationError("codec not available", AVERROR(EINVAL));
    return false;
  }
  OSP_LOG_INFO << "Found codec: " << codec_name_ << " (known to FFMPEG as "
               << avcodec_get_name(codec_->id) << ')';

  parser_ = MakeUniqueAVCodecParserContext(codec_->id);
  if (!parser_) {
    HandleInitializationError("failed to allocate parser context",
                              AVERROR(ENOMEM));
    return false;
  }

  context_ = MakeUniqueAVCodecContext(codec_);
  if (!context_) {
    HandleInitializationError("failed to allocate codec context",
                              AVERROR(ENOMEM));
    return false;
  }

  // This should always be greater than zero, so that decoding doesn't block the
  // main thread of this receiver app and cause playback timing issues. The
  // actual number should be tuned, based on the number of CPU cores.
  //
  // This should also be 16 or less, since the encoder implementations emit
  // warnings about too many encode threads. FFMPEG's VP8 implementation
  // actually silently freezes if this is 10 or more. Thus, 8 is used for the
  // max here, just to be safe.
  context_->thread_count =
      std::min(std::max<int>(std::thread::hardware_concurrency(), 1), 8);
  const int open_result = avcodec_open2(context_.get(), codec_, nullptr);
  if (open_result < 0) {
    HandleInitializationError("failed to open codec", open_result);
    return false;
  }

  packet_ = MakeUniqueAVPacket();
  if (!packet_) {
    HandleInitializationError("failed to allocate AVPacket", AVERROR(ENOMEM));
    return false;
  }

  decoded_frame_ = MakeUniqueAVFrame();
  if (!decoded_frame_) {
    HandleInitializationError("failed to allocate AVFrame", AVERROR(ENOMEM));
    return false;
  }

  return true;
}

FrameId Decoder::DidReceiveFrameFromDecoder() {
  const auto it = frames_decoding_.begin();
  OSP_DCHECK(it != frames_decoding_.end());
  const auto frame_id = *it;
  frames_decoding_.erase(it);
  return frame_id;
}

void Decoder::HandleInitializationError(const char* what, int av_errnum) {
  // If the codec was found, get FFMPEG's canonical name for it.
  const char* const canonical_name =
      codec_ ? avcodec_get_name(codec_->id) : nullptr;

  codec_ = nullptr;  // Set null to mean "not initialized."

  if (!client_) {
    return;  // Nowhere to emit error to, so don't bother.
  }

  std::ostringstream error;
  error << "Could not initialize codec " << codec_name_;
  if (canonical_name) {
    error << " (known to FFMPEG as " << canonical_name << ')';
  }
  error << " because " << what << " (" << av_err2str(av_errnum) << ").";
  client_->OnFatalError(error.str());
}

void Decoder::OnError(const char* what, int av_errnum, FrameId frame_id) {
  if (!client_) {
    return;
  }

  // Make a human-readable string from the libavcodec error.
  std::ostringstream error;
  if (!frame_id.is_null()) {
    error << "frame: " << frame_id << "; ";
  }

  char human_readable_error[AV_ERROR_MAX_STRING_SIZE]{0};
  av_make_error_string(human_readable_error, AV_ERROR_MAX_STRING_SIZE,
                       av_errnum);
  error << "what: " << what << "; error: " << human_readable_error;

  // Dispatch to either the fatal error handler, or the one for decode errors,
  // as appropriate.
  switch (av_errnum) {
    case AVERROR_EOF:
    case AVERROR(EINVAL):
    case AVERROR(ENOMEM):
      client_->OnFatalError(error.str());
      break;
    default:
      client_->OnDecodeError(frame_id, error.str());
      break;
  }
}

}  // namespace cast
}  // namespace openscreen
