blob: 85c807e8ed6e6102bdddeebbf9e89f7c0d280404 [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/virtual_device.h"
#include <lib/media/cpp/timeline_function.h>
#include "src/media/audio/lib/format/driver_format.h"
#include "src/media/audio/lib/logging/logging.h"
namespace media::audio::test {
namespace internal {
size_t virtual_output_next_inspect_id = 0; // ids start at 0
size_t virtual_input_next_inspect_id = 0; // ids start at 0
} // namespace internal
template <class Iface>
VirtualDevice<Iface>::VirtualDevice(TestFixture* fixture, HermeticAudioEnvironment* environment,
const audio_stream_unique_id_t& device_id, Format format,
size_t frame_count, size_t inspect_id)
: format_(format),
frame_count_(frame_count),
inspect_id_(inspect_id),
rb_(format, frame_count) {
environment->ConnectToService(device_.NewRequest());
fixture->AddErrorHandler(device_, "VirtualAudioDevice");
WatchEvents();
std::array<uint8_t, 16> device_id_array;
std::copy(std::begin(device_id.data), std::end(device_id.data), std::begin(device_id_array));
device_->SetUniqueId(device_id_array);
if (!AudioSampleFormatToDriverSampleFormat(format_.sample_format(), &driver_format_)) {
FX_CHECK(false) << "Failed to convert Fmt 0x" << std::hex
<< static_cast<uint32_t>(format_.sample_format()) << " to driver format.";
}
device_->ClearFormatRanges();
device_->AddFormatRange(driver_format_, format_.frames_per_second(), format_.frames_per_second(),
format_.channels(), format_.channels(), ASF_RANGE_FLAG_FPS_CONTINUOUS);
device_->SetFifoDepth(kFifoDepthBytes);
device_->SetExternalDelay(kExternalDelay.get());
device_->SetRingBufferRestrictions(frame_count, frame_count, frame_count);
auto ring_buffer_ms = static_cast<size_t>(
static_cast<double>(frame_count) / static_cast<double>(format_.frames_per_second()) * 1000);
device_->SetNotificationFrequency(ring_buffer_ms / kNotifyMs);
device_->Add();
}
template <class Iface>
VirtualDevice<Iface>::~VirtualDevice() {
ResetEvents();
if (device_.is_bound()) {
device_->Remove();
}
}
template <class Iface>
void VirtualDevice<Iface>::ResetEvents() {
device_.events().OnSetFormat = nullptr;
device_.events().OnSetGain = nullptr;
device_.events().OnBufferCreated = nullptr;
device_.events().OnStart = nullptr;
device_.events().OnStop = nullptr;
device_.events().OnPositionNotify = nullptr;
}
template <class Iface>
void VirtualDevice<Iface>::WatchEvents() {
device_.events().OnSetFormat = [this](uint32_t fps, uint32_t fmt, uint32_t num_chans,
zx_duration_t ext_delay) {
received_set_format_ = true;
EXPECT_EQ(fps, format_.frames_per_second());
EXPECT_EQ(fmt, driver_format_);
EXPECT_EQ(num_chans, format_.channels());
EXPECT_EQ(ext_delay, kExternalDelay.get());
AUDIO_LOG(DEBUG) << "OnSetFormat callback: " << fps << ", " << fmt << ", " << num_chans << ", "
<< ext_delay;
};
device_.events().OnSetGain = [](bool cur_mute, bool cur_agc, float cur_gain_db) {
EXPECT_EQ(cur_gain_db, 0.0f);
EXPECT_FALSE(cur_mute);
EXPECT_FALSE(cur_agc);
AUDIO_LOG(DEBUG) << "OnSetGain callback: " << cur_mute << ", " << cur_agc << ", "
<< cur_gain_db;
};
device_.events().OnBufferCreated = [this](zx::vmo ring_buffer_vmo,
uint32_t driver_reported_frame_count,
uint32_t notifications_per_ring) {
ASSERT_EQ(frame_count_, driver_reported_frame_count);
ASSERT_TRUE(received_set_format_);
rb_vmo_ = std::move(ring_buffer_vmo);
rb_.MapVmo(rb_vmo_);
AUDIO_LOG(DEBUG) << "OnBufferCreated callback: " << driver_reported_frame_count << " frames, "
<< notifications_per_ring << " notifs/ring";
};
device_.events().OnStart = [this](zx_time_t start_time) {
ASSERT_TRUE(received_set_format_);
ASSERT_TRUE(rb_vmo_.is_valid());
received_start_ = true;
start_time_ = start_time;
AUDIO_LOG(DEBUG) << "OnStart callback: " << start_time;
};
device_.events().OnStop = [this](zx_time_t stop_time, uint32_t ring_pos) {
received_stop_ = true;
stop_time_ = stop_time;
stop_pos_ = ring_pos;
AUDIO_LOG(DEBUG) << "OnStop callback: " << stop_time << ", " << ring_pos;
};
device_.events().OnPositionNotify = [this](zx_time_t monotonic_time, uint32_t ring_pos) {
// compare to prev ring_pos - if less, then add rb_.SizeBytes().
if (ring_pos < ring_pos_) {
running_ring_pos_ += rb_.SizeBytes();
}
running_ring_pos_ += ring_pos;
running_ring_pos_ -= ring_pos_;
ring_pos_ = ring_pos;
AUDIO_LOG(TRACE) << "OnPositionNotify callback: " << monotonic_time << ", " << ring_pos;
};
}
template <class Iface>
int64_t VirtualDevice<Iface>::NextSynchronizedTimestamp(TestFixture* fixture) const {
// Wait for a ring buffer rollover, to give the client enough time before the next one.
auto min_num_rings_for_measurement = (running_ring_pos_ + rb_.SizeBytes()) / rb_.SizeBytes();
auto pos_for_measurement = min_num_rings_for_measurement * rb_.SizeBytes();
fixture->RunLoopUntil(
[this, pos_for_measurement]() { return running_ring_pos_ >= pos_for_measurement; });
// Calculate the reference time for the start of the next ring buffer.
auto ns_per_byte =
TimelineRate(zx::sec(1).get(), format_.frames_per_second() * format_.bytes_per_frame());
int64_t running_pos_for_play = ((running_ring_pos_ / rb_.SizeBytes()) + 1) * rb_.SizeBytes();
auto running_pos_to_ref_time = TimelineFunction(start_time_, 0, ns_per_byte);
return running_pos_to_ref_time.Apply(running_pos_for_play);
}
// Only two instantiations are needed.
template class VirtualDevice<fuchsia::virtualaudio::Output>;
template class VirtualDevice<fuchsia::virtualaudio::Input>;
} // namespace media::audio::test