Skip to content

Commit

Permalink
Hold output buffer at ResamplerBase.
Browse files Browse the repository at this point in the history
  - `ResamplerBase`:
    - Fed at most one frame at a time. We can calculate at initialization the maximum output frames.
    - Ala cl/703469056 we can pre allocated the buffer.
    - Add some tests now that it is not pure abstract.
  - `ResamplerQWrapper`:
    - Update to new interface. Use `ResamblerBase` buffer instead of being passed in.
    - Switch to a factory function, so we can seed `ResamplerBase` with an accurate maximum output frames.
    - Add a factory function argument, which represents the number of samples per frame in the input codec.
    - Use `audio_dsp::MaxOutputFrames` based on the new argument to determine the maximum number of frames in the output codec (to seed the new held buffer).
  - `codec_transcoder_test`:
    - Some tests were testing both the mock receiving the argument, and the transcoder using it. Split these into a mock test to receive, and a simple implementation to check the output works.

PiperOrigin-RevId: 704802974
  • Loading branch information
jwcullen committed Dec 12, 2024
1 parent b22c17b commit 12e8aeb
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 16 deletions.
54 changes: 38 additions & 16 deletions iamf/cli/resampler_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#ifndef CLI_RESAMPLER_BASE_H_
#define CLI_RESAMPLER_BASE_H_

#include <cstddef>
#include <cstdint>
#include <vector>

Expand All @@ -31,41 +32,62 @@ namespace iamf_tools {
* underlying IAMF codec.
*
* Usage pattern:
* - Repeatedly call `PushFrame()` to push in samples. Available samples will
* be located via the output argument.
* - While input samples are available:
* - Call `PushFrame()` to push in samples
* - Call `GetOutputSamplesAsSpan()` to retrieve the samples.
* - Call `Flush()` to signal that no more frames will be pushed.
* - Call `GetOutputSamplesAsSpan()` one last time to retrieve any remaining
* samples.
*
* - After all samples have been pushed it is RECOMMENDED to call `Flush()` to
* retrieve any remaining ticks. Available samples will be located via the
* output argument.
* - It is safe and encouraged to reuse the buffers between calls.
* Implementations are free to clear and resize the buffers as needed.
* - Note: Results from `GetOutputSamplesAsSpan()` must always be used before
* further calls to `PushFrame()` or `Flush()`.
*/
// TODO(b/382257677): Return an error, or at least test that it is safe, to push
// further frames after `Flush()` calls.
class ResamplerBase {
public:
/*!\brief Constructor.
*
* \param max_output_ticks Maximum number of ticks in the output timescale.
* \param num_channels Number of channels.
*/
ResamplerBase(size_t max_output_ticks, size_t num_channels)
: output_time_channel_samples_(max_output_ticks,
std::vector<int32_t>(num_channels)) {}

/*!\brief Destructor. */
virtual ~ResamplerBase() = 0;

/*!\brief Pushes a frame of samples to the resampler.
*
* \param time_channel_samples Samples to push arranged in (time, channel).
* \param output_time_channel_samples Output samples arranged in (time,
* channel).
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
virtual absl::Status PushFrame(
absl::Span<const std::vector<int32_t>> time_channel_samples,
std::vector<std::vector<int32_t>>& output_time_channel_samples) = 0;
absl::Span<const std::vector<int32_t>> time_channel_samples) = 0;

/*!\brief Signals to close the resampler and flush any remaining samples.
*
* It is bad practice to reuse the resampler after calling this function.
* After calling `Flush()`, it is implementation-defined to call `PushFrame()`
* or `Flush()` again.
*
* \param output_time_channel_samples Output samples arranged in (time,
* channel).
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
virtual absl::Status Flush(
std::vector<std::vector<int32_t>>& output_time_channel_samples) = 0;
virtual absl::Status Flush() = 0;

/*!\brief Gets a span of the output samples.
*
* \return Span of the output samples. The span will be invalidated when
* `PushFrame()` or `Flush()` is called.
*/
absl::Span<const std::vector<int32_t>> GetOutputSamplesAsSpan() const {
return absl::MakeConstSpan(output_time_channel_samples_)
.first(num_valid_ticks_);
}

protected:
std::vector<std::vector<int32_t>> output_time_channel_samples_;
size_t num_valid_ticks_ = 0;
};

} // namespace iamf_tools
Expand Down
14 changes: 14 additions & 0 deletions iamf/cli/tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ cc_library(
"@com_google_absl//absl/types:span",
"//iamf/cli:audio_element_with_data",
"//iamf/cli:demixing_module",
"//iamf/cli:resampler_base",
"//iamf/cli:wav_reader",
"//iamf/cli/proto:mix_presentation_cc_proto",
"//iamf/cli/proto:user_metadata_cc_proto",
Expand Down Expand Up @@ -397,6 +398,19 @@ cc_test(
],
)

cc_test(
name = "resampler_base_test",
srcs = ["resampler_base_test.cc"],
deps = [
":cli_test_utils",
"//iamf/cli:resampler_base",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:status_matchers",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "textproto_templates_test",
srcs = ["textproto_templates_test.cc"],
Expand Down
18 changes: 18 additions & 0 deletions iamf/cli/tests/cli_test_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
Expand Down Expand Up @@ -533,4 +534,21 @@ absl::Status ReadFileToBytes(const std::filesystem::path& file_path,
return absl::OkStatus();
}

absl::Status EverySecondTickResampler::PushFrame(
absl::Span<const std::vector<int32_t>> time_channel_samples) {
num_valid_ticks_ = 0;
for (size_t i = 0; i < time_channel_samples.size(); ++i) {
if (i % 2 == 1) {
output_time_channel_samples_[num_valid_ticks_] = time_channel_samples[i];
++num_valid_ticks_;
}
}
return absl::OkStatus();
}

absl::Status EverySecondTickResampler::Flush() {
num_valid_ticks_ = 0;
return absl::OkStatus();
}

} // namespace iamf_tools
39 changes: 39 additions & 0 deletions iamf/cli/tests/cli_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define CLI_TESTS_CLI_TEST_UTILS_H_

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <list>
Expand All @@ -30,6 +31,7 @@
#include "iamf/cli/demixing_module.h"
#include "iamf/cli/proto/user_metadata.pb.h"
#include "iamf/cli/renderer/audio_element_renderer_base.h"
#include "iamf/cli/resampler_base.h"
#include "iamf/cli/wav_reader.h"
#include "iamf/common/obu_util.h"
#include "iamf/obu/audio_element.h"
Expand Down Expand Up @@ -345,6 +347,43 @@ MATCHER(InternalSampleMatchesIntegralSample, "") {
equivalent_integral_sample == testing::get<1>(arg);
}

class MockResampler : public ResamplerBase {
public:
MockResampler(uint32_t max_num_samples_per_frame, size_t num_channels)
: ResamplerBase(max_num_samples_per_frame, num_channels) {}

MOCK_METHOD(absl::Status, PushFrame,
(absl::Span<const std::vector<int32_t>> time_channel_samples),
(override));

MOCK_METHOD(absl::Status, Flush, (), (override));
};

/*!\brief A simple resampler whose output represents every second tick.
*/
class EverySecondTickResampler : public ResamplerBase {
public:
EverySecondTickResampler(uint32_t max_num_samples_per_frame,
size_t num_channels)
: ResamplerBase(max_num_samples_per_frame / 2, num_channels) {}

/*!\brief Pushes a frame of samples to the resampler.
*
* \param time_channel_samples Samples to push arranged in (time, channel).
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
absl::Status PushFrame(
absl::Span<const std::vector<int32_t>> time_channel_samples) override;

/*!\brief Signals to close the resampler and flush any remaining samples.
*
* It is bad practice to reuse the resampler after calling this function.
*
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
absl::Status Flush() override;
};

} // namespace iamf_tools

#endif // CLI_TESTS_CLI_TEST_UTILS_H_
68 changes: 68 additions & 0 deletions iamf/cli/tests/resampler_base_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2024, Alliance for Open Media. All rights reserved
*
* This source code is subject to the terms of the BSD 3-Clause Clear License
* and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
* License was not distributed with this source code in the LICENSE file, you
* can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
* Alliance for Open Media Patent License 1.0 was not distributed with this
* source code in the PATENTS file, you can obtain it at
* www.aomedia.org/license/patent.
*/

#include "iamf/cli/resampler_base.h"

#include <cstddef>
#include <cstdint>
#include <vector>

#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/types/span.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "iamf/cli/tests/cli_test_utils.h"

namespace iamf_tools {
namespace {

using ::absl_testing::IsOk;

constexpr uint32_t kMaxNumSamplesPerFrame = 4;
constexpr size_t kNumChannels = 2;

class MockResampler : public ResamplerBase {
public:
MockResampler(uint32_t max_num_samples_per_frame, size_t num_channels)
: ResamplerBase(max_num_samples_per_frame, num_channels) {}

MOCK_METHOD(absl::Status, PushFrame,
(absl::Span<const std::vector<int32_t>> time_channel_samples),
(override));

MOCK_METHOD(absl::Status, Flush, (), (override));
};

TEST(GetOutputSamplesAsSpan, ReturnsEmptyAfterConstruction) {
MockResampler mock_resampler(kMaxNumSamplesPerFrame, kNumChannels);
EXPECT_TRUE(mock_resampler.GetOutputSamplesAsSpan().empty());
}

TEST(GetOutputSamplesAsSpan, SizeMatchesNumValidTicks) {
EverySecondTickResampler every_second_tick_resampler(kMaxNumSamplesPerFrame,
kNumChannels);
EXPECT_THAT(
every_second_tick_resampler.PushFrame({{1, 2}, {3, 4}, {5, 6}, {7, 8}}),
IsOk());
EXPECT_EQ(every_second_tick_resampler.GetOutputSamplesAsSpan().size(), 2);

EXPECT_THAT(every_second_tick_resampler.PushFrame({{9, 10}, {11, 12}}),
IsOk());
EXPECT_EQ(every_second_tick_resampler.GetOutputSamplesAsSpan().size(), 1);

EXPECT_THAT(every_second_tick_resampler.Flush(), IsOk());
EXPECT_TRUE(every_second_tick_resampler.GetOutputSamplesAsSpan().empty());
}

} // namespace
} // namespace iamf_tools

0 comments on commit 12e8aeb

Please sign in to comment.