diff options
Diffstat (limited to 'src/codecs/opus.cpp')
| -rw-r--r-- | src/codecs/opus.cpp | 152 |
1 files changed, 62 insertions, 90 deletions
diff --git a/src/codecs/opus.cpp b/src/codecs/opus.cpp index e0bc0c29..a71c5fc0 100644 --- a/src/codecs/opus.cpp +++ b/src/codecs/opus.cpp @@ -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)}; - } - } - - 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(); - } - - 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)}; - } + 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 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 |
