| // 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 |