blob: c4e59e9548764feec7da15032660c548db032219 [file] [log] [blame]
// 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