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