// Copyright 2015 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/base/android/media_codec_audio_decoder.h"

#include "base/bind.h"
#include "base/logging.h"
#include "media/base/android/media_statistics.h"
#include "media/base/android/sdk_media_codec_bridge.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/demuxer_stream.h"

namespace {

// Use 16bit PCM for audio output. Keep this value in sync with the output
// format we passed to AudioTrack in MediaCodecBridge.
const int kBytesPerAudioOutputSample = 2;
}

namespace media {

MediaCodecAudioDecoder::MediaCodecAudioDecoder(
    const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
    FrameStatistics* frame_statistics,
    const base::Closure& request_data_cb,
    const base::Closure& starvation_cb,
    const base::Closure& decoder_drained_cb,
    const base::Closure& stop_done_cb,
    const base::Closure& waiting_for_decryption_key_cb,
    const base::Closure& error_cb,
    const SetTimeCallback& update_current_time_cb)
    : MediaCodecDecoder("AudioDecoder",
                        media_task_runner,
                        frame_statistics,
                        request_data_cb,
                        starvation_cb,
                        decoder_drained_cb,
                        stop_done_cb,
                        waiting_for_decryption_key_cb,
                        error_cb),
      volume_(-1.0),
      bytes_per_frame_(0),
      output_sampling_rate_(0),
      frame_count_(0),
      update_current_time_cb_(update_current_time_cb) {
}

MediaCodecAudioDecoder::~MediaCodecAudioDecoder() {
  DCHECK(media_task_runner_->BelongsToCurrentThread());
  DVLOG(1) << "AudioDecoder::~AudioDecoder()";
  ReleaseDecoderResources();
}

const char* MediaCodecAudioDecoder::class_name() const {
  return "AudioDecoder";
}

bool MediaCodecAudioDecoder::HasStream() const {
  DCHECK(media_task_runner_->BelongsToCurrentThread());

  return configs_.audio_codec != kUnknownAudioCodec;
}

void MediaCodecAudioDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) {
  DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << configs;

  configs_ = configs;
  if (!media_codec_bridge_)
    output_sampling_rate_ = configs.audio_sampling_rate;
}

bool MediaCodecAudioDecoder::IsContentEncrypted() const {
  // Make sure SetDemuxerConfigs() as been called.
  DCHECK(configs_.audio_codec != kUnknownAudioCodec);
  return configs_.is_audio_encrypted;
}

void MediaCodecAudioDecoder::ReleaseDecoderResources() {
  DCHECK(media_task_runner_->BelongsToCurrentThread());
  DVLOG(1) << class_name() << "::" << __FUNCTION__;

  DoEmergencyStop();

  ReleaseMediaCodec();
}

void MediaCodecAudioDecoder::Flush() {
  DCHECK(media_task_runner_->BelongsToCurrentThread());

  MediaCodecDecoder::Flush();
  frame_count_ = 0;
}

void MediaCodecAudioDecoder::SetVolume(double volume) {
  DCHECK(media_task_runner_->BelongsToCurrentThread());

  DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << volume;

  volume_ = volume;
  SetVolumeInternal();
}

void MediaCodecAudioDecoder::SetBaseTimestamp(base::TimeDelta base_timestamp) {
  // Called from Media thread and Decoder thread. When called from Media thread
  // Decoder thread should not be running.

  DVLOG(1) << __FUNCTION__ << " " << base_timestamp;

  base_timestamp_ = base_timestamp;
  if (audio_timestamp_helper_)
    audio_timestamp_helper_->SetBaseTimestamp(base_timestamp_);
}

bool MediaCodecAudioDecoder::IsCodecReconfigureNeeded(
    const DemuxerConfigs& next) const {
  if (always_reconfigure_for_tests_)
    return true;

  return configs_.audio_codec != next.audio_codec ||
         configs_.audio_channels != next.audio_channels ||
         configs_.audio_sampling_rate != next.audio_sampling_rate ||
         configs_.is_audio_encrypted != next.is_audio_encrypted ||
         configs_.audio_extra_data.size() != next.audio_extra_data.size() ||
         !std::equal(configs_.audio_extra_data.begin(),
                     configs_.audio_extra_data.end(),
                     next.audio_extra_data.begin());
}

MediaCodecDecoder::ConfigStatus MediaCodecAudioDecoder::ConfigureInternal(
    jobject media_crypto) {
  DCHECK(media_task_runner_->BelongsToCurrentThread());

  DVLOG(1) << class_name() << "::" << __FUNCTION__;

  if (configs_.audio_codec == kUnknownAudioCodec) {
    DVLOG(0) << class_name() << "::" << __FUNCTION__
             << " configuration parameters are required";
    return kConfigFailure;
  }

  media_codec_bridge_.reset(AudioCodecBridge::Create(configs_.audio_codec));
  if (!media_codec_bridge_)
    return kConfigFailure;

  if (!(static_cast<AudioCodecBridge*>(media_codec_bridge_.get()))
           ->ConfigureAndStart(
               configs_.audio_codec, configs_.audio_sampling_rate,
               configs_.audio_channels, &configs_.audio_extra_data[0],
               configs_.audio_extra_data.size(), configs_.audio_codec_delay_ns,
               configs_.audio_seek_preroll_ns, true, media_crypto)) {
    DVLOG(0) << class_name() << "::" << __FUNCTION__
             << " failed: cannot start audio codec";

    media_codec_bridge_.reset();
    return kConfigFailure;
  }

  DVLOG(0) << class_name() << "::" << __FUNCTION__ << " succeeded";

  SetVolumeInternal();

  bytes_per_frame_ = kBytesPerAudioOutputSample * configs_.audio_channels;
  frame_count_ = 0;
  ResetTimestampHelper();

  if (!codec_created_for_tests_cb_.is_null())
    media_task_runner_->PostTask(FROM_HERE, codec_created_for_tests_cb_);

  return kConfigOk;
}

void MediaCodecAudioDecoder::OnOutputFormatChanged() {
  DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());

  DCHECK(media_codec_bridge_);

  int old_sampling_rate = output_sampling_rate_;
  output_sampling_rate_ = media_codec_bridge_->GetOutputSamplingRate();
  if (output_sampling_rate_ != old_sampling_rate)
    ResetTimestampHelper();
}

void MediaCodecAudioDecoder::Render(int buffer_index,
                                    size_t offset,
                                    size_t size,
                                    RenderMode render_mode,
                                    base::TimeDelta pts,
                                    bool eos_encountered) {
  DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());

  DVLOG(2) << class_name() << "::" << __FUNCTION__ << " pts:" << pts << " "
           << AsString(render_mode);

  const bool do_play = (render_mode != kRenderSkip);

  if (do_play) {
    AudioCodecBridge* audio_codec =
        static_cast<AudioCodecBridge*>(media_codec_bridge_.get());

    DCHECK(audio_codec);

    const bool postpone = (render_mode == kRenderAfterPreroll);

    int64_t head_position =
        audio_codec->PlayOutputBuffer(buffer_index, size, offset, postpone);

    base::TimeTicks current_time = base::TimeTicks::Now();

    frame_statistics_->IncrementFrameCount();

    // Reset the base timestamp if we have not started playing.
    // SetBaseTimestamp() must be called before AddFrames() since it resets the
    // internal frame count.
    if (postpone && !frame_count_)
      SetBaseTimestamp(pts);

    size_t new_frames_count = size / bytes_per_frame_;
    frame_count_ += new_frames_count;
    audio_timestamp_helper_->AddFrames(new_frames_count);

    if (postpone) {
      DVLOG(2) << class_name() << "::" << __FUNCTION__ << " pts:" << pts
               << " POSTPONE";

      // Let the player adjust the start time.
      media_task_runner_->PostTask(
          FROM_HERE, base::Bind(update_current_time_cb_, pts, pts, true));
    } else {
      int64_t frames_to_play = frame_count_ - head_position;

      DCHECK_GE(frames_to_play, 0) << class_name() << "::" << __FUNCTION__
                                   << " pts:" << pts
                                   << " frame_count_:" << frame_count_
                                   << " head_position:" << head_position;

      base::TimeDelta last_buffered = audio_timestamp_helper_->GetTimestamp();
      base::TimeDelta now_playing =
          last_buffered -
          audio_timestamp_helper_->GetFrameDuration(frames_to_play);

      DVLOG(2) << class_name() << "::" << __FUNCTION__ << " pts:" << pts
               << " will play: [" << now_playing << "," << last_buffered << "]";

      // Statistics
      if (!next_frame_time_limit_.is_null() &&
          next_frame_time_limit_ < current_time) {
        DVLOG(2) << class_name() << "::" << __FUNCTION__ << " LATE FRAME delay:"
                 << current_time - next_frame_time_limit_;
        frame_statistics_->IncrementLateFrameCount();
      }

      next_frame_time_limit_ = current_time + (last_buffered - now_playing);

      media_task_runner_->PostTask(
          FROM_HERE, base::Bind(update_current_time_cb_, now_playing,
                                last_buffered, false));
    }
  }

  media_codec_bridge_->ReleaseOutputBuffer(buffer_index, false);

  CheckLastFrame(eos_encountered, false);  // no delayed tasks
}

void MediaCodecAudioDecoder::SetVolumeInternal() {
  DCHECK(media_task_runner_->BelongsToCurrentThread());

  if (media_codec_bridge_) {
    static_cast<AudioCodecBridge*>(media_codec_bridge_.get())
        ->SetVolume(volume_);
  }
}

void MediaCodecAudioDecoder::ResetTimestampHelper() {
  // Media thread or Decoder thread
  // When this method is called on Media thread, decoder thread
  // should not be running.

  if (audio_timestamp_helper_)
    base_timestamp_ = audio_timestamp_helper_->GetTimestamp();

  audio_timestamp_helper_.reset(
      new AudioTimestampHelper(configs_.audio_sampling_rate));

  audio_timestamp_helper_->SetBaseTimestamp(base_timestamp_);
}

}  // namespace media
