diff --git a/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp index a558964550..e780ca5ac9 100644 --- a/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp @@ -167,6 +167,8 @@ namespace FlacNamespace //============================================================================== static const char* const flacFormatName = "FLAC file"; +template +auto emptyRange (Item item) { return Range::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 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 { 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 (reservoir.getWritePointer(i)); + auto* dest = reinterpret_cast (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 reservoir; - int64 reservoirStart = 0, samplesInReservoir = 0; + Range bufferedRange; bool ok = false, scanningForLength = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) diff --git a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp index 387ace0c04..7cb8d4d784 100644 --- a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp @@ -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 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 { 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 (ov_read_float (&ovFile, &dataIn, numToRead, &bitStream)); - while (numToRead > 0) - { - float** dataIn = nullptr; - auto samps = static_cast (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 { 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 reservoir; - int64 reservoirStart = 0, samplesInReservoir = 0; + Range bufferedRange; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) }; diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h index 777eb583b1..3302d93231 100644 --- a/modules/juce_core/juce_core.h +++ b/modules/juce_core/juce_core.h @@ -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" diff --git a/modules/juce_core/memory/juce_Reservoir.h b/modules/juce_core/memory/juce_Reservoir.h new file mode 100644 index 0000000000..e05d505af5 --- /dev/null +++ b/modules/juce_core/memory/juce_Reservoir.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 that returns + the region currently held in the reservoir + @param readFromReservoir a function Range -> 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 + static Range doBufferedRead (Range 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 diff --git a/modules/juce_core/streams/juce_BufferedInputStream.cpp b/modules/juce_core/streams/juce_BufferedInputStream.cpp index ddc2d23fb9..c60f3774b5 100644 --- a/modules/juce_core/streams/juce_BufferedInputStream.cpp +++ b/modules/juce_core/streams/juce_BufferedInputStream.cpp @@ -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 (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 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 (destBuffer) + numToRead; - } + memcpy (static_cast (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 (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 + static void applyImpl (Fn&& fn, std::index_sequence, Values&& values) + { + fn (std::get (values)...); + } + + template + static void apply (Fn&& fn, std::tuple values) + { + applyImpl (fn, std::make_index_sequence(), values); + } + + template + static void allCombinationsImpl (Fn&& fn, Values&& values) + { + apply (fn, values); + } + + template + 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 + 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::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); } }; diff --git a/modules/juce_core/streams/juce_BufferedInputStream.h b/modules/juce_core/streams/juce_BufferedInputStream.h index 42ca402fe6..3ad216b65b 100644 --- a/modules/juce_core/streams/juce_BufferedInputStream.h +++ b/modules/juce_core/streams/juce_BufferedInputStream.h @@ -79,8 +79,8 @@ public: private: //============================================================================== OptionalScopedPointer source; - int bufferSize; - int64 position, lastReadPos = 0, bufferStart, bufferOverlap = 128; + Range bufferedRange; + int64 position, bufferLength, lastReadPos = 0, bufferOverlap = 128; HeapBlock buffer; bool ensureBuffered(); diff --git a/modules/juce_core/streams/juce_MemoryInputStream.cpp b/modules/juce_core/streams/juce_MemoryInputStream.cpp index 89924c5738..d75b2b7159 100644 --- a/modules/juce_core/streams/juce_MemoryInputStream.cpp +++ b/modules/juce_core/streams/juce_MemoryInputStream.cpp @@ -52,9 +52,7 @@ MemoryInputStream::MemoryInputStream (MemoryBlock&& source) dataSize = internalCopy.getSize(); } -MemoryInputStream::~MemoryInputStream() -{ -} +MemoryInputStream::~MemoryInputStream() = default; int64 MemoryInputStream::getTotalLength() {