// 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/cast/receiver/audio_decoder.h"

#include <stdint.h>

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/sys_byteorder.h"
#include "build/build_config.h"
#include "third_party/opus/src/include/opus.h"

namespace media {
namespace cast {

// Base class that handles the common problem of detecting dropped frames, and
// then invoking the Decode() method implemented by the subclasses to convert
// the encoded payload data into usable audio data.
class AudioDecoder::ImplBase
    : public base::RefCountedThreadSafe<AudioDecoder::ImplBase> {
 public:
  ImplBase(const scoped_refptr<CastEnvironment>& cast_environment,
           Codec codec,
           int num_channels,
           int sampling_rate)
      : cast_environment_(cast_environment),
        codec_(codec),
        num_channels_(num_channels),
        operational_status_(STATUS_UNINITIALIZED) {
    if (num_channels_ <= 0 || sampling_rate <= 0 || sampling_rate % 100 != 0)
      operational_status_ = STATUS_INVALID_CONFIGURATION;
  }

  OperationalStatus InitializationResult() const {
    return operational_status_;
  }

  void DecodeFrame(std::unique_ptr<EncodedFrame> encoded_frame,
                   const DecodeFrameCallback& callback) {
    DCHECK_EQ(operational_status_, STATUS_INITIALIZED);

    bool is_continuous = true;
    DCHECK(!encoded_frame->frame_id.is_null());
    if (!last_frame_id_.is_null()) {
      if (encoded_frame->frame_id > (last_frame_id_ + 1)) {
        RecoverBecauseFramesWereDropped();
        is_continuous = false;
      }
    }
    last_frame_id_ = encoded_frame->frame_id;

    std::unique_ptr<AudioBus> decoded_audio =
        Decode(encoded_frame->mutable_bytes(),
               static_cast<int>(encoded_frame->data.size()));

    std::unique_ptr<FrameEvent> event(new FrameEvent());
    event->timestamp = cast_environment_->Clock()->NowTicks();
    event->type = FRAME_DECODED;
    event->media_type = AUDIO_EVENT;
    event->rtp_timestamp = encoded_frame->rtp_timestamp;
    event->frame_id = encoded_frame->frame_id;
    cast_environment_->logger()->DispatchFrameEvent(std::move(event));

    cast_environment_->PostTask(CastEnvironment::MAIN,
                                FROM_HERE,
                                base::Bind(callback,
                                           base::Passed(&decoded_audio),
                                           is_continuous));
  }

 protected:
  friend class base::RefCountedThreadSafe<ImplBase>;
  virtual ~ImplBase() {}

  virtual void RecoverBecauseFramesWereDropped() {}

  // Note: Implementation of Decode() is allowed to mutate |data|.
  virtual std::unique_ptr<AudioBus> Decode(uint8_t* data, int len) = 0;

  const scoped_refptr<CastEnvironment> cast_environment_;
  const Codec codec_;
  const int num_channels_;

  // Subclass' ctor is expected to set this to STATUS_INITIALIZED.
  OperationalStatus operational_status_;

 private:
  FrameId last_frame_id_;

  DISALLOW_COPY_AND_ASSIGN(ImplBase);
};

class AudioDecoder::OpusImpl : public AudioDecoder::ImplBase {
 public:
  OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment,
           int num_channels,
           int sampling_rate)
      : ImplBase(cast_environment,
                 CODEC_AUDIO_OPUS,
                 num_channels,
                 sampling_rate),
        decoder_memory_(new uint8_t[opus_decoder_get_size(num_channels)]),
        opus_decoder_(reinterpret_cast<OpusDecoder*>(decoder_memory_.get())),
        max_samples_per_frame_(kOpusMaxFrameDurationMillis * sampling_rate /
                               1000),
        buffer_(new float[max_samples_per_frame_ * num_channels]) {
    if (ImplBase::operational_status_ != STATUS_UNINITIALIZED)
      return;
    if (opus_decoder_init(opus_decoder_, sampling_rate, num_channels) !=
            OPUS_OK) {
      ImplBase::operational_status_ = STATUS_INVALID_CONFIGURATION;
      return;
    }
    ImplBase::operational_status_ = STATUS_INITIALIZED;
  }

 private:
  ~OpusImpl() final {}

  void RecoverBecauseFramesWereDropped() final {
    // Passing NULL for the input data notifies the decoder of frame loss.
    const opus_int32 result =
        opus_decode_float(
            opus_decoder_, NULL, 0, buffer_.get(), max_samples_per_frame_, 0);
    DCHECK_GE(result, 0);
  }

  std::unique_ptr<AudioBus> Decode(uint8_t* data, int len) final {
    std::unique_ptr<AudioBus> audio_bus;
    const opus_int32 num_samples_decoded = opus_decode_float(
        opus_decoder_, data, len, buffer_.get(), max_samples_per_frame_, 0);
    if (num_samples_decoded <= 0)
      return audio_bus;  // Decode error.

    // Copy interleaved samples from |buffer_| into a new AudioBus (where
    // samples are stored in planar format, for each channel).
    audio_bus = AudioBus::Create(num_channels_, num_samples_decoded);
    // TODO(miu): This should be moved into AudioBus::FromInterleaved().
    for (int ch = 0; ch < num_channels_; ++ch) {
      const float* src = buffer_.get() + ch;
      const float* const src_end = src + num_samples_decoded * num_channels_;
      float* dest = audio_bus->channel(ch);
      for (; src < src_end; src += num_channels_, ++dest)
        *dest = *src;
    }
    return audio_bus;
  }

  const std::unique_ptr<uint8_t[]> decoder_memory_;
  OpusDecoder* const opus_decoder_;
  const int max_samples_per_frame_;
  const std::unique_ptr<float[]> buffer_;

  // According to documentation in third_party/opus/src/include/opus.h, we must
  // provide enough space in |buffer_| to contain 120ms of samples.  At 48 kHz,
  // then, that means 5760 samples times the number of channels.
  static const int kOpusMaxFrameDurationMillis = 120;

  DISALLOW_COPY_AND_ASSIGN(OpusImpl);
};

class AudioDecoder::Pcm16Impl : public AudioDecoder::ImplBase {
 public:
  Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment,
            int num_channels,
            int sampling_rate)
      : ImplBase(cast_environment,
                 CODEC_AUDIO_PCM16,
                 num_channels,
                 sampling_rate) {
    if (ImplBase::operational_status_ != STATUS_UNINITIALIZED)
      return;
    ImplBase::operational_status_ = STATUS_INITIALIZED;
  }

 private:
  ~Pcm16Impl() final {}

  std::unique_ptr<AudioBus> Decode(uint8_t* data, int len) final {
    std::unique_ptr<AudioBus> audio_bus;
    const int num_samples = len / sizeof(int16_t) / num_channels_;
    if (num_samples <= 0)
      return audio_bus;

    int16_t* const pcm_data = reinterpret_cast<int16_t*>(data);
#if defined(ARCH_CPU_LITTLE_ENDIAN)
    // Convert endianness.
    const int num_elements = num_samples * num_channels_;
    for (int i = 0; i < num_elements; ++i)
      pcm_data[i] = static_cast<int16_t>(base::NetToHost16(pcm_data[i]));
#endif
    audio_bus = AudioBus::Create(num_channels_, num_samples);
    audio_bus->FromInterleaved(pcm_data, num_samples, sizeof(int16_t));
    return audio_bus;
  }

  DISALLOW_COPY_AND_ASSIGN(Pcm16Impl);
};

AudioDecoder::AudioDecoder(
    const scoped_refptr<CastEnvironment>& cast_environment,
    int channels,
    int sampling_rate,
    Codec codec)
    : cast_environment_(cast_environment) {
  switch (codec) {
    case CODEC_AUDIO_OPUS:
      impl_ = new OpusImpl(cast_environment, channels, sampling_rate);
      break;
    case CODEC_AUDIO_PCM16:
      impl_ = new Pcm16Impl(cast_environment, channels, sampling_rate);
      break;
    default:
      NOTREACHED() << "Unknown or unspecified codec.";
      break;
  }
}

AudioDecoder::~AudioDecoder() {}

OperationalStatus AudioDecoder::InitializationResult() const {
  if (impl_.get())
    return impl_->InitializationResult();
  return STATUS_UNSUPPORTED_CODEC;
}

void AudioDecoder::DecodeFrame(std::unique_ptr<EncodedFrame> encoded_frame,
                               const DecodeFrameCallback& callback) {
  DCHECK(encoded_frame.get());
  DCHECK(!callback.is_null());
  if (!impl_.get() || impl_->InitializationResult() != STATUS_INITIALIZED) {
    callback.Run(base::WrapUnique<AudioBus>(NULL), false);
    return;
  }
  cast_environment_->PostTask(CastEnvironment::AUDIO,
                              FROM_HERE,
                              base::Bind(&AudioDecoder::ImplBase::DecodeFrame,
                                         impl_,
                                         base::Passed(&encoded_frame),
                                         callback));
}

}  // namespace cast
}  // namespace media
