blob: 6e9ce1b08ee62e697003f2f8babf484400b634c0 [file] [log] [blame]
/*
* Copyright (c) 2023, 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/wav_reader.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <ios>
#include <string>
#include <utility>
#include <vector>
// [internal] Placeholder for get runfiles header.
#include "absl/status/status_matchers.h"
#include "absl/strings/string_view.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 size_t kArbitraryNumSamplesPerFrame = 1;
constexpr absl::string_view kAdmBwfWithOneStereoAndOneMonoObject(
"RIFF"
"\xf5\x00\x00\x00" // Size of `RIFF` chunk (the whole file).
"WAVE"
"fmt "
"\x10\x00\x00\x00" // Size of the `fmt ` chunk.
"\x01\x00" // Format tag.
"\x03\x00" // Number of channels.
"\x80\xbb\x00\x00" // Sample per second
"\x00\x65\x04\x00" // Bytes per second = [number_of_channels *
// ceil(bits_per_sample / 8) * sample_per_second].
"\x06\x00" // Block align = [number of channels * bits per sample]
"\x10\x00" // Bits per sample.
"data"
"\x0c\x00\x00\x00" // Size of `data` chunk.
"\x01\x23" // Sample[0] for object[0] L.
"\x45\x67" // Sample[0] for object[0] R.
"\xaa\xbb" // Sample[0] for object[1] M.
"\x89\xab" // Sample[1] for object[0] L.
"\xcd\xef" // Sample[1] for object[0] R.
"\xcc\xdd" // Sample[1] for object[1] M.
"axml"
"\xbd\x00\x00\x00" // Size of `axml` chunk.
"<topLevel>"
"<audioObject>"
"<audioTrackUIDRef>L</audioTrackUIDRef>"
"<audioTrackUIDRef>R</audioTrackUIDRef>"
"</audioObject>"
"<audioObject>"
"<audioTrackUIDRef>M</audioTrackUIDRef>"
"</audioObject>"
"</topLevel>",
253);
void CreateFileFromStringView(absl::string_view file_contents,
absl::string_view file_path_suffix,
std::string& file_name) {
file_name = GetAndCleanupOutputFileName(file_path_suffix);
std::ofstream file_stream(file_name, std::ios::binary | std::ios::out);
file_stream << file_contents;
file_stream.close();
ASSERT_TRUE(std::filesystem::exists(file_name));
}
TEST(CreateFromFile, SucceedsOnValidAdmFile) {
std::string adm_file_name;
CreateFileFromStringView(kAdmBwfWithOneStereoAndOneMonoObject, ".adm",
adm_file_name);
EXPECT_THAT(
WavReader::CreateFromFile(adm_file_name, kArbitraryNumSamplesPerFrame),
IsOk());
}
TEST(CreateFromFile, SucceedsOnValidWavFile) {
const auto input_wav_file =
(std::filesystem::current_path() / std::string("iamf/cli/testdata/") /
"stereo_8_samples_48khz_s16le.wav")
.string();
ASSERT_TRUE(std::filesystem::exists(input_wav_file));
EXPECT_THAT(
WavReader::CreateFromFile(input_wav_file, kArbitraryNumSamplesPerFrame),
IsOk());
}
TEST(CreateFromFile, FailsWhenNumSamplesPerFrameIsZero) {
const size_t kInvalidNumSamplesPerFrame = 0;
const auto input_wav_file =
(std::filesystem::current_path() / std::string("iamf/cli/testdata/") /
"stereo_8_samples_48khz_s16le.wav")
.string();
ASSERT_TRUE(std::filesystem::exists(input_wav_file));
EXPECT_FALSE(
WavReader::CreateFromFile(input_wav_file, kInvalidNumSamplesPerFrame)
.ok());
}
TEST(CreateFromFile, FailsOnMissingFile) {
const std::string non_existent_file(GetAndCleanupOutputFileName(".wav"));
ASSERT_FALSE(std::filesystem::exists(non_existent_file));
EXPECT_FALSE(
WavReader::CreateFromFile(non_existent_file, kArbitraryNumSamplesPerFrame)
.ok());
}
TEST(CreateFromFile, FailsOnNonWavFile) {
const std::string non_wav_file(GetAndCleanupOutputFileName(".txt"));
std::ofstream(non_wav_file, std::ios::binary | std::ios::out)
<< "This is not a wav file.";
ASSERT_TRUE(std::filesystem::exists(non_wav_file));
EXPECT_FALSE(
WavReader::CreateFromFile(non_wav_file, kArbitraryNumSamplesPerFrame)
.ok());
}
WavReader InitAndValidate(const std::filesystem::path& filename,
const size_t num_samples_per_frame) {
const auto input_wav_file = (std::filesystem::current_path() /
std::string("iamf/cli/testdata/") / filename)
.string();
auto wav_reader =
WavReader::CreateFromFile(input_wav_file, num_samples_per_frame);
EXPECT_THAT(wav_reader, IsOk());
// Validate `wav_reader` sees the expected properties from the wav header.
EXPECT_EQ(wav_reader->num_samples_per_frame_, num_samples_per_frame);
return std::move(*wav_reader);
}
TEST(WavReader, GetNumChannelsMatchesWavFile) {
EXPECT_EQ(InitAndValidate("stereo_8_samples_48khz_s16le.wav",
kArbitraryNumSamplesPerFrame)
.num_channels(),
2);
EXPECT_EQ(InitAndValidate("stereo_8_samples_48khz_s24le.wav",
kArbitraryNumSamplesPerFrame)
.num_channels(),
2);
EXPECT_EQ(InitAndValidate("sine_1000_16khz_512ms_s32le.wav",
kArbitraryNumSamplesPerFrame)
.num_channels(),
1);
}
TEST(WavReader, GetNumChannelsMatchesAdmFile) {
auto adm_file_name = GetAndCleanupOutputFileName(".adm");
CreateFileFromStringView(kAdmBwfWithOneStereoAndOneMonoObject, ".adm",
adm_file_name);
EXPECT_EQ(InitAndValidate(adm_file_name, kArbitraryNumSamplesPerFrame)
.num_channels(),
3);
}
TEST(WavReader, GetSampleRateHzMatchesWavFile) {
const size_t kNumSamplesPerFrame = 8;
EXPECT_EQ(
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame)
.sample_rate_hz(),
48000);
EXPECT_EQ(
InitAndValidate("stereo_8_samples_48khz_s24le.wav", kNumSamplesPerFrame)
.sample_rate_hz(),
48000);
EXPECT_EQ(
InitAndValidate("sine_1000_16khz_512ms_s32le.wav", kNumSamplesPerFrame)
.sample_rate_hz(),
16000);
}
TEST(WavReader, GetSampleRateHzMatchesAdmFile) {
auto adm_file_name = GetAndCleanupOutputFileName(".adm");
CreateFileFromStringView(kAdmBwfWithOneStereoAndOneMonoObject, ".adm",
adm_file_name);
EXPECT_EQ(InitAndValidate(adm_file_name, kArbitraryNumSamplesPerFrame)
.sample_rate_hz(),
48000);
}
TEST(WavReader, GetBitDepthMatchesWavFile) {
const size_t kNumSamplesPerFrame = 8;
EXPECT_EQ(
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame)
.bit_depth(),
16);
EXPECT_EQ(
InitAndValidate("stereo_8_samples_48khz_s24le.wav", kNumSamplesPerFrame)
.bit_depth(),
24);
EXPECT_EQ(
InitAndValidate("sine_1000_16khz_512ms_s32le.wav", kNumSamplesPerFrame)
.bit_depth(),
32);
}
TEST(WavReader, GetBitDepthMatchesAdmFile) {
auto adm_file_name = GetAndCleanupOutputFileName(".adm");
CreateFileFromStringView(kAdmBwfWithOneStereoAndOneMonoObject, ".adm",
adm_file_name);
EXPECT_EQ(
InitAndValidate(adm_file_name, kArbitraryNumSamplesPerFrame).bit_depth(),
16);
}
TEST(WavReader, GetNumRemainingSamplesUpdatesWithRead) {
// Read four samples x two channels per frame.
const size_t kNumSamplesPerFrame = 4;
auto wav_reader =
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame);
EXPECT_EQ(wav_reader.remaining_samples(), 16);
wav_reader.ReadFrame();
EXPECT_EQ(wav_reader.remaining_samples(), 8);
wav_reader.ReadFrame();
EXPECT_EQ(wav_reader.remaining_samples(), 0);
}
TEST(WavReader, GetNumRemainingSamplesMatchesUpdatesWithReadForAdm) {
// Read one sample x three channels per frame.
const size_t kNumSamplesPerFrame = 1;
auto adm_file_name = GetAndCleanupOutputFileName(".adm");
CreateFileFromStringView(kAdmBwfWithOneStereoAndOneMonoObject, ".adm",
adm_file_name);
auto wav_reader = InitAndValidate(adm_file_name, kNumSamplesPerFrame);
EXPECT_EQ(wav_reader.remaining_samples(), 6);
wav_reader.ReadFrame();
EXPECT_EQ(wav_reader.remaining_samples(), 3);
wav_reader.ReadFrame();
EXPECT_EQ(wav_reader.remaining_samples(), 0);
}
TEST(WavReader, OneFrame16BitLittleEndian) {
const size_t kNumSamplesPerFrame = 8;
auto wav_reader =
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame);
// Read one frame. The result of n-bit samples are stored in the upper `n`
// bits.
EXPECT_EQ(wav_reader.ReadFrame(), 16);
std::vector<std::vector<int32_t>> expected_frame = {
{0x00010000, static_cast<int32_t>(0xffff0000)},
{0x00020000, static_cast<int32_t>(0xfffe0000)},
{0x00030000, static_cast<int32_t>(0xfffd0000)},
{0x00040000, static_cast<int32_t>(0xfffc0000)},
{0x00050000, static_cast<int32_t>(0xfffb0000)},
{0x00060000, static_cast<int32_t>(0xfffa0000)},
{0x00070000, static_cast<int32_t>(0xfff90000)},
{0x00080000, static_cast<int32_t>(0xfff80000)},
};
EXPECT_EQ(wav_reader.buffers_, expected_frame);
}
TEST(WavReader, TwoFrames16BitLittleEndian) {
const size_t kNumSamplesPerFrame = 4;
auto wav_reader =
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame);
EXPECT_EQ(wav_reader.ReadFrame(), 8);
std::vector<std::vector<int32_t>> expected_frame = {
{0x00010000, static_cast<int32_t>(0xffff0000)},
{0x00020000, static_cast<int32_t>(0xfffe0000)},
{0x00030000, static_cast<int32_t>(0xfffd0000)},
{0x00040000, static_cast<int32_t>(0xfffc0000)},
};
EXPECT_EQ(wav_reader.buffers_, expected_frame);
expected_frame = {{0x00050000, static_cast<int32_t>(0xfffb0000)},
{0x00060000, static_cast<int32_t>(0xfffa0000)},
{0x00070000, static_cast<int32_t>(0xfff90000)},
{0x00080000, static_cast<int32_t>(0xfff80000)}};
wav_reader.ReadFrame();
EXPECT_EQ(wav_reader.buffers_, expected_frame);
}
TEST(WavReader, OneFrame24BitLittleEndian) {
const size_t kNumSamplesPerFrame = 2;
auto wav_reader =
InitAndValidate("stereo_8_samples_48khz_s24le.wav", kNumSamplesPerFrame);
EXPECT_EQ(wav_reader.ReadFrame(), 4);
std::vector<std::vector<int32_t>> expected_frame = {
{0x00000100, static_cast<int32_t>(0xffffff00)},
{0x00000200, static_cast<int32_t>(0xfffffe00)},
};
EXPECT_EQ(wav_reader.buffers_, expected_frame);
}
TEST(WavReader, OneFrame32BitLittleEndian) {
const size_t kNumSamplesPerFrame = 8;
auto wav_reader =
InitAndValidate("sine_1000_16khz_512ms_s32le.wav", kNumSamplesPerFrame);
EXPECT_EQ(wav_reader.ReadFrame(), 8);
std::vector<std::vector<int32_t>> expected_frame = {
{0}, {82180641}, {151850024}, {198401618},
{214748364}, {198401618}, {151850024}, {82180641},
};
EXPECT_EQ(wav_reader.buffers_, expected_frame);
}
TEST(WavReader, OneFrameAdm) {
const size_t kNumSamplesPerFrame = 1;
auto adm_file_name = GetAndCleanupOutputFileName(".adm");
CreateFileFromStringView(kAdmBwfWithOneStereoAndOneMonoObject, ".adm",
adm_file_name);
auto wav_reader = InitAndValidate(adm_file_name, kNumSamplesPerFrame);
// Read one frame. The result of n-bit samples are stored in the upper `n`
// bits.
EXPECT_EQ(wav_reader.ReadFrame(), 3);
const std::vector<std::vector<int32_t>> kExpectedFrame = {
{0x23010000, 0x67450000, static_cast<int32_t>(0xbbaa0000)}};
EXPECT_EQ(wav_reader.buffers_, kExpectedFrame);
}
TEST(WavReader, IsSafeToCallReadFrameAfterMove) {
const size_t kNumSamplesPerFrame = 1;
auto wav_reader =
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame);
auto wav_reader_moved = std::move(wav_reader);
EXPECT_EQ(wav_reader_moved.ReadFrame(), 2);
const std::vector<std::vector<int32_t>> kExpectedFrame = {
{0x00010000, static_cast<int32_t>(0xffff0000)}};
EXPECT_EQ(wav_reader_moved.buffers_, kExpectedFrame);
}
template <typename T>
std::vector<uint8_t> GetRawBytes(const T& object) {
std::vector<uint8_t> raw_object(sizeof(object));
std::memcpy(raw_object.data(), static_cast<const void*>(&object),
raw_object.size());
return raw_object;
}
TEST(WavReader, IsByteEquivalentAfterMoving) {
const size_t kNumSamplesPerFrame = 1;
auto wav_reader =
InitAndValidate("stereo_8_samples_48khz_s16le.wav", kNumSamplesPerFrame);
const auto raw_wav_reader_before_move = GetRawBytes(wav_reader);
const auto wav_reader_moved = std::move(wav_reader);
const auto raw_wav_reader_after_move = GetRawBytes(wav_reader_moved);
EXPECT_EQ(raw_wav_reader_before_move, raw_wav_reader_after_move);
}
} // namespace
} // namespace iamf_tools