// Copyright 2014 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 "media/formats/mp2t/es_adapter_video.h"

#include <stddef.h>

#include "media/base/timestamp_constants.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/mp2t_common.h"

namespace media {
namespace mp2t {

// Arbitrary decision about the frame duration when there is no previous
// hint about what could be the frame duration.
static const int kDefaultFrameDurationMs = 40;

// To calculate the frame duration, we make an assumption
// that the timestamp of the next frame in presentation order
// is no further than 5 frames away in decode order.
// TODO(damienv): the previous assumption should cover most of the practical
// cases. However, the right way to calculate the frame duration would be
// to emulate the H264 dpb bumping process.
static const size_t kHistorySize = 5;

EsAdapterVideo::EsAdapterVideo(
    const NewVideoConfigCB& new_video_config_cb,
    const EmitBufferCB& emit_buffer_cb)
    : new_video_config_cb_(new_video_config_cb),
      emit_buffer_cb_(emit_buffer_cb),
      has_valid_config_(false),
      has_valid_frame_(false),
      last_frame_duration_(
          base::TimeDelta::FromMilliseconds(kDefaultFrameDurationMs)),
      buffer_index_(0),
      has_valid_initial_timestamp_(false),
      discarded_frame_count_(0) {
}

EsAdapterVideo::~EsAdapterVideo() {
}

void EsAdapterVideo::Flush() {
  ProcessPendingBuffers(true);
}

void EsAdapterVideo::Reset() {
  has_valid_config_ = false;
  has_valid_frame_ = false;

  last_frame_duration_ =
      base::TimeDelta::FromMilliseconds(kDefaultFrameDurationMs);

  config_list_.clear();
  buffer_index_ = 0;
  buffer_list_.clear();
  emitted_pts_.clear();

  has_valid_initial_timestamp_ = false;
  min_pts_ = base::TimeDelta();
  min_dts_ = DecodeTimestamp();

  discarded_frame_count_ = 0;
}

void EsAdapterVideo::OnConfigChanged(
    const VideoDecoderConfig& video_decoder_config) {
  config_list_.push_back(
      ConfigEntry(buffer_index_ + buffer_list_.size(), video_decoder_config));
  has_valid_config_ = true;
  ProcessPendingBuffers(false);
}

bool EsAdapterVideo::OnNewBuffer(
    const scoped_refptr<StreamParserBuffer>& stream_parser_buffer) {
  if (stream_parser_buffer->timestamp() == kNoTimestamp) {
    if (has_valid_frame_) {
      // There is currently no error concealment for a missing timestamp
      // in the middle of the stream.
      DVLOG(1) << "Missing timestamp in the middle of the stream";
      return false;
    }

    if (!has_valid_initial_timestamp_) {
      // MPEG-2 TS requires the first access unit to be given a timestamp.
      // However, some streams do not comply with this requirement.
      // So simply drop the frame if it is a leading frame with no timestamp.
      DVLOG(1)
          << "Stream not compliant: ignoring leading frame with no timestamp";
      return true;
    }

    // In all the other cases, this frame will be replaced by the following
    // valid key frame, using timestamp interpolation.
    DCHECK(has_valid_initial_timestamp_);
    DCHECK_GE(discarded_frame_count_, 1);
    discarded_frame_count_++;
    return true;
  }

  // At this point, timestamps of the incoming frame are valid.
  if (!has_valid_initial_timestamp_) {
    min_pts_ = stream_parser_buffer->timestamp();
    min_dts_ = stream_parser_buffer->GetDecodeTimestamp();
    has_valid_initial_timestamp_ = true;
  }
  if (stream_parser_buffer->timestamp() < min_pts_)
    min_pts_ = stream_parser_buffer->timestamp();

  // Discard the incoming frame:
  // - if it is not associated with any config,
  // - or if no valid key frame has been found so far.
  if (!has_valid_config_ ||
      (!has_valid_frame_ && !stream_parser_buffer->is_key_frame())) {
    discarded_frame_count_++;
    return true;
  }

  has_valid_frame_ = true;

  if (discarded_frame_count_ > 0)
    ReplaceDiscardedFrames(stream_parser_buffer);

  buffer_list_.push_back(stream_parser_buffer);
  ProcessPendingBuffers(false);
  return true;
}

void EsAdapterVideo::ProcessPendingBuffers(bool flush) {
  DCHECK(has_valid_config_);

  while (!buffer_list_.empty() &&
         (flush || buffer_list_.size() > kHistorySize)) {
    // Signal a config change, just before emitting the corresponding frame.
    if (!config_list_.empty() && config_list_.front().first == buffer_index_) {
      new_video_config_cb_.Run(config_list_.front().second);
      config_list_.pop_front();
    }

    scoped_refptr<StreamParserBuffer> buffer = buffer_list_.front();
    buffer_list_.pop_front();
    buffer_index_++;

    if (buffer->duration() == kNoTimestamp) {
      base::TimeDelta next_frame_pts = GetNextFramePts(buffer->timestamp());
      if (next_frame_pts == kNoTimestamp) {
        // This can happen when emitting the very last buffer
        // or if the stream do not meet the assumption behind |kHistorySize|.
        DVLOG(LOG_LEVEL_ES) << "Using last frame duration: "
                            << last_frame_duration_.InMilliseconds();
        buffer->set_duration(last_frame_duration_);
      } else {
        base::TimeDelta duration = next_frame_pts - buffer->timestamp();
        DVLOG(LOG_LEVEL_ES) << "Frame duration: " << duration.InMilliseconds();
        buffer->set_duration(duration);
      }
    }

    emitted_pts_.push_back(buffer->timestamp());
    if (emitted_pts_.size() > kHistorySize)
      emitted_pts_.pop_front();

    last_frame_duration_ = buffer->duration();
    emit_buffer_cb_.Run(buffer);
  }
}

base::TimeDelta EsAdapterVideo::GetNextFramePts(base::TimeDelta current_pts) {
  base::TimeDelta next_pts = kNoTimestamp;

  // Consider the timestamps of future frames (in decode order).
  // Note: the next frame is not enough when the GOP includes some B frames.
  for (BufferQueue::const_iterator it = buffer_list_.begin();
       it != buffer_list_.end(); ++it) {
    if ((*it)->timestamp() < current_pts)
      continue;
    if (next_pts == kNoTimestamp || next_pts > (*it)->timestamp())
      next_pts = (*it)->timestamp();
  }

  // Consider the timestamps of previous frames (in decode order).
  // In a simple GOP structure with B frames, the frame next to the last B
  // frame (in presentation order) is located before in decode order.
  for (std::list<base::TimeDelta>::const_iterator it = emitted_pts_.begin();
       it != emitted_pts_.end(); ++it) {
    if (*it < current_pts)
      continue;
    if (next_pts == kNoTimestamp || next_pts > *it)
      next_pts = *it;
  }

  return next_pts;
}

void EsAdapterVideo::ReplaceDiscardedFrames(
    const scoped_refptr<StreamParserBuffer>& stream_parser_buffer) {
  DCHECK_GT(discarded_frame_count_, 0);
  DCHECK(stream_parser_buffer->is_key_frame());

  // PTS/DTS are interpolated between the min PTS/DTS of discarded frames
  // and the PTS/DTS of the first valid buffer.
  // Note: |pts_delta| and |dts_delta| are calculated using integer division.
  // Interpolation thus accumulutes small errors. However, since timestamps
  // are given in microseconds, only a high number of discarded frames
  // (in the order of 10000s) could have an impact and create a gap (from MSE
  // point of view) between the last interpolated frame and
  // |stream_parser_buffer|.
  base::TimeDelta pts = min_pts_;
  base::TimeDelta pts_delta =
      (stream_parser_buffer->timestamp() - pts) / discarded_frame_count_;
  DecodeTimestamp dts = min_dts_;
  base::TimeDelta dts_delta =
      (stream_parser_buffer->GetDecodeTimestamp() - dts) /
      discarded_frame_count_;

  for (int i = 0; i < discarded_frame_count_; i++) {
    scoped_refptr<StreamParserBuffer> frame =
        StreamParserBuffer::CopyFrom(
            stream_parser_buffer->data(),
            stream_parser_buffer->data_size(),
            stream_parser_buffer->is_key_frame(),
            stream_parser_buffer->type(),
            stream_parser_buffer->track_id());
    frame->SetDecodeTimestamp(dts);
    frame->set_timestamp(pts);
    frame->set_duration(pts_delta);
    buffer_list_.push_back(frame);
    pts += pts_delta;
    dts += dts_delta;
  }
  discarded_frame_count_ = 0;
}

}  // namespace mp2t
}  // namespace media
