blob: 240979cdc2c92617786a8b51e5afa28685b22105 [file] [log] [blame]
// Copyright 2019 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/audio_core/audio_output.h"
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/loudness_transform.h"
#include "src/media/audio/audio_core/testing/fake_audio_renderer.h"
#include "src/media/audio/audio_core/testing/threading_model_fixture.h"
namespace media::audio {
namespace {
class TestAudioOutput : public AudioOutput {
public:
TestAudioOutput(ThreadingModel* threading_model, DeviceRegistry* registry,
LinkMatrix* link_matrix)
: AudioOutput(threading_model, registry, link_matrix) {}
using AudioOutput::SetNextSchedTime;
void SetupMixTask(const PipelineConfig& config, uint32_t channels, uint32_t max_frames,
TimelineFunction clock_mono_to_output_frame) {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &mix_domain());
AudioOutput::SetupMixTask(config, channels, max_frames, clock_mono_to_output_frame);
}
void Process() {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &mix_domain());
AudioOutput::Process();
}
// Allow a test to provide a delegate to handle |AudioOutput::StartMixJob| invocations.
using StartMixDelegate = fit::function<std::optional<MixStage::FrameSpan>(zx::time)>;
void set_start_mix_delegate(StartMixDelegate delegate) {
start_mix_delegate_ = std::move(delegate);
}
// Allow a test to provide a delegate to handle |AudioOutput::FinishMixJob| invocations.
using FinishMixDelegate = fit::function<void(const MixStage::FrameSpan&, float* buffer)>;
void set_finish_mix_delegate(FinishMixDelegate delegate) {
finish_mix_delegate_ = std::move(delegate);
}
// |AudioOutput|
std::optional<MixStage::FrameSpan> StartMixJob(zx::time process_start) override {
if (start_mix_delegate_) {
return start_mix_delegate_(process_start);
} else {
return std::nullopt;
}
}
void FinishMixJob(const MixStage::FrameSpan& span, float* buffer) {
if (finish_mix_delegate_) {
finish_mix_delegate_(span, buffer);
}
}
// |AudioDevice|
void ApplyGainLimits(fuchsia::media::AudioGainInfo* in_out_info, uint32_t set_flags) override {}
void OnWakeup() {}
private:
StartMixDelegate start_mix_delegate_;
FinishMixDelegate finish_mix_delegate_;
};
class AudioOutputTest : public testing::ThreadingModelFixture {
protected:
VolumeCurve volume_curve_ = VolumeCurve::DefaultForMinGain(Gain::kMinGainDb);
std::shared_ptr<TestAudioOutput> audio_output_ = std::make_shared<TestAudioOutput>(
&threading_model(), &context().device_manager(), &context().link_matrix());
};
TEST_F(AudioOutputTest, ProcessTrimsInputStreamsIfNoMixJobProvided) {
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
static const TimelineFunction kOneFramePerMs = TimelineFunction(TimelineRate(1, 1'000'000));
static PipelineConfig config = PipelineConfig::Default();
audio_output_->SetupMixTask(config, renderer->format()->channels(), zx::msec(1).to_msecs(),
kOneFramePerMs);
context().link_matrix().LinkObjects(renderer, audio_output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
// StartMixJob always returns nullopt (no work) and schedules another mix 1ms in the future.
audio_output_->set_start_mix_delegate([this, audio_output = audio_output_.get()](zx::time now) {
audio_output->SetNextSchedTime(Now() + zx::msec(1));
return std::nullopt;
});
// Enqueue 2 packets:
// * packet 1 from 0ms -> 5ms.
// * packet 2 from 5ms -> 10ms.
bool packet1_released = false;
bool packet2_released = false;
renderer->EnqueueAudioPacket(1.0, zx::msec(5), [&packet1_released] {
FX_LOGS(ERROR) << "Release packet 1";
packet1_released = true;
});
renderer->EnqueueAudioPacket(1.0, zx::msec(5), [&packet2_released] {
FX_LOGS(ERROR) << "Release packet 2";
packet2_released = true;
});
// Process kicks off the periodic mix task.
audio_output_->Process();
// After 4ms we should still be retaining packet1.
RunLoopFor(zx::msec(4));
ASSERT_FALSE(packet1_released);
// 5ms; all the audio from packet1 is consumed and it should be released. We should still have
// packet2, however.
RunLoopFor(zx::msec(1));
ASSERT_TRUE(packet1_released && !packet2_released);
// After 9ms we should still be retaining packet2.
RunLoopFor(zx::msec(4));
ASSERT_FALSE(packet2_released);
// Finally after 10ms we will have released packet2.
RunLoopFor(zx::msec(1));
ASSERT_TRUE(packet2_released);
}
} // namespace
} // namespace media::audio