mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-22 13:36:18 +02:00
Add optional resampling of the output stream
This commit is contained in:
parent
549fcba40f
commit
3fc8b9ca08
8 changed files with 185 additions and 98 deletions
|
@ -43,7 +43,7 @@ ifneq ($(SANITIZE), )
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CXXFLAGS += $(ADD_CFLAGS) -std=c++14 -Wall -Wextra -Wpedantic -Wno-unused-function -DBOOST_ERROR_CODE_HEADER_ONLY -DHAS_FLAC -DHAS_OGG -DHAS_OPUS -DVERSION=\"$(VERSION)\" -I. -I.. -I../common
|
CXXFLAGS += $(ADD_CFLAGS) -std=c++14 -Wall -Wextra -Wpedantic -Wno-unused-function -DBOOST_ERROR_CODE_HEADER_ONLY -DHAS_FLAC -DHAS_OGG -DHAS_OPUS -DVERSION=\"$(VERSION)\" -I. -I.. -I../common
|
||||||
LDFLAGS += $(ADD_LDFLAGS) -logg -lFLAC -lopus
|
LDFLAGS += $(ADD_LDFLAGS) -logg -lFLAC -lopus -lsoxr
|
||||||
OBJ = snapclient.o stream.o client_connection.o time_provider.o player/player.o decoder/pcm_decoder.o decoder/ogg_decoder.o decoder/flac_decoder.o decoder/opus_decoder.o controller.o ../common/sample_format.o
|
OBJ = snapclient.o stream.o client_connection.o time_provider.o player/player.o decoder/pcm_decoder.o decoder/ogg_decoder.o decoder/flac_decoder.o decoder/opus_decoder.o controller.o ../common/sample_format.o
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ struct ClientSettings
|
||||||
std::string player_name{""};
|
std::string player_name{""};
|
||||||
int latency{0};
|
int latency{0};
|
||||||
PcmDevice pcm_device;
|
PcmDevice pcm_device;
|
||||||
|
SampleFormat sample_format;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LoggingSettings
|
struct LoggingSettings
|
||||||
|
|
|
@ -121,7 +121,7 @@ void Controller::onMessageReceived(ClientConnection* /*connection*/, const msg::
|
||||||
sampleFormat_ = decoder_->setHeader(headerChunk_.get());
|
sampleFormat_ = decoder_->setHeader(headerChunk_.get());
|
||||||
LOG(NOTICE) << TAG("state") << "sampleformat: " << sampleFormat_.rate << ":" << sampleFormat_.bits << ":" << sampleFormat_.channels << "\n";
|
LOG(NOTICE) << TAG("state") << "sampleformat: " << sampleFormat_.rate << ":" << sampleFormat_.bits << ":" << sampleFormat_.channels << "\n";
|
||||||
|
|
||||||
stream_ = make_shared<Stream>(sampleFormat_);
|
stream_ = make_shared<Stream>(sampleFormat_, settings_.player.sample_format);
|
||||||
stream_->setBufferLen(serverSettings_->getBufferMs() - settings_.player.latency);
|
stream_->setBufferLen(serverSettings_->getBufferMs() - settings_.player.latency);
|
||||||
|
|
||||||
const auto& pcm_device = settings_.player.pcm_device;
|
const auto& pcm_device = settings_.player.pcm_device;
|
||||||
|
|
|
@ -84,7 +84,6 @@ int main(int argc, char** argv)
|
||||||
ClientSettings settings;
|
ClientSettings settings;
|
||||||
string pcm_device("default");
|
string pcm_device("default");
|
||||||
|
|
||||||
|
|
||||||
OptionParser op("Allowed options");
|
OptionParser op("Allowed options");
|
||||||
auto helpSwitch = op.add<Switch>("", "help", "produce help message");
|
auto helpSwitch = op.add<Switch>("", "help", "produce help message");
|
||||||
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
|
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
|
||||||
|
@ -106,6 +105,7 @@ int main(int argc, char** argv)
|
||||||
/*auto instanceValue =*/op.add<Value<size_t>>("i", "instance", "instance id", 1, &settings.instance);
|
/*auto instanceValue =*/op.add<Value<size_t>>("i", "instance", "instance id", 1, &settings.instance);
|
||||||
/*auto hostIdValue =*/op.add<Value<string>>("", "hostID", "unique host id", "", &settings.host_id);
|
/*auto hostIdValue =*/op.add<Value<string>>("", "hostID", "unique host id", "", &settings.host_id);
|
||||||
op.add<Value<string>>("", "player", "audio backend", "", &settings.player.player_name);
|
op.add<Value<string>>("", "player", "audio backend", "", &settings.player.player_name);
|
||||||
|
auto sample_format = op.add<Value<string>>("", "sampleformat", "resample audio stream to sampleformat", "");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -210,6 +210,11 @@ int main(int argc, char** argv)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (sample_format->is_set())
|
||||||
|
{
|
||||||
|
settings.player.sample_format = SampleFormat(sample_format->value());
|
||||||
|
}
|
||||||
|
|
||||||
bool active = true;
|
bool active = true;
|
||||||
std::shared_ptr<Controller> controller;
|
std::shared_ptr<Controller> controller;
|
||||||
auto signal_handler = install_signal_handler({SIGHUP, SIGTERM, SIGINT},
|
auto signal_handler = install_signal_handler({SIGHUP, SIGTERM, SIGINT},
|
||||||
|
|
|
@ -28,17 +28,19 @@ using namespace std;
|
||||||
namespace cs = chronos;
|
namespace cs = chronos;
|
||||||
|
|
||||||
|
|
||||||
Stream::Stream(const SampleFormat& sampleFormat)
|
Stream::Stream(const SampleFormat& in_format, const SampleFormat& out_format)
|
||||||
: format_(sampleFormat), sleep_(0), median_(0), shortMedian_(0), lastUpdate_(0), playedFrames_(0), bufferMs_(cs::msec(500))
|
: in_format_(in_format), sleep_(0), median_(0), shortMedian_(0), lastUpdate_(0), playedFrames_(0), bufferMs_(cs::msec(500)), soxr_(nullptr), frame_delta_(0)
|
||||||
{
|
{
|
||||||
buffer_.setSize(500);
|
buffer_.setSize(500);
|
||||||
shortBuffer_.setSize(100);
|
shortBuffer_.setSize(100);
|
||||||
miniBuffer_.setSize(20);
|
miniBuffer_.setSize(20);
|
||||||
// cardBuffer_.setSize(50);
|
// cardBuffer_.setSize(50);
|
||||||
|
|
||||||
// input_rate_ = format_.rate;
|
if (out_format.rate != 0)
|
||||||
// format_.rate = 48000;
|
format_ = out_format;
|
||||||
// output_rate_ = static_cast<double>(format_.rate);
|
else
|
||||||
|
format_ = in_format_;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
48000 x
|
48000 x
|
||||||
------- = -----
|
------- = -----
|
||||||
|
@ -46,16 +48,37 @@ Stream::Stream(const SampleFormat& sampleFormat)
|
||||||
|
|
||||||
x = 1,000016667 / (1,000016667 - 1)
|
x = 1,000016667 / (1,000016667 - 1)
|
||||||
*/
|
*/
|
||||||
setRealSampleRate(format_.rate);
|
// setRealSampleRate(format_.rate);
|
||||||
|
|
||||||
// soxr_error_t error;
|
if ((format_.rate != in_format_.rate) || (format_.bits != in_format_.bits))
|
||||||
// soxr_io_spec_t iospec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
|
{
|
||||||
// soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_HQ, 0);
|
LOG(INFO) << "Resampling from " << in_format_.getFormat() << " to " << format_.getFormat() << "\n";
|
||||||
// soxr_ = soxr_create(static_cast<double>(input_rate_), static_cast<double>(format_.rate), format_.channels, &error, &iospec, &q_spec, NULL);
|
soxr_error_t error;
|
||||||
// if (error)
|
|
||||||
// {
|
soxr_datatype_t in_type = SOXR_INT16_I;
|
||||||
// LOG(ERROR) << "Error soxr_create: " << error << "\n";
|
soxr_datatype_t out_type = SOXR_INT16_I;
|
||||||
// }
|
if (in_format_.sampleSize > 2)
|
||||||
|
in_type = SOXR_INT32_I;
|
||||||
|
if (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>(format_.rate), format_.channels, &error, &iospec, &q_spec, NULL);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Error soxr_create: " << error << "\n";
|
||||||
|
}
|
||||||
|
// initialize the buffer with 20ms (~latency of the reampler)
|
||||||
|
resample_buffer_.resize(format_.frameSize * ceil(format_.msRate()) * 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Stream::~Stream()
|
||||||
|
{
|
||||||
|
if (soxr_)
|
||||||
|
soxr_delete(soxr_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +98,6 @@ void Stream::setBufferLen(size_t bufferLenMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Stream::clearChunks()
|
void Stream::clearChunks()
|
||||||
{
|
{
|
||||||
while (chunks_.size() > 0)
|
while (chunks_.size() > 0)
|
||||||
|
@ -89,41 +111,85 @@ void Stream::addChunk(unique_ptr<msg::PcmChunk> chunk)
|
||||||
while (chunks_.size() * chunk->duration<cs::msec>().count() > 10000)
|
while (chunks_.size() * chunk->duration<cs::msec>().count() > 10000)
|
||||||
chunks_.pop();
|
chunks_.pop();
|
||||||
|
|
||||||
chunks_.push(move(chunk));
|
// chunks_.push(move(chunk));
|
||||||
// LOG(DEBUG) << "new chunk: " << chunk->duration<cs::msec>().count() << ", Chunks: " << chunks_.size() << "\n";
|
// LOG(DEBUG) << "new chunk: " << chunk->duration<cs::msec>().count() << ", Chunks: " << chunks_.size() << "\n";
|
||||||
|
|
||||||
// if (std::abs(input_rate_ - output_rate_) <= 0.0000001)
|
if (soxr_ == nullptr)
|
||||||
// {
|
{
|
||||||
// chunks_.push(shared_ptr<msg::PcmChunk>(chunk));
|
chunks_.push(move(chunk));
|
||||||
// }
|
}
|
||||||
// else
|
else
|
||||||
// {
|
{
|
||||||
// size_t idone;
|
size_t idone;
|
||||||
// size_t odone;
|
size_t odone;
|
||||||
// auto out = new msg::PcmChunk(format_, 0);
|
|
||||||
// out->timestamp = chunk->timestamp;
|
|
||||||
// out->payloadSize = ceil(chunk->payloadSize * static_cast<double>(output_rate_) / static_cast<double>(input_rate_));
|
|
||||||
// out->payload = (char*)malloc(out->payloadSize);
|
|
||||||
|
|
||||||
// soxr_io_spec_t iospec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
|
if (in_format_.bits == 24)
|
||||||
// soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_HQ, 0);
|
{
|
||||||
// // auto error = soxr_oneshot(static_cast<double>(input_rate_), output_rate_, format_.channels, chunk->payload, chunk->getFrameCount(), &idone,
|
// sox expects 32 bit input, shift 8 bits left
|
||||||
// // out->payload, out->payloadSize, &odone, &iospec, &q_spec, nullptr);
|
int32_t* frames = (int32_t*)chunk->payload;
|
||||||
// auto error = soxr_process(soxr_, chunk->payload, chunk->getFrameCount(), &idone, out->payload, out->getFrameCount(), &odone);
|
for (size_t n = 0; n < chunk->getSampleCount(); ++n)
|
||||||
// if (error)
|
frames[n] = frames[n] << 8;
|
||||||
// {
|
}
|
||||||
// LOG(ERROR) << "Error soxr_process: " << error << "\n";
|
|
||||||
|
auto resample_buffer_framesize = resample_buffer_.size() / format_.frameSize;
|
||||||
|
auto error = soxr_process(soxr_, chunk->payload, chunk->getFrameCount(), &idone, resample_buffer_.data(), resample_buffer_framesize, &odone);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Error soxr_process: " << error << "\n";
|
||||||
// delete out;
|
// delete out;
|
||||||
// }
|
}
|
||||||
// else
|
else
|
||||||
// {
|
{
|
||||||
// out->payloadSize = odone * out->format.frameSize;
|
LOG(TRACE) << "Resample idone: " << idone << "/" << chunk->getFrameCount() << ", odone: " << odone << "/"
|
||||||
// LOG(TRACE) << "Resample idone: " << idone << "/" << chunk->getFrameCount() << ", odone: " << odone << "/"
|
<< resample_buffer_.size() / format_.frameSize << ", delay: " << soxr_delay(soxr_) << "\n";
|
||||||
// << out->payloadSize / out->format.frameSize << "\n";
|
|
||||||
// chunks_.push(shared_ptr<msg::PcmChunk>(out));
|
// some data has been resampled (odone frames) and some is still in the pipe (soxr_delay frames)
|
||||||
// }
|
if (odone > 0)
|
||||||
// delete chunk;
|
{
|
||||||
// }
|
// get the resamples ts from the input ts
|
||||||
|
auto input_end_ts = chunk->start() + chunk->duration<std::chrono::microseconds>();
|
||||||
|
double resampled_ms = (odone + soxr_delay(soxr_)) / format_.msRate();
|
||||||
|
auto resampled_start = input_end_ts - std::chrono::microseconds(static_cast<int>(resampled_ms * 1000.));
|
||||||
|
|
||||||
|
auto resampled_chunk = new msg::PcmChunk(format_, 0);
|
||||||
|
auto us = chrono::duration_cast<chrono::microseconds>(resampled_start.time_since_epoch()).count();
|
||||||
|
resampled_chunk->timestamp.sec = us / 1000000;
|
||||||
|
resampled_chunk->timestamp.usec = us % 1000000;
|
||||||
|
|
||||||
|
// copy from the resample_buffer to the resampled chunk
|
||||||
|
resampled_chunk->payloadSize = odone * format_.frameSize;
|
||||||
|
resampled_chunk->payload = (char*)realloc(resampled_chunk->payload, resampled_chunk->payloadSize);
|
||||||
|
memcpy(resampled_chunk->payload, resample_buffer_.data(), resampled_chunk->payloadSize);
|
||||||
|
|
||||||
|
if (format_.bits == 24)
|
||||||
|
{
|
||||||
|
// sox has quantized to 32 bit, shift 8 bits right
|
||||||
|
int32_t* frames = (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunks_.push(shared_ptr<msg::PcmChunk>(resampled_chunk));
|
||||||
|
|
||||||
|
// 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() + format_.frameSize * ceil(format_.msRate()) * 5);
|
||||||
|
LOG(INFO) << "Resample buffer completely filled, adding space for 5ms; new buffer size: " << resample_buffer_.size() << " bytes\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// //LOG(TRACE) << "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) << "ts: " << us << ", next: " << next_us << ", diff: " << next_us_ - us << "\n";
|
||||||
|
// next_us_ = next_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,12 +200,12 @@ bool Stream::waitForChunk(size_t ms) const
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cs::time_point_clk Stream::getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer)
|
cs::time_point_clk Stream::getSilentPlayerChunk(void* outputBuffer, unsigned long frames)
|
||||||
{
|
{
|
||||||
if (!chunk_)
|
if (!chunk_)
|
||||||
chunk_ = chunks_.pop();
|
chunk_ = chunks_.pop();
|
||||||
cs::time_point_clk tp = chunk_->start();
|
cs::time_point_clk tp = chunk_->start();
|
||||||
memset(outputBuffer, 0, framesPerBuffer * format_.frameSize);
|
memset(outputBuffer, 0, frames * format_.frameSize);
|
||||||
return tp;
|
return tp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,16 +248,16 @@ time_point_clk Stream::seek(long ms)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec& timeout, unsigned long framesPerBuffer)
|
cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec& timeout, unsigned long frames)
|
||||||
{
|
{
|
||||||
if (!chunk_ && !chunks_.try_pop(chunk_, timeout))
|
if (!chunk_ && !chunks_.try_pop(chunk_, timeout))
|
||||||
throw 0;
|
throw 0;
|
||||||
|
|
||||||
cs::time_point_clk tp = chunk_->start();
|
cs::time_point_clk tp = chunk_->start();
|
||||||
unsigned long read = 0;
|
unsigned long read = 0;
|
||||||
while (read < framesPerBuffer)
|
while (read < frames)
|
||||||
{
|
{
|
||||||
read += chunk_->readFrames(static_cast<char*>(outputBuffer) + read * format_.frameSize, framesPerBuffer - read);
|
read += chunk_->readFrames(static_cast<char*>(outputBuffer) + read * format_.frameSize, frames - read);
|
||||||
if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout))
|
if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout))
|
||||||
throw 0;
|
throw 0;
|
||||||
}
|
}
|
||||||
|
@ -199,22 +265,24 @@ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec& timeout, unsigned long framesPerBuffer, long framesCorrection)
|
cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec& timeout, unsigned long frames, long framesCorrection)
|
||||||
{
|
{
|
||||||
if (framesCorrection < 0 && framesPerBuffer + framesCorrection <= 0)
|
if (framesCorrection < 0 && frames + framesCorrection <= 0)
|
||||||
{
|
{
|
||||||
// Avoid underflow in new char[] constructor.
|
// Avoid underflow in new char[] constructor.
|
||||||
framesCorrection = -framesPerBuffer + 1;
|
framesCorrection = -frames + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (framesCorrection == 0)
|
frame_delta_ -= framesCorrection;
|
||||||
return getNextPlayerChunk(outputBuffer, timeout, framesPerBuffer);
|
|
||||||
|
|
||||||
long toRead = framesPerBuffer + framesCorrection;
|
if (framesCorrection == 0)
|
||||||
|
return getNextPlayerChunk(outputBuffer, timeout, frames);
|
||||||
|
|
||||||
|
long toRead = frames + framesCorrection;
|
||||||
char* buffer = new char[toRead * format_.frameSize];
|
char* buffer = new char[toRead * format_.frameSize];
|
||||||
cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead);
|
cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead);
|
||||||
|
|
||||||
const auto max = framesCorrection < 0 ? framesPerBuffer : toRead;
|
const auto max = framesCorrection < 0 ? frames : toRead;
|
||||||
// Divide the buffer into one more slice than frames that need to be dropped.
|
// Divide the buffer into one more slice than frames that need to be dropped.
|
||||||
// We will drop/repeat 0 frames from the first slice, 1 frame from the second, ..., and framesCorrection frames from the last slice.
|
// We will drop/repeat 0 frames from the first slice, 1 frame from the second, ..., and framesCorrection frames from the last slice.
|
||||||
size_t slices = abs(framesCorrection) + 1;
|
size_t slices = abs(framesCorrection) + 1;
|
||||||
|
@ -228,42 +296,34 @@ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec
|
||||||
// Size of each slice. The last slice may be bigger.
|
// Size of each slice. The last slice may be bigger.
|
||||||
int size = max / slices;
|
int size = max / slices;
|
||||||
|
|
||||||
// LOG(TRACE) << "getNextPlayerChunk, frames: " << framesPerBuffer << ", correction: " << framesCorrection << " (" << toRead << "), slices: " << slices
|
// LOG(TRACE) << "getNextPlayerChunk, frames: " << frames << ", correction: " << framesCorrection << " (" << toRead << "), slices: " << slices
|
||||||
// << "\n";
|
// << "\n";
|
||||||
|
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
for (size_t n = 0; n < slices; ++n)
|
for (size_t n = 0; n < slices; ++n)
|
||||||
{
|
{
|
||||||
if (n + 1 == slices)
|
|
||||||
// Adjust size in the last iteration, because the last slice may be bigger
|
// Adjust size in the last iteration, because the last slice may be bigger
|
||||||
|
if (n + 1 == slices)
|
||||||
size = max - pos;
|
size = max - pos;
|
||||||
|
|
||||||
if (framesCorrection < 0)
|
if (framesCorrection < 0)
|
||||||
{
|
{
|
||||||
// Read one frame less per slice from the input, but write a duplicated frame per slice to the output
|
// Read one frame less per slice from the input, but write a duplicated frame per slice to the output
|
||||||
|
// LOG(TRACE) << "duplicate - requested: " << frames << ", read: " << toRead << ", slice: " << n << ", size: " << size << ", out pos: " << pos << ",
|
||||||
// LOG(TRACE) << "slice: " << n << ", size: " << size << ", out pos: " << pos << ", source pos: " << pos - n << "\n";
|
// source pos: " << pos - n << "\n";
|
||||||
memcpy(static_cast<char*>(outputBuffer) + pos * format_.frameSize, buffer + (pos - n) * format_.frameSize, size * format_.frameSize);
|
memcpy(static_cast<char*>(outputBuffer) + pos * format_.frameSize, buffer + (pos - n) * format_.frameSize, size * format_.frameSize);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Read all input frames, but skip a frame per slice when writing to the output.
|
// Read all input frames, but skip a frame per slice when writing to the output.
|
||||||
|
// LOG(TRACE) << "remove - requested: " << frames << ", read: " << toRead << ", slice: " << n << ", size: " << size << ", out pos: " << pos - n <<
|
||||||
// LOG(TRACE) << "slice: " << n << ", size: " << size << ", out pos: " << pos - n << ", source pos: " << pos << "\n";
|
// ", source pos: " << pos << "\n";
|
||||||
memcpy(static_cast<char*>(outputBuffer) + (pos - n) * format_.frameSize, buffer + pos * format_.frameSize, size * format_.frameSize);
|
memcpy(static_cast<char*>(outputBuffer) + (pos - n) * format_.frameSize, buffer + pos * format_.frameSize, size * format_.frameSize);
|
||||||
}
|
}
|
||||||
pos += size;
|
pos += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// float idx = 0;
|
|
||||||
// for (size_t n = 0; n < framesPerBuffer; ++n)
|
|
||||||
// {
|
|
||||||
// size_t index(floor(idx)); // = (int)(ceil(n*factor));
|
|
||||||
// memcpy((char*)outputBuffer + n * format_.frameSize, buffer + index * format_.frameSize, format_.frameSize);
|
|
||||||
// idx += factor;
|
|
||||||
// }
|
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
|
|
||||||
return tp;
|
return tp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,7 +372,7 @@ void Stream::resetBuffers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacTime, unsigned long framesPerBuffer)
|
bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacTime, unsigned long frames)
|
||||||
{
|
{
|
||||||
if (outputBufferDacTime > bufferMs_)
|
if (outputBufferDacTime > bufferMs_)
|
||||||
{
|
{
|
||||||
|
@ -328,7 +388,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
playedFrames_ += framesPerBuffer;
|
playedFrames_ += frames;
|
||||||
|
|
||||||
/// we have a chunk
|
/// we have a chunk
|
||||||
/// age = chunk age (server now - rec time: some positive value) - buffer (e.g. 1000ms) + time to DAC
|
/// age = chunk age (server now - rec time: some positive value) - buffer (e.g. 1000ms) + time to DAC
|
||||||
|
@ -346,8 +406,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
// LOG(DEBUG) << "framesPerBuffer: " << framesPerBuffer << "\tms: " << framesPerBuffer*2 / PLAYER_CHUNK_MS_SIZE << "\t" << PLAYER_CHUNK_SIZE << "\n";
|
// LOG(DEBUG) << "frames: " << frames << "\tms: " << frames*2 / PLAYER_CHUNK_MS_SIZE << "\t" << PLAYER_CHUNK_SIZE << "\n";
|
||||||
cs::nsec bufferDuration = cs::nsec(static_cast<cs::nsec::rep>(framesPerBuffer / format_.nsRate()));
|
cs::nsec bufferDuration = cs::nsec(static_cast<cs::nsec::rep>(frames / format_.nsRate()));
|
||||||
// LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n";
|
// LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n";
|
||||||
|
|
||||||
cs::usec correction = cs::usec(0);
|
cs::usec correction = cs::usec(0);
|
||||||
|
@ -358,8 +418,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
{
|
{
|
||||||
LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " < " << -cs::duration<cs::msec>(bufferDuration) / 2 << ", ";
|
LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " < " << -cs::duration<cs::msec>(bufferDuration) / 2 << ", ";
|
||||||
// We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one
|
// We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one
|
||||||
sleep_ = chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ +
|
sleep_ =
|
||||||
outputBufferDacTime);
|
chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, frames) - bufferMs_ + outputBufferDacTime);
|
||||||
LOG(INFO) << "sleep: " << cs::duration<cs::msec>(sleep_) << "\n";
|
LOG(INFO) << "sleep: " << cs::duration<cs::msec>(sleep_) << "\n";
|
||||||
if (sleep_ < -bufferDuration / 2)
|
if (sleep_ < -bufferDuration / 2)
|
||||||
return true;
|
return true;
|
||||||
|
@ -413,9 +473,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
playedFrames_ = 0; //-= abs(correctAfterXFrames_);
|
playedFrames_ = 0; //-= abs(correctAfterXFrames_);
|
||||||
}
|
}
|
||||||
|
|
||||||
age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() -
|
age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getNextPlayerChunk(outputBuffer, outputBufferDacTime, frames, framesCorrection) -
|
||||||
getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ +
|
bufferMs_ + outputBufferDacTime);
|
||||||
outputBufferDacTime);
|
|
||||||
|
|
||||||
setRealSampleRate(format_.rate);
|
setRealSampleRate(format_.rate);
|
||||||
if (sleep_.count() == 0)
|
if (sleep_.count() == 0)
|
||||||
|
@ -495,9 +554,10 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
median_ = buffer_.median();
|
median_ = buffer_.median();
|
||||||
shortMedian_ = shortBuffer_.median();
|
shortMedian_ = shortBuffer_.median();
|
||||||
LOG(INFO) << "Chunk: " << age.count() / 100 << "\t" << miniBuffer_.median() / 100 << "\t" << shortMedian_ / 100 << "\t" << median_ / 100 << "\t"
|
LOG(INFO) << "Chunk: " << age.count() / 100 << "\t" << miniBuffer_.median() / 100 << "\t" << shortMedian_ / 100 << "\t" << median_ / 100 << "\t"
|
||||||
<< buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
<< buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\t" << frame_delta_ << "\n";
|
||||||
// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t" <<
|
// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t" <<
|
||||||
// buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
// buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
||||||
|
frame_delta_ = 0;
|
||||||
}
|
}
|
||||||
return (abs(cs::duration<cs::msec>(age)) < 500);
|
return (abs(cs::duration<cs::msec>(age)) < 500);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
#include "message/pcm_chunk.hpp"
|
#include "message/pcm_chunk.hpp"
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
// #include <soxr.h>
|
#include <soxr.h>
|
||||||
|
|
||||||
|
|
||||||
/// Time synchronized audio stream
|
/// Time synchronized audio stream
|
||||||
|
@ -37,7 +37,8 @@
|
||||||
class Stream
|
class Stream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Stream(const SampleFormat& format);
|
Stream(const SampleFormat& in_format, const SampleFormat& out_format);
|
||||||
|
virtual ~Stream();
|
||||||
|
|
||||||
/// Adds PCM data to the queue
|
/// Adds PCM data to the queue
|
||||||
void addChunk(std::unique_ptr<msg::PcmChunk> chunk);
|
void addChunk(std::unique_ptr<msg::PcmChunk> chunk);
|
||||||
|
@ -58,9 +59,9 @@ public:
|
||||||
bool waitForChunk(size_t ms) const;
|
bool waitForChunk(size_t ms) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer);
|
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long frames);
|
||||||
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer, long framesCorrection);
|
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long frames, long framesCorrection);
|
||||||
chronos::time_point_clk getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer);
|
chronos::time_point_clk getSilentPlayerChunk(void* outputBuffer, unsigned long frames);
|
||||||
chronos::time_point_clk seek(long ms);
|
chronos::time_point_clk seek(long ms);
|
||||||
// time_point_ms seekTo(const time_point_ms& to);
|
// time_point_ms seekTo(const time_point_ms& to);
|
||||||
void updateBuffers(int age);
|
void updateBuffers(int age);
|
||||||
|
@ -68,6 +69,7 @@ private:
|
||||||
void setRealSampleRate(double sampleRate);
|
void setRealSampleRate(double sampleRate);
|
||||||
|
|
||||||
SampleFormat format_;
|
SampleFormat format_;
|
||||||
|
SampleFormat in_format_;
|
||||||
|
|
||||||
chronos::usec sleep_;
|
chronos::usec sleep_;
|
||||||
|
|
||||||
|
@ -84,9 +86,11 @@ private:
|
||||||
unsigned long playedFrames_;
|
unsigned long playedFrames_;
|
||||||
long correctAfterXFrames_;
|
long correctAfterXFrames_;
|
||||||
chronos::msec bufferMs_;
|
chronos::msec bufferMs_;
|
||||||
// size_t input_rate_;
|
|
||||||
// double output_rate_;
|
soxr_t soxr_;
|
||||||
// soxr_t soxr_;
|
std::vector<char> resample_buffer_;
|
||||||
|
int frame_delta_;
|
||||||
|
// int64_t next_us_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,10 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
SampleFormat::SampleFormat() = default;
|
SampleFormat::SampleFormat()
|
||||||
|
{
|
||||||
|
setFormat(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SampleFormat::SampleFormat(const std::string& format)
|
SampleFormat::SampleFormat(const std::string& format)
|
||||||
|
|
18
externals/Makefile
vendored
18
externals/Makefile
vendored
|
@ -14,9 +14,9 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
.PHONY: all check-env flac ogg opus tremor oboe
|
.PHONY: all check-env flac ogg opus tremor oboe soxr
|
||||||
|
|
||||||
all: flac ogg opus tremor oboe
|
all: flac ogg opus tremor oboe soxr
|
||||||
|
|
||||||
check-env:
|
check-env:
|
||||||
# if [ ! -d "flac" ]; then \
|
# if [ ! -d "flac" ]; then \
|
||||||
|
@ -128,3 +128,17 @@ oboe: check-env
|
||||||
make clean; \
|
make clean; \
|
||||||
cd ..; \
|
cd ..; \
|
||||||
rm -rf build;
|
rm -rf build;
|
||||||
|
|
||||||
|
soxr: check-env
|
||||||
|
@cd /home/johannes/Develop/soxr; \
|
||||||
|
export CC="$(CC)"; \
|
||||||
|
export CXX="$(CXX)"; \
|
||||||
|
export CPPFLAGS="$(CPPFLAGS)"; \
|
||||||
|
mkdir build; \
|
||||||
|
cd build; \
|
||||||
|
cmake -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTS=OFF -DWITH_OPENMP=OFF ..; \
|
||||||
|
make; \
|
||||||
|
make DESTDIR=$(NDK_DIR) install; \
|
||||||
|
make clean; \
|
||||||
|
cd ..; \
|
||||||
|
rm -rf build;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue