parent
f277bd5d0c
commit
578c3737f8
@ -1,42 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <memory> |
|
||||||
#include <optional> |
|
||||||
#include <string> |
|
||||||
#include <utility> |
|
||||||
|
|
||||||
#include "stb_vorbis.h" |
|
||||||
|
|
||||||
#include "codec.hpp" |
|
||||||
|
|
||||||
namespace codecs { |
|
||||||
|
|
||||||
class StbVorbisDecoder : public ICodec { |
|
||||||
public: |
|
||||||
StbVorbisDecoder(); |
|
||||||
~StbVorbisDecoder(); |
|
||||||
|
|
||||||
auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override; |
|
||||||
auto ContinueStream(cpp::span<const std::byte>, cpp::span<std::byte>) |
|
||||||
-> Result<OutputInfo> override; |
|
||||||
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample) |
|
||||||
-> Result<void> override; |
|
||||||
|
|
||||||
private: |
|
||||||
stb_vorbis* vorbis_; |
|
||||||
|
|
||||||
int current_sample_; |
|
||||||
int num_channels_; |
|
||||||
int num_samples_; |
|
||||||
float** samples_array_; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace codecs
|
|
@ -0,0 +1,58 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <memory> |
||||||
|
#include <optional> |
||||||
|
#include <string> |
||||||
|
#include <utility> |
||||||
|
|
||||||
|
#include "ivorbisfile.h" |
||||||
|
#include "ogg.hpp" |
||||||
|
#include "ogg/ogg.h" |
||||||
|
#include "opus.h" |
||||||
|
#include "sample.hpp" |
||||||
|
#include "span.hpp" |
||||||
|
|
||||||
|
#include "codec.hpp" |
||||||
|
|
||||||
|
namespace codecs { |
||||||
|
|
||||||
|
class TremorVorbisDecoder : public ICodec { |
||||||
|
public: |
||||||
|
TremorVorbisDecoder(); |
||||||
|
~TremorVorbisDecoder(); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the output format for the next frame in the stream. MP3 streams |
||||||
|
* may represent multiple distinct tracks, with different bitrates, and so we |
||||||
|
* handle the stream only on a frame-by-frame basis. |
||||||
|
*/ |
||||||
|
auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes samples for the current frame. |
||||||
|
*/ |
||||||
|
auto ContinueStream(cpp::span<const std::byte> input, |
||||||
|
cpp::span<sample::Sample> output) |
||||||
|
-> Result<OutputInfo> override; |
||||||
|
|
||||||
|
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample) |
||||||
|
-> Result<void> override; |
||||||
|
|
||||||
|
auto ReadCallback() -> cpp::span<const std::byte>; |
||||||
|
auto AfterReadCallback(size_t bytes_read) -> void; |
||||||
|
|
||||||
|
private: |
||||||
|
OggVorbis_File vorbis_; |
||||||
|
cpp::span<const std::byte> input_; |
||||||
|
size_t pos_in_input_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace codecs
|
@ -0,0 +1,109 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "ogg.hpp" |
||||||
|
#include <cstring> |
||||||
|
|
||||||
|
#include "esp_log.h" |
||||||
|
#include "ogg/ogg.h" |
||||||
|
|
||||||
|
namespace codecs { |
||||||
|
|
||||||
|
static constexpr char kTag[] = "ogg"; |
||||||
|
|
||||||
|
OggContainer::OggContainer() |
||||||
|
: sync_(), |
||||||
|
stream_(), |
||||||
|
page_(), |
||||||
|
packet_(), |
||||||
|
has_stream_(false), |
||||||
|
has_packet_(false) { |
||||||
|
ogg_sync_init(&sync_); |
||||||
|
ogg_sync_pageout(&sync_, &page_); |
||||||
|
} |
||||||
|
|
||||||
|
OggContainer::~OggContainer() { |
||||||
|
ogg_sync_clear(&sync_); |
||||||
|
if (has_stream_) { |
||||||
|
ogg_stream_clear(&stream_); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto OggContainer::AddBytes(cpp::span<const std::byte> in) -> bool { |
||||||
|
ESP_LOGI(kTag, "adding %u bytes to buffer", in.size()); |
||||||
|
char* buf = ogg_sync_buffer(&sync_, in.size()); |
||||||
|
if (buf == NULL) { |
||||||
|
ESP_LOGE(kTag, "failed to allocate sync buffer"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
std::memcpy(buf, in.data(), in.size()); |
||||||
|
if (ogg_sync_wrote(&sync_, in.size()) < 0) { |
||||||
|
ESP_LOGE(kTag, "failed to write to sync buffer"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return AdvancePage() && AdvancePacket(); |
||||||
|
} |
||||||
|
|
||||||
|
auto OggContainer::HasPacket() -> bool { |
||||||
|
return has_packet_; |
||||||
|
} |
||||||
|
|
||||||
|
auto OggContainer::Next() -> bool { |
||||||
|
if (AdvancePacket()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (AdvancePage() && AdvancePacket()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
auto OggContainer::Current() -> cpp::span<uint8_t> { |
||||||
|
if (!has_packet_) { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
ESP_LOGI(kTag, "getting packet, location %p size %li", packet_.packet, |
||||||
|
packet_.bytes); |
||||||
|
return {packet_.packet, static_cast<size_t>(packet_.bytes)}; |
||||||
|
} |
||||||
|
|
||||||
|
auto OggContainer::AdvancePage() -> bool { |
||||||
|
int err; |
||||||
|
if ((err = ogg_sync_pageout(&sync_, &page_)) != 1) { |
||||||
|
ESP_LOGE(kTag, "failed to assemble page, res %i", err); |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!has_stream_) { |
||||||
|
int serialno = ogg_page_serialno(&page_); |
||||||
|
ESP_LOGI(kTag, "beginning ogg stream, serial number %i", serialno); |
||||||
|
if ((err = ogg_stream_init(&stream_, serialno) < 0)) { |
||||||
|
ESP_LOGE(kTag, "failed to init stream page, res %i", err); |
||||||
|
return false; |
||||||
|
} |
||||||
|
has_stream_ = true; |
||||||
|
} |
||||||
|
if (ogg_stream_pagein(&stream_, &page_) < 0) { |
||||||
|
ESP_LOGE(kTag, "failed to read in page"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
auto OggContainer::AdvancePacket() -> bool { |
||||||
|
has_packet_ = false; |
||||||
|
int res; |
||||||
|
while ((res = ogg_stream_packetout(&stream_, &packet_)) == -1) { |
||||||
|
// Retry until we sync, or run out of data.
|
||||||
|
ESP_LOGW(kTag, "trying to sync stream..."); |
||||||
|
} |
||||||
|
has_packet_ = res; |
||||||
|
if (!has_packet_) { |
||||||
|
ESP_LOGE(kTag, "failed to read out packet"); |
||||||
|
} |
||||||
|
return has_packet_; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace codecs
|
@ -1,128 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "stbvorbis.hpp" |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <optional> |
|
||||||
|
|
||||||
#include "stb_vorbis.h" |
|
||||||
|
|
||||||
namespace codecs { |
|
||||||
|
|
||||||
StbVorbisDecoder::StbVorbisDecoder() |
|
||||||
: vorbis_(nullptr), |
|
||||||
current_sample_(-1), |
|
||||||
num_channels_(0), |
|
||||||
num_samples_(0), |
|
||||||
samples_array_(NULL) {} |
|
||||||
|
|
||||||
StbVorbisDecoder::~StbVorbisDecoder() { |
|
||||||
if (vorbis_ != nullptr) { |
|
||||||
stb_vorbis_close(vorbis_); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static uint32_t scaleToBits(float sample, uint8_t bits) { |
|
||||||
// Scale to range.
|
|
||||||
int32_t max_val = (1 << (bits - 1)); |
|
||||||
int32_t fixed_point = sample * max_val; |
|
||||||
|
|
||||||
// Clamp within bounds.
|
|
||||||
fixed_point = std::clamp(fixed_point, -max_val, max_val); |
|
||||||
|
|
||||||
// Remove sign.
|
|
||||||
return *reinterpret_cast<uint32_t*>(&fixed_point); |
|
||||||
} |
|
||||||
|
|
||||||
auto StbVorbisDecoder::BeginStream(const cpp::span<const std::byte> input) |
|
||||||
-> Result<OutputFormat> { |
|
||||||
if (vorbis_ != nullptr) { |
|
||||||
stb_vorbis_close(vorbis_); |
|
||||||
vorbis_ = nullptr; |
|
||||||
} |
|
||||||
current_sample_ = -1; |
|
||||||
int bytes_read = 0; |
|
||||||
int error = 0; |
|
||||||
vorbis_ = |
|
||||||
stb_vorbis_open_pushdata(reinterpret_cast<const uint8_t*>(input.data()), |
|
||||||
input.size_bytes(), &bytes_read, &error, NULL); |
|
||||||
if (error != 0) { |
|
||||||
return {0, cpp::fail(Error::kMalformedData)}; |
|
||||||
} |
|
||||||
stb_vorbis_info info = stb_vorbis_get_info(vorbis_); |
|
||||||
return {bytes_read, |
|
||||||
OutputFormat{.num_channels = static_cast<uint8_t>(info.channels), |
|
||||||
.bits_per_sample = 24, |
|
||||||
.sample_rate_hz = info.sample_rate}}; |
|
||||||
} |
|
||||||
|
|
||||||
auto StbVorbisDecoder::ContinueStream(cpp::span<const std::byte> input, |
|
||||||
cpp::span<std::byte> output) |
|
||||||
-> Result<OutputInfo> { |
|
||||||
std::size_t bytes_used = 0; |
|
||||||
if (current_sample_ < 0) { |
|
||||||
num_channels_ = 0; |
|
||||||
num_samples_ = 0; |
|
||||||
samples_array_ = NULL; |
|
||||||
|
|
||||||
while (true) { |
|
||||||
auto cropped = input.subspan(bytes_used); |
|
||||||
std::size_t b = stb_vorbis_decode_frame_pushdata( |
|
||||||
vorbis_, reinterpret_cast<const uint8_t*>(cropped.data()), |
|
||||||
cropped.size_bytes(), &num_channels_, &samples_array_, &num_samples_); |
|
||||||
if (b == 0) { |
|
||||||
return {bytes_used, cpp::fail(Error::kOutOfInput)}; |
|
||||||
} |
|
||||||
bytes_used += b; |
|
||||||
|
|
||||||
if (num_samples_ == 0) { |
|
||||||
// Decoder is synchronising. Decode more bytes.
|
|
||||||
continue; |
|
||||||
} |
|
||||||
if (num_channels_ == 0 || samples_array_ == NULL) { |
|
||||||
// The decoder isn't satisfying its contract.
|
|
||||||
return {bytes_used, cpp::fail(Error::kInternalError)}; |
|
||||||
} |
|
||||||
current_sample_ = 0; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// We successfully decoded a frame. Time to write out the samples.
|
|
||||||
std::size_t output_byte = 0; |
|
||||||
while (current_sample_ < num_samples_) { |
|
||||||
if (output_byte + (2 * num_channels_) >= output.size()) { |
|
||||||
return {0, OutputInfo{.bytes_written = output_byte, |
|
||||||
.is_finished_writing = false}}; |
|
||||||
} |
|
||||||
|
|
||||||
for (int channel = 0; channel < num_channels_; channel++) { |
|
||||||
float raw_sample = samples_array_[channel][current_sample_]; |
|
||||||
|
|
||||||
uint16_t sample_24 = scaleToBits(raw_sample, 24); |
|
||||||
output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF); |
|
||||||
output[output_byte++] = static_cast<std::byte>((sample_24 >> 8) & 0xFF); |
|
||||||
output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF); |
|
||||||
// Pad to 32 bits for alignment.
|
|
||||||
output[output_byte++] = static_cast<std::byte>(0); |
|
||||||
} |
|
||||||
current_sample_++; |
|
||||||
} |
|
||||||
|
|
||||||
current_sample_ = -1; |
|
||||||
return {bytes_used, OutputInfo{.bytes_written = output_byte, |
|
||||||
.is_finished_writing = true}}; |
|
||||||
} |
|
||||||
|
|
||||||
auto StbVorbisDecoder::SeekStream(cpp::span<const std::byte> input, |
|
||||||
std::size_t target_sample) -> Result<void> { |
|
||||||
// TODO(jacqueline): Implement me.
|
|
||||||
return {0, {}}; |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace codecs
|
|
@ -0,0 +1,162 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "ivorbiscodec.h" |
||||||
|
#include "ivorbisfile.h" |
||||||
|
#include "ogg/config_types.h" |
||||||
|
#include "opus.hpp" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <sys/_stdint.h> |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include <cstring> |
||||||
|
#include <optional> |
||||||
|
|
||||||
|
#include "esp_heap_caps.h" |
||||||
|
#include "mad.h" |
||||||
|
|
||||||
|
#include "codec.hpp" |
||||||
|
#include "esp_log.h" |
||||||
|
#include "ogg/ogg.h" |
||||||
|
#include "opus.h" |
||||||
|
#include "opus_defines.h" |
||||||
|
#include "opus_types.h" |
||||||
|
#include "result.hpp" |
||||||
|
#include "sample.hpp" |
||||||
|
#include "types.hpp" |
||||||
|
#include "vorbis.hpp" |
||||||
|
|
||||||
|
namespace codecs { |
||||||
|
|
||||||
|
static constexpr char kTag[] = "vorbis"; |
||||||
|
|
||||||
|
size_t read_cb(void* ptr, size_t size, size_t nmemb, void* instance) { |
||||||
|
TremorVorbisDecoder* dec = reinterpret_cast<TremorVorbisDecoder*>(instance); |
||||||
|
auto input = dec->ReadCallback(); |
||||||
|
size_t amount_to_read = std::min<size_t>(size * nmemb, input.size_bytes()); |
||||||
|
std::memcpy(ptr, input.data(), amount_to_read); |
||||||
|
dec->AfterReadCallback(amount_to_read); |
||||||
|
return amount_to_read; |
||||||
|
} |
||||||
|
|
||||||
|
int seek_cb(void* instance, ogg_int64_t offset, int whence) { |
||||||
|
// Seeking is handled separately.
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int close_cb(void* instance) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static const ov_callbacks kCallbacks{ |
||||||
|
.read_func = read_cb, |
||||||
|
.seek_func = seek_cb, |
||||||
|
.close_func = close_cb, |
||||||
|
.tell_func = NULL, // Not seekable
|
||||||
|
}; |
||||||
|
|
||||||
|
TremorVorbisDecoder::TremorVorbisDecoder() |
||||||
|
: vorbis_(), input_(), pos_in_input_(0) {} |
||||||
|
|
||||||
|
TremorVorbisDecoder::~TremorVorbisDecoder() { |
||||||
|
ov_clear(&vorbis_); |
||||||
|
} |
||||||
|
|
||||||
|
auto TremorVorbisDecoder::BeginStream(const cpp::span<const std::byte> input) |
||||||
|
-> Result<OutputFormat> { |
||||||
|
int res = ov_open_callbacks(this, &vorbis_, |
||||||
|
reinterpret_cast<const char*>(input.data()), |
||||||
|
input.size(), kCallbacks); |
||||||
|
if (res < 0) { |
||||||
|
std::string err; |
||||||
|
switch (res) { |
||||||
|
case OV_EREAD: |
||||||
|
err = "OV_EREAD"; |
||||||
|
break; |
||||||
|
case OV_ENOTVORBIS: |
||||||
|
err = "OV_ENOTVORBIS"; |
||||||
|
break; |
||||||
|
case OV_EVERSION: |
||||||
|
err = "OV_EVERSION"; |
||||||
|
break; |
||||||
|
case OV_EBADHEADER: |
||||||
|
err = "OV_EBADHEADER"; |
||||||
|
break; |
||||||
|
case OV_EFAULT: |
||||||
|
err = "OV_EFAULT"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
err = "unknown"; |
||||||
|
} |
||||||
|
ESP_LOGE(kTag, "error beginning stream: %s", err.c_str()); |
||||||
|
return {input.size(), cpp::fail(Error::kMalformedData)}; |
||||||
|
} |
||||||
|
|
||||||
|
vorbis_info* info = ov_info(&vorbis_, -1); |
||||||
|
if (info == NULL) { |
||||||
|
ESP_LOGE(kTag, "failed to get stream info"); |
||||||
|
return {input.size(), cpp::fail(Error::kMalformedData)}; |
||||||
|
} |
||||||
|
|
||||||
|
return {input.size(), |
||||||
|
OutputFormat{ |
||||||
|
.num_channels = static_cast<uint8_t>(info->channels), |
||||||
|
.sample_rate_hz = static_cast<uint32_t>(info->rate), |
||||||
|
.bits_per_second = info->bitrate_nominal, |
||||||
|
}}; |
||||||
|
} |
||||||
|
|
||||||
|
auto TremorVorbisDecoder::ContinueStream(cpp::span<const std::byte> input, |
||||||
|
cpp::span<sample::Sample> output) |
||||||
|
-> Result<OutputInfo> { |
||||||
|
cpp::span<int16_t> staging_buffer{ |
||||||
|
reinterpret_cast<int16_t*>(output.subspan(output.size() / 2).data()), |
||||||
|
output.size_bytes() / 2}; |
||||||
|
|
||||||
|
input_ = input; |
||||||
|
pos_in_input_ = 0; |
||||||
|
|
||||||
|
int bitstream; |
||||||
|
long bytes_written = |
||||||
|
ov_read(&vorbis_, reinterpret_cast<char*>(staging_buffer.data()), |
||||||
|
staging_buffer.size_bytes(), &bitstream); |
||||||
|
if (bytes_written == OV_HOLE) { |
||||||
|
ESP_LOGE(kTag, "got OV_HOLE"); |
||||||
|
return {pos_in_input_, cpp::fail(Error::kMalformedData)}; |
||||||
|
} else if (bytes_written == OV_EBADLINK) { |
||||||
|
ESP_LOGE(kTag, "got OV_EBADLINK"); |
||||||
|
return {pos_in_input_, cpp::fail(Error::kMalformedData)}; |
||||||
|
} else if (bytes_written == 0) { |
||||||
|
return {pos_in_input_, cpp::fail(Error::kOutOfInput)}; |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < bytes_written / 2; i++) { |
||||||
|
output[i] = sample::FromSigned(staging_buffer[i], 16); |
||||||
|
} |
||||||
|
|
||||||
|
return {pos_in_input_, |
||||||
|
OutputInfo{ |
||||||
|
.samples_written = static_cast<size_t>(bytes_written / 2), |
||||||
|
.is_finished_writing = bytes_written == 0, |
||||||
|
}}; |
||||||
|
} |
||||||
|
|
||||||
|
auto TremorVorbisDecoder::SeekStream(cpp::span<const std::byte> input, |
||||||
|
std::size_t target_sample) |
||||||
|
-> Result<void> { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
auto TremorVorbisDecoder::ReadCallback() -> cpp::span<const std::byte> { |
||||||
|
return input_.subspan(pos_in_input_); |
||||||
|
} |
||||||
|
|
||||||
|
auto TremorVorbisDecoder::AfterReadCallback(size_t bytes_read) -> void { |
||||||
|
pos_in_input_ += bytes_read; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace codecs
|
Loading…
Reference in new issue