| // Copyright 2020 The Chromium Authors |
| // 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 |