blob: 957bf937535f52562141f1872af03942d7c56af3 [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/mix_stage.h"
#include <gmock/gmock.h>
#include "src/media/audio/audio_core/packet_queue.h"
#include "src/media/audio/audio_core/ring_buffer.h"
#include "src/media/audio/audio_core/testing/packet_factory.h"
#include "src/media/audio/audio_core/testing/threading_model_fixture.h"
using testing::Each;
using testing::FloatEq;
namespace media::audio {
namespace {
const Format kDefaultFormat =
Format::Create(fuchsia::media::AudioStreamType{
.sample_format = fuchsia::media::AudioSampleFormat::FLOAT,
.channels = 2,
.frames_per_second = 48000,
})
.take_value();
class MixStageTest : public testing::ThreadingModelFixture {
protected:
zx::time time_until(zx::duration delta) { return zx::time(delta.to_nsecs()); }
fbl::RefPtr<VersionedTimelineFunction> timeline_function_ =
fbl::MakeRefCounted<VersionedTimelineFunction>(TimelineFunction(
TimelineRate(FractionalFrames<int64_t>(kDefaultFormat.frames_per_second()).raw_value(),
zx::sec(1).to_nsecs())));
std::shared_ptr<MixStage> mix_stage_ =
std::make_shared<MixStage>(kDefaultFormat, 128, timeline_function_);
// Views the memory at |ptr| as a std::array of |N| elements of |T|. If |offset| is provided, it
// is the number of |T| sized elements to skip at the beginning of |ptr|.
//
// It is entirely up to the caller to ensure that values of |T|, |N|, and |offset| are chosen to
// not overflow |ptr|.
template <typename T, size_t N>
std::array<T, N>& as_array(void* ptr, size_t offset = 0) {
return reinterpret_cast<std::array<T, N>&>(static_cast<T*>(ptr)[offset]);
}
};
TEST_F(MixStageTest, Trim) {
// Set timeline rate to match our format.
auto timeline_function = fbl::MakeRefCounted<VersionedTimelineFunction>(TimelineFunction(
TimelineRate(FractionalFrames<uint32_t>(kDefaultFormat.frames_per_second()).raw_value(),
zx::sec(1).to_nsecs())));
auto packet_queue = std::make_shared<PacketQueue>(kDefaultFormat, timeline_function);
mix_stage_->AddInput(packet_queue);
bool packet1_released = false;
bool packet2_released = false;
testing::PacketFactory packet_factory(dispatcher(), kDefaultFormat, PAGE_SIZE);
packet_queue->PushPacket(packet_factory.CreatePacket(
1.0, zx::msec(5), [&packet1_released] { packet1_released = true; }));
packet_queue->PushPacket(packet_factory.CreatePacket(
0.5, zx::msec(5), [&packet2_released] { packet2_released = true; }));
// After 4ms we should still be retaining packet1.
mix_stage_->Trim(time_until(zx::msec(4)));
RunLoopUntilIdle();
ASSERT_FALSE(packet1_released);
// 5ms; all the audio from packet1 is consumed and it should be released. We should still have
// packet2, however.
mix_stage_->Trim(time_until(zx::msec(5)));
RunLoopUntilIdle();
ASSERT_TRUE(packet1_released && !packet2_released);
// After 9ms we should still be retaining packet2.
mix_stage_->Trim(time_until(zx::msec(9)));
RunLoopUntilIdle();
ASSERT_FALSE(packet2_released);
// Finally after 10ms we will have released packet2.
mix_stage_->Trim(time_until(zx::msec(10)));
RunLoopUntilIdle();
ASSERT_TRUE(packet2_released);
}
TEST_F(MixStageTest, MixUniformFormats) {
// Set timeline rate to match our format.
auto timeline_function = fbl::MakeRefCounted<VersionedTimelineFunction>(TimelineFunction(
TimelineRate(FractionalFrames<uint32_t>(kDefaultFormat.frames_per_second()).raw_value(),
zx::sec(1).to_nsecs())));
// Create 2 packet queues that we will mix together.
auto packet_queue1 = std::make_shared<PacketQueue>(kDefaultFormat, timeline_function);
auto packet_queue2 = std::make_shared<PacketQueue>(kDefaultFormat, timeline_function);
mix_stage_->AddInput(packet_queue1);
mix_stage_->AddInput(packet_queue2);
// Mix 2 packet queues with the following samples and expected outputs. We'll feed this data
// though the mix stage in 3 passes of 2ms windows:
//
// -----------------------------------
// q1 | 0.1 | 0.2 | 0.2 | 0.3 | 0.3 | 0.3 |
// -----------------------------------
// q2 | 0.7 | 0.7 | 0.7 | 0.5 | 0.5 | 0.3 |
// -----------------------------------
// mix | 0.8 | 0.9 | 0.9 | 0.8 | 0.8 | 0.6 |
// -----------------------------------
// pass | 1 | 2 | 3 |
// -----------------------------------
testing::PacketFactory packet_factory1(dispatcher(), kDefaultFormat, PAGE_SIZE);
{
packet_queue1->PushPacket(packet_factory1.CreatePacket(0.1, zx::msec(1)));
packet_queue1->PushPacket(packet_factory1.CreatePacket(0.2, zx::msec(2)));
packet_queue1->PushPacket(packet_factory1.CreatePacket(0.3, zx::msec(3)));
}
testing::PacketFactory packet_factory2(dispatcher(), kDefaultFormat, PAGE_SIZE);
{
packet_queue2->PushPacket(packet_factory2.CreatePacket(0.7, zx::msec(3)));
packet_queue2->PushPacket(packet_factory2.CreatePacket(0.5, zx::msec(2)));
packet_queue2->PushPacket(packet_factory2.CreatePacket(0.3, zx::msec(1)));
}
int64_t output_frame_start = 0;
uint32_t output_frame_count = 96;
{ // Mix frames 0-2ms. Expect the first 1ms to be 0.8 samples and the second ms to be 0.9
// samples.
auto buf =
mix_stage_->LockBuffer(time_until(zx::msec(2)), output_frame_start, output_frame_count);
// 1ms @ 48000hz == 48 frames. 2ms == 96 (frames).
ASSERT_TRUE(buf);
ASSERT_EQ(buf->length().Floor(), 96u);
// Each frame is 2 channels, so 1ms will be 96 samples.
auto& arr1 = as_array<float, 96>(buf->payload(), 0);
EXPECT_THAT(arr1, Each(FloatEq(0.8f)));
auto& arr2 = as_array<float, 96>(buf->payload(), 96);
EXPECT_THAT(arr2, Each(FloatEq(0.9f)));
mix_stage_->UnlockBuffer(true);
}
output_frame_start += output_frame_count;
{ // Mix frames 2-4ms. Expect the first 1ms to be 0.9 samples and the second ms to be 0.8
// samples.
auto buf =
mix_stage_->LockBuffer(time_until(zx::msec(4)), output_frame_start, output_frame_count);
ASSERT_TRUE(buf);
ASSERT_EQ(buf->length().Floor(), 96u);
auto& arr1 = as_array<float, 96>(buf->payload(), 0);
EXPECT_THAT(arr1, Each(FloatEq(0.9f)));
auto& arr2 = as_array<float, 96>(buf->payload(), 96);
EXPECT_THAT(arr2, Each(FloatEq(0.8f)));
mix_stage_->UnlockBuffer(true);
}
output_frame_start += output_frame_count;
{ // Mix frames 4-6ms. Expect the first 1ms to be 0.8 samples and the second ms to be 0.6
// samples.
auto buf =
mix_stage_->LockBuffer(time_until(zx::msec(6)), output_frame_start, output_frame_count);
ASSERT_TRUE(buf);
ASSERT_EQ(buf->length().Floor(), 96u);
auto& arr1 = as_array<float, 96>(buf->payload(), 0);
EXPECT_THAT(arr1, Each(FloatEq(0.8f)));
auto& arr2 = as_array<float, 96>(buf->payload(), 96);
EXPECT_THAT(arr2, Each(FloatEq(0.6f)));
mix_stage_->UnlockBuffer(true);
}
}
TEST_F(MixStageTest, MixFromRingBuffersSinc) {
// Create a new RingBuffer and add it to our mix stage.
constexpr uint32_t kRingSizeFrames = 72;
// We explictly request a SincSampler here to get a non-trivial filter width.
auto ring_buffer_endpoints =
RingBuffer::AllocateSoftwareBuffer(kDefaultFormat, timeline_function_, kRingSizeFrames);
mix_stage_->AddInput(ring_buffer_endpoints.reader, Mixer::Resampler::WindowedSinc);
// Fill up the ring buffer with some non-empty samples so that we can observe these values in
// the mix output.
constexpr float kRingBufferSampleValue1 = 0.5;
constexpr float kRingBufferSampleValue2 = 0.7;
float* ring_buffer_samples = reinterpret_cast<float*>(ring_buffer_endpoints.writer->virt());
for (size_t sample = 0; sample < kRingSizeFrames; ++sample) {
ring_buffer_samples[sample] = kRingBufferSampleValue1;
ring_buffer_samples[kRingSizeFrames + sample] = kRingBufferSampleValue2;
}
// Read the ring in two halves, each has been assigned a different source value in the ring
// above.
constexpr uint32_t kRequestedFrames = kRingSizeFrames / 2;
{
auto buf = mix_stage_->LockBuffer(time_until(zx::msec(1)), 0, kRequestedFrames);
ASSERT_TRUE(buf);
ASSERT_EQ(buf->start().Floor(), 0u);
ASSERT_EQ(buf->length().Floor(), kRequestedFrames);
mix_stage_->UnlockBuffer(true);
auto& arr = as_array<float, kRequestedFrames>(buf->payload(), 0);
EXPECT_THAT(arr, Each(FloatEq(kRingBufferSampleValue1)));
}
{
auto buf = mix_stage_->LockBuffer(time_until(zx::msec(2)), kRequestedFrames, kRequestedFrames);
ASSERT_TRUE(buf);
ASSERT_EQ(buf->start().Floor(), kRequestedFrames);
ASSERT_EQ(buf->length().Floor(), kRequestedFrames);
mix_stage_->UnlockBuffer(true);
auto& arr = as_array<float, 2 * kRequestedFrames>(buf->payload(), 0);
EXPECT_THAT(arr, Each(FloatEq(kRingBufferSampleValue2)));
}
}
} // namespace
} // namespace media::audio