Skip to content

Commit

Permalink
Implement FLAC decoder initialization + store the pointer to the stre…
Browse files Browse the repository at this point in the history
…am decoder.

This CL also moves all the libflac callback functions to the FlacDecoder class; this is because in the current structure, there is a cyclic dependency between flac_decoder.h and flac_decoder_stream_callback.h. In order to break it, we add the callbacks back to flac_decoder.h. The tests for the callbacks are moved to the newly created flac_decoder_test.cc as well.

I'll remove flac_decoder_stream_callback.h & co in a separate cl.

PiperOrigin-RevId: 687382376
  • Loading branch information
Googler authored and jwcullen committed Oct 18, 2024
1 parent 4cf116f commit 20b43bb
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 3 deletions.
3 changes: 3 additions & 0 deletions iamf/cli/codec/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ cc_library(
":decoder_base",
"//iamf/obu:codec_config",
"//iamf/obu/decoder_config:flac_decoder_config",
"@com_google_absl//absl/log",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@flac//:src",
],
)

Expand Down
91 changes: 90 additions & 1 deletion iamf/cli/codec/flac_decoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,114 @@

#include "iamf/cli/codec/flac_decoder.h"

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

#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "iamf/cli/codec/decoder_base.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/decoder_config/flac_decoder_config.h"
#include "include/FLAC/format.h"
#include "include/FLAC/ordinals.h"
#include "include/FLAC/stream_decoder.h"

namespace iamf_tools {

FLAC__StreamDecoderReadStatus FlacDecoder::LibFlacReadCallback(
const FLAC__StreamDecoder* /*decoder*/, FLAC__byte buffer[], size_t* bytes,
void* client_data) {
auto flac_decoder = static_cast<FlacDecoder*>(client_data);
auto encoded_frame = flac_decoder->GetEncodedFrame();
if (encoded_frame.empty()) {
// No more data to read.
*bytes = 0;
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
}
if (encoded_frame.size() > *bytes) {
LOG(ERROR) << "Encoded frame size " << encoded_frame.size()
<< " is larger than the libflac buffer size " << *bytes;
*bytes = 0;
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
for (int i = 0; i < encoded_frame.size(); ++i) {
buffer[i] = encoded_frame[i];
}
*bytes = encoded_frame.size();
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}

FLAC__StreamDecoderWriteStatus FlacDecoder::LibFlacWriteCallback(
const FLAC__StreamDecoder* /*decoder*/, const FLAC__Frame* frame,
const FLAC__int32* const buffer[], void* client_data) {
std::vector<std::vector<int32_t>> decoded_samples(frame->header.channels);
auto flac_decoder = static_cast<FlacDecoder*>(client_data);
for (int i = 0; i < frame->header.channels; ++i) {
decoded_samples[i].resize(frame->header.blocksize);
const FLAC__int32* const channel_buffer = buffer[i];
for (int j = 0; j < frame->header.blocksize; ++j) {
// Assumes that bits_per_sample is 32.
decoded_samples[i][j] = channel_buffer[j];
}
}
flac_decoder->SetDecodedFrame(decoded_samples);
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void FlacDecoder::LibFlacErrorCallback(const FLAC__StreamDecoder* /*decoder*/,
FLAC__StreamDecoderErrorStatus status,
void* /*client_data*/) {
switch (status) {
case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
LOG(ERROR) << "FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC";
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
LOG(ERROR) << "FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER";
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
LOG(ERROR) << "FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH";
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM:
LOG(ERROR) << "FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM";
break;
default:
LOG(ERROR) << "Unknown FLAC__StreamDecoderErrorStatus= " << status;
break;
}
}

FlacDecoder::FlacDecoder(const CodecConfigObu& codec_config_obu,
int num_channels)
: DecoderBase(num_channels,
static_cast<int>(codec_config_obu.GetNumSamplesPerFrame())),
decoder_config_(std::get<FlacDecoderConfig>(
codec_config_obu.GetCodecConfig().decoder_config)) {}

FlacDecoder::~FlacDecoder() {
if (decoder_ != nullptr) {
FLAC__stream_decoder_delete(decoder_);
}
}

absl::Status FlacDecoder::Initialize() {
return absl::UnimplementedError("Not implemented.");
decoder_ = FLAC__stream_decoder_new();
if (decoder_ == nullptr) {
return absl::InternalError("Failed to create FLAC stream decoder.");
}
FLAC__StreamDecoderInitStatus status = FLAC__stream_decoder_init_stream(
decoder_, LibFlacReadCallback, /*seek_callback=*/nullptr,
/*tell_callback=*/nullptr, /*length_callback=*/nullptr,
/*eof_callback=*/nullptr, LibFlacWriteCallback,
/*metadata_callback=*/nullptr, LibFlacErrorCallback,
static_cast<void*>(this));

if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
return absl::InternalError(
absl::StrCat("Failed to initialize FLAC stream decoder: ", status));
}
return absl::OkStatus();
}

absl::Status FlacDecoder::DecodeAudioFrame(
Expand Down
67 changes: 65 additions & 2 deletions iamf/cli/codec/flac_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
#ifndef CLI_CODEC_FLAC_DECODER_H_
#define CLI_CODEC_FLAC_DECODER_H_

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

#include "absl/status/status.h"
#include "iamf/cli/codec/decoder_base.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/decoder_config/flac_decoder_config.h"
#include "include/FLAC/format.h"
#include "include/FLAC/ordinals.h"
#include "include/FLAC/stream_decoder.h"
namespace iamf_tools {

/*!brief Decoder for FLAC audio streams.
Expand All @@ -33,9 +37,9 @@ class FlacDecoder : public DecoderBase {
*/
FlacDecoder(const CodecConfigObu& codec_config_obu, int num_channels);

~FlacDecoder() override = default;
~FlacDecoder() override;

/*!\brief Initializes the underlying decoder.
/*!\brief Initializes the underlying libflac decoder.
*
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
Expand Down Expand Up @@ -82,10 +86,69 @@ class FlacDecoder : public DecoderBase {
return decoded_frame_;
}

/*!\brief Reads an encoded flac frame into the libflac decoder
*
* This callback function is used whenever the decoder needs more input data.
*
* \param decoder Unused libflac stream decoder. This parameter is not used in
* this implementation, but is included to override the libflac
* signature.
* \param buffer Output buffer for the encoded frame.
* \param bytes Maximum size of the buffer; in the case of a successful read,
* this will be set to the actual number of bytes read.
* \param client_data universal pointer, which in this case should point to
* FlacDecoder.
*
* \return A libflac read status indicating whether the read was successful.
*/
static FLAC__StreamDecoderReadStatus LibFlacReadCallback(
const FLAC__StreamDecoder* /*decoder*/, FLAC__byte buffer[],
size_t* bytes, void* client_data);

/*!\brief Writes a decoded flac frame to an instance of FlacDecoder.
*
* This callback function is used to write out a decoded frame from the
* libflac decoder.
*
* \param decoder Unused libflac stream decoder. This parameter is not used in
* this implementation, but is included to override the libflac
* signature.
* \param frame libflac encoded frame metadata.
* \param buffer Array of pointers to decoded channels of data. Each pointer
* will point to an array of signed samples of length
* `frame->header.blocksize`. Channels will be ordered according to the
* FLAC specification.
* \param client_data Universal pointer, which in this case should point to
* FlacDecoder.
*
* \return A libflac write status indicating whether the write was successful.
*/
static FLAC__StreamDecoderWriteStatus LibFlacWriteCallback(
const FLAC__StreamDecoder* /*decoder*/, const FLAC__Frame* frame,
const FLAC__int32* const buffer[], void* client_data);

/*!\brief Logs an error from the libflac decoder.
*
* This function will be called whenever an error occurs during libflac
* decoding.
*
* \param decoder Unused libflac stream decoder. This parameter is not used in
* this implementation, but is included to override the libflac
* signature.
* \param status The error encountered by the decoder.
* \param client_data Universal pointer, which in this case should point to
* FlacDecoder. Unused in this implementation.
*/
static void LibFlacErrorCallback(const FLAC__StreamDecoder* /*decoder*/,
FLAC__StreamDecoderErrorStatus status,
void* /*client_data*/);

private:
std::vector<uint8_t> encoded_frame_ = {};
std::vector<std::vector<int32_t>> decoded_frame_ = {};
const FlacDecoderConfig decoder_config_;
// A pointer to the `libflac` decoder.
FLAC__StreamDecoder* decoder_ = nullptr;
};

} // namespace iamf_tools
Expand Down
14 changes: 14 additions & 0 deletions iamf/cli/codec/tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,17 @@ cc_test(
"@flac//:src",
],
)

cc_test(
name = "flac_decoder_test",
srcs = ["flac_decoder_test.cc"],
deps = [
"//iamf/cli/codec:flac_decoder",
"//iamf/obu:codec_config",
"//iamf/obu:obu_header",
"//iamf/obu/decoder_config:flac_decoder_config",
"@com_google_absl//absl/status:status_matchers",
"@com_google_googletest//:gtest_main",
"@flac//:src",
],
)
120 changes: 120 additions & 0 deletions iamf/cli/codec/tests/flac_decoder_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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/codec/flac_decoder.h"

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

#include "absl/status/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/decoder_config/flac_decoder_config.h"
#include "iamf/obu/obu_header.h"
#include "include/FLAC/format.h"
#include "include/FLAC/ordinals.h"
#include "include/FLAC/stream_decoder.h"

namespace iamf_tools {
namespace {

using ::absl_testing::IsOk;
using ::testing::ElementsAreArray;
using ::testing::Test;

class FlacDecoderTest : public Test {
public:
FlacDecoderTest()
: flac_decoder_(FlacDecoder(
(CodecConfigObu(ObuHeader(), 0,
{.codec_id = CodecConfig::kCodecIdFlac,
.num_samples_per_frame = 1024,
.audio_roll_distance = -1,
.decoder_config = FlacDecoderConfig{}})),
/*num_channels=*/2)) {}

protected:
FlacDecoder flac_decoder_;
};

TEST_F(FlacDecoderTest, ReadCallbackEmptyFrame) {
FLAC__byte buffer[1024];
size_t bytes = 1024;
auto status = FlacDecoder::LibFlacReadCallback(
/*stream_decoder=*/nullptr, buffer, &bytes, &flac_decoder_);
EXPECT_EQ(status, FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM);
EXPECT_EQ(bytes, 0);
}

TEST_F(FlacDecoderTest, ReadCallbackFrameTooLarge) {
FLAC__byte buffer[1024];
size_t bytes = 1024;
flac_decoder_.SetEncodedFrame(std::vector<uint8_t>(1025));
auto status = FlacDecoder::LibFlacReadCallback(
/*stream_decoder=*/nullptr, buffer, &bytes, &flac_decoder_);
EXPECT_EQ(status, FLAC__STREAM_DECODER_READ_STATUS_ABORT);
EXPECT_EQ(bytes, 0);
}

TEST_F(FlacDecoderTest, ReadCallbackSuccess) {
FLAC__byte buffer[1024];
size_t bytes = 1028;
const std::vector<uint8_t> encoded_frame(1024, 1);
flac_decoder_.SetEncodedFrame(encoded_frame);
auto status = FlacDecoder::LibFlacReadCallback(
/*stream_decoder=*/nullptr, buffer, &bytes, &flac_decoder_);
EXPECT_EQ(status, FLAC__STREAM_DECODER_READ_STATUS_CONTINUE);
EXPECT_EQ(bytes, 1024);
EXPECT_THAT(buffer, ElementsAreArray(encoded_frame));
}

TEST_F(FlacDecoderTest, WriteCallbackSuccess32BitSamples) {
FLAC__Frame frame;
frame.header.channels = 2;
frame.header.blocksize = 3;
frame.header.bits_per_sample = 32;
FLAC__int32 channel_0[] = {1, 0x7fffffff, 3};
FLAC__int32 channel_1[] = {2, 3, 4};
const FLAC__int32 *const buffer[] = {channel_0, channel_1};
auto status = FlacDecoder::LibFlacWriteCallback(
/*stream_decoder=*/nullptr, &frame, buffer, &flac_decoder_);
EXPECT_EQ(status, FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE);
EXPECT_THAT(flac_decoder_.GetDecodedFrame(),
ElementsAreArray(std::vector<std::vector<int32_t>>(
{{1, 0x7fffffff, 3}, {2, 3, 4}})));
}

TEST_F(FlacDecoderTest, WriteCallbackSuccess16BitSamples) {
FLAC__Frame frame;
frame.header.channels = 2;
frame.header.blocksize = 2;
frame.header.bits_per_sample = 16;
FLAC__int32 channel_0[] = {0x11110000, 0x7fff0000};
FLAC__int32 channel_1[] = {0x01010000, 0x22220000};
const FLAC__int32 *const buffer[] = {channel_0, channel_1};
auto status = FlacDecoder::LibFlacWriteCallback(
/*stream_decoder=*/nullptr, &frame, buffer, &flac_decoder_);
EXPECT_EQ(status, FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE);
EXPECT_THAT(flac_decoder_.GetDecodedFrame(),
ElementsAreArray(std::vector<std::vector<int32_t>>(
{{0x11110000, 0x7fff0000}, {0x01010000, 0x22220000}})));
}

TEST_F(FlacDecoderTest, InitializeSuccess) {
EXPECT_THAT(flac_decoder_.Initialize(), IsOk());
}

} // namespace

} // namespace iamf_tools

0 comments on commit 20b43bb

Please sign in to comment.