blob: bd77e2b3b0497503fead7b8b246d6cb773cb7ad6 [file] [log] [blame]
/*
* 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/lpcm_decoder.h"
#include <cstddef>
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "iamf/cli/codec/decoder_base.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/utils/numeric_utils.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/decoder_config/lpcm_decoder_config.h"
namespace iamf_tools {
LpcmDecoder::LpcmDecoder(const CodecConfigObu& codec_config_obu,
int num_channels)
: DecoderBase(num_channels,
static_cast<int>(codec_config_obu.GetNumSamplesPerFrame())),
decoder_config_(std::get<LpcmDecoderConfig>(
codec_config_obu.GetCodecConfig().decoder_config)),
audio_roll_distance_(
codec_config_obu.GetCodecConfig().audio_roll_distance) {}
absl::Status LpcmDecoder::Initialize() {
RETURN_IF_NOT_OK(decoder_config_.Validate(audio_roll_distance_));
return absl::OkStatus();
}
absl::Status LpcmDecoder::DecodeAudioFrame(
const std::vector<uint8_t>& encoded_frame) {
num_valid_ticks_ = 0;
uint8_t bit_depth;
auto status = decoder_config_.GetBitDepthToMeasureLoudness(bit_depth);
if (!status.ok()) {
return status;
}
// The LpcmDecoderConfig should have checked for valid values before returning
// the bit depth, but we defensively check that it's a multiple of 8 here.
if (bit_depth % 8 != 0) {
return absl::InvalidArgumentError(
absl::StrCat("LpcmDecoder::DecodeAudioFrame() failed: bit_depth (",
bit_depth, ") is not a multiple of 8."));
}
const size_t bytes_per_sample = bit_depth / 8;
// Make sure we have a valid number of bytes. There needs to be an equal
// number of samples for each channel.
if (encoded_frame.size() % bytes_per_sample != 0 ||
(encoded_frame.size() / bytes_per_sample) % num_channels_ != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"LpcmDecoder::DecodeAudioFrame() failed: encoded_frame has ",
encoded_frame.size(),
" bytes, which is not a multiple of the bytes per sample (",
bytes_per_sample, ") * number of channels (", num_channels_, ")."));
}
// Each time tick has one sample for each channel.
const size_t num_ticks =
encoded_frame.size() / bytes_per_sample / num_channels_;
if (num_ticks > num_samples_per_channel_) {
return absl::InvalidArgumentError(
absl::StrCat("Detected num_ticks= ", num_ticks,
", but the decoder is only configured for up to "
"num_samples_per_channel_= ",
num_samples_per_channel_, "."));
}
num_valid_ticks_ = num_ticks;
const bool little_endian = decoder_config_.IsLittleEndian();
int32_t sample_result;
for (size_t t = 0; t < num_valid_ticks_; ++t) {
// One sample for each channel in this time tick.
for (size_t c = 0; c < num_channels_; ++c) {
const size_t offset = (t * num_channels_ + c) * bytes_per_sample;
absl::Span<const uint8_t> input_bytes(encoded_frame.data() + offset,
bytes_per_sample);
if (little_endian) {
status = LittleEndianBytesToInt32(input_bytes, sample_result);
} else {
status = BigEndianBytesToInt32(input_bytes, sample_result);
}
RETURN_IF_NOT_OK(status);
decoded_samples_[t][c] = sample_result;
}
}
return absl::OkStatus();
}
} // namespace iamf_tools