Fork of Tangara with customizations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
tangara-fw/src/codecs/opus.cpp

166 lines
3.7 KiB

/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "opus.hpp"
#include <stdint.h>
#include <sys/_stdint.h>
#include <sys/unistd.h>
#include <cstdint>
#include <cstring>
#include <optional>
#include "esp_heap_caps.h"
#include "mad.h"
#include "codec.hpp"
#include "esp_log.h"
#include "opusfile.h"
#include "result.hpp"
#include "sample.hpp"
#include "types.hpp"
namespace codecs {
[[maybe_unused]] static constexpr char kTag[] = "opus";
static int read_cb(void* src, unsigned char* ptr, int nbytes) {
IStream* source = reinterpret_cast<IStream*>(src);
return source->Read(
{reinterpret_cast<std::byte*>(ptr), static_cast<size_t>(nbytes)});
}
static int seek_cb(void* src, int64_t offset, int whence) {
IStream* source = reinterpret_cast<IStream*>(src);
if (!source->CanSeek()) {
return -1;
}
IStream::SeekFrom from;
switch (whence) {
case SEEK_CUR:
from = IStream::SeekFrom::kCurrentPosition;
break;
case SEEK_END:
from = IStream::SeekFrom::kEndOfStream;
break;
case SEEK_SET:
from = IStream::SeekFrom::kStartOfStream;
break;
default:
return -1;
}
source->SeekTo(offset, from);
return 0;
}
static int64_t tell_cb(void* src) {
IStream* source = reinterpret_cast<IStream*>(src);
return source->CurrentPosition();
}
static const OpusFileCallbacks kCallbacks{
.read = read_cb,
.seek = seek_cb,
.tell = tell_cb,
.close = NULL,
};
XiphOpusDecoder::XiphOpusDecoder()
: input_(nullptr), opus_(nullptr), num_channels_() {}
XiphOpusDecoder::~XiphOpusDecoder() {
if (opus_ != nullptr) {
op_free(opus_);
}
}
auto XiphOpusDecoder::OpenStream(std::shared_ptr<IStream> input,
uint32_t offset)
-> cpp::result<OutputFormat, Error> {
input_ = input;
int res;
opus_ = op_open_callbacks(input.get(), &kCallbacks, nullptr, 0, &res);
if (res < 0) {
std::pmr::string err;
switch (res) {
case OP_EREAD:
err = "OP_EREAD";
break;
case OP_EFAULT:
err = "OP_EFAULT";
break;
case OP_EIMPL:
err = "OP_EIMPL";
break;
case OP_EINVAL:
err = "OP_EINVAL";
break;
case OP_ENOTFORMAT:
err = "OP_ENOTFORMAT";
break;
case OP_EBADHEADER:
err = "OP_EBADHEADER";
break;
case OP_EVERSION:
err = "OP_EVERSION";
break;
case OP_EBADLINK:
err = "OP_EBADLINK";
break;
case OP_EBADTIMESTAMP:
err = "OP_BADTIMESTAMP";
break;
default:
err = "unknown";
}
ESP_LOGE(kTag, "error beginning stream: %s", err.c_str());
return cpp::fail(Error::kMalformedData);
}
auto l = op_pcm_total(opus_, -1);
std::optional<uint32_t> length;
if (l > 0) {
length = l * 2;
}
auto b = op_bitrate(opus_, -1);
std::optional<uint32_t> bitrate_kbps;
if (b > 0) {
bitrate_kbps = b / 1024;
}
if (offset && op_pcm_seek(opus_, offset * 48000) != 0) {
return cpp::fail(Error::kInternalError);
}
return OutputFormat{
.num_channels = 2,
.sample_rate_hz = 48000,
.total_samples = length,
.bitrate_kbps = bitrate_kbps,
};
}
auto XiphOpusDecoder::DecodeTo(std::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> {
int samples_written = op_read_stereo(opus_, output.data(), output.size());
if (samples_written < 0) {
ESP_LOGE(kTag, "read failed %i", samples_written);
return cpp::fail(Error::kMalformedData);
}
samples_written *= 2; // Fixed to stereo
return OutputInfo{
.samples_written = static_cast<size_t>(samples_written),
.is_stream_finished = samples_written == 0,
};
}
} // namespace codecs