| // Copyright 2020 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/fuchsia/audio/fake_audio_consumer.h" | 
 |  | 
 | #include <lib/vfs/cpp/pseudo_dir.h> | 
 | #include <lib/vfs/cpp/service.h> | 
 |  | 
 | #include "base/fuchsia/fuchsia_logging.h" | 
 |  | 
 | namespace media { | 
 |  | 
 | const base::TimeDelta FakeAudioConsumer::kMinLeadTime = base::Milliseconds(100); | 
 | const base::TimeDelta FakeAudioConsumer::kMaxLeadTime = base::Milliseconds(500); | 
 |  | 
 | FakeAudioConsumer::FakeAudioConsumer( | 
 |     uint64_t session_id, | 
 |     fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request) | 
 |     : session_id_(session_id), | 
 |       audio_consumer_binding_(this), | 
 |       stream_sink_binding_(this), | 
 |       volume_control_binding_(this) { | 
 |   audio_consumer_binding_.Bind(std::move(request)); | 
 | } | 
 |  | 
 | FakeAudioConsumer::~FakeAudioConsumer() = default; | 
 |  | 
 | base::TimeDelta FakeAudioConsumer::GetMediaPosition() { | 
 |   base::TimeDelta result = media_pos_; | 
 |   if (state_ == State::kPlaying) { | 
 |     result += (base::TimeTicks::Now() - reference_time_) * media_delta_ / | 
 |               reference_delta_; | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | void FakeAudioConsumer::CreateStreamSink( | 
 |     std::vector<zx::vmo> buffers, | 
 |     fuchsia::media::AudioStreamType stream_type, | 
 |     std::unique_ptr<fuchsia::media::Compression> compression, | 
 |     fidl::InterfaceRequest<fuchsia::media::StreamSink> stream_sink_request) { | 
 |   num_buffers_ = buffers.size(); | 
 |   CHECK_GT(num_buffers_, 0U); | 
 |   stream_sink_binding_.Bind(std::move(stream_sink_request)); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::Start(fuchsia::media::AudioConsumerStartFlags flags, | 
 |                               int64_t reference_time, | 
 |                               int64_t media_time) { | 
 |   CHECK(state_ == State::kStopped); | 
 |  | 
 |   if (reference_time != fuchsia::media::NO_TIMESTAMP) { | 
 |     reference_time_ = base::TimeTicks::FromZxTime(reference_time); | 
 |   } else { | 
 |     reference_time_ = base::TimeTicks::Now() + kMinLeadTime; | 
 |   } | 
 |  | 
 |   if (media_time != fuchsia::media::NO_TIMESTAMP) { | 
 |     media_pos_ = base::TimeDelta::FromZxDuration(media_time); | 
 |   } else { | 
 |     if (media_pos_.is_min()) { | 
 |       media_pos_ = base::TimeDelta(); | 
 |     } | 
 |   } | 
 |  | 
 |   state_ = State::kPlaying; | 
 |  | 
 |   OnStatusUpdate(); | 
 |   ScheduleNextStreamPosUpdate(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::Stop() { | 
 |   CHECK(state_ != State::kStopped); | 
 |  | 
 |   state_ = State::kStopped; | 
 |   OnStatusUpdate(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::WatchStatus(WatchStatusCallback callback) { | 
 |   status_callback_ = std::move(callback); | 
 |   if (have_status_update_) { | 
 |     CallStatusCallback(); | 
 |   } | 
 | } | 
 |  | 
 | void FakeAudioConsumer::SetRate(float rate) { | 
 |   // Playback rate must not be negative. | 
 |   CHECK_GE(rate, 0.0); | 
 |  | 
 |   // Update reference position. | 
 |   auto now = base::TimeTicks::Now(); | 
 |   media_pos_ = | 
 |       media_pos_ + (now - reference_time_) * media_delta_ / reference_delta_; | 
 |   reference_time_ = now; | 
 |  | 
 |   // Approximate the rate as n/1000; | 
 |   reference_delta_ = 1000; | 
 |   media_delta_ = static_cast<int>(rate * 1000.0); | 
 |  | 
 |   OnStatusUpdate(); | 
 |  | 
 |   if (update_timer_.IsRunning()) | 
 |     update_timer_.Reset(); | 
 |   ScheduleNextStreamPosUpdate(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::BindVolumeControl( | 
 |     fidl::InterfaceRequest<fuchsia::media::audio::VolumeControl> | 
 |         volume_control_request) { | 
 |   volume_control_binding_.Bind(std::move(volume_control_request)); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::SendPacket(fuchsia::media::StreamPacket stream_packet, | 
 |                                    SendPacketCallback callback) { | 
 |   CHECK_LT(stream_packet.payload_buffer_id, num_buffers_); | 
 |  | 
 |   Packet packet; | 
 |   if (stream_packet.pts == fuchsia::media::NO_TIMESTAMP) { | 
 |     if (media_pos_.is_min()) { | 
 |       packet.pts = base::TimeDelta(); | 
 |     } else { | 
 |       packet.pts = media_pos_; | 
 |     } | 
 |   } else { | 
 |     packet.pts = base::TimeDelta::FromZxDuration(stream_packet.pts); | 
 |   } | 
 |   pending_packets_.push_back(std::move(packet)); | 
 |  | 
 |   callback(); | 
 |  | 
 |   ScheduleNextStreamPosUpdate(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::SendPacketNoReply(fuchsia::media::StreamPacket packet) { | 
 |   NOTREACHED(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::EndOfStream() { | 
 |   Packet packet; | 
 |   packet.is_eos = true; | 
 |   pending_packets_.push_back(std::move(packet)); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::DiscardAllPackets(DiscardAllPacketsCallback callback) { | 
 |   DiscardAllPacketsNoReply(); | 
 |   std::move(callback)(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::DiscardAllPacketsNoReply() { | 
 |   pending_packets_.clear(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::SetVolume(float volume) { | 
 |   volume_ = volume; | 
 | } | 
 |  | 
 | void FakeAudioConsumer::SetMute(bool mute) { | 
 |   is_muted_ = mute; | 
 | } | 
 |  | 
 | void FakeAudioConsumer::NotImplemented_(const std::string& name) { | 
 |   LOG(FATAL) << "Reached non-implemented " << name; | 
 | } | 
 |  | 
 | void FakeAudioConsumer::ScheduleNextStreamPosUpdate() { | 
 |   if (pending_packets_.empty() || update_timer_.IsRunning() || | 
 |       media_delta_ == 0 || state_ != State::kPlaying) { | 
 |     return; | 
 |   } | 
 |   base::TimeDelta delay; | 
 |   if (!pending_packets_.front().is_eos) { | 
 |     auto next_packet_time = | 
 |         reference_time_ + (pending_packets_.front().pts - media_pos_) * | 
 |                               reference_delta_ / media_delta_; | 
 |     delay = (next_packet_time - base::TimeTicks::Now()); | 
 |   } | 
 |   update_timer_.Start(FROM_HERE, delay, | 
 |                       base::BindOnce(&FakeAudioConsumer::UpdateStreamPos, | 
 |                                      base::Unretained(this))); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::UpdateStreamPos() { | 
 |   if (state_ != State::kPlaying) | 
 |     return; | 
 |  | 
 |   auto now = base::TimeTicks::Now(); | 
 |   auto new_media_pos = | 
 |       media_pos_ + (now - reference_time_) * media_delta_ / reference_delta_; | 
 |  | 
 |   // Drop all packets with PTS before the current position. | 
 |   while (!pending_packets_.empty()) { | 
 |     if (!pending_packets_.front().is_eos && | 
 |         pending_packets_.front().pts > new_media_pos) { | 
 |       break; | 
 |     } | 
 |  | 
 |     Packet packet = pending_packets_.front(); | 
 |     pending_packets_.pop_front(); | 
 |  | 
 |     if (packet.is_eos) { | 
 |       // No data should be submitted after EOS. | 
 |       CHECK(pending_packets_.empty()); | 
 |       audio_consumer_binding_.events().OnEndOfStream(); | 
 |       state_ = State::kEndOfStream; | 
 |       media_pos_ = new_media_pos; | 
 |       reference_time_ = now; | 
 |     } | 
 |   } | 
 |  | 
 |   ScheduleNextStreamPosUpdate(); | 
 | } | 
 |  | 
 | void FakeAudioConsumer::OnStatusUpdate() { | 
 |   have_status_update_ = true; | 
 |   if (status_callback_) { | 
 |     CallStatusCallback(); | 
 |   } | 
 | } | 
 |  | 
 | void FakeAudioConsumer::CallStatusCallback() { | 
 |   DCHECK(status_callback_); | 
 |   DCHECK(have_status_update_); | 
 |  | 
 |   fuchsia::media::AudioConsumerStatus status; | 
 |   if (state_ == State::kPlaying) { | 
 |     fuchsia::media::TimelineFunction timeline; | 
 |     timeline.reference_time = reference_time_.ToZxTime(); | 
 |     timeline.subject_time = media_pos_.ToZxDuration(); | 
 |     timeline.reference_delta = reference_delta_; | 
 |     timeline.subject_delta = media_delta_; | 
 |  | 
 |     status.set_presentation_timeline(std::move(timeline)); | 
 |   } | 
 |  | 
 |   status.set_min_lead_time(kMinLeadTime.ToZxDuration()); | 
 |   status.set_max_lead_time(kMaxLeadTime.ToZxDuration()); | 
 |  | 
 |   have_status_update_ = false; | 
 |   std::move(status_callback_)(std::move(status)); | 
 |   status_callback_ = {}; | 
 | } | 
 |  | 
 | FakeAudioConsumerService::FakeAudioConsumerService(vfs::PseudoDir* pseudo_dir) | 
 |     : binding_(pseudo_dir, this) {} | 
 |  | 
 | FakeAudioConsumerService::~FakeAudioConsumerService() {} | 
 |  | 
 | void FakeAudioConsumerService::CreateAudioConsumer( | 
 |     uint64_t session_id, | 
 |     fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request) { | 
 |   audio_consumers_.push_back( | 
 |       std::make_unique<FakeAudioConsumer>(session_id, std::move(request))); | 
 | } | 
 |  | 
 | void FakeAudioConsumerService::NotImplemented_(const std::string& name) { | 
 |   LOG(FATAL) << "Reached non-implemented " << name; | 
 | } | 
 |  | 
 | }  // namespace media |