blob: a93451056e28986fa86480c13486a7d6f121a602 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 "src/media/audio/lib/test/renderer_shim.h"
#include <algorithm>
#include "lib/zx/time.h"
#include "src/media/audio/lib/clock/utils.h"
#include "src/media/audio/lib/logging/logging.h"
#include "src/media/audio/lib/test/virtual_device.h"
namespace media::audio::test {
RendererShimImpl::~RendererShimImpl() { ResetEvents(); }
void RendererShimImpl::ResetEvents() {
fidl_->EnableMinLeadTimeEvents(false);
fidl_.events().OnMinLeadTimeChanged = nullptr;
}
void RendererShimImpl::WatchEvents() {
fidl_->EnableMinLeadTimeEvents(true);
fidl_.events().OnMinLeadTimeChanged = [this](int64_t min_lead_time_nsec) {
AUDIO_LOG(DEBUG) << "OnMinLeadTimeChanged: " << min_lead_time_nsec;
// Sometimes, this can be invoked before the Renderer is actually linked.
// When that happens, the reported lead time is zero as it hasn't been computed yet.
// Wait until the renderer is linked before updating our lead time.
if (min_lead_time_nsec > 0) {
min_lead_time_ = zx::nsec(min_lead_time_nsec);
}
};
}
void RendererShimImpl::SetPtsUnits(uint32_t ticks_per_second_numerator,
uint32_t ticks_per_second_denominator) {
fidl_->SetPtsUnits(ticks_per_second_numerator, ticks_per_second_denominator);
pts_ticks_per_second_ = TimelineRate(ticks_per_second_numerator, ticks_per_second_denominator);
pts_ticks_per_frame_ =
TimelineRate::Product(pts_ticks_per_second_, TimelineRate(1, format_.frames_per_second()));
}
void RendererShimImpl::SetReferenceClock(TestFixture* fixture, const zx::clock& clock) {
if (clock.is_valid()) {
zx::clock dup = ::media::audio::clock::DuplicateClock(clock).take_value();
fidl()->SetReferenceClock(std::move(dup));
} else {
fidl()->SetReferenceClock(zx::clock());
}
RetrieveReferenceClock(fixture);
}
void RendererShimImpl::RetrieveReferenceClock(TestFixture* fixture) {
bool done = false;
fidl_->GetReferenceClock([this, &done](zx::clock c) {
done = true;
reference_clock_ = std::move(c);
});
fixture->RunLoopUntil([&done]() { return done; });
}
zx::time RendererShimImpl::ReferenceTimeFromMonotonicTime(zx::time mono_time) {
return ::media::audio::clock::ReferenceTimeFromMonotonicTime(reference_clock_, mono_time).value();
}
void RendererShimImpl::Play(TestFixture* fixture, zx::time reference_time, int64_t media_time) {
fidl_->Play(
reference_time.get(), media_time,
fixture->AddCallback("Play", [&reference_time, &media_time](int64_t actual_reference_time,
int64_t actual_media_time) {
if (reference_time.get() != fuchsia::media::NO_TIMESTAMP) {
EXPECT_EQ(reference_time.get(), actual_reference_time);
} else {
reference_time = zx::time(actual_reference_time);
}
if (media_time != fuchsia::media::NO_TIMESTAMP) {
EXPECT_EQ(media_time, actual_media_time);
} else {
media_time = actual_media_time;
}
}));
fixture->ExpectCallback();
// Update the reference times for each queued packet.
TimelineRate ns_per_pts_tick =
TimelineRate::Product(pts_ticks_per_second_.Inverse(), TimelineRate::NsPerSecond);
for (auto p : queued_packets_) {
p->start_ref_time = reference_time + zx::nsec(ns_per_pts_tick.Scale(p->start_pts - media_time));
p->end_ref_time = reference_time + zx::nsec(ns_per_pts_tick.Scale(p->end_pts - media_time));
}
queued_packets_.clear();
}
zx::time RendererShimImpl::PlaySynchronized(
TestFixture* fixture, VirtualDevice<fuchsia::virtualaudio::Output>* output_device,
int64_t media_time) {
// Synchronize at some point that is at least min_lead_time + tolerance in the future,
// where tolerance estimates the maximum execution delay between the time we compute the
// next synchronized time and the time we call Play.
const auto tolerance = zx::msec(5);
auto min_start_time = zx::clock::get_monotonic() + *min_lead_time_ + tolerance;
auto reference_time =
ReferenceTimeFromMonotonicTime(output_device->NextSynchronizedTimestamp(min_start_time));
Play(fixture, reference_time, media_time);
return reference_time;
}
template <fuchsia::media::AudioSampleFormat SampleFormat>
RendererShimImpl::PacketVector RendererShimImpl::AppendPackets(
const std::vector<AudioBufferSlice<SampleFormat>>& slices, int64_t initial_pts) {
// Where in the payload buffer (in bytes) to write the next packet.
size_t payload_offset = payload_buffer_.GetCurrentOffset() * format().bytes_per_frame();
// Where in the media timeline (in PTS units; frames by default) to write the next packet.
int64_t pts = initial_pts;
PacketVector out;
for (auto& slice : slices) {
payload_buffer_.Append(slice);
initial_pts = pts;
for (size_t frame = 0; frame < slice.NumFrames(); frame += num_packet_frames()) {
// Every packet is kPacketMs long, except the last packet might be shorter.
size_t num_frames = std::min(num_packet_frames(), slice.NumFrames() - frame);
auto packet = std::make_shared<Packet>();
packet->start_pts = pts;
packet->end_pts = initial_pts + pts_ticks_per_frame_.Scale(frame + num_frames);
out.push_back(packet);
fuchsia::media::StreamPacket stream_packet{
.pts = pts,
.payload_offset = payload_offset,
.payload_size = num_frames * slice.format().bytes_per_frame(),
};
AUDIO_LOG(TRACE) << " sending pkt at pts " << packet->start_pts << ", frame " << frame
<< " of slice";
fidl_->SendPacket(stream_packet, [packet]() {
AUDIO_LOG(TRACE) << " return pkt at pts " << packet->start_pts;
packet->returned = true;
});
pts = packet->end_pts;
payload_offset += stream_packet.payload_size;
}
}
queued_packets_.insert(queued_packets_.end(), out.begin(), out.end());
return out;
}
void RendererShimImpl::WaitForPackets(TestFixture* fixture,
const std::vector<std::shared_ptr<Packet>>& packets,
size_t ring_out_frames) {
FX_CHECK(!packets.empty());
auto end_time_reference = (*packets.rbegin())->end_ref_time;
auto end_time_mono =
::media::audio::clock::MonotonicTimeFromReferenceTime(reference_clock_, end_time_reference)
.value();
auto timeout = end_time_mono - zx::clock::get_monotonic();
// Wait until all packets are rendered AND the timeout is reached.
// It's not sufficient to wait for just the packets, because that may not include ring_out_frames.
// It's not sufficient to just wait for the timeout, because the SendPacket callbacks may not have
// executed yet.
fixture->RunLoopWithTimeout(timeout);
fixture->RunLoopUntil([packets]() {
for (auto& p : packets) {
if (!p->returned) {
return false;
}
}
return true;
});
fixture->ExpectNoUnexpectedErrors("during WaitForPackets");
}
// Explicitly instantiate all possible implementations.
#define INSTANTIATE(T) \
template RendererShimImpl::PacketVector RendererShimImpl::AppendPackets<T>( \
const std::vector<AudioBufferSlice<T>>&, int64_t);
INSTANTIATE_FOR_ALL_FORMATS(INSTANTIATE)
} // namespace media::audio::test