@@ -167,6 +167,8 @@ namespace FlacNamespace | |||||
//============================================================================== | //============================================================================== | ||||
static const char* const flacFormatName = "FLAC file"; | static const char* const flacFormatName = "FLAC file"; | ||||
template <typename Item> | |||||
auto emptyRange (Item item) { return Range<Item>::emptyRange (item); } | |||||
//============================================================================== | //============================================================================== | ||||
class FlacReader : public AudioFormatReader | class FlacReader : public AudioFormatReader | ||||
@@ -217,66 +219,61 @@ public: | |||||
reservoir.setSize ((int) numChannels, 2 * (int) info.max_blocksize, false, false, true); | reservoir.setSize ((int) numChannels, 2 * (int) info.max_blocksize, false, false, true); | ||||
} | } | ||||
// returns the number of samples read | |||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | ||||
int64 startSampleInFile, int numSamples) override | int64 startSampleInFile, int numSamples) override | ||||
{ | { | ||||
if (! ok) | if (! ok) | ||||
return false; | return false; | ||||
while (numSamples > 0) | |||||
{ | |||||
if (startSampleInFile >= reservoirStart | |||||
&& startSampleInFile < reservoirStart + samplesInReservoir) | |||||
{ | |||||
auto num = (int) jmin ((int64) numSamples, | |||||
reservoirStart + samplesInReservoir - startSampleInFile); | |||||
jassert (num > 0); | |||||
const auto getBufferedRange = [this] { return bufferedRange; }; | |||||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||||
if (destSamples[i] != nullptr) | |||||
memcpy (destSamples[i] + startOffsetInDestBuffer, | |||||
reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)), | |||||
(size_t) num * sizeof (int)); | |||||
const auto readFromReservoir = [this, &destSamples, &numDestChannels, &startOffsetInDestBuffer, &startSampleInFile] (const Range<int64> rangeToRead) | |||||
{ | |||||
const auto bufferIndices = rangeToRead - bufferedRange.getStart(); | |||||
const auto writePos = (int64) startOffsetInDestBuffer + (rangeToRead.getStart() - startSampleInFile); | |||||
startOffsetInDestBuffer += num; | |||||
startSampleInFile += num; | |||||
numSamples -= num; | |||||
} | |||||
else | |||||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||||
{ | { | ||||
if (startSampleInFile >= lengthInSamples) | |||||
{ | |||||
samplesInReservoir = 0; | |||||
} | |||||
else if (startSampleInFile < reservoirStart | |||||
|| startSampleInFile > reservoirStart + jmax (samplesInReservoir, (int64) 511)) | |||||
{ | |||||
// had some problems with flac crashing if the read pos is aligned more | |||||
// accurately than this. Probably fixed in newer versions of the library, though. | |||||
reservoirStart = (int) (startSampleInFile & ~511); | |||||
samplesInReservoir = 0; | |||||
FLAC__stream_decoder_seek_absolute (decoder, (FlacNamespace::FLAC__uint64) reservoirStart); | |||||
} | |||||
else | |||||
if (destSamples[i] != nullptr) | |||||
{ | { | ||||
reservoirStart += samplesInReservoir; | |||||
samplesInReservoir = 0; | |||||
FLAC__stream_decoder_process_single (decoder); | |||||
memcpy (destSamples[i] + writePos, | |||||
reservoir.getReadPointer (i) + bufferIndices.getStart(), | |||||
(size_t) bufferIndices.getLength() * sizeof (int)); | |||||
} | } | ||||
if (samplesInReservoir == 0) | |||||
break; | |||||
} | } | ||||
} | |||||
}; | |||||
if (numSamples > 0) | |||||
const auto fillReservoir = [this] (const int64 requestedStart) | |||||
{ | { | ||||
if (requestedStart >= lengthInSamples) | |||||
{ | |||||
bufferedRange = emptyRange (requestedStart); | |||||
return; | |||||
} | |||||
if (requestedStart < bufferedRange.getStart() | |||||
|| jmax (bufferedRange.getEnd(), bufferedRange.getStart() + (int64) 511) < requestedStart) | |||||
{ | |||||
// had some problems with flac crashing if the read pos is aligned more | |||||
// accurately than this. Probably fixed in newer versions of the library, though. | |||||
bufferedRange = emptyRange (requestedStart & ~511); | |||||
FLAC__stream_decoder_seek_absolute (decoder, (FlacNamespace::FLAC__uint64) bufferedRange.getStart()); | |||||
return; | |||||
} | |||||
bufferedRange = emptyRange (bufferedRange.getEnd()); | |||||
FLAC__stream_decoder_process_single (decoder); | |||||
}; | |||||
const auto remainingSamples = Reservoir::doBufferedRead (Range<int64> { startSampleInFile, startSampleInFile + numSamples }, | |||||
getBufferedRange, | |||||
readFromReservoir, | |||||
fillReservoir); | |||||
if (! remainingSamples.isEmpty()) | |||||
for (int i = numDestChannels; --i >= 0;) | for (int i = numDestChannels; --i >= 0;) | ||||
if (destSamples[i] != nullptr) | if (destSamples[i] != nullptr) | ||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) numSamples * sizeof (int)); | |||||
} | |||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) remainingSamples.getLength() * sizeof (int)); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -304,14 +301,14 @@ public: | |||||
if (src != nullptr) | if (src != nullptr) | ||||
{ | { | ||||
auto* dest = reinterpret_cast<int*> (reservoir.getWritePointer(i)); | |||||
auto* dest = reinterpret_cast<int*> (reservoir.getWritePointer (i)); | |||||
for (int j = 0; j < numSamples; ++j) | for (int j = 0; j < numSamples; ++j) | ||||
dest[j] = src[j] << bitsToShift; | dest[j] = src[j] << bitsToShift; | ||||
} | } | ||||
} | } | ||||
samplesInReservoir = numSamples; | |||||
bufferedRange.setLength (numSamples); | |||||
} | } | ||||
} | } | ||||
@@ -368,7 +365,7 @@ public: | |||||
private: | private: | ||||
FlacNamespace::FLAC__StreamDecoder* decoder; | FlacNamespace::FLAC__StreamDecoder* decoder; | ||||
AudioBuffer<float> reservoir; | AudioBuffer<float> reservoir; | ||||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||||
Range<int64> bufferedRange; | |||||
bool ok = false, scanningForLength = false; | bool ok = false, scanningForLength = false; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | ||||
@@ -158,72 +158,62 @@ public: | |||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | ||||
int64 startSampleInFile, int numSamples) override | int64 startSampleInFile, int numSamples) override | ||||
{ | { | ||||
while (numSamples > 0) | |||||
const auto getBufferedRange = [this] { return bufferedRange; }; | |||||
const auto readFromReservoir = [this, &destSamples, &numDestChannels, &startOffsetInDestBuffer, &startSampleInFile] (const Range<int64> rangeToRead) | |||||
{ | { | ||||
auto numAvailable = (reservoirStart + samplesInReservoir - startSampleInFile); | |||||
const auto bufferIndices = rangeToRead - bufferedRange.getStart(); | |||||
const auto writePos = (int64) startOffsetInDestBuffer + (rangeToRead.getStart() - startSampleInFile); | |||||
if (startSampleInFile >= reservoirStart && numAvailable > 0) | |||||
{ | |||||
// got a few samples overlapping, so use them before seeking.. | |||||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||||
if (destSamples[i] != nullptr) | |||||
memcpy (destSamples[i] + writePos, | |||||
reservoir.getReadPointer (i) + bufferIndices.getStart(), | |||||
(size_t) bufferIndices.getLength() * sizeof (float)); | |||||
}; | |||||
auto numToUse = jmin ((int64) numSamples, numAvailable); | |||||
const auto fillReservoir = [this] (int64 requestedStart) | |||||
{ | |||||
const auto newStart = jmax ((int64) 0, requestedStart); | |||||
bufferedRange = Range<int64> { newStart, newStart + reservoir.getNumSamples() }; | |||||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||||
if (destSamples[i] != nullptr) | |||||
memcpy (destSamples[i] + startOffsetInDestBuffer, | |||||
reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)), | |||||
(size_t) numToUse * sizeof (float)); | |||||
if (bufferedRange.getStart() != ov_pcm_tell (&ovFile)) | |||||
ov_pcm_seek (&ovFile, bufferedRange.getStart()); | |||||
startSampleInFile += numToUse; | |||||
numSamples -= (int) numToUse; | |||||
startOffsetInDestBuffer += (int) numToUse; | |||||
int bitStream = 0; | |||||
int offset = 0; | |||||
int numToRead = (int) bufferedRange.getLength(); | |||||
if (numSamples == 0) | |||||
break; | |||||
} | |||||
if (startSampleInFile < reservoirStart | |||||
|| startSampleInFile + numSamples > reservoirStart + samplesInReservoir) | |||||
while (numToRead > 0) | |||||
{ | { | ||||
// buffer miss, so refill the reservoir | |||||
reservoirStart = jmax (0, (int) startSampleInFile); | |||||
samplesInReservoir = reservoir.getNumSamples(); | |||||
if (reservoirStart != (int) ov_pcm_tell (&ovFile)) | |||||
ov_pcm_seek (&ovFile, reservoirStart); | |||||
int bitStream = 0; | |||||
int offset = 0; | |||||
int numToRead = (int) samplesInReservoir; | |||||
float** dataIn = nullptr; | |||||
auto samps = static_cast<int> (ov_read_float (&ovFile, &dataIn, numToRead, &bitStream)); | |||||
while (numToRead > 0) | |||||
{ | |||||
float** dataIn = nullptr; | |||||
auto samps = static_cast<int> (ov_read_float (&ovFile, &dataIn, numToRead, &bitStream)); | |||||
if (samps <= 0) | |||||
break; | |||||
if (samps <= 0) | |||||
break; | |||||
jassert (samps <= numToRead); | |||||
jassert (samps <= numToRead); | |||||
for (int i = jmin ((int) numChannels, reservoir.getNumChannels()); --i >= 0;) | |||||
memcpy (reservoir.getWritePointer (i, offset), dataIn[i], (size_t) samps * sizeof (float)); | |||||
for (int i = jmin ((int) numChannels, reservoir.getNumChannels()); --i >= 0;) | |||||
memcpy (reservoir.getWritePointer (i, offset), dataIn[i], (size_t) samps * sizeof (float)); | |||||
numToRead -= samps; | |||||
offset += samps; | |||||
} | |||||
numToRead -= samps; | |||||
offset += samps; | |||||
} | |||||
if (numToRead > 0) | |||||
reservoir.clear (offset, numToRead); | |||||
}; | |||||
if (numToRead > 0) | |||||
reservoir.clear (offset, numToRead); | |||||
} | |||||
} | |||||
const auto remainingSamples = Reservoir::doBufferedRead (Range<int64> { startSampleInFile, startSampleInFile + numSamples }, | |||||
getBufferedRange, | |||||
readFromReservoir, | |||||
fillReservoir); | |||||
if (numSamples > 0) | |||||
{ | |||||
if (! remainingSamples.isEmpty()) | |||||
for (int i = numDestChannels; --i >= 0;) | for (int i = numDestChannels; --i >= 0;) | ||||
if (destSamples[i] != nullptr) | if (destSamples[i] != nullptr) | ||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) numSamples * sizeof (int)); | |||||
} | |||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) remainingSamples.getLength() * sizeof (int)); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -261,7 +251,7 @@ private: | |||||
OggVorbisNamespace::OggVorbis_File ovFile; | OggVorbisNamespace::OggVorbis_File ovFile; | ||||
OggVorbisNamespace::ov_callbacks callbacks; | OggVorbisNamespace::ov_callbacks callbacks; | ||||
AudioBuffer<float> reservoir; | AudioBuffer<float> reservoir; | ||||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||||
Range<int64> bufferedRange; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) | ||||
}; | }; | ||||
@@ -341,6 +341,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC | |||||
#include "containers/juce_PropertySet.h" | #include "containers/juce_PropertySet.h" | ||||
#include "memory/juce_SharedResourcePointer.h" | #include "memory/juce_SharedResourcePointer.h" | ||||
#include "memory/juce_AllocationHooks.h" | #include "memory/juce_AllocationHooks.h" | ||||
#include "memory/juce_Reservoir.h" | |||||
#if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) | #if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) | ||||
#include "native/juce_mac_ObjCHelpers.h" | #include "native/juce_mac_ObjCHelpers.h" | ||||
@@ -0,0 +1,100 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
End User License Agreement: www.juce.com/juce-6-licence | |||||
Privacy Policy: www.juce.com/juce-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
/** | |||||
Helper functions for managing buffered readers. | |||||
*/ | |||||
struct Reservoir | |||||
{ | |||||
/** Attempts to read the requested range from some kind of input stream, | |||||
with intermediate buffering in a 'reservoir'. | |||||
While there are still samples in the requested range left to read, this | |||||
function will check whether the next part of the requested range is | |||||
already loaded into the reservoir. If the range is available, then | |||||
doBufferedRead will call readFromReservoir with the range that should | |||||
be copied to the output buffer. If the range is not available, | |||||
doBufferedRead will call fillReservoir to request that a new region is | |||||
loaded into the reservoir. It will repeat these steps until either the | |||||
entire requested region has been read, or the stream ends. | |||||
This will return the range that could not be read successfully, if any. | |||||
An empty range implies that the entire read was successful. | |||||
Note that all ranges, including those provided as arguments to the | |||||
callbacks, are relative to the original unbuffered input. That is, if | |||||
getBufferedRange returns the range [200, 300), then readFromReservoir | |||||
might be passed the range [250, 300) in order to copy the final 50 | |||||
samples from the reservoir. | |||||
@param rangeToRead the absolute position of the range that should | |||||
be read | |||||
@param getBufferedRange a function void -> Range<Index> that returns | |||||
the region currently held in the reservoir | |||||
@param readFromReservoir a function Range<Index> -> void that can be | |||||
used to copy samples from the region in the | |||||
reservoir specified in the input range | |||||
@param fillReservoir a function Index -> void that is given a | |||||
requested read location, and that should | |||||
attempt to fill the reservoir starting at this | |||||
location. After this function, | |||||
getBufferedRange should return the new region | |||||
contained in the managed buffer | |||||
*/ | |||||
template <typename Index, typename GetBufferedRange, typename ReadFromReservoir, typename FillReservoir> | |||||
static Range<Index> doBufferedRead (Range<Index> rangeToRead, | |||||
GetBufferedRange&& getBufferedRange, | |||||
ReadFromReservoir&& readFromReservoir, | |||||
FillReservoir&& fillReservoir) | |||||
{ | |||||
while (! rangeToRead.isEmpty()) | |||||
{ | |||||
const auto rangeToReadInBuffer = rangeToRead.getIntersectionWith (getBufferedRange()); | |||||
if (rangeToReadInBuffer.isEmpty()) | |||||
{ | |||||
fillReservoir (rangeToRead.getStart()); | |||||
const auto newRange = getBufferedRange(); | |||||
if (newRange.isEmpty() || ! newRange.contains (rangeToRead.getStart())) | |||||
break; | |||||
} | |||||
else | |||||
{ | |||||
readFromReservoir (rangeToReadInBuffer); | |||||
rangeToRead.setStart (rangeToReadInBuffer.getEnd()); | |||||
} | |||||
} | |||||
return rangeToRead; | |||||
} | |||||
}; | |||||
} // namespace juce |
@@ -39,22 +39,20 @@ static int calcBufferStreamBufferSize (int requestedSize, InputStream* source) n | |||||
//============================================================================== | //============================================================================== | ||||
BufferedInputStream::BufferedInputStream (InputStream* sourceStream, int size, bool takeOwnership) | BufferedInputStream::BufferedInputStream (InputStream* sourceStream, int size, bool takeOwnership) | ||||
: source (sourceStream, takeOwnership), | |||||
bufferSize (calcBufferStreamBufferSize (size, sourceStream)), | |||||
position (sourceStream->getPosition()), | |||||
bufferStart (position) | |||||
: source (sourceStream, takeOwnership), | |||||
bufferedRange (sourceStream->getPosition(), sourceStream->getPosition()), | |||||
position (bufferedRange.getStart()), | |||||
bufferLength (calcBufferStreamBufferSize (size, sourceStream)) | |||||
{ | { | ||||
buffer.malloc (bufferSize); | |||||
buffer.malloc (bufferLength); | |||||
} | } | ||||
BufferedInputStream::BufferedInputStream (InputStream& sourceStream, int size) | BufferedInputStream::BufferedInputStream (InputStream& sourceStream, int size) | ||||
: BufferedInputStream (&sourceStream, size, false) | |||||
: BufferedInputStream (&sourceStream, size, false) | |||||
{ | { | ||||
} | } | ||||
BufferedInputStream::~BufferedInputStream() | |||||
{ | |||||
} | |||||
BufferedInputStream::~BufferedInputStream() = default; | |||||
//============================================================================== | //============================================================================== | ||||
char BufferedInputStream::peekByte() | char BufferedInputStream::peekByte() | ||||
@@ -62,7 +60,7 @@ char BufferedInputStream::peekByte() | |||||
if (! ensureBuffered()) | if (! ensureBuffered()) | ||||
return 0; | return 0; | ||||
return position < lastReadPos ? buffer[(int) (position - bufferStart)] : 0; | |||||
return position < lastReadPos ? buffer[(int) (position - bufferedRange.getStart())] : 0; | |||||
} | } | ||||
int64 BufferedInputStream::getTotalLength() | int64 BufferedInputStream::getTotalLength() | ||||
@@ -90,20 +88,19 @@ bool BufferedInputStream::ensureBuffered() | |||||
{ | { | ||||
auto bufferEndOverlap = lastReadPos - bufferOverlap; | auto bufferEndOverlap = lastReadPos - bufferOverlap; | ||||
if (position < bufferStart || position >= bufferEndOverlap) | |||||
if (position < bufferedRange.getStart() || position >= bufferEndOverlap) | |||||
{ | { | ||||
int bytesRead; | |||||
int bytesRead = 0; | |||||
if (position < lastReadPos | if (position < lastReadPos | ||||
&& position >= bufferEndOverlap | && position >= bufferEndOverlap | ||||
&& position >= bufferStart) | |||||
&& position >= bufferedRange.getStart()) | |||||
{ | { | ||||
auto bytesToKeep = (int) (lastReadPos - position); | auto bytesToKeep = (int) (lastReadPos - position); | ||||
memmove (buffer, buffer + (int) (position - bufferStart), (size_t) bytesToKeep); | |||||
memmove (buffer, buffer + (int) (position - bufferedRange.getStart()), (size_t) bytesToKeep); | |||||
bufferStart = position; | |||||
bytesRead = source->read (buffer + bytesToKeep, | bytesRead = source->read (buffer + bytesToKeep, | ||||
(int) (bufferSize - bytesToKeep)); | |||||
(int) (bufferLength - bytesToKeep)); | |||||
if (bytesRead < 0) | if (bytesRead < 0) | ||||
return false; | return false; | ||||
@@ -113,75 +110,62 @@ bool BufferedInputStream::ensureBuffered() | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
bufferStart = position; | |||||
if (! source->setPosition (bufferStart)) | |||||
if (! source->setPosition (position)) | |||||
return false; | return false; | ||||
bytesRead = source->read (buffer, bufferSize); | |||||
bytesRead = (int) source->read (buffer, (size_t) bufferLength); | |||||
if (bytesRead < 0) | if (bytesRead < 0) | ||||
return false; | return false; | ||||
lastReadPos = bufferStart + bytesRead; | |||||
lastReadPos = position + bytesRead; | |||||
} | } | ||||
while (bytesRead < bufferSize) | |||||
bufferedRange = Range<int64> (position, lastReadPos); | |||||
while (bytesRead < bufferLength) | |||||
buffer[bytesRead++] = 0; | buffer[bytesRead++] = 0; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) | |||||
int BufferedInputStream::read (void* destBuffer, const int maxBytesToRead) | |||||
{ | { | ||||
jassert (destBuffer != nullptr && maxBytesToRead >= 0); | |||||
const auto initialPosition = position; | |||||
if (position >= bufferStart | |||||
&& position + maxBytesToRead <= lastReadPos) | |||||
{ | |||||
memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) maxBytesToRead); | |||||
position += maxBytesToRead; | |||||
return maxBytesToRead; | |||||
} | |||||
const auto getBufferedRange = [this] { return bufferedRange; }; | |||||
if (position < bufferStart || position >= lastReadPos) | |||||
if (! ensureBuffered()) | |||||
return 0; | |||||
int bytesRead = 0; | |||||
while (maxBytesToRead > 0) | |||||
const auto readFromReservoir = [this, &destBuffer, &initialPosition] (const Range<int64> rangeToRead) | |||||
{ | { | ||||
auto numToRead = jmin (maxBytesToRead, (int) (lastReadPos - position)); | |||||
if (numToRead > 0) | |||||
{ | |||||
memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) numToRead); | |||||
maxBytesToRead -= numToRead; | |||||
bytesRead += numToRead; | |||||
position += numToRead; | |||||
destBuffer = static_cast<char*> (destBuffer) + numToRead; | |||||
} | |||||
memcpy (static_cast<char*> (destBuffer) + (rangeToRead.getStart() - initialPosition), | |||||
buffer + (rangeToRead.getStart() - bufferedRange.getStart()), | |||||
(size_t) rangeToRead.getLength()); | |||||
}; | |||||
auto oldLastReadPos = lastReadPos; | |||||
if (! ensureBuffered() | |||||
|| oldLastReadPos == lastReadPos | |||||
|| isExhausted()) | |||||
break; | |||||
} | |||||
return bytesRead; | |||||
const auto fillReservoir = [this] (int64 requestedStart) | |||||
{ | |||||
position = requestedStart; | |||||
ensureBuffered(); | |||||
}; | |||||
const auto remaining = Reservoir::doBufferedRead (Range<int64> (position, position + maxBytesToRead), | |||||
getBufferedRange, | |||||
readFromReservoir, | |||||
fillReservoir); | |||||
const auto bytesRead = maxBytesToRead - remaining.getLength(); | |||||
position = remaining.getStart(); | |||||
return (int) bytesRead; | |||||
} | } | ||||
String BufferedInputStream::readString() | String BufferedInputStream::readString() | ||||
{ | { | ||||
if (position >= bufferStart | |||||
if (position >= bufferedRange.getStart() | |||||
&& position < lastReadPos) | && position < lastReadPos) | ||||
{ | { | ||||
auto maxChars = (int) (lastReadPos - position); | auto maxChars = (int) (lastReadPos - position); | ||||
auto* src = buffer + (int) (position - bufferStart); | |||||
auto* src = buffer + (int) (position - bufferedRange.getStart()); | |||||
for (int i = 0; i < maxChars; ++i) | for (int i = 0; i < maxChars; ++i) | ||||
{ | { | ||||
@@ -203,71 +187,131 @@ String BufferedInputStream::readString() | |||||
struct BufferedInputStreamTests : public UnitTest | struct BufferedInputStreamTests : public UnitTest | ||||
{ | { | ||||
template <typename Fn, size_t... Ix, typename Values> | |||||
static void applyImpl (Fn&& fn, std::index_sequence<Ix...>, Values&& values) | |||||
{ | |||||
fn (std::get<Ix> (values)...); | |||||
} | |||||
template <typename Fn, typename... Values> | |||||
static void apply (Fn&& fn, std::tuple<Values...> values) | |||||
{ | |||||
applyImpl (fn, std::make_index_sequence<sizeof... (Values)>(), values); | |||||
} | |||||
template <typename Fn, typename Values> | |||||
static void allCombinationsImpl (Fn&& fn, Values&& values) | |||||
{ | |||||
apply (fn, values); | |||||
} | |||||
template <typename Fn, typename Values, typename Range, typename... Ranges> | |||||
static void allCombinationsImpl (Fn&& fn, Values&& values, Range&& range, Ranges&&... ranges) | |||||
{ | |||||
for (auto& item : range) | |||||
allCombinationsImpl (fn, std::tuple_cat (values, std::tie (item)), ranges...); | |||||
} | |||||
template <typename Fn, typename... Ranges> | |||||
static void allCombinations (Fn&& fn, Ranges&&... ranges) | |||||
{ | |||||
allCombinationsImpl (fn, std::tie(), ranges...); | |||||
} | |||||
BufferedInputStreamTests() | BufferedInputStreamTests() | ||||
: UnitTest ("BufferedInputStream", UnitTestCategories::streams) | : UnitTest ("BufferedInputStream", UnitTestCategories::streams) | ||||
{} | {} | ||||
void runTest() override | void runTest() override | ||||
{ | { | ||||
const MemoryBlock data ("abcdefghijklmnopqrstuvwxyz", 26); | |||||
MemoryInputStream mi (data, true); | |||||
const MemoryBlock testBufferA ("abcdefghijklmnopqrstuvwxyz", 26); | |||||
BufferedInputStream stream (mi, (int) data.getSize()); | |||||
const auto testBufferB = [&] | |||||
{ | |||||
MemoryBlock mb { 8192 }; | |||||
auto r = getRandom(); | |||||
beginTest ("Read"); | |||||
std::for_each (mb.begin(), mb.end(), [&] (char& item) | |||||
{ | |||||
item = (char) r.nextInt (std::numeric_limits<char>::max()); | |||||
}); | |||||
expectEquals (stream.getPosition(), (int64) 0); | |||||
expectEquals (stream.getTotalLength(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), stream.getTotalLength()); | |||||
expect (! stream.isExhausted()); | |||||
return mb; | |||||
}(); | |||||
size_t numBytesRead = 0; | |||||
MemoryBlock readBuffer (data.getSize()); | |||||
const MemoryBlock buffers[] { testBufferA, testBufferB }; | |||||
const int readSizes[] { 3, 10, 50 }; | |||||
const bool shouldPeek[] { false, true }; | |||||
while (numBytesRead < data.getSize()) | |||||
const auto runTest = [this] (const MemoryBlock& data, const int readSize, const bool peek) | |||||
{ | { | ||||
expectEquals (stream.peekByte(), *(char*) (data.begin() + numBytesRead)); | |||||
MemoryInputStream mi (data, true); | |||||
numBytesRead += (size_t) stream.read (&readBuffer[numBytesRead], 3); | |||||
BufferedInputStream stream (mi, jmin (200, (int) data.getSize())); | |||||
expectEquals (stream.getPosition(), (int64) numBytesRead); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) (data.getSize() - numBytesRead)); | |||||
expect (stream.isExhausted() == (numBytesRead == data.getSize())); | |||||
} | |||||
beginTest ("Read"); | |||||
expectEquals (stream.getPosition(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) 0); | |||||
expect (stream.isExhausted()); | |||||
expectEquals (stream.getPosition(), (int64) 0); | |||||
expectEquals (stream.getTotalLength(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), stream.getTotalLength()); | |||||
expect (! stream.isExhausted()); | |||||
expect (readBuffer == data); | |||||
size_t numBytesRead = 0; | |||||
MemoryBlock readBuffer (data.getSize()); | |||||
beginTest ("Skip"); | |||||
while (numBytesRead < data.getSize()) | |||||
{ | |||||
if (peek) | |||||
expectEquals (stream.peekByte(), *(char*) (data.begin() + numBytesRead)); | |||||
const auto startingPos = numBytesRead; | |||||
numBytesRead += (size_t) stream.read (readBuffer.begin() + numBytesRead, readSize); | |||||
expect (std::equal (readBuffer.begin() + startingPos, | |||||
readBuffer.begin() + numBytesRead, | |||||
data.begin() + startingPos, | |||||
data.begin() + numBytesRead)); | |||||
expectEquals (stream.getPosition(), (int64) numBytesRead); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) (data.getSize() - numBytesRead)); | |||||
expect (stream.isExhausted() == (numBytesRead == data.getSize())); | |||||
} | |||||
stream.setPosition (0); | |||||
expectEquals (stream.getPosition(), (int64) 0); | |||||
expectEquals (stream.getTotalLength(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), stream.getTotalLength()); | |||||
expect (! stream.isExhausted()); | |||||
expectEquals (stream.getPosition(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) 0); | |||||
expect (stream.isExhausted()); | |||||
numBytesRead = 0; | |||||
const int numBytesToSkip = 5; | |||||
expect (readBuffer == data); | |||||
while (numBytesRead < data.getSize()) | |||||
{ | |||||
expectEquals (stream.peekByte(), *(char*) (data.begin() + numBytesRead)); | |||||
beginTest ("Skip"); | |||||
stream.skipNextBytes (numBytesToSkip); | |||||
numBytesRead += numBytesToSkip; | |||||
numBytesRead = std::min (numBytesRead, data.getSize()); | |||||
stream.setPosition (0); | |||||
expectEquals (stream.getPosition(), (int64) 0); | |||||
expectEquals (stream.getTotalLength(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), stream.getTotalLength()); | |||||
expect (! stream.isExhausted()); | |||||
expectEquals (stream.getPosition(), (int64) numBytesRead); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) (data.getSize() - numBytesRead)); | |||||
expect (stream.isExhausted() == (numBytesRead == data.getSize())); | |||||
} | |||||
numBytesRead = 0; | |||||
const int numBytesToSkip = 5; | |||||
while (numBytesRead < data.getSize()) | |||||
{ | |||||
expectEquals (stream.peekByte(), *(char*) (data.begin() + numBytesRead)); | |||||
stream.skipNextBytes (numBytesToSkip); | |||||
numBytesRead += numBytesToSkip; | |||||
numBytesRead = std::min (numBytesRead, data.getSize()); | |||||
expectEquals (stream.getPosition(), (int64) numBytesRead); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) (data.getSize() - numBytesRead)); | |||||
expect (stream.isExhausted() == (numBytesRead == data.getSize())); | |||||
} | |||||
expectEquals (stream.getPosition(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) 0); | |||||
expect (stream.isExhausted()); | |||||
}; | |||||
expectEquals (stream.getPosition(), (int64) data.getSize()); | |||||
expectEquals (stream.getNumBytesRemaining(), (int64) 0); | |||||
expect (stream.isExhausted()); | |||||
allCombinations (runTest, buffers, readSizes, shouldPeek); | |||||
} | } | ||||
}; | }; | ||||
@@ -79,8 +79,8 @@ public: | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
OptionalScopedPointer<InputStream> source; | OptionalScopedPointer<InputStream> source; | ||||
int bufferSize; | |||||
int64 position, lastReadPos = 0, bufferStart, bufferOverlap = 128; | |||||
Range<int64> bufferedRange; | |||||
int64 position, bufferLength, lastReadPos = 0, bufferOverlap = 128; | |||||
HeapBlock<char> buffer; | HeapBlock<char> buffer; | ||||
bool ensureBuffered(); | bool ensureBuffered(); | ||||
@@ -52,9 +52,7 @@ MemoryInputStream::MemoryInputStream (MemoryBlock&& source) | |||||
dataSize = internalCopy.getSize(); | dataSize = internalCopy.getSize(); | ||||
} | } | ||||
MemoryInputStream::~MemoryInputStream() | |||||
{ | |||||
} | |||||
MemoryInputStream::~MemoryInputStream() = default; | |||||
int64 MemoryInputStream::getTotalLength() | int64 MemoryInputStream::getTotalLength() | ||||
{ | { | ||||