Use opusfile instead of working directly with ogg and opus
This commit is contained in:
@@ -3,20 +3,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
idf_component_register(
|
||||
SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp" "ogg.cpp" "vorbis.cpp"
|
||||
SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp" "vorbis.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "result" "span" "libmad" "libfoxenflac" "tremor" "ogg")
|
||||
REQUIRES "result" "span" "libmad" "libfoxenflac" "tremor" "opusfile")
|
||||
|
||||
target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
|
||||
set(OPUS_FIXED_POINT ON)
|
||||
set(OPUS_ENABLE_FLOAT_API OFF)
|
||||
set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
|
||||
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
|
||||
set(OPUS_BUILD_TESTING OFF)
|
||||
set(OPUS_BUILD_SHARED_LIBS OFF)
|
||||
|
||||
add_subdirectory($ENV{PROJ_PATH}/lib/opus ${CMAKE_CURRENT_BINARY_DIR}/opus)
|
||||
target_link_libraries(${COMPONENT_LIB} PUBLIC opus)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/_stdint.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "ogg/ogg.h"
|
||||
#include "span.hpp"
|
||||
|
||||
namespace codecs {
|
||||
|
||||
class OggContainer {
|
||||
public:
|
||||
OggContainer();
|
||||
~OggContainer();
|
||||
|
||||
auto AddBytes(cpp::span<const std::byte>) -> bool;
|
||||
|
||||
auto Next() -> bool;
|
||||
auto Current() -> cpp::span<uint8_t>;
|
||||
auto HasPacket() -> bool;
|
||||
|
||||
private:
|
||||
auto AdvancePage() -> bool;
|
||||
auto AdvancePacket() -> bool;
|
||||
|
||||
ogg_sync_state sync_;
|
||||
ogg_stream_state stream_;
|
||||
ogg_page page_;
|
||||
ogg_packet packet_;
|
||||
|
||||
bool has_stream_;
|
||||
bool has_packet_;
|
||||
};
|
||||
|
||||
} // namespace codecs
|
||||
@@ -13,9 +13,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "ogg.hpp"
|
||||
#include "ogg/ogg.h"
|
||||
#include "opus.h"
|
||||
#include "opusfile.h"
|
||||
#include "sample.hpp"
|
||||
#include "span.hpp"
|
||||
|
||||
@@ -45,13 +43,13 @@ class XiphOpusDecoder : public ICodec {
|
||||
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample)
|
||||
-> Result<void> override;
|
||||
|
||||
private:
|
||||
OggContainer ogg_;
|
||||
OpusDecoder* opus_;
|
||||
cpp::span<int16_t> sample_buffer_;
|
||||
int32_t pos_in_buffer_;
|
||||
int32_t samples_in_buffer_;
|
||||
auto ReadCallback() -> cpp::span<const std::byte>;
|
||||
auto AfterReadCallback(size_t bytes_read) -> void;
|
||||
|
||||
private:
|
||||
OggOpusFile* opus_;
|
||||
cpp::span<const std::byte> input_;
|
||||
size_t pos_in_input_;
|
||||
};
|
||||
|
||||
} // namespace codecs
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <utility>
|
||||
|
||||
#include "ivorbisfile.h"
|
||||
#include "ogg.hpp"
|
||||
#include "ogg/ogg.h"
|
||||
#include "opus.h"
|
||||
#include "sample.hpp"
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
+60
-88
@@ -18,10 +18,7 @@
|
||||
|
||||
#include "codec.hpp"
|
||||
#include "esp_log.h"
|
||||
#include "ogg/ogg.h"
|
||||
#include "opus.h"
|
||||
#include "opus_defines.h"
|
||||
#include "opus_types.h"
|
||||
#include "opusfile.h"
|
||||
#include "result.hpp"
|
||||
#include "sample.hpp"
|
||||
#include "types.hpp"
|
||||
@@ -30,47 +27,52 @@ namespace codecs {
|
||||
|
||||
static constexpr char kTag[] = "opus";
|
||||
|
||||
// "If this is less than the maximum packet duration (120ms; 5760 for 48kHz),
|
||||
// this function will not be capable of decoding some packets"
|
||||
static constexpr size_t kSampleBufferSize = 5760;
|
||||
|
||||
XiphOpusDecoder::XiphOpusDecoder() : opus_(nullptr) {
|
||||
pos_in_buffer_ = 0;
|
||||
sample_buffer_ = {reinterpret_cast<opus_int16*>(
|
||||
heap_caps_calloc(kSampleBufferSize, sizeof(opus_int16),
|
||||
MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM)),
|
||||
kSampleBufferSize};
|
||||
int read_cb(void* instance, unsigned char* ptr, int nbytes) {
|
||||
XiphOpusDecoder* dec = reinterpret_cast<XiphOpusDecoder*>(instance);
|
||||
auto input = dec->ReadCallback();
|
||||
size_t amount_to_read = std::min<size_t>(nbytes, input.size_bytes());
|
||||
std::memcpy(ptr, input.data(), amount_to_read);
|
||||
dec->AfterReadCallback(amount_to_read);
|
||||
return amount_to_read;
|
||||
}
|
||||
|
||||
static const OpusFileCallbacks kCallbacks{
|
||||
.read = read_cb,
|
||||
.seek = NULL,
|
||||
.tell = NULL, // Not seekable
|
||||
.close = NULL,
|
||||
};
|
||||
|
||||
XiphOpusDecoder::XiphOpusDecoder() : opus_(nullptr) {}
|
||||
|
||||
XiphOpusDecoder::~XiphOpusDecoder() {
|
||||
if (opus_ != nullptr) {
|
||||
opus_decoder_destroy(opus_);
|
||||
op_free(opus_);
|
||||
}
|
||||
heap_caps_free(sample_buffer_.data());
|
||||
}
|
||||
|
||||
auto XiphOpusDecoder::BeginStream(const cpp::span<const std::byte> input)
|
||||
-> Result<OutputFormat> {
|
||||
if (!ogg_.AddBytes(input)) {
|
||||
ESP_LOGI(kTag, "need more input to begin");
|
||||
return {input.size(), cpp::fail(Error::kOutOfInput)};
|
||||
}
|
||||
auto packet = ogg_.Current();
|
||||
int num_channels = opus_packet_get_nb_channels(packet.data());
|
||||
ESP_LOGI(kTag, "opus stream has %i channels", num_channels);
|
||||
if (num_channels > 2) {
|
||||
// Too many channels; we can't handle this.
|
||||
// TODO: better error
|
||||
int res;
|
||||
opus_ = op_open_callbacks(
|
||||
this, &kCallbacks, reinterpret_cast<const unsigned char*>(input.data()),
|
||||
input.size(), &res);
|
||||
|
||||
if (res < 0) {
|
||||
std::string err;
|
||||
switch (res) {
|
||||
case OP_EREAD:
|
||||
err = "OP_EREAD";
|
||||
break;
|
||||
default:
|
||||
err = "unknown";
|
||||
}
|
||||
ESP_LOGE(kTag, "error beginning stream: %s", err.c_str());
|
||||
return {input.size(), cpp::fail(Error::kMalformedData)};
|
||||
}
|
||||
|
||||
int err;
|
||||
opus_ = opus_decoder_create(48000, num_channels, &err);
|
||||
if (err != OPUS_OK) {
|
||||
return {input.size(), cpp::fail(Error::kInternalError)};
|
||||
}
|
||||
|
||||
return {input.size(), OutputFormat{
|
||||
.num_channels = static_cast<uint8_t>(num_channels),
|
||||
.num_channels = 2,
|
||||
.sample_rate_hz = 48000,
|
||||
}};
|
||||
}
|
||||
@@ -78,68 +80,30 @@ auto XiphOpusDecoder::BeginStream(const cpp::span<const std::byte> input)
|
||||
auto XiphOpusDecoder::ContinueStream(cpp::span<const std::byte> input,
|
||||
cpp::span<sample::Sample> output)
|
||||
-> Result<OutputInfo> {
|
||||
size_t bytes_used = 0;
|
||||
if (pos_in_buffer_ >= samples_in_buffer_) {
|
||||
if (!ogg_.HasPacket()) {
|
||||
bytes_used = input.size();
|
||||
if (!ogg_.AddBytes(input)) {
|
||||
return {bytes_used, cpp::fail(Error::kOutOfInput)};
|
||||
}
|
||||
}
|
||||
cpp::span<int16_t> staging_buffer{
|
||||
reinterpret_cast<int16_t*>(output.subspan(output.size() / 2).data()),
|
||||
output.size_bytes() / 2};
|
||||
|
||||
auto packet = ogg_.Current();
|
||||
pos_in_buffer_ = 0;
|
||||
samples_in_buffer_ = 0;
|
||||
while (samples_in_buffer_ <= 0 && ogg_.HasPacket()) {
|
||||
samples_in_buffer_ =
|
||||
opus_decode(opus_, packet.data(), packet.size_bytes(),
|
||||
sample_buffer_.data(), sample_buffer_.size(), 0);
|
||||
ogg_.Next();
|
||||
}
|
||||
input_ = input;
|
||||
pos_in_input_ = 0;
|
||||
|
||||
if (samples_in_buffer_ < 0) {
|
||||
std::string err_str;
|
||||
switch (samples_in_buffer_) {
|
||||
case OPUS_BAD_ARG:
|
||||
err_str = "OPUS_BAD_ARG";
|
||||
break;
|
||||
case OPUS_BUFFER_TOO_SMALL:
|
||||
err_str = "OPUS_BUFFER_TOO_SMALL";
|
||||
break;
|
||||
case OPUS_INTERNAL_ERROR:
|
||||
err_str = "OPUS_INTERNAL_ERROR";
|
||||
break;
|
||||
case OPUS_INVALID_PACKET:
|
||||
err_str = "OPUS_INVALID_PACKET";
|
||||
break;
|
||||
case OPUS_UNIMPLEMENTED:
|
||||
err_str = "OPUS_UNIMPLEMENTED";
|
||||
break;
|
||||
case OPUS_INVALID_STATE:
|
||||
err_str = "OPUS_INVALID_STATE";
|
||||
break;
|
||||
case OPUS_ALLOC_FAIL:
|
||||
err_str = "OPUS_ALLOC_FAIL";
|
||||
break;
|
||||
default:
|
||||
err_str = "unknown";
|
||||
}
|
||||
ESP_LOGE(kTag, "error decoding stream, err %s", err_str.c_str());
|
||||
return {bytes_used, cpp::fail(Error::kMalformedData)};
|
||||
}
|
||||
int bytes_written =
|
||||
op_read_stereo(opus_, staging_buffer.data(), staging_buffer.size());
|
||||
if (bytes_written < 0) {
|
||||
ESP_LOGE(kTag, "read failed %i", bytes_written);
|
||||
return {pos_in_input_, cpp::fail(Error::kMalformedData)};
|
||||
} else if (bytes_written == 0) {
|
||||
return {pos_in_input_, cpp::fail(Error::kOutOfInput)};
|
||||
}
|
||||
|
||||
size_t samples_written = 0;
|
||||
while (pos_in_buffer_ < samples_in_buffer_ &&
|
||||
samples_written < output.size()) {
|
||||
output[samples_written++] =
|
||||
sample::FromSigned(sample_buffer_[pos_in_buffer_++], 16);
|
||||
for (int i = 0; i < bytes_written / 2; i++) {
|
||||
output[i] = sample::FromSigned(staging_buffer[i], 16);
|
||||
}
|
||||
|
||||
return {bytes_used,
|
||||
return {pos_in_input_,
|
||||
OutputInfo{
|
||||
.samples_written = samples_written,
|
||||
.is_finished_writing = pos_in_buffer_ >= samples_in_buffer_,
|
||||
.samples_written = static_cast<size_t>(bytes_written / 2),
|
||||
.is_finished_writing = bytes_written == 0,
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -148,4 +112,12 @@ auto XiphOpusDecoder::SeekStream(cpp::span<const std::byte> input,
|
||||
return {};
|
||||
}
|
||||
|
||||
auto XiphOpusDecoder::ReadCallback() -> cpp::span<const std::byte> {
|
||||
return input_.subspan(pos_in_input_);
|
||||
}
|
||||
|
||||
auto XiphOpusDecoder::AfterReadCallback(size_t bytes_read) -> void {
|
||||
pos_in_input_ += bytes_read;
|
||||
}
|
||||
|
||||
} // namespace codecs
|
||||
|
||||
Reference in New Issue
Block a user