blob: 8a4539ef22985e63a6ef77cec14bddfe847f872a [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 <fuchsia/media/cpp/fidl.h>
#include <gtest/gtest.h>
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/media/audio/lib/wav/wav_reader.h"
#include "src/media/audio/lib/wav/wav_writer.h"
namespace media::audio {
namespace {
struct __PACKED RiffChunkHeader {
uint32_t four_cc;
uint32_t length = 0;
};
constexpr char kFileName[] = "/tmp/test.wav";
TEST(WavWriterTest, EmptyFileRiffChunkSize) {
files::DeletePath(kFileName, false);
WavWriter wav_writer;
wav_writer.Initialize(kFileName, fuchsia::media::AudioSampleFormat::SIGNED_16,
8, // channels
192000, // frame_rate
16 // bits_per_sample
);
wav_writer.Close();
// Read WAV header
std::vector<uint8_t> data;
ASSERT_TRUE(files::ReadFileToVector(kFileName, &data));
ASSERT_GE(data.size(), static_cast<size_t>(0));
int byte_offset = 0;
auto riff_header = reinterpret_cast<RiffChunkHeader*>(&data.data()[byte_offset]);
EXPECT_EQ(36u, riff_header->length) << "Riff chunk size is wrong";
}
TEST(WavWriterTest, NonEmptyFileRiffChunkSize) {
WavWriter wav_writer;
files::DeletePath(kFileName, false);
wav_writer.Initialize(kFileName, fuchsia::media::AudioSampleFormat::SIGNED_16,
5, // channels
96000, // frame_rate
16 // bits_per_sample
);
char buf[10];
wav_writer.Write(buf, 10);
wav_writer.Close();
// Read WAV header
std::vector<uint8_t> data;
ASSERT_TRUE(files::ReadFileToVector(kFileName, &data));
ASSERT_GE(data.size(), static_cast<size_t>(0));
int byte_offset = 0;
auto riff_header = reinterpret_cast<RiffChunkHeader*>(&data.data()[byte_offset]);
EXPECT_EQ(46u, riff_header->length) << "Riff chunk size is wrong";
}
class WavReaderTest : public testing::Test {
protected:
// To test WavReader separately from WavWriter, this raw byte stream defines a PCMWAVEFORMAT file
// with 24 bytes of audio (3 bytes/sample). Set24BitFileAsPadded() alters the header info so that
// a file from these bytes contains identical data but is interpreted as 4 bytes/sample.
uint8_t k24BitFile[68]{
0x52, 0x49, 0x46, 0x46, // ---- 'RIFF' chunk
0x3c, 0x0, 0x0, 0x0, // 60 more bytes in this chunk (including RIFF type, 'fmt ', 'data')
0x57, 0x41, 0x56, 0x45, // 'WAVE' type of RIFF
0x66, 0x6d, 0x74, 0x20, // ---- 'fmt ' subchunk
0x10, 0x0, 0x0, 0x0, // 16 more bytes in this subchunk
0x1, 0x0, 0x1, 0x0, // format_tag 1 | num_channels 1
0x1, 0x0, 0x0, 0x0, // frame rate 1
0x3, 0x0, 0x0, 0x0, // avg bytes/sec 3
0x3, 0x0, 0x18, 0x0, // block_align 3 | bits_per_sample 24
0x64, 0x61, 0x74, 0x61, // ---- 'data' subchunk
0x18, 0x0, 0x0, 0x0, // 24 more bytes in this chunk (24 bytes of audio data)
0x01, 0x02, 0x03, 0x04, // -- (first bytes of audio data) --
0x05, 0x06, 0x07, 0x08, // RIFF files are little-endian (regardless of host endian-ness).
0x09, 0x0a, 0x0b, 0x0c, // Interpreted as 'packed-24', these (expanded) values span from
0x0d, 0x0e, 0x0f, 0x10, // an initial value of 0x03020100
0x11, 0x12, 0x13, 0x14, // to a final value of 0x18171600
0x15, 0x16, 0x17, 0x18, // -- (final bytes of audio data) --
};
// Transform the above byte stream of 8 samples of packed-24, into 6 samples of padded-24-in-32.
void Set24BitFileAsPadded() {
k24BitFile[28] = 4; // avg_bytes_per_sec 3 => 4
k24BitFile[32] = 4; // block_align (bytes per frame) 3 => 4
k24BitFile[34] = 32; // bits_per_sample 24 => 32
}
};
TEST_F(WavReaderTest, CanReadWrittenFile) {
const char kWant[] = "abcdefghij";
WavWriter writer;
files::DeletePath(kFileName, false);
writer.Initialize(kFileName, fuchsia::media::AudioSampleFormat::UNSIGNED_8,
2, // channels
12000, // frame_rate
8 // bits_per_sample
);
writer.Write((void*)kWant, strlen(kWant));
writer.Close();
// Read WAV header
auto open_result = WavReader::Open(kFileName);
ASSERT_TRUE(open_result.is_ok());
auto reader = std::move(open_result.value());
EXPECT_EQ(fuchsia::media::AudioSampleFormat::UNSIGNED_8, reader->sample_format());
EXPECT_EQ(reader->channel_count(), 2u);
EXPECT_EQ(reader->frame_rate(), 12000u);
EXPECT_EQ(reader->bits_per_sample(), 8u);
char buf[128];
auto read_bytes = reader->Read(static_cast<void*>(buf), sizeof(buf));
ASSERT_TRUE(read_bytes.is_ok()) << read_bytes.error();
EXPECT_EQ(static_cast<size_t>(strlen(kWant)), read_bytes.value());
std::string got(buf, read_bytes.value());
EXPECT_STREQ(kWant, got.c_str());
}
TEST_F(WavReaderTest, CanResetAndRereadWrittenFile) {
const char kWant[] = "abcdefghijkl";
char buf[128];
// Create the test file
WavWriter writer;
files::DeletePath(kFileName, false);
writer.Initialize(kFileName, fuchsia::media::AudioSampleFormat::UNSIGNED_8,
1, // channels
32000, // frame_rate
8 // bits_per_sample
);
writer.Write((void*)kWant, strlen(kWant));
writer.Close();
// Read WAV header and the entire contents
auto open_result = WavReader::Open(kFileName);
ASSERT_TRUE(open_result.is_ok());
auto reader = std::move(open_result.value());
auto read_bytes = reader->Read(static_cast<void*>(buf), sizeof(buf));
ASSERT_TRUE(read_bytes.is_ok()) << read_bytes.error();
ASSERT_EQ(static_cast<size_t>(strlen(kWant)), read_bytes.value());
std::string got(buf, read_bytes.value());
ASSERT_STREQ(kWant, got.c_str());
// Ensure that once we reach the end of the file, Read returns 0 and no error.
auto end_of_file = reader->Read(static_cast<void*>(buf), sizeof(buf));
ASSERT_TRUE(end_of_file.is_ok()) << end_of_file.error();
EXPECT_EQ(0u, end_of_file.value());
// Reset should not fail.
auto status = reader->Reset();
EXPECT_EQ(status, 0);
// Reset should seek the file read position to right after the header (same as first time).
read_bytes = reader->Read(static_cast<void*>(buf), sizeof(buf));
ASSERT_TRUE(read_bytes.is_ok()) << read_bytes.error();
EXPECT_EQ(static_cast<size_t>(strlen(kWant)), read_bytes.value());
std::string got2(buf, read_bytes.value());
EXPECT_STREQ(kWant, got2.c_str());
}
TEST_F(WavReaderTest, CanReadPacked24File) {
WavWriter writer;
files::DeletePath(kFileName, false);
files::WriteFile(kFileName, reinterpret_cast<const char*>(k24BitFile), sizeof(k24BitFile));
auto open_result = WavReader::Open(kFileName);
ASSERT_TRUE(open_result.is_ok());
auto reader = std::move(open_result.value());
const std::array<int32_t, 8> kExpect{0x03020100, 0x06050400, 0x09080700, 0x0c0b0a00,
0x0f0e0d00, 0x12111000, 0x15141300, 0x18171600};
int32_t data_read[64];
auto read_bytes = reader->Read(static_cast<void*>(data_read), sizeof(data_read));
ASSERT_TRUE(read_bytes.is_ok()) << read_bytes.error();
EXPECT_EQ(read_bytes.value(), kExpect.size() * sizeof(kExpect[0]));
for (auto idx = 0u; idx < kExpect.size(); ++idx) {
EXPECT_EQ(data_read[idx], kExpect[idx])
<< "[" << idx << "] got " << std::hex << data_read[idx] << ", wanted " << kExpect[idx];
}
}
TEST_F(WavReaderTest, CanReadPadded24File) {
Set24BitFileAsPadded();
WavWriter writer;
files::DeletePath(kFileName, false);
files::WriteFile(kFileName, reinterpret_cast<const char*>(k24BitFile), sizeof(k24BitFile));
auto open_result = WavReader::Open(kFileName);
ASSERT_TRUE(open_result.is_ok());
auto reader = std::move(open_result.value());
const std::array<int32_t, 6> kExpect{0x04030201, 0x08070605, 0x0c0b0a09,
0x100f0e0d, 0x14131211, 0x18171615};
int32_t data_read[64];
auto read_bytes = reader->Read(static_cast<void*>(data_read), sizeof(data_read));
ASSERT_TRUE(read_bytes.is_ok()) << read_bytes.error();
EXPECT_EQ(read_bytes.value(), kExpect.size() * sizeof(kExpect[0]));
for (auto idx = 0u; idx < kExpect.size(); ++idx) {
EXPECT_EQ(data_read[idx], kExpect[idx])
<< "[" << idx << "] got " << std::hex << data_read[idx] << ", wanted " << kExpect[idx];
}
}
struct Format {
Format() {}
Format(fuchsia::media::AudioSampleFormat format, int32_t rate, int32_t f_size, int32_t s_size)
: sample_format(format),
frame_rate(rate),
file_sample_size(f_size),
stream_sample_size(s_size) {}
fuchsia::media::AudioSampleFormat sample_format;
int32_t frame_rate;
int32_t file_sample_size;
int32_t stream_sample_size;
};
class WavWriterReaderTest : public testing::TestWithParam<std::tuple<Format, int32_t>> {};
TEST_P(WavWriterReaderTest, FormatSpecifics) {
Format format = std::get<0>(GetParam());
int32_t num_channels = std::get<1>(GetParam());
constexpr int64_t kDataSize = 24;
constexpr char kFileContent[kDataSize + 1] = "abcdefghijklmnopqrstuvwx";
WavWriter writer;
files::DeletePath(kFileName, false);
// Create the test file
ASSERT_TRUE(writer.Initialize(kFileName, format.sample_format,
static_cast<uint16_t>(num_channels), format.frame_rate,
static_cast<uint16_t>(format.file_sample_size * 8)));
for (auto chan = 1; chan <= num_channels; ++chan) {
// write out the same amount of file content for each channel
ASSERT_TRUE(writer.Write((void*)kFileContent, strlen(kFileContent)));
}
ASSERT_TRUE(writer.Close());
// Read WAV header and the entire contents.
// When testing 24-bit file writing and reading (both "packed" and "padded") with the
// WavWriter/Reader, we convey data both directions as "padded" 24-in-32-bit samples.
// Although we tell WavWriter to use 24-bit, or 32-bit samples (in the FILE it saves),
// WavReader will always tell us that the audio is 32-bit data (in the STREAM it produces).
// To verify WavReader, we check the byte-count (did all data get read in) and the frame-count
// (does WavReader correctly interpret the in-file packed/padded frame size).
auto open_result = WavReader::Open(kFileName);
ASSERT_TRUE(open_result.is_ok()) << "sample_format " << static_cast<int>(format.sample_format)
<< ", bits " << format.file_sample_size * 8 << ", rate "
<< format.frame_rate << ", chans " << num_channels;
auto reader = std::move(open_result.value());
EXPECT_EQ(reader->sample_format(), format.sample_format);
EXPECT_EQ(static_cast<int32_t>(reader->bits_per_sample()), format.stream_sample_size * 8);
EXPECT_EQ(static_cast<int32_t>(reader->frame_rate()), format.frame_rate);
EXPECT_EQ(static_cast<int>(reader->channel_count()), num_channels);
EXPECT_EQ(static_cast<int64_t>(reader->length_in_bytes()), kDataSize * num_channels);
EXPECT_EQ(static_cast<int64_t>(reader->length_in_frames()),
kDataSize / format.stream_sample_size);
EXPECT_TRUE(writer.Delete());
}
std::array<Format, 5> formats{
Format(fuchsia::media::AudioSampleFormat::FLOAT, 48000, 4, 4),
Format(fuchsia::media::AudioSampleFormat::SIGNED_16, 96000, 2, 2),
Format(fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32, 16000, 3, 4),
Format(fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32, 192000, 4, 4),
Format(fuchsia::media::AudioSampleFormat::UNSIGNED_8, 44100, 1, 1),
};
INSTANTIATE_TEST_SUITE_P(null, WavWriterReaderTest,
testing::Combine(testing::ValuesIn(formats), testing::Range(1, 9)),
[](const testing::TestParamInfo<WavWriterReaderTest::ParamType>& info) {
std::string name;
switch (std::get<0>(info.param).sample_format) {
case fuchsia::media::AudioSampleFormat::UNSIGNED_8:
name = "Uint8";
break;
case fuchsia::media::AudioSampleFormat::SIGNED_16:
name = "Int16";
break;
case fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32:
name = (std::get<0>(info.param).file_sample_size == 3) ? "Packed24"
: "Padded24";
break;
case fuchsia::media::AudioSampleFormat::FLOAT:
name = "Float32";
break;
}
name += "_" + std::to_string(std::get<1>(info.param)) + "chan";
return name;
});
} // namespace
} // namespace media::audio