diff --git a/iamf/cli/audio_frame_decoder.cc b/iamf/cli/audio_frame_decoder.cc index 71b2b373..4c91b4b9 100644 --- a/iamf/cli/audio_frame_decoder.cc +++ b/iamf/cli/audio_frame_decoder.cc @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -23,6 +22,7 @@ #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" #include "iamf/cli/audio_element_with_data.h" #include "iamf/cli/audio_frame_with_data.h" #include "iamf/cli/cli_util.h" @@ -68,7 +68,7 @@ absl::Status InitializeDecoder(const CodecConfigObu& codec_config, } absl::Status InitializeWavWriters( - const std::string& output_wav_directory, const std::string& file_prefix, + absl::string_view output_wav_directory, absl::string_view file_prefix, const std::list& decoded_audio_frames, absl::node_hash_map& wav_writers) { for (const auto& decoded_audio_frame : decoded_audio_frames) { @@ -178,7 +178,7 @@ absl::Status WriteInterlacedSamplesToWav( // Dumps the interlaced `decoded_frames` field of the input // `decoded_audio_frames` to a WAV file per substream. absl::Status DumpDecodedAudioFramesWav( - const std::string& output_wav_directory, const std::string& file_prefix, + absl::string_view output_wav_directory, absl::string_view file_prefix, const std::list& decoded_audio_frames) { // Initialize all `WavWriter`s. absl::node_hash_map wav_writers; diff --git a/iamf/cli/audio_frame_decoder.h b/iamf/cli/audio_frame_decoder.h index c2949187..56777bc8 100644 --- a/iamf/cli/audio_frame_decoder.h +++ b/iamf/cli/audio_frame_decoder.h @@ -18,6 +18,7 @@ #include #include "absl/status/status.h" +#include "absl/strings/string_view.h" #include "iamf/cli/audio_element_with_data.h" #include "iamf/cli/audio_frame_with_data.h" @@ -39,6 +40,16 @@ struct DecodedAudioFrame { const AudioElementWithData* audio_element_with_data; }; +/*\!brief Decodes Audio Frame OBUs based on the associated codec. + * + * This class is related to the "Codec Decoder" as used in the IAMF + * specification. "The Codec Decoder for each Audio Substream outputs the + * decoded channels." + * + * This class manages the underlying codec decoders for all substreams. Codec + * decoders may be stateful; this class manages a one-to-one mapping between + * codec decoders and substream. + */ class AudioFrameDecoder { public: /*\!brief Constructor. @@ -46,8 +57,8 @@ class AudioFrameDecoder { * \param output_wav_directory Directory to write debugging wav files to. * \param file_prefix File name prefix for debugging files. */ - AudioFrameDecoder(const std::string& output_wav_directory, - const std::string& file_prefix) + AudioFrameDecoder(absl::string_view output_wav_directory, + absl::string_view file_prefix) : output_wav_directory_(output_wav_directory), file_prefix_(file_prefix) {} diff --git a/iamf/cli/tests/BUILD b/iamf/cli/tests/BUILD index e486071e..9879e4e7 100644 --- a/iamf/cli/tests/BUILD +++ b/iamf/cli/tests/BUILD @@ -64,11 +64,14 @@ cc_test( "//iamf/cli:audio_element_with_data", "//iamf/cli:audio_frame_decoder", "//iamf/cli:audio_frame_with_data", + "//iamf/cli:wav_reader", "//iamf/obu:audio_frame", "//iamf/obu:codec_config", "//iamf/obu:leb128", "//iamf/obu:obu_header", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:string_view", "@com_google_googletest//:gtest_main", ], ) diff --git a/iamf/cli/tests/audio_frame_decoder_test.cc b/iamf/cli/tests/audio_frame_decoder_test.cc index 96c3ddd3..6d9ac1f6 100644 --- a/iamf/cli/tests/audio_frame_decoder_test.cc +++ b/iamf/cli/tests/audio_frame_decoder_test.cc @@ -1,15 +1,18 @@ #include "iamf/cli/audio_frame_decoder.h" #include +#include #include -#include #include #include "absl/container/flat_hash_map.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" #include "gtest/gtest.h" #include "iamf/cli/audio_element_with_data.h" #include "iamf/cli/audio_frame_with_data.h" #include "iamf/cli/tests/cli_test_utils.h" +#include "iamf/cli/wav_reader.h" #include "iamf/obu/audio_frame.h" #include "iamf/obu/codec_config.h" #include "iamf/obu/leb128.h" @@ -22,9 +25,13 @@ constexpr DecodedUleb128 kCodecConfigId = 44; constexpr uint32_t kSampleRate = 16000; constexpr DecodedUleb128 kAudioElementId = 13; constexpr DecodedUleb128 kSubstreamId = 0; +const int kNumChannels = 1; +const int kNumSamplesPerFrame = 8; +const int kBytesPerSample = 2; +constexpr absl::string_view kWavFilePrefix = "test"; TEST(AudioFrameDecoderTest, NoAudioFrames) { - AudioFrameDecoder decoder(::testing::TempDir(), "test"); + AudioFrameDecoder decoder(::testing::TempDir(), kWavFilePrefix); std::list decoded_audio_frames; EXPECT_TRUE(decoder.Decode({}, decoded_audio_frames).ok()); @@ -43,11 +50,14 @@ std::list PrepareEncodedAudioFrames( std::list encoded_audio_frames; encoded_audio_frames.push_back({ - .obu = AudioFrameObu(ObuHeader(), kSubstreamId, - /*audio_frame=*/std::vector(16, 0)), + .obu = AudioFrameObu( + ObuHeader(), kSubstreamId, + /*audio_frame=*/ + std::vector(kNumSamplesPerFrame * kBytesPerSample, 0)), .start_timestamp = 0, - .end_timestamp = 8, - .raw_samples = {std::vector(8, 0)}, + .end_timestamp = kNumSamplesPerFrame, + .raw_samples = std::vector>( + kNumSamplesPerFrame, std::vector(kNumChannels, 0)), .audio_element_with_data = &audio_elements.at(kAudioElementId), }); @@ -55,7 +65,7 @@ std::list PrepareEncodedAudioFrames( } TEST(AudioFrameDecoderTest, DecodeLpcmFrame) { - AudioFrameDecoder decoder(::testing::TempDir(), "test"); + AudioFrameDecoder decoder(::testing::TempDir(), kWavFilePrefix); // Encoded frames. absl::flat_hash_map codec_config_obus; @@ -72,7 +82,7 @@ TEST(AudioFrameDecoderTest, DecodeLpcmFrame) { const auto& decoded_audio_frame = decoded_audio_frames.back(); EXPECT_EQ(decoded_audio_frame.substream_id, kSubstreamId); EXPECT_EQ(decoded_audio_frame.start_timestamp, 0); - EXPECT_EQ(decoded_audio_frame.end_timestamp, 8); + EXPECT_EQ(decoded_audio_frame.end_timestamp, kNumSamplesPerFrame); EXPECT_EQ(decoded_audio_frame.audio_element_with_data, &audio_elements.at(kAudioElementId)); @@ -81,6 +91,55 @@ TEST(AudioFrameDecoderTest, DecodeLpcmFrame) { encoded_audio_frames.back().raw_samples); } +std::filesystem::path GetFirstExpectedWavFile(uint32_t substream_id) { + return std::filesystem::path(::testing::TempDir()) / + absl::StrCat(kWavFilePrefix, "_decoded_substream_", substream_id, + ".wav"); +} + +void CleanupExpectedFileForSubstream(uint32_t substream_id) { + std::filesystem::remove(GetFirstExpectedWavFile(substream_id)); +} + +void DecodeEightSampleAudioFrame(uint32_t num_samples_to_trim_at_end = 0, + uint32_t num_samples_to_trim_at_start = 0) { + CleanupExpectedFileForSubstream(kSubstreamId); + AudioFrameDecoder decoder(::testing::TempDir(), kWavFilePrefix); + // Encoded frames. + absl::flat_hash_map codec_config_obus; + absl::flat_hash_map audio_elements; + std::list encoded_audio_frames = + PrepareEncodedAudioFrames(codec_config_obus, audio_elements); + encoded_audio_frames.front().obu.header_.num_samples_to_trim_at_end = + num_samples_to_trim_at_end; + encoded_audio_frames.front().obu.header_.num_samples_to_trim_at_start = + num_samples_to_trim_at_start; + // Decode. + std::list decoded_audio_frames; + EXPECT_TRUE(decoder.Decode(encoded_audio_frames, decoded_audio_frames).ok()); +} + +TEST(AudioFrameDecoderTest, WritesDebuggingWavFileWithExpectedNumberOfSamples) { + DecodeEightSampleAudioFrame(); + + EXPECT_TRUE(std::filesystem::exists(GetFirstExpectedWavFile(kSubstreamId))); + WavReader reader(GetFirstExpectedWavFile(kSubstreamId).string(), + kNumSamplesPerFrame); + EXPECT_EQ(reader.remaining_samples(), kNumSamplesPerFrame); +} + +TEST(AudioFrameDecoderTest, DebuggingWavFileHasSamplesTrimmed) { + const uint32_t kNumSamplesToTrimAtEnd = 5; + const uint32_t kNumSamplesToTrimAtStart = 2; + DecodeEightSampleAudioFrame(kNumSamplesToTrimAtEnd, kNumSamplesToTrimAtStart); + const uint32_t kExpectedNumSamples = 1; + EXPECT_TRUE(std::filesystem::exists(GetFirstExpectedWavFile(kSubstreamId))); + WavReader reader(GetFirstExpectedWavFile(kSubstreamId).string(), + kNumSamplesPerFrame); + + EXPECT_EQ(reader.remaining_samples(), kExpectedNumSamples); +} + // TODO(b/308073716): Add tests for more kinds of decoders. } // namespace