blob: 3a905112b779d267e9c7092cb82dffbfe68e5c6e [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.
#ifndef SRC_MEDIA_AUDIO_LIB_TEST_RENDERER_SHIM_H_
#define SRC_MEDIA_AUDIO_LIB_TEST_RENDERER_SHIM_H_
#include <fuchsia/media/cpp/fidl.h>
#include <fuchsia/ultrasound/cpp/fidl.h>
#include <lib/zx/clock.h>
#include <memory>
#include <vector>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
#include "src/media/audio/lib/clock/utils.h"
#include "src/media/audio/lib/format/audio_buffer.h"
#include "src/media/audio/lib/format/format.h"
#include "src/media/audio/lib/test/test_fixture.h"
#include "src/media/audio/lib/test/vmo_backed_buffer.h"
namespace media::audio::test {
template <class Interface>
class VirtualDevice;
// This class is thread hostile: none of its methods can be called concurrently.
class RendererShimImpl {
public:
static constexpr uint32_t kPacketMs = 10;
~RendererShimImpl();
fuchsia::media::AudioRendererPtr& fidl() { return fidl_; }
VmoBackedBuffer& payload() { return payload_buffer_; }
const Format& format() const { return format_; }
size_t num_packet_frames() const { return format_.frames_per_second() / 1000 * kPacketMs; }
size_t num_packet_samples() const { return num_packet_frames() * format_.channels(); }
size_t num_packet_bytes() const { return num_packet_frames() * format_.bytes_per_frame(); }
size_t num_payload_frames() const { return payload_frame_count_; }
size_t num_payload_packets() const { return payload_frame_count_ / num_packet_frames(); }
size_t num_payload_samples() const { return payload_frame_count_ * format_.channels(); }
size_t num_payload_bytes() const { return payload_frame_count_ * format_.bytes_per_frame(); }
// Minimum lead time for the AudioRenderer.
zx::duration min_lead_time() const { return min_lead_time_.value(); }
// Sets the units used by the presentation (media) timeline.
// By default, we use format.frames_per_second / 1, which means 1 PTS tick = 1 frame.
// See FIDL's AudioRenderer::SetPtsUnits.
void SetPtsUnits(uint32_t ticks_per_second_numerator, uint32_t ticks_per_second_denominator);
// Return the time in the current reference clock that corresponds to the given monotonic time.
zx::time ReferenceTimeFromMonotonicTime(zx::time mono_time);
// Send a Play command to the renderer and wait until it is processed.
// Either time may be NO_TIMESTAMP, as described in the FIDL documentation.
void Play(TestFixture* fixture, zx::time reference_time, int64_t media_time);
// Like Play, but aligns the reference_time with the start of output_device's ring buffer.
// Returns the reference_time at which the audio will start playing.
zx::time PlaySynchronized(TestFixture* fixture,
VirtualDevice<fuchsia::virtualaudio::Output>* output_device,
int64_t media_time);
struct Packet {
// The packet spans timestamps [start_pts, end_pts), so end_pts is the start_pts of the
// next contiguous packet. By default, unless overriden by SetPtsUnits, 1 PTS = 1 frame.
int64_t start_pts;
int64_t end_pts;
zx::time start_ref_time; // reference time corresponding to start_pts (set by Play)
zx::time end_ref_time; // reference time corresponding to end_pts (set by Play)
bool returned = false; // set after the packet was returned from AudioCore
};
using PacketVector = std::vector<std::shared_ptr<Packet>>;
// Submit the given slices as a sequence of timestamped packets of length at most kPacketMs.
// The packets are appended to the payload buffer after the last call to ClearPayload().
template <fuchsia::media::AudioSampleFormat SampleFormat>
PacketVector AppendPackets(const std::vector<AudioBufferSlice<SampleFormat>>& slices,
int64_t initial_pts = 0);
// Wait until the given packets are rendered. |packets| must be non-empty and must be ordered by
// start_pts. If |ring_out_frames| > 0, we wait for all |packets| to be rendered, plus an
// additional |ring_out_frames|.
void WaitForPackets(TestFixture* fixture, const PacketVector& packets,
size_t ring_out_frames = 0);
// Reset the payload buffer to all zeros and seek back to the start.
void ClearPayload() { payload_buffer_.Clear(); }
// For validating properties exported by inspect.
size_t inspect_id() const { return inspect_id_; }
const zx::clock& reference_clock() const { return reference_clock_; }
protected:
RendererShimImpl(Format format, size_t payload_frame_count, size_t inspect_id)
: format_(format),
payload_frame_count_(payload_frame_count),
inspect_id_(inspect_id),
payload_buffer_(format, payload_frame_count) {}
void SetReferenceClock(TestFixture* fixture, const zx::clock& clock);
void RetrieveReferenceClock(TestFixture* fixture);
void ResetEvents();
void WatchEvents();
VmoBackedBuffer& payload_buffer() { return payload_buffer_; }
bool has_min_lead_time() const { return min_lead_time_.has_value(); }
void set_reference_clock(zx::clock reference_clock) {
reference_clock_ = std::move(reference_clock);
}
private:
const Format format_;
const size_t payload_frame_count_;
const size_t inspect_id_;
zx::clock reference_clock_;
VmoBackedBuffer payload_buffer_;
fuchsia::media::AudioRendererPtr fidl_;
std::optional<zx::duration> min_lead_time_;
TimelineRate pts_ticks_per_second_;
TimelineRate pts_ticks_per_frame_;
PacketVector queued_packets_;
};
template <fuchsia::media::AudioSampleFormat SampleFormat>
class AudioRendererShim : public RendererShimImpl {
public:
using SampleT = typename AudioBuffer<SampleFormat>::SampleT;
PacketVector AppendPackets(const std::vector<AudioBufferSlice<SampleFormat>>& slices,
int64_t initial_pts = 0) {
return RendererShimImpl::AppendPackets<SampleFormat>(slices, initial_pts);
}
// Don't call this directly. Use HermeticAudioTest::CreateAudioRenderer so the object is
// appropriately bound into the test environment.
AudioRendererShim(TestFixture* fixture, fuchsia::media::AudioCorePtr& audio_core, Format fmt,
size_t payload_frame_count, fuchsia::media::AudioRenderUsage usage,
size_t inspect_id, std::optional<zx::clock> reference_clock)
: RendererShimImpl(fmt, payload_frame_count, inspect_id) {
audio_core->CreateAudioRenderer(fidl().NewRequest());
fixture->AddErrorHandler(fidl(), "AudioRenderer");
WatchEvents();
if (reference_clock) {
SetReferenceClock(fixture, *reference_clock);
}
fidl()->SetUsage(usage);
fidl()->SetPcmStreamType({.sample_format = format().sample_format(),
.channels = format().channels(),
.frames_per_second = format().frames_per_second()});
SetPtsUnits(format().frames_per_second(), 1);
fidl()->AddPayloadBuffer(0, payload_buffer().CreateAndMapVmo(false));
RetrieveReferenceClock(fixture);
}
bool created() const { return has_min_lead_time(); }
};
template <fuchsia::media::AudioSampleFormat SampleFormat>
class UltrasoundRendererShim : public RendererShimImpl {
public:
using SampleT = typename AudioBuffer<SampleFormat>::SampleT;
PacketVector AppendPackets(const std::vector<AudioBufferSlice<SampleFormat>>& slices,
int64_t initial_pts = 0) {
return RendererShimImpl::AppendPackets<SampleFormat>(slices, initial_pts);
}
// Don't call this directly. Use HermeticAudioTest::CreateUltrasoundRenderer so the object is
// appropriately bound into the test environment.
UltrasoundRendererShim(TestFixture* fixture, fuchsia::ultrasound::FactoryPtr& ultrasound_factory,
Format fmt, size_t payload_frame_count, size_t inspect_id)
: RendererShimImpl(fmt, payload_frame_count, inspect_id), fixture_(fixture) {
ultrasound_factory->CreateRenderer(
fidl().NewRequest(), [this](auto ref_clock, auto stream_type) {
created_ = true;
set_reference_clock(std::move(ref_clock));
EXPECT_EQ(stream_type.sample_format, format().sample_format());
EXPECT_EQ(stream_type.channels, format().channels());
EXPECT_EQ(stream_type.frames_per_second, format().frames_per_second());
});
fixture->AddErrorHandler(fidl(), "UltrasoundRenderer");
WatchEvents();
SetPtsUnits(format().frames_per_second(), 1);
fidl()->AddPayloadBuffer(0, payload_buffer().CreateAndMapVmo(false));
RetrieveReferenceClock(fixture);
}
void WaitForDevice() {
fixture_->RunLoopUntil(
[this] { return (created_ && has_min_lead_time()) || fixture_->ErrorOccurred(); });
}
bool created() const { return created_ && has_min_lead_time(); }
private:
bool created_ = false;
TestFixture* fixture_;
};
} // namespace media::audio::test
#endif // SRC_MEDIA_AUDIO_LIB_TEST_RENDERER_SHIM_H_