diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 96f1bbb9dd..260c682cde 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -24,33 +24,34 @@ #include "AudioRingBuffer.h" static const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." }; +static const QString DROPPED_SILENT_DEBUG { "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." }; -AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) : +AudioRingBuffer::AudioRingBuffer(int numFrameSamples, int numFramesCapacity) : + _numFrameSamples(numFrameSamples), _frameCapacity(numFramesCapacity), _sampleCapacity(numFrameSamples * numFramesCapacity), - _bufferLength(numFrameSamples * (numFramesCapacity + 1)), - _numFrameSamples(numFrameSamples), - _randomAccessMode(randomAccessMode), - _overflowCount(0) + _bufferLength(numFrameSamples * (numFramesCapacity + 1)) { if (numFrameSamples) { _buffer = new int16_t[_bufferLength]; memset(_buffer, 0, _bufferLength * sizeof(int16_t)); _nextOutput = _buffer; _endOfLastWrite = _buffer; - } else { - _buffer = NULL; - _nextOutput = NULL; - _endOfLastWrite = NULL; } - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); + static QString repeatedOverflowMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); + static QString repeatedDroppedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); }; AudioRingBuffer::~AudioRingBuffer() { delete[] _buffer; } +void AudioRingBuffer::clear() { + _endOfLastWrite = _buffer; + _nextOutput = _buffer; +} + void AudioRingBuffer::reset() { clear(); _overflowCount = 0; @@ -58,109 +59,82 @@ void AudioRingBuffer::reset() { void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) { delete[] _buffer; + _numFrameSamples = numFrameSamples; _sampleCapacity = numFrameSamples * _frameCapacity; _bufferLength = numFrameSamples * (_frameCapacity + 1); - _numFrameSamples = numFrameSamples; - _buffer = new int16_t[_bufferLength]; - memset(_buffer, 0, _bufferLength * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_buffer, 0, _bufferLength * sizeof(int16_t)); - } - reset(); -} -void AudioRingBuffer::clear() { - _endOfLastWrite = _buffer; - _nextOutput = _buffer; + if (numFrameSamples) { + _buffer = new int16_t[_bufferLength]; + memset(_buffer, 0, _bufferLength * sizeof(int16_t)); + } else { + _buffer = nullptr; + } + + reset(); } int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) { return readData((char*)destination, maxSamples * sizeof(int16_t)) / sizeof(int16_t); } +int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { + return writeData((char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); +} + int AudioRingBuffer::readData(char *data, int maxSize) { - // only copy up to the number of samples we have available - int numReadSamples = std::min((int)(maxSize / sizeof(int16_t)), samplesAvailable()); - - // If we're in random access mode, then we consider our number of available read samples slightly - // differently. Namely, if anything has been written, we say we have as many samples as they ask for - // otherwise we say we have nothing available - if (_randomAccessMode) { - numReadSamples = _endOfLastWrite ? (maxSize / sizeof(int16_t)) : 0; - } + int maxSamples = maxSize / sizeof(int16_t); + int numReadSamples = std::min(maxSamples, samplesAvailable()); if (_nextOutput + numReadSamples > _buffer + _bufferLength) { // we're going to need to do two reads to get this data, it wraps around the edge + int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; // read to the end of the buffer - int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_nextOutput, 0, numSamplesToEnd * sizeof(int16_t)); // clear it - } // read the rest from the beginning of the buffer memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_buffer, 0, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); // clear it - } } else { - // read the data memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_nextOutput, 0, numReadSamples * sizeof(int16_t)); // clear it - } } - // push the position of _nextOutput by the number of samples read - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples); + shiftReadPosition(numReadSamples); return numReadSamples * sizeof(int16_t); } -int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { - return writeData((const char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); -} - int AudioRingBuffer::writeData(const char* data, int maxSize) { - // make sure we have enough bytes left for this to be the right amount of audio - // otherwise we should not copy that data, and leave the buffer pointers where they are - int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity); - + // only copy up to the number of samples we have capacity for + int maxSamples = maxSize / sizeof(int16_t); + int numWriteSamples = std::min(maxSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (samplesToCopy > samplesRoomFor) { - // there's not enough room for this write. erase old data to make room for this new data - int samplesToDelete = samplesToCopy - samplesRoomFor; + + if (numWriteSamples > samplesRoomFor) { + // there's not enough room for this write. erase old data to make room for this new data + int samplesToDelete = numWriteSamples - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); } - if (_endOfLastWrite + samplesToCopy <= _buffer + _bufferLength) { - memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t)); - } else { + if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { + // we're going to need to do two writes to set this data, it wraps around the edge int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; + + // write to the end of the buffer memcpy(_endOfLastWrite, data, numSamplesToEnd * sizeof(int16_t)); - memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (samplesToCopy - numSamplesToEnd) * sizeof(int16_t)); + + // write the rest to the beginning of the buffer + memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (numWriteSamples - numSamplesToEnd) * sizeof(int16_t)); + } else { + memcpy(_endOfLastWrite, data, numWriteSamples * sizeof(int16_t)); } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy); + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples); - return samplesToCopy * sizeof(int16_t); -} - -int16_t& AudioRingBuffer::operator[](const int index) { - return *shiftedPositionAccomodatingWrap(_nextOutput, index); -} - -const int16_t& AudioRingBuffer::operator[] (const int index) const { - return *shiftedPositionAccomodatingWrap(_nextOutput, index); -} - -void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) { - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); + return numWriteSamples * sizeof(int16_t); } int AudioRingBuffer::samplesAvailable() const { @@ -176,35 +150,31 @@ int AudioRingBuffer::samplesAvailable() const { } int AudioRingBuffer::addSilentSamples(int silentSamples) { - + // NOTE: This implementation is nearly identical to writeData save for s/memcpy/memset, refer to comments there + int numWriteSamples = std::min(silentSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (silentSamples > samplesRoomFor) { - // there's not enough room for this write. write as many silent samples as we have room for - silentSamples = samplesRoomFor; - static const QString DROPPED_SILENT_DEBUG { - "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." - }; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); + if (numWriteSamples > samplesRoomFor) { + numWriteSamples = samplesRoomFor; + qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG); } - // memset zeroes into the buffer, accomodate a wrap around the end - // push the _endOfLastWrite to the correct spot - if (_endOfLastWrite + silentSamples <= _buffer + _bufferLength) { - memset(_endOfLastWrite, 0, silentSamples * sizeof(int16_t)); - } else { + if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t)); - memset(_buffer, 0, (silentSamples - numSamplesToEnd) * sizeof(int16_t)); + memset(_buffer, 0, (numWriteSamples - numSamplesToEnd) * sizeof(int16_t)); + } else { + memset(_endOfLastWrite, 0, numWriteSamples * sizeof(int16_t)); } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, silentSamples); - return silentSamples; + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples); + + return numWriteSamples; } int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const { - + // NOTE: It is possible to shift out-of-bounds if (|numSamplesShift| > 2 * _bufferLength), but this should not occur if (numSamplesShift > 0 && position + numSamplesShift >= _buffer + _bufferLength) { // this shift will wrap the position around to the beginning of the ring return position + numSamplesShift - _bufferLength; @@ -217,13 +187,15 @@ int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int } float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const { + // FIXME: This is a bad measure of loudness - normal estimation uses sqrt(sum(x*x)) float loudness = 0.0f; const int16_t* sampleAt = frameStart; - const int16_t* _bufferLastAt = _buffer + _bufferLength - 1; + const int16_t* bufferLastAt = _buffer + _bufferLength - 1; for (int i = 0; i < _numFrameSamples; ++i) { loudness += (float) std::abs(*sampleAt); - sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1; + // wrap if necessary + sampleAt = sampleAt == bufferLastAt ? _buffer : sampleAt + 1; } loudness /= _numFrameSamples; loudness /= AudioConstants::MAX_SAMPLE_VALUE; @@ -238,10 +210,6 @@ float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { return getFrameLoudness(&(*frameStart)); } -float AudioRingBuffer::getNextOutputFrameLoudness() const { - return getFrameLoudness(_nextOutput); -} - int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) { int samplesToCopy = std::min(maxSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 2b25b1044b..7ccb32ce10 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -23,73 +23,69 @@ const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10; class AudioRingBuffer { public: - AudioRingBuffer(int numFrameSamples, bool randomAccessMode = false, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY); + AudioRingBuffer(int numFrameSamples, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY); ~AudioRingBuffer(); - void reset(); - void resizeForFrameSize(int numFrameSamples); + // disallow copying + AudioRingBuffer(const AudioRingBuffer&) = delete; + AudioRingBuffer(AudioRingBuffer&&) = delete; + AudioRingBuffer& operator=(const AudioRingBuffer&) = delete; + /// Invalidate any data in the buffer void clear(); - int getSampleCapacity() const { return _sampleCapacity; } - int getFrameCapacity() const { return _frameCapacity; } + /// Clear and reset the overflow count + void reset(); + /// Resize frame size (causes a reset()) + // FIXME: discards any data in the buffer + void resizeForFrameSize(int numFrameSamples); + + /// Read up to maxSamples into destination (will only read up to samplesAvailable()) + /// Returns number of read samples int readSamples(int16_t* destination, int maxSamples); + + /// Write up to maxSamples from source (will only write up to sample capacity) + /// Returns number of written samples int writeSamples(const int16_t* source, int maxSamples); - int readData(char* data, int maxSize); - int writeData(const char* data, int maxSize); + /// Write up to maxSamples silent samples (will only write until other data exists in the buffer) + /// This method will not overwrite existing data in the buffer, instead dropping silent samples that would overflow + /// Returns number of written silent samples + int addSilentSamples(int maxSamples); - int16_t& operator[](const int index); - const int16_t& operator[] (const int index) const; + /// Read up to maxSize into destination + /// Returns number of read bytes + int readData(char* destination, int maxSize); - void shiftReadPosition(unsigned int numSamples); + /// Write up to maxSize from source + /// Returns number of written bytes + int writeData(const char* source, int maxSize); - float getNextOutputFrameLoudness() const; + /// Returns a reference to the index-th sample offset from the current read sample + int16_t& operator[](const int index) { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } + const int16_t& operator[] (const int index) const { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } + + /// Essentially discards the next numSamples from the ring buffer + /// NOTE: This is not checked - it is possible to shift past written data + /// Use samplesAvailable() to see the distance a valid shift can go + void shiftReadPosition(unsigned int numSamples) { _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); } int samplesAvailable() const; int framesAvailable() const { return (_numFrameSamples == 0) ? 0 : samplesAvailable() / _numFrameSamples; } + float getNextOutputFrameLoudness() const { return getFrameLoudness(_nextOutput); } + int getNumFrameSamples() const { return _numFrameSamples; } + int getFrameCapacity() const { return _frameCapacity; } + int getSampleCapacity() const { return _sampleCapacity; } + /// Return times the ring buffer has overwritten old data + int getOverflowCount() const { return _overflowCount; } - int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data - - int addSilentSamples(int samples); - -private: - float getFrameLoudness(const int16_t* frameStart) const; - -protected: - // disallow copying of AudioRingBuffer objects - AudioRingBuffer(const AudioRingBuffer&); - AudioRingBuffer& operator= (const AudioRingBuffer&); - - int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; - - int _frameCapacity; - int _sampleCapacity; - int _bufferLength; // actual length of _buffer: will be one frame larger than _sampleCapacity - int _numFrameSamples; - int16_t* _nextOutput; - int16_t* _endOfLastWrite; - int16_t* _buffer; - bool _randomAccessMode; /// will this ringbuffer be used for random access? if so, do some special processing - - int _overflowCount; /// how many times has the ring buffer has overwritten old data - -public: - class ConstIterator { //public std::iterator < std::forward_iterator_tag, int16_t > { + class ConstIterator { public: - ConstIterator() - : _bufferLength(0), - _bufferFirst(NULL), - _bufferLast(NULL), - _at(NULL) {} - ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) - : _bufferLength(capacity), - _bufferFirst(bufferFirst), - _bufferLast(bufferFirst + capacity - 1), - _at(at) {} + ConstIterator(); + ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at); ConstIterator(const ConstIterator& rhs) = default; bool isNull() const { return _at == NULL; } @@ -98,95 +94,143 @@ public: bool operator!=(const ConstIterator& rhs) { return _at != rhs._at; } const int16_t& operator*() { return *_at; } - ConstIterator& operator=(const ConstIterator& rhs) { - _bufferLength = rhs._bufferLength; - _bufferFirst = rhs._bufferFirst; - _bufferLast = rhs._bufferLast; - _at = rhs._at; - return *this; - } + ConstIterator& operator=(const ConstIterator& rhs); + ConstIterator& operator++(); + ConstIterator operator++(int); + ConstIterator& operator--(); + ConstIterator operator--(int); + const int16_t& operator[] (int i); + ConstIterator operator+(int i); + ConstIterator operator-(int i); - ConstIterator& operator++() { - _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; - return *this; - } - - ConstIterator operator++(int) { - ConstIterator tmp(*this); - ++(*this); - return tmp; - } - - ConstIterator& operator--() { - _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; - return *this; - } - - ConstIterator operator--(int) { - ConstIterator tmp(*this); - --(*this); - return tmp; - } - - const int16_t& operator[] (int i) { - return *atShiftedBy(i); - } - - ConstIterator operator+(int i) { - return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); - } - - ConstIterator operator-(int i) { - return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); - } - - void readSamples(int16_t* dest, int numSamples) { - auto samplesToEnd = _bufferLast - _at + 1; - - if (samplesToEnd >= numSamples) { - memcpy(dest, _at, numSamples * sizeof(int16_t)); - _at += numSamples; - } else { - auto samplesFromStart = numSamples - samplesToEnd; - memcpy(dest, _at, samplesToEnd * sizeof(int16_t)); - memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t)); - - _at = _bufferFirst + samplesFromStart; - } - } - - void readSamplesWithFade(int16_t* dest, int numSamples, float fade) { - int16_t* at = _at; - for (int i = 0; i < numSamples; i++) { - *dest = (float)*at * fade; - ++dest; - at = (at == _bufferLast) ? _bufferFirst : at + 1; - } - } + void readSamples(int16_t* dest, int numSamples); + void readSamplesWithFade(int16_t* dest, int numSamples, float fade); private: - int16_t* atShiftedBy(int i) { - i = (_at - _bufferFirst + i) % _bufferLength; - if (i < 0) { - i += _bufferLength; - } - return _bufferFirst + i; - } + int16_t* atShiftedBy(int i); - private: int _bufferLength; int16_t* _bufferFirst; int16_t* _bufferLast; int16_t* _at; }; - ConstIterator nextOutput() const { return ConstIterator(_buffer, _bufferLength, _nextOutput); } - ConstIterator lastFrameWritten() const { return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; } - - float getFrameLoudness(ConstIterator frameStart) const; + ConstIterator nextOutput() const; + ConstIterator lastFrameWritten() const; int writeSamples(ConstIterator source, int maxSamples); int writeSamplesWithFade(ConstIterator source, int maxSamples, float fade); + + float getFrameLoudness(ConstIterator frameStart) const; + +protected: + int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; + float getFrameLoudness(const int16_t* frameStart) const; + + int _numFrameSamples; + int _frameCapacity; + int _sampleCapacity; + int _bufferLength; // actual _buffer length (_sampleCapacity + 1) + int _overflowCount{ 0 }; // times the ring buffer has overwritten data + + int16_t* _nextOutput{ nullptr }; + int16_t* _endOfLastWrite{ nullptr }; + int16_t* _buffer{ nullptr }; }; +// inline the iterator: +inline AudioRingBuffer::ConstIterator::ConstIterator() : + _bufferLength(0), + _bufferFirst(NULL), + _bufferLast(NULL), + _at(NULL) {} + +inline AudioRingBuffer::ConstIterator::ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) : + _bufferLength(capacity), + _bufferFirst(bufferFirst), + _bufferLast(bufferFirst + capacity - 1), + _at(at) {} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator=(const ConstIterator& rhs) { + _bufferLength = rhs._bufferLength; + _bufferFirst = rhs._bufferFirst; + _bufferLast = rhs._bufferLast; + _at = rhs._at; + return *this; +} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator++() { + _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; + return *this; +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator++(int) { + ConstIterator tmp(*this); + ++(*this); + return tmp; +} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator--() { + _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; + return *this; +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator--(int) { + ConstIterator tmp(*this); + --(*this); + return tmp; +} + +inline const int16_t& AudioRingBuffer::ConstIterator::operator[] (int i) { + return *atShiftedBy(i); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator+(int i) { + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator-(int i) { + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); +} + +inline int16_t* AudioRingBuffer::ConstIterator::atShiftedBy(int i) { + i = (_at - _bufferFirst + i) % _bufferLength; + if (i < 0) { + i += _bufferLength; + } + return _bufferFirst + i; +} + +inline void AudioRingBuffer::ConstIterator::readSamples(int16_t* dest, int numSamples) { + auto samplesToEnd = _bufferLast - _at + 1; + + if (samplesToEnd >= numSamples) { + memcpy(dest, _at, numSamples * sizeof(int16_t)); + _at += numSamples; + } else { + auto samplesFromStart = numSamples - samplesToEnd; + memcpy(dest, _at, samplesToEnd * sizeof(int16_t)); + memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t)); + + _at = _bufferFirst + samplesFromStart; + } +} + +inline void AudioRingBuffer::ConstIterator::readSamplesWithFade(int16_t* dest, int numSamples, float fade) { + int16_t* at = _at; + for (int i = 0; i < numSamples; i++) { + *dest = (float)*at * fade; + ++dest; + at = (at == _bufferLast) ? _bufferFirst : at + 1; + } +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::nextOutput() const { + return ConstIterator(_buffer, _bufferLength, _nextOutput); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::lastFrameWritten() const { + return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; +} + #endif // hifi_AudioRingBuffer_h diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index b908f57439..6b79879bb7 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -20,7 +20,7 @@ const int STARVE_HISTORY_CAPACITY = 50; InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings) : - _ringBuffer(numFrameSamples, false, numFramesCapacity), + _ringBuffer(numFrameSamples, numFramesCapacity), _lastPopSucceeded(false), _lastPopOutput(), _dynamicJitterBuffers(settings._dynamicJitterBuffers), diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index f8712872ee..27e1e67560 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -205,8 +205,10 @@ void Context::create() { formatAttribs.push_back(24); formatAttribs.push_back(WGL_STENCIL_BITS_ARB); formatAttribs.push_back(8); - formatAttribs.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB); - formatAttribs.push_back(GL_TRUE); +#ifdef NATIVE_SRGB_FRAMEBUFFER + // formatAttribs.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB); + // formatAttribs.push_back(GL_TRUE); +#endif // terminate the list formatAttribs.push_back(0); UINT numFormats; diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt new file mode 100644 index 0000000000..ecf910f434 --- /dev/null +++ b/tests/render-texture-load/CMakeLists.txt @@ -0,0 +1,28 @@ + +set(TARGET_NAME render-texture-load) + +if (WIN32) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") +endif() + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics) + +package_libraries_for_deployment() + +target_zlib() +add_dependency_external_projects(quazip) +find_package(QuaZip REQUIRED) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + +if (WIN32) +add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) +endif () + + +target_bullet() diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp new file mode 100644 index 0000000000..fd6885c381 --- /dev/null +++ b/tests/render-texture-load/src/main.cpp @@ -0,0 +1,585 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +extern QThread* RENDER_THREAD; + +static const QString DATA_SET = "https://hifi-content.s3.amazonaws.com/austin/textures.zip"; +static const QTemporaryDir DATA_DIR; + + +class FileDownloader : public QObject { + Q_OBJECT +public: + using Handler = std::function; + + FileDownloader(QUrl url, const Handler& handler, QObject *parent = 0) : QObject(parent), _handler(handler) { + connect(&_accessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); + _accessManager.get(QNetworkRequest(url)); + } + + void waitForDownload() { + while (!_complete) { + QCoreApplication::processEvents(); + } + } + +private slots: + void fileDownloaded(QNetworkReply* pReply) { + _handler(pReply->readAll()); + pReply->deleteLater(); + _complete = true; + } + +private: + QNetworkAccessManager _accessManager; + Handler _handler; + bool _complete { false }; +}; + +class RenderThread : public GenericThread { + using Parent = GenericThread; +public: + gl::Context _context; + gpu::PipelinePointer _presentPipeline; + gpu::ContextPointer _gpuContext; // initialized during window creation + std::atomic _presentCount; + QElapsedTimer _elapsed; + std::atomic _fps{ 1 }; + RateCounter<200> _fpsCounter; + std::mutex _mutex; + std::shared_ptr _backend; + std::vector _frameTimes; + size_t _frameIndex; + std::mutex _frameLock; + std::queue _pendingFrames; + gpu::FramePointer _activeFrame; + QSize _size; + static const size_t FRAME_TIME_BUFFER_SIZE{ 1024 }; + + void submitFrame(const gpu::FramePointer& frame) { + std::unique_lock lock(_frameLock); + _pendingFrames.push(frame); + } + + + void initialize(QWindow* window, gl::Context& initContext) { + setObjectName("RenderThread"); + _context.setWindow(window); + _context.create(); + _context.makeCurrent(); + window->setSurfaceType(QSurface::OpenGLSurface); + _context.makeCurrent(_context.qglContext(), window); + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _context.makeCurrent(); + DependencyManager::get()->init(); + _context.makeCurrent(); + initContext.create(); + _context.doneCurrent(); + std::unique_lock lock(_mutex); + Parent::initialize(); + _context.moveToThread(_thread); + } + + void setup() override { + RENDER_THREAD = QThread::currentThread(); + + // Wait until the context has been moved to this thread + { + std::unique_lock lock(_mutex); + } + + _context.makeCurrent(); + glewExperimental = true; + glewInit(); + glGetError(); + + //wglSwapIntervalEXT(0); + _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::StandardShaderLib::getDrawTexturePS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + _presentPipeline = gpu::Pipeline::create(program, state); + } + + //_textOverlay = new TextOverlay(glm::uvec2(800, 600)); + glViewport(0, 0, 800, 600); + (void)CHECK_GL_ERROR(); + _elapsed.start(); + } + + void shutdown() override { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _presentPipeline.reset(); + _gpuContext.reset(); + } + + void renderFrame(gpu::FramePointer& frame) { + ++_presentCount; + _context.makeCurrent(); + _backend->recycle(); + _backend->syncCache(); + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + + { + + auto geometryCache = DependencyManager::get(); + gpu::Batch presentBatch; + presentBatch.setViewportTransform({ 0, 0, _size.width(), _size.height() }); + presentBatch.enableStereo(false); + presentBatch.resetViewTransform(); + presentBatch.setFramebuffer(gpu::FramebufferPointer()); + presentBatch.setResourceTexture(0, frame->framebuffer->getRenderBuffer(0)); + presentBatch.setPipeline(_presentPipeline); + presentBatch.draw(gpu::TRIANGLE_STRIP, 4); + _gpuContext->executeBatch(presentBatch); + } + (void)CHECK_GL_ERROR(); + } + _context.makeCurrent(); + _context.swapBuffers(); + _fpsCounter.increment(); + static size_t _frameCount{ 0 }; + ++_frameCount; + if (_elapsed.elapsed() >= 500) { + _fps = _fpsCounter.rate(); + _frameCount = 0; + _elapsed.restart(); + } + (void)CHECK_GL_ERROR(); + _context.doneCurrent(); + } + + void report() { + uint64_t total = 0; + for (const auto& t : _frameTimes) { + total += t; + } + auto averageFrameTime = total / FRAME_TIME_BUFFER_SIZE; + qDebug() << "Average frame " << averageFrameTime; + + std::list> sortedHighFrames; + for (size_t i = 0; i < _frameTimes.size(); ++i) { + const auto& t = _frameTimes[i]; + if (t > averageFrameTime * 6) { + sortedHighFrames.push_back({ t, i } ); + } + } + + sortedHighFrames.sort(); + for (const auto& p : sortedHighFrames) { + qDebug() << "Long frame " << p.first << " " << p.second; + } + } + + + bool process() override { + std::queue pendingFrames; + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + } + + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + if (_activeFrame) { + _gpuContext->consumeFrameUpdates(_activeFrame); + } + pendingFrames.pop(); + } + + if (!_activeFrame) { + QThread::msleep(1); + return true; + } + + { + auto start = usecTimestampNow(); + renderFrame(_activeFrame); + auto duration = usecTimestampNow() - start; + auto frameBufferIndex = _frameIndex % FRAME_TIME_BUFFER_SIZE; + _frameTimes[frameBufferIndex] = duration; + ++_frameIndex; + if (0 == _frameIndex % FRAME_TIME_BUFFER_SIZE) { + report(); + } + } + return true; + } +}; + +QString fileForPath(const QString& name) { + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(name.toLocal8Bit().data(), name.length()); + QString hashStr = QString(hash.result().toHex()); + auto dot = name.lastIndexOf('.'); + QString extension = name.right(name.length() - dot); + QString result = DATA_DIR.path() + "/" + hashStr + extension; + return result; +} + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow { +public: + //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" + static void setup() { + DependencyManager::registerInheritance(); + //DependencyManager::registerInheritance(); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, 0); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + } + + struct TextureLoad { + uint32_t time; + QString file; + QString src; + }; + + QTestWindow() { + + _currentTexture = _textures.end(); + { + QStringList stringList; + QFile textFile("h:/textures/loads.txt"); + textFile.open(QFile::ReadOnly); + //... (open the file for reading, etc.) + QTextStream textStream(&textFile); + while (true) { + QString line = textStream.readLine(); + if (line.isNull()) + break; + else + stringList.append(line); + } + + for (QString s : stringList) { + auto index = s.indexOf(" "); + QString timeStr = s.left(index); + auto time = timeStr.toUInt(); + QString path = s.right(s.length() - index).trimmed(); + path = fileForPath(path); + qDebug() << "Path " << path; + if (!QFileInfo(path).exists()) { + continue; + } + _textureLoads.push({ time, path, s }); + } + } + + installEventFilter(this); + QThreadPool::globalInstance()->setMaxThreadCount(2); + QThread::currentThread()->setPriority(QThread::HighestPriority); + ResourceManager::init(); + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + _size = QSize(800, 600); + _renderThread._size = _size; + setGeometry(QRect(QPoint(), _size)); + create(); + show(); + QCoreApplication::processEvents(); + // Create the initial context + _renderThread.initialize(this, _initContext); + _initContext.makeCurrent(); + // FIXME use a wait condition + QThread::msleep(1000); + _renderThread.submitFrame(gpu::FramePointer()); + _initContext.makeCurrent(); + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::StandardShaderLib::getDrawTexturePS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram(*program); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false)); + state->setScissorEnable(true); + _simplePipeline = gpu::Pipeline::create(program, state); + } + + QTimer* timer = new QTimer(this); + timer->setInterval(0); + connect(timer, &QTimer::timeout, this, [this] { + draw(); + }); + timer->start(); + _ready = true; + } + + virtual ~QTestWindow() { + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + ResourceManager::cleanup(); + } + +protected: + + bool eventFilter(QObject *obj, QEvent *event) override { + if (event->type() == QEvent::Close) { + _renderThread.terminate(); + } + + return QWindow::eventFilter(obj, event); + } + + void keyPressEvent(QKeyEvent* event) override { + } + + void keyReleaseEvent(QKeyEvent* event) override { + } + + void mouseMoveEvent(QMouseEvent* event) override { + } + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } + +private: + std::queue _textureLoads; + std::list _textures; + std::list::iterator _currentTexture; + + uint16_t _fps; + gpu::PipelinePointer _simplePipeline; + + void draw() { + if (!_ready) { + return; + } + if (!isVisible()) { + return; + } + if (_renderCount.load() != 0 && _renderCount.load() >= _renderThread._presentCount.load()) { + QThread::usleep(1); + return; + } + _renderCount = _renderThread._presentCount.load(); + update(); + + QSize windowSize = _size; + auto framebufferCache = DependencyManager::get(); + framebufferCache->setFrameBufferSize(windowSize); + + // Final framebuffer that will be handled to the display-plugin + render(); + + if (_fps != _renderThread._fps) { + _fps = _renderThread._fps; + updateText(); + } + } + + void updateText() { + setTitle(QString("FPS %1").arg(_fps)); + } + + void update() { + auto now = usecTimestampNow(); + static auto last = now; + auto delta = (now - last) / USECS_PER_MSEC; + if (!_textureLoads.empty()) { + const auto& front = _textureLoads.front(); + if (delta >= front.time) { + QFileInfo fileInfo(front.file); + if (!fileInfo.exists()) { + qDebug() << "Missing file " << front.file; + } else { + qDebug() << "Loading " << front.src; + _textures.push_back(DependencyManager::get()->getImageTexture(front.file)); + _currentTexture = _textures.begin(); + } + _textureLoads.pop(); + if (_textureLoads.empty()) { + qDebug() << "Done"; + } + } + } + } + + void render() { + auto& gpuContext = _renderThread._gpuContext; + gpuContext->beginFrame(); + gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + batch.resetStages(); + }); + PROFILE_RANGE(__FUNCTION__); + auto framebuffer = DependencyManager::get()->getFramebuffer(); + + gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(framebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(1, 0, 0, 1)); + auto vpsize = framebuffer->getSize(); + auto vppos = ivec2(0); + batch.setViewportTransform(ivec4(vppos, vpsize)); + if (_currentTexture != _textures.end()) { + ++_currentTexture; + } + if (_currentTexture == _textures.end()) { + _currentTexture = _textures.begin(); + } + + if (_currentTexture != _textures.end()) { + batch.setResourceTexture(0, *_currentTexture); + } + batch.setPipeline(_simplePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + + auto frame = gpuContext->endFrame(); + frame->framebuffer = framebuffer; + frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer){ + DependencyManager::get()->releaseFramebuffer(framebuffer); + }; + _renderThread.submitFrame(frame); + if (!_renderThread.isThreaded()) { + _renderThread.process(); + } + } + + void resizeWindow(const QSize& size) { + _size = size; + if (!_ready) { + return; + } + _renderThread._size = size; + } + +private: + QSize _size; + std::atomic _renderCount; + gl::OffscreenContext _initContext; + RenderThread _renderThread; + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. + bool _ready { false }; +}; + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + if (!message.isEmpty()) { +#ifdef Q_OS_WIN + OutputDebugStringA(message.toLocal8Bit().constData()); + OutputDebugStringA("\n"); +#endif + std::cout << message.toLocal8Bit().constData() << std::endl; + } +} + +const char * LOG_FILTER_RULES = R"V0G0N( +hifi.gpu=true +)V0G0N"; + +void unzipTestData(const QByteArray& zipData) { + QTemporaryFile zipFile; + if (zipFile.open()) { + zipFile.write(zipData); + zipFile.close(); + } + qDebug() << zipFile.fileName(); + if (!DATA_DIR.isValid()) { + qFatal("Unable to create temp dir"); + } + + //auto files = JlCompress::getFileList(zipData); + auto files = JlCompress::extractDir(zipFile.fileName(), DATA_DIR.path()); + qDebug() << DATA_DIR.path(); + +} + +int main(int argc, char** argv) { + QApplication app(argc, argv); + QCoreApplication::setApplicationName("RenderPerf"); + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setOrganizationDomain("highfidelity.com"); + qInstallMessageHandler(messageHandler); + QLoggingCategory::setFilterRules(LOG_FILTER_RULES); + + + FileDownloader(DATA_SET, [&](const QByteArray& data) { + qDebug() << "Fetched size " << data.size(); + unzipTestData(data); + }).waitForDownload(); + + QTestWindow::setup(); + QTestWindow window; + app.exec(); + return 0; +} + +#include "main.moc"