// 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/cast_receiver_impl.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "media/cast/receiver/audio_decoder.h"
#include "media/cast/receiver/video_decoder.h"

namespace media {
namespace cast {

scoped_ptr<CastReceiver> CastReceiver::Create(
    scoped_refptr<CastEnvironment> cast_environment,
    const FrameReceiverConfig& audio_config,
    const FrameReceiverConfig& video_config,
    transport::PacketSender* const packet_sender) {
  return scoped_ptr<CastReceiver>(new CastReceiverImpl(
      cast_environment, audio_config, video_config, packet_sender));
}

CastReceiverImpl::CastReceiverImpl(
    scoped_refptr<CastEnvironment> cast_environment,
    const FrameReceiverConfig& audio_config,
    const FrameReceiverConfig& video_config,
    transport::PacketSender* const packet_sender)
    : cast_environment_(cast_environment),
      pacer_(cast_environment->Clock(),
             cast_environment->Logging(),
             packet_sender,
             cast_environment->GetTaskRunner(CastEnvironment::MAIN)),
      audio_receiver_(cast_environment, audio_config, AUDIO_EVENT, &pacer_),
      video_receiver_(cast_environment, video_config, VIDEO_EVENT, &pacer_),
      ssrc_of_audio_sender_(audio_config.incoming_ssrc),
      ssrc_of_video_sender_(video_config.incoming_ssrc),
      num_audio_channels_(audio_config.channels),
      audio_sampling_rate_(audio_config.frequency),
      audio_codec_(audio_config.codec.audio),
      video_codec_(video_config.codec.video) {}

CastReceiverImpl::~CastReceiverImpl() {}

void CastReceiverImpl::DispatchReceivedPacket(scoped_ptr<Packet> packet) {
  const uint8_t* const data = &packet->front();
  const size_t length = packet->size();

  uint32 ssrc_of_sender;
  if (Rtcp::IsRtcpPacket(data, length)) {
    ssrc_of_sender = Rtcp::GetSsrcOfSender(data, length);
  } else if (!FrameReceiver::ParseSenderSsrc(data, length, &ssrc_of_sender)) {
    VLOG(1) << "Invalid RTP packet.";
    return;
  }

  base::WeakPtr<FrameReceiver> target;
  if (ssrc_of_sender == ssrc_of_video_sender_) {
    target = video_receiver_.AsWeakPtr();
  } else if (ssrc_of_sender == ssrc_of_audio_sender_) {
    target = audio_receiver_.AsWeakPtr();
  } else {
    VLOG(1) << "Dropping packet with a non matching sender SSRC: "
            << ssrc_of_sender;
    return;
  }
  cast_environment_->PostTask(
      CastEnvironment::MAIN,
      FROM_HERE,
      base::Bind(base::IgnoreResult(&FrameReceiver::ProcessPacket),
                 target,
                 base::Passed(&packet)));
}

transport::PacketReceiverCallback CastReceiverImpl::packet_receiver() {
  return base::Bind(&CastReceiverImpl::DispatchReceivedPacket,
                    // TODO(miu): This code structure is dangerous, since the
                    // callback could be stored and then invoked after
                    // destruction of |this|.
                    base::Unretained(this));
}

void CastReceiverImpl::RequestDecodedAudioFrame(
    const AudioFrameDecodedCallback& callback) {
  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
  DCHECK(!callback.is_null());
  audio_receiver_.RequestEncodedFrame(base::Bind(
      &CastReceiverImpl::DecodeEncodedAudioFrame,
      // Note: Use of Unretained is safe since this Closure is guaranteed to be
      // invoked or discarded by |audio_receiver_| before destruction of |this|.
      base::Unretained(this),
      callback));
}

void CastReceiverImpl::RequestEncodedAudioFrame(
    const ReceiveEncodedFrameCallback& callback) {
  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
  audio_receiver_.RequestEncodedFrame(callback);
}

void CastReceiverImpl::RequestDecodedVideoFrame(
    const VideoFrameDecodedCallback& callback) {
  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
  DCHECK(!callback.is_null());
  video_receiver_.RequestEncodedFrame(base::Bind(
      &CastReceiverImpl::DecodeEncodedVideoFrame,
      // Note: Use of Unretained is safe since this Closure is guaranteed to be
      // invoked or discarded by |video_receiver_| before destruction of |this|.
      base::Unretained(this),
      callback));
}

void CastReceiverImpl::RequestEncodedVideoFrame(
    const ReceiveEncodedFrameCallback& callback) {
  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
  video_receiver_.RequestEncodedFrame(callback);
}

void CastReceiverImpl::DecodeEncodedAudioFrame(
    const AudioFrameDecodedCallback& callback,
    scoped_ptr<transport::EncodedFrame> encoded_frame) {
  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
  if (!encoded_frame) {
    callback.Run(make_scoped_ptr<AudioBus>(NULL), base::TimeTicks(), false);
    return;
  }

  if (!audio_decoder_) {
    audio_decoder_.reset(new AudioDecoder(cast_environment_,
                                          num_audio_channels_,
                                          audio_sampling_rate_,
                                          audio_codec_));
  }
  const uint32 frame_id = encoded_frame->frame_id;
  const uint32 rtp_timestamp = encoded_frame->rtp_timestamp;
  const base::TimeTicks playout_time = encoded_frame->reference_time;
  audio_decoder_->DecodeFrame(
      encoded_frame.Pass(),
      base::Bind(&CastReceiverImpl::EmitDecodedAudioFrame,
                 cast_environment_,
                 callback,
                 frame_id,
                 rtp_timestamp,
                 playout_time));
}

void CastReceiverImpl::DecodeEncodedVideoFrame(
    const VideoFrameDecodedCallback& callback,
    scoped_ptr<transport::EncodedFrame> encoded_frame) {
  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
  if (!encoded_frame) {
    callback.Run(
        make_scoped_refptr<VideoFrame>(NULL), base::TimeTicks(), false);
    return;
  }

  // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
  TRACE_EVENT_INSTANT2(
      "cast_perf_test", "PullEncodedVideoFrame",
      TRACE_EVENT_SCOPE_THREAD,
      "rtp_timestamp", encoded_frame->rtp_timestamp,
      "render_time", encoded_frame->reference_time.ToInternalValue());

  if (!video_decoder_)
    video_decoder_.reset(new VideoDecoder(cast_environment_, video_codec_));
  const uint32 frame_id = encoded_frame->frame_id;
  const uint32 rtp_timestamp = encoded_frame->rtp_timestamp;
  const base::TimeTicks playout_time = encoded_frame->reference_time;
  video_decoder_->DecodeFrame(
      encoded_frame.Pass(),
      base::Bind(&CastReceiverImpl::EmitDecodedVideoFrame,
                 cast_environment_,
                 callback,
                 frame_id,
                 rtp_timestamp,
                 playout_time));
}

// static
void CastReceiverImpl::EmitDecodedAudioFrame(
    const scoped_refptr<CastEnvironment>& cast_environment,
    const AudioFrameDecodedCallback& callback,
    uint32 frame_id,
    uint32 rtp_timestamp,
    const base::TimeTicks& playout_time,
    scoped_ptr<AudioBus> audio_bus,
    bool is_continuous) {
  DCHECK(cast_environment->CurrentlyOn(CastEnvironment::MAIN));
  if (audio_bus.get()) {
    const base::TimeTicks now = cast_environment->Clock()->NowTicks();
    cast_environment->Logging()->InsertFrameEvent(
        now, FRAME_DECODED, AUDIO_EVENT, rtp_timestamp, frame_id);
    cast_environment->Logging()->InsertFrameEventWithDelay(
        now, FRAME_PLAYOUT, AUDIO_EVENT, rtp_timestamp, frame_id,
        playout_time - now);
  }
  callback.Run(audio_bus.Pass(), playout_time, is_continuous);
}

// static
void CastReceiverImpl::EmitDecodedVideoFrame(
    const scoped_refptr<CastEnvironment>& cast_environment,
    const VideoFrameDecodedCallback& callback,
    uint32 frame_id,
    uint32 rtp_timestamp,
    const base::TimeTicks& playout_time,
    const scoped_refptr<VideoFrame>& video_frame,
    bool is_continuous) {
  DCHECK(cast_environment->CurrentlyOn(CastEnvironment::MAIN));
  if (video_frame) {
    const base::TimeTicks now = cast_environment->Clock()->NowTicks();
    cast_environment->Logging()->InsertFrameEvent(
        now, FRAME_DECODED, VIDEO_EVENT, rtp_timestamp, frame_id);
    cast_environment->Logging()->InsertFrameEventWithDelay(
        now, FRAME_PLAYOUT, VIDEO_EVENT, rtp_timestamp, frame_id,
        playout_time - now);

    // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
    TRACE_EVENT_INSTANT1(
        "cast_perf_test", "FrameDecoded",
        TRACE_EVENT_SCOPE_THREAD,
        "rtp_timestamp", rtp_timestamp);
  }
  callback.Run(video_frame, playout_time, is_continuous);
}

}  // namespace cast
}  // namespace media