@@ -167,6 +167,8 @@ namespace FlacNamespace | |||
//============================================================================== | |||
static const char* const flacFormatName = "FLAC file"; | |||
template <typename Item> | |||
auto emptyRange (Item item) { return Range<Item>::emptyRange (item); } | |||
//============================================================================== | |||
class FlacReader : public AudioFormatReader | |||
@@ -217,66 +219,61 @@ public: | |||
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, | |||
int64 startSampleInFile, int numSamples) override | |||
{ | |||
if (! ok) | |||
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;) | |||
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; | |||
} | |||
@@ -304,14 +301,14 @@ public: | |||
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) | |||
dest[j] = src[j] << bitsToShift; | |||
} | |||
} | |||
samplesInReservoir = numSamples; | |||
bufferedRange.setLength (numSamples); | |||
} | |||
} | |||
@@ -368,7 +365,7 @@ public: | |||
private: | |||
FlacNamespace::FLAC__StreamDecoder* decoder; | |||
AudioBuffer<float> reservoir; | |||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||
Range<int64> bufferedRange; | |||
bool ok = false, scanningForLength = false; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | |||
@@ -158,72 +158,62 @@ public: | |||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
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;) | |||
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; | |||
} | |||
@@ -261,7 +251,7 @@ private: | |||
OggVorbisNamespace::OggVorbis_File ovFile; | |||
OggVorbisNamespace::ov_callbacks callbacks; | |||
AudioBuffer<float> reservoir; | |||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||
Range<int64> bufferedRange; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) | |||
}; | |||
@@ -341,6 +341,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC | |||
#include "containers/juce_PropertySet.h" | |||
#include "memory/juce_SharedResourcePointer.h" | |||
#include "memory/juce_AllocationHooks.h" | |||
#include "memory/juce_Reservoir.h" | |||
#if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) | |||
#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) | |||
: 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 (&sourceStream, size, false) | |||
: BufferedInputStream (&sourceStream, size, false) | |||
{ | |||
} | |||
BufferedInputStream::~BufferedInputStream() | |||
{ | |||
} | |||
BufferedInputStream::~BufferedInputStream() = default; | |||
//============================================================================== | |||
char BufferedInputStream::peekByte() | |||
@@ -62,7 +60,7 @@ char BufferedInputStream::peekByte() | |||
if (! ensureBuffered()) | |||
return 0; | |||
return position < lastReadPos ? buffer[(int) (position - bufferStart)] : 0; | |||
return position < lastReadPos ? buffer[(int) (position - bufferedRange.getStart())] : 0; | |||
} | |||
int64 BufferedInputStream::getTotalLength() | |||
@@ -90,20 +88,19 @@ bool BufferedInputStream::ensureBuffered() | |||
{ | |||
auto bufferEndOverlap = lastReadPos - bufferOverlap; | |||
if (position < bufferStart || position >= bufferEndOverlap) | |||
if (position < bufferedRange.getStart() || position >= bufferEndOverlap) | |||
{ | |||
int bytesRead; | |||
int bytesRead = 0; | |||
if (position < lastReadPos | |||
&& position >= bufferEndOverlap | |||
&& position >= bufferStart) | |||
&& position >= bufferedRange.getStart()) | |||
{ | |||
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, | |||
(int) (bufferSize - bytesToKeep)); | |||
(int) (bufferLength - bytesToKeep)); | |||
if (bytesRead < 0) | |||
return false; | |||
@@ -113,75 +110,62 @@ bool BufferedInputStream::ensureBuffered() | |||
} | |||
else | |||
{ | |||
bufferStart = position; | |||
if (! source->setPosition (bufferStart)) | |||
if (! source->setPosition (position)) | |||
return false; | |||
bytesRead = source->read (buffer, bufferSize); | |||
bytesRead = (int) source->read (buffer, (size_t) bufferLength); | |||
if (bytesRead < 0) | |||
return false; | |||
lastReadPos = bufferStart + bytesRead; | |||
lastReadPos = position + bytesRead; | |||
} | |||
while (bytesRead < bufferSize) | |||
bufferedRange = Range<int64> (position, lastReadPos); | |||
while (bytesRead < bufferLength) | |||
buffer[bytesRead++] = 0; | |||
} | |||
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() | |||
{ | |||
if (position >= bufferStart | |||
if (position >= bufferedRange.getStart() | |||
&& position < lastReadPos) | |||
{ | |||
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) | |||
{ | |||
@@ -203,71 +187,131 @@ String BufferedInputStream::readString() | |||
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() | |||
: UnitTest ("BufferedInputStream", UnitTestCategories::streams) | |||
{} | |||
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: | |||
//============================================================================== | |||
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; | |||
bool ensureBuffered(); | |||
@@ -52,9 +52,7 @@ MemoryInputStream::MemoryInputStream (MemoryBlock&& source) | |||
dataSize = internalCopy.getSize(); | |||
} | |||
MemoryInputStream::~MemoryInputStream() | |||
{ | |||
} | |||
MemoryInputStream::~MemoryInputStream() = default; | |||
int64 MemoryInputStream::getTotalLength() | |||
{ | |||