mirror of
https://github.com/badaix/snapcast
synced 2025-02-22 14:54:30 +01:00
221 lines
8.5 KiB
C++
221 lines
8.5 KiB
C++
/***
|
|
This file is part of snapcast
|
|
Copyright (C) 2014-2024 Johannes Pohl
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
// prototype/interface header file
|
|
#include "resampler.hpp"
|
|
|
|
// local headers
|
|
#include "common/aixlog.hpp"
|
|
#include "common/snap_exception.hpp"
|
|
|
|
// standard headers
|
|
#include <cmath>
|
|
|
|
|
|
using namespace std;
|
|
|
|
static constexpr auto LOG_TAG = "Resampler";
|
|
|
|
Resampler::Resampler(const SampleFormat& in_format, const SampleFormat& out_format) : in_format_(in_format), out_format_(out_format)
|
|
{
|
|
#ifdef HAS_SOXR
|
|
if ((out_format_.rate() != in_format_.rate()) || (out_format_.bits() != in_format_.bits()))
|
|
{
|
|
LOG(INFO, LOG_TAG) << "Resampling from " << in_format_.toString() << " to " << out_format_.toString() << "\n";
|
|
soxr_error_t error;
|
|
|
|
soxr_datatype_t in_type = SOXR_INT16_I;
|
|
soxr_datatype_t out_type = SOXR_INT16_I;
|
|
if (in_format_.sampleSize() > 2)
|
|
in_type = SOXR_INT32_I;
|
|
if (out_format_.sampleSize() > 2)
|
|
out_type = SOXR_INT32_I;
|
|
soxr_io_spec_t iospec = soxr_io_spec(in_type, out_type);
|
|
// HQ should be fine: http://sox.sourceforge.net/Docs/FAQ
|
|
soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_HQ, 0);
|
|
soxr_ = soxr_create(static_cast<double>(in_format_.rate()), static_cast<double>(out_format_.rate()), in_format_.channels(), &error, &iospec, &q_spec,
|
|
nullptr);
|
|
if (error != nullptr)
|
|
{
|
|
LOG(ERROR, LOG_TAG) << "Error soxr_create: " << error << "\n";
|
|
soxr_ = nullptr;
|
|
}
|
|
// initialize the buffer with 20ms (~latency of the reampler)
|
|
resample_buffer_.resize(out_format_.frameSize() * static_cast<uint16_t>(ceil(out_format_.msRate() * 20)));
|
|
}
|
|
#else
|
|
LOG(WARNING, LOG_TAG) << "Soxr not available, resampling not supported\n";
|
|
if ((out_format_.rate() != in_format_.rate()) || (out_format_.bits() != in_format_.bits()))
|
|
{
|
|
throw SnapException("Resampling requested, but not supported");
|
|
}
|
|
#endif
|
|
// resampled_chunk_ = std::make_unique<msg::PcmChunk>(out_format_, 0);
|
|
}
|
|
|
|
|
|
bool Resampler::resamplingNeeded() const
|
|
{
|
|
#ifdef HAS_SOXR
|
|
return soxr_ != nullptr;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
// std::shared_ptr<msg::PcmChunk> Resampler::resample(std::shared_ptr<msg::PcmChunk> chunk, chronos::usec duration)
|
|
// {
|
|
// auto resampled_chunk = resample(chunk);
|
|
// if (!resampled_chunk)
|
|
// return nullptr;
|
|
// std::cerr << "1\n";
|
|
// resampled_chunk_->append(*resampled_chunk);
|
|
// std::cerr << "2\n";
|
|
// while (resampled_chunk_->duration<chronos::usec>() >= duration)
|
|
// {
|
|
// LOG(DEBUG, LOG_TAG) << "resampled duration: " << resampled_chunk_->durationMs() << ", consuming: " << out_format_.usRate() * duration.count() <<
|
|
// "\n";
|
|
// auto chunk = resampled_chunk_->consume(out_format_.usRate() * duration.count());
|
|
// LOG(DEBUG, LOG_TAG) << "consumed: " << chunk->durationMs() << ", resampled duration: " << resampled_chunk_->durationMs() << "\n";
|
|
// return chunk;
|
|
// }
|
|
// // resampled_chunks_.push_back(resampled_chunk);
|
|
// // chronos::usec avail;
|
|
// // for (const auto& chunk: resampled_chunks_)
|
|
// // {
|
|
// // avail += chunk->durationLeft<chronos::usec>();
|
|
// // if (avail >= duration)
|
|
// // {
|
|
|
|
// // }
|
|
// // }
|
|
// }
|
|
|
|
|
|
std::shared_ptr<msg::PcmChunk> Resampler::resample(const msg::PcmChunk& chunk)
|
|
{
|
|
#ifndef HAS_SOXR
|
|
return std::make_shared<msg::PcmChunk>(chunk);
|
|
#else
|
|
if (!resamplingNeeded())
|
|
{
|
|
return std::make_shared<msg::PcmChunk>(chunk);
|
|
}
|
|
else
|
|
{
|
|
if (in_format_.bits() == 24)
|
|
{
|
|
// sox expects 32 bit input, shift 8 bits left
|
|
auto* frames = reinterpret_cast<int32_t*>(chunk.payload);
|
|
for (size_t n = 0; n < chunk.getSampleCount(); ++n)
|
|
frames[n] = frames[n] << 8;
|
|
}
|
|
|
|
size_t idone;
|
|
size_t odone;
|
|
auto resample_buffer_framesize = resample_buffer_.size() / out_format_.frameSize();
|
|
const auto* error = soxr_process(soxr_, chunk.payload, chunk.getFrameCount(), &idone, resample_buffer_.data(), resample_buffer_framesize, &odone);
|
|
if (error != nullptr)
|
|
{
|
|
LOG(ERROR, LOG_TAG) << "Error soxr_process: " << error << "\n";
|
|
}
|
|
else
|
|
{
|
|
LOG(TRACE, LOG_TAG) << "Resample idone: " << idone << "/" << chunk.getFrameCount() << ", odone: " << odone << "/"
|
|
<< resample_buffer_.size() / out_format_.frameSize() << ", delay: " << soxr_delay(soxr_) << "\n";
|
|
|
|
// some data has been resampled (odone frames) and some is still in the pipe (soxr_delay frames)
|
|
if (odone > 0)
|
|
{
|
|
// get the resampled ts from the input ts
|
|
auto input_end_ts = chunk.start() + chunk.duration<std::chrono::microseconds>();
|
|
double resampled_ms = (odone + soxr_delay(soxr_)) / out_format_.msRate();
|
|
auto resampled_start = input_end_ts - std::chrono::microseconds(static_cast<int>(resampled_ms * 1000.));
|
|
|
|
auto resampled_chunk = std::make_shared<msg::PcmChunk>(out_format_, 0);
|
|
auto us = chrono::duration_cast<chrono::microseconds>(resampled_start.time_since_epoch()).count();
|
|
resampled_chunk->timestamp.sec = static_cast<int32_t>(us / 1000000);
|
|
resampled_chunk->timestamp.usec = static_cast<int32_t>(us % 1000000);
|
|
|
|
// copy from the resample_buffer to the resampled chunk
|
|
resampled_chunk->payloadSize = static_cast<uint32_t>(odone * out_format_.frameSize());
|
|
resampled_chunk->payload = static_cast<char*>(realloc(resampled_chunk->payload, resampled_chunk->payloadSize));
|
|
memcpy(resampled_chunk->payload, resample_buffer_.data(), resampled_chunk->payloadSize);
|
|
|
|
if (out_format_.bits() == 24)
|
|
{
|
|
// sox has quantized to 32 bit, shift 8 bits right
|
|
auto* frames = reinterpret_cast<int32_t*>(resampled_chunk->payload);
|
|
for (size_t n = 0; n < resampled_chunk->getSampleCount(); ++n)
|
|
{
|
|
// +128 to round to the nearest so that quantisation steps are distributed evenly
|
|
frames[n] = (frames[n] + 128) >> 8;
|
|
if (frames[n] > 0x7fffffff)
|
|
frames[n] = 0x7fffffff;
|
|
}
|
|
}
|
|
|
|
// check if the resample_buffer is large enough, or if soxr was using all available space
|
|
if (odone == resample_buffer_framesize)
|
|
{
|
|
// buffer for resampled data too small, add space for 5ms
|
|
resample_buffer_.resize(resample_buffer_.size() + out_format_.frameSize() * static_cast<uint16_t>(ceil(out_format_.msRate() * 5)));
|
|
LOG(DEBUG, LOG_TAG) << "Resample buffer completely filled, adding space for 5ms; new buffer size: " << resample_buffer_.size()
|
|
<< " bytes\n";
|
|
}
|
|
|
|
// //LOG(TRACE, LOG_TAG) << "ts: " << out->timestamp.sec << "s, " << out->timestamp.usec/1000.f << " ms, duration: " << odone / format_.msRate()
|
|
// << "\n";
|
|
// int64_t next_us = us + static_cast<int64_t>(odone / format_.msRate() * 1000);
|
|
// LOG(TRACE, LOG_TAG) << "ts: " << us << ", next: " << next_us << ", diff: " << next_us_ - us << "\n";
|
|
// next_us_ = next_us;
|
|
|
|
return resampled_chunk;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
|
|
shared_ptr<msg::PcmChunk> Resampler::resample(shared_ptr<msg::PcmChunk> chunk)
|
|
{
|
|
#ifndef HAS_SOXR
|
|
return chunk;
|
|
#else
|
|
if (!resamplingNeeded())
|
|
{
|
|
return chunk;
|
|
}
|
|
else
|
|
{
|
|
return resample(*chunk);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
Resampler::~Resampler()
|
|
{
|
|
#ifdef HAS_SOXR
|
|
if (soxr_ != nullptr)
|
|
soxr_delete(soxr_);
|
|
#endif
|
|
}
|