Significant further work on internal buffer sizes

This commit is contained in:
Chris Cannam
2023-06-01 14:09:39 +01:00
parent af6759f74b
commit b1cd0913e2
4 changed files with 116 additions and 75 deletions

View File

@@ -803,6 +803,13 @@ public:
* study() (to which you may pass any number of samples at a time, * study() (to which you may pass any number of samples at a time,
* and from which there is no output). * and from which there is no output).
* *
* Despite the existence of this call and its use of a size_t
* argument, there is an internal limit to the maximum process
* buffer size that can be requested. This is currently 524288 (or
* 2^19). The Rubber Band API is essentially block-based and is
* not designed to process an entire signal within a single
* process cycle.
*
* Note that the value of "samples" refers to the number of audio * Note that the value of "samples" refers to the number of audio
* sample frames, which may be multi-channel, not the number of * sample frames, which may be multi-channel, not the number of
* individual samples. (For example, one second of stereo audio * individual samples. (For example, one second of stereo audio

View File

@@ -637,32 +637,58 @@ R3Stretcher::getSamplesRequired() const
} }
void void
R3Stretcher::setMaxProcessSize(size_t n) R3Stretcher::setMaxProcessSize(size_t requested)
{ {
size_t oldInSize = m_channelData[0]->inbuf->getSize(); m_log.log(2, "R3Stretcher::setMaxProcessSize", requested);
size_t newInSize = getWindowSourceSize() + n;
size_t oldOutSize = m_channelData[0]->outbuf->getSize(); int n = m_limits.overallMaxProcessSize;
size_t newOutSize = newInSize; if (requested > size_t(n)) {
m_log.log(0, "R3Stretcher::setMaxProcessSize: request exceeds overall limit", requested, n);
if (newInSize > oldInSize) {
m_log.log(1, "setMaxProcessSize: resizing inbuf from and to", oldInSize, newInSize);
for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf = std::unique_ptr<RingBuffer<float>>
(m_channelData[c]->inbuf->resized(newInSize));
}
} else { } else {
m_log.log(1, "setMaxProcessSize: nothing to be done at inbuf, newInSize <= oldInSize", newInSize, oldInSize); n = int(requested);
} }
if (newOutSize > oldOutSize) { ensureInbuf(n * 2, false);
m_log.log(1, "setMaxProcessSize: resizing outbuf from and to", oldOutSize, newOutSize); ensureOutbuf(n * 8, false);
for (int c = 0; c < m_parameters.channels; ++c) { }
m_channelData[c]->outbuf = std::unique_ptr<RingBuffer<float>>
(m_channelData[c]->outbuf->resized(newOutSize)); void
R3Stretcher::ensureInbuf(int required, bool warn)
{
int ws = m_channelData[0]->inbuf->getWriteSpace();
if (required < ws) {
return;
} }
} else { if (warn) {
m_log.log(1, "setMaxProcessSize: nothing to be done at outbuf, newOutSize <= oldOutSize", newOutSize, oldOutSize); m_log.log(0, "R3Stretcher::ensureInbuf: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called, process is being called repeatedly without retrieve, or an internal error has led to an incorrect resampler output calculation. Samples to write and space available", required, ws);
}
size_t oldSize = m_channelData[0]->inbuf->getSize();
size_t newSize = oldSize - ws + required;
if (newSize < oldSize * 2) newSize = oldSize * 2;
m_log.log(warn ? 0 : 2, "R3Stretcher::ensureInbuf: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->inbuf->resized(newSize);
m_channelData[c]->inbuf = std::unique_ptr<RingBuffer<float>>(newBuf);
}
}
void
R3Stretcher::ensureOutbuf(int required, bool warn)
{
int ws = m_channelData[0]->outbuf->getWriteSpace();
if (required < ws) {
return;
}
if (warn) {
m_log.log(0, "R3Stretcher::ensureOutbuf: WARNING: Forced to increase output buffer size. Using smaller process blocks or an artificially larger value for setMaxProcessSize may avoid this. Samples to write and space available", required, ws);
}
size_t oldSize = m_channelData[0]->outbuf->getSize();
size_t newSize = oldSize - ws + required;
if (newSize < oldSize * 2) newSize = oldSize * 2;
m_log.log(warn ? 0 : 2, "R3Stretcher::ensureOutbuf: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->outbuf->resized(newSize);
m_channelData[c]->outbuf = std::unique_ptr<RingBuffer<float>>(newBuf);
} }
} }
@@ -676,6 +702,13 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
return; return;
} }
int n = m_limits.overallMaxProcessSize;
if (samples > size_t(n)) {
m_log.log(0, "R3Stretcher::process: request exceeds overall limit", samples, n);
} else {
n = int(samples);
}
if (!isRealTime()) { if (!isRealTime()) {
if (m_mode == ProcessMode::Studying) { if (m_mode == ProcessMode::Studying) {
@@ -714,8 +747,12 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
// when the ratio changes. // when the ratio changes.
int pad = getWindowSourceSize() / 2; int pad = getWindowSourceSize() / 2;
m_log.log(1, "offline mode: prefilling with", pad); m_log.log(1, "offline mode: prefilling with", pad);
ensureInbuf(pad);
for (int c = 0; c < m_parameters.channels; ++c) { for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->zero(pad); int zeroed = m_channelData[c]->inbuf->zero(pad);
if (zeroed != pad) {
m_log.log(0, "R3Stretcher: WARNING: too few padding samples written", zeroed, pad);
}
} }
// NB by the time we skip this later we may have resampled // NB by the time we skip this later we may have resampled
@@ -731,35 +768,22 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
int channels = m_parameters.channels; int channels = m_parameters.channels;
int inputIx = 0; int inputIx = 0;
if (samples == 0 && final) { if (n == 0 && final) {
m_log.log(2, "process: no samples but final specified, consuming"); m_log.log(2, "process: no samples but final specified, consuming");
consume(true); consume(true);
} else while (inputIx < int(samples)) { } else while (inputIx < n) {
int remaining = n - inputIx;
int remaining = int(samples) - inputIx;
int ws = m_channelData[0]->inbuf->getWriteSpace(); int ws = m_channelData[0]->inbuf->getWriteSpace();
if (ws == 0) { if (ws == 0) {
consume(false); consume(false);
}
ensureInbuf(remaining);
ws = m_channelData[0]->inbuf->getWriteSpace(); ws = m_channelData[0]->inbuf->getWriteSpace();
}
if (ws == 0) {
m_log.log(0, "R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called, process is being called repeatedly without retrieve, or an internal error has led to an incorrect resampler output calculation. Samples to write", remaining);
size_t oldSize = m_channelData[0]->inbuf->getSize();
size_t newSize = oldSize + remaining;
if (newSize < oldSize * 2) newSize = oldSize * 2;
m_log.log(0, "R3Stretcher::process: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->inbuf->resized(newSize);
m_channelData[c]->inbuf =
std::unique_ptr<RingBuffer<float>>(newBuf);
}
continue;
}
if (resamplingBefore) { if (resamplingBefore) {
@@ -775,10 +799,11 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
int resampleInput = std::min(remaining, maxResampleInput); int resampleInput = std::min(remaining, maxResampleInput);
if (resampleInput == 0) resampleInput = 1; if (resampleInput == 0) resampleInput = 1;
m_log.log(2, "R3Stretcher::process: resamplingBefore is true, resampleInput and maxResampleOutput", resampleInput, maxResampleOutput);
prepareInput(input, inputIx, resampleInput); prepareInput(input, inputIx, resampleInput);
bool finalHop = (final && bool finalHop = (final && inputIx + resampleInput >= n);
inputIx + resampleInput >= int(samples));
int resampleOutput = m_resampler->resample int resampleOutput = m_resampler->resample
(m_channelAssembly.resampled.data(), (m_channelAssembly.resampled.data(),
@@ -790,12 +815,15 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
inputIx += resampleInput; inputIx += resampleInput;
m_log.log(2, "process: resamplingBefore is true, writing to inbuf from resampled data, former read space and samples being added", m_channelData[0]->inbuf->getReadSpace(), resampleOutput);
for (int c = 0; c < m_parameters.channels; ++c) { for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->write
int written = m_channelData[c]->inbuf->write
(m_channelData.at(c)->resampled.data(), (m_channelData.at(c)->resampled.data(),
resampleOutput); resampleOutput);
if (written != resampleOutput) {
m_log.log(0, "R3Stretcher: WARNING: too few samples written to input buffer from resampler", written, resampleOutput);
}
} }
} else { } else {
@@ -806,13 +834,19 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
prepareInput(input, inputIx, toWrite); prepareInput(input, inputIx, toWrite);
for (int c = 0; c < m_parameters.channels; ++c) { for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->write
int written = m_channelData[c]->inbuf->write
(m_channelAssembly.input[c], toWrite); (m_channelAssembly.input[c], toWrite);
if (written != toWrite) {
m_log.log(0, "R3Stretcher: WARNING: too few samples written to input buffer", written, toWrite);
} }
}
inputIx += toWrite; inputIx += toWrite;
} }
consume(final && inputIx >= int(samples)); consume(final && inputIx >= n);
} }
if (final) { if (final) {
@@ -961,9 +995,7 @@ R3Stretcher::consume(bool final)
Profiler profiler("R3Stretcher::consume/loop"); Profiler profiler("R3Stretcher::consume/loop");
int readSpace = cd0->inbuf->getReadSpace(); int readSpace = cd0->inbuf->getReadSpace();
int writeSpace = cd0->outbuf->getWriteSpace(); m_log.log(2, "consume: read space", readSpace);
m_log.log(2, "consume: read space and write space", readSpace, writeSpace);
if (readSpace < getWindowSourceSize()) { if (readSpace < getWindowSourceSize()) {
if (final) { if (final) {
@@ -981,18 +1013,7 @@ R3Stretcher::consume(bool final)
} }
} }
while (writeSpace < outhop) { ensureOutbuf(outhop);
m_log.log(0, "R3Stretcher::process: WARNING: Forced to increase output buffer size. Using smaller process blocks or an artificially larger value for setMaxProcessSize may avoid this");
size_t oldSize = m_channelData[0]->outbuf->getSize();
size_t newSize = oldSize * 2;
m_log.log(0, "R3Stretcher::process: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->outbuf->resized(newSize);
m_channelData[c]->outbuf =
std::unique_ptr<RingBuffer<float>>(newBuf);
}
writeSpace = m_channelData[0]->outbuf->getWriteSpace();
}
// Analysis // Analysis
@@ -1086,12 +1107,19 @@ R3Stretcher::consume(bool final)
for (int c = 0; c < channels; ++c) { for (int c = 0; c < channels; ++c) {
auto &cd = m_channelData.at(c); auto &cd = m_channelData.at(c);
int written = 0;
if (resamplingAfter) { if (resamplingAfter) {
cd->outbuf->write(cd->resampled.data(), writeCount); written = cd->outbuf->write(cd->resampled.data(), writeCount);
} else { } else {
cd->outbuf->write(cd->mixdown.data(), writeCount); written = cd->outbuf->write(cd->mixdown.data(), writeCount);
}
if (written != writeCount) {
m_log.log(0, "R3Stretcher: WARNING: too few samples written to output buffer", written, writeCount);
}
int skipped = cd->inbuf->skip(advanceCount);
if (skipped != advanceCount) {
m_log.log(0, "R3Stretcher: WARNING: too few samples advanced", skipped, advanceCount);
} }
cd->inbuf->skip(advanceCount);
} }
m_consumedInputDuration += advanceCount; m_consumedInputDuration += advanceCount;
@@ -1101,7 +1129,10 @@ R3Stretcher::consume(bool final)
int rs = cd0->outbuf->getReadSpace(); int rs = cd0->outbuf->getReadSpace();
int toSkip = std::min(m_startSkip, rs); int toSkip = std::min(m_startSkip, rs);
for (int c = 0; c < channels; ++c) { for (int c = 0; c < channels; ++c) {
m_channelData.at(c)->outbuf->skip(toSkip); int skipped = m_channelData.at(c)->outbuf->skip(toSkip);
if (skipped != toSkip) {
m_log.log(0, "R3Stretcher: WARNING: too few samples skipped at output", skipped, toSkip);
}
} }
m_startSkip -= toSkip; m_startSkip -= toSkip;
m_totalOutputDuration = rs - toSkip; m_totalOutputDuration = rs - toSkip;

View File

@@ -110,13 +110,15 @@ protected:
int minInhop; int minInhop;
int maxInhopWithReadahead; int maxInhopWithReadahead;
int maxInhop; int maxInhop;
int overallMaxProcessSize;
Limits(RubberBandStretcher::Options options, double rate) : Limits(RubberBandStretcher::Options options, double rate) :
// commented values are results when rate = 44100 or 48000 // commented values are results when rate = 44100 or 48000
minPreferredOuthop(roundUpDiv(rate, 512)), // 128 minPreferredOuthop(roundUpDiv(rate, 512)), // 128
maxPreferredOuthop(roundUpDiv(rate, 128)), // 512 maxPreferredOuthop(roundUpDiv(rate, 128)), // 512
minInhop(1), minInhop(1),
maxInhopWithReadahead(roundUpDiv(rate, 64)), // 1024 maxInhopWithReadahead(roundUpDiv(rate, 64)), // 1024
maxInhop(roundUpDiv(rate, 32)) { // 2048 maxInhop(roundUpDiv(rate, 32)), // 2048
overallMaxProcessSize(524288) {
if (options & RubberBandStretcher::OptionWindowShort) { if (options & RubberBandStretcher::OptionWindowShort) {
// See note in calculateHop // See note in calculateHop
minPreferredOuthop = roundUpDiv(rate, 256); // 256 minPreferredOuthop = roundUpDiv(rate, 256); // 256
@@ -361,6 +363,8 @@ protected:
void prepareInput(const float *const *input, int ix, int n); void prepareInput(const float *const *input, int ix, int n);
void consume(bool final); void consume(bool final);
void createResampler(); void createResampler();
void ensureInbuf(int, bool warn = true);
void ensureOutbuf(int, bool warn = true);
void calculateHop(); void calculateHop();
void updateRatioFromMap(); void updateRatioFromMap();
void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop);

View File

@@ -680,8 +680,7 @@ BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_stretch)
sinusoid_realtime(RubberBandStretcher::OptionEngineFiner | sinusoid_realtime(RubberBandStretcher::OptionEngineFiner |
RubberBandStretcher::OptionProcessRealTime, RubberBandStretcher::OptionProcessRealTime,
2.0, 1.0, 2.0, 1.0,
80000, 80000);
true);
} }
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_shift) BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_shift)
@@ -1394,8 +1393,8 @@ static void with_resets(RubberBandStretcher::Options options,
for (int i = 0; i < nActual; ++i) { for (int i = 0; i < nActual; ++i) {
BOOST_TEST(out[i] == outRef[i]); BOOST_TEST(out[i] == outRef[i]);
if (out[i] != outRef[i]) { if (out[i] != outRef[i]) {
std::cerr << "Failure at index " << i << " in run " std::cerr << "Failure at index " << i << " of "
<< run << std::endl; << nActual << " in run " << run << std::endl;
break; break;
} }
} }