123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #pragma once
- #include <AzCore/std/parallel/lock.h>
- #include <AzCore/std/parallel/mutex.h>
- #include <AzCore/std/typetraits/is_arithmetic.h>
- #define AUDIO_ALLOCATION_ALIGNMENT 16
- namespace Audio
- {
- /*
- * RingBufferBase defines an interface for a generic ring buffer.
- */
- class RingBufferBase
- {
- public:
- AZ_CLASS_ALLOCATOR(RingBufferBase, AZ::SystemAllocator);
- RingBufferBase()
- {}
- RingBufferBase([[maybe_unused]] const size_t numSamples)
- {}
- virtual ~RingBufferBase()
- {}
- /**
- * Adds new data to the ringbuffer.
- * @param source Source buffer to copy from.
- * @param numFrames Number of sample frames available to copy.
- * @param numChannels Number of channels in the sample data, samples = numFrames * numChannels.
- * @return Number of sample frames copied.
- */
- virtual size_t AddData(const void* source, size_t numFrames, size_t numChannels) = 0;
- /**
- * Adds new multi-track/multi-channel data to the ringbuffer in interleaved format.
- * Not a required interface.
- * @param source Source buffer to copy from.
- * @param numFrames Number of sample frames available to copy.
- * @param numChannels Number of tracks/channels in the source data, numSamples = numFrames * numChannels.
- * @return Number of sample frames copied.
- */
- virtual size_t AddMultiTrackDataInterleaved([[maybe_unused]] const void** source, [[maybe_unused]] size_t numFrames, [[maybe_unused]] size_t numChannels) { return 0; }
- /**
- * Consumes stored data from the ringbuffer.
- * @param dest Where the data will be written to, typically an array of SampleType pointers.
- * @param numFrames Number of sample frames requested to consume.
- * @param numChannels Number of channels laid out in the dest parameter.
- * @param deinterleaveMultichannel In the case of multichannel data, if true do a deinterleaved copy into the dest array channels otherwise straight copy into dest[0].
- * @return Number of sample frames consumed.
- */
- virtual size_t ConsumeData(void** dest, size_t numFrames, size_t numChannels, bool deinterleaveMultichannel = false) = 0;
- /**
- * Zeros the ringbuffer data and resets indices.
- */
- virtual void ResetBuffer() = 0;
- };
- /*
- * RingBuffer<T>
- *
- * m_read ----> m_write ---->
- * V V
- * +-------------------------------------------------+
- * | DATADATADATADATADATADATADATA |
- * +-------------------------------------------------+
- * ^
- * m_buffer
- *
- * <---------------------m_size---------------------->
- */
- template <typename SampleType, typename = AZStd::enable_if_t<AZStd::is_arithmetic<SampleType>::value>>
- class RingBuffer
- : public RingBufferBase
- {
- public:
- AZ_CLASS_ALLOCATOR(RingBuffer<SampleType>, AZ::SystemAllocator);
- static const size_t s_bytesPerSample = sizeof(SampleType);
- ///////////////////////////////////////////////////////////////////////////////////////////
- RingBuffer(size_t numSamples)
- {
- AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
- AllocateData(numSamples);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- ~RingBuffer() override
- {
- AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
- DeallocateData();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- size_t AddData(const void* source, size_t numFrames, size_t numChannels) override
- {
- AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
- const size_t numSamples = numFrames * numChannels;
- if (numSamples > SamplesUnused())
- {
- // Writing this many samples will cross the read index, can't proceed.
- return 0;
- }
- const SampleType* sourceBuffer = static_cast<const SampleType*>(source);
- if (m_write + numSamples < m_size)
- {
- // write operation won't wrap the buffer
- if (sourceBuffer)
- {
- size_t copySize = numSamples * s_bytesPerSample;
- ::memcpy(&m_buffer[m_write], sourceBuffer, copySize);
- }
- else
- {
- ::memset(&m_buffer[m_write], 0, numSamples * s_bytesPerSample);
- }
- m_write += numSamples;
- }
- else
- {
- // Split the copy operations to handle wrap-around
- size_t currentToEndSamples = m_size - m_write;
- size_t wraparoundSamples = numSamples - currentToEndSamples;
- if (sourceBuffer)
- {
- ::memcpy(&m_buffer[m_write], sourceBuffer, currentToEndSamples * s_bytesPerSample);
- ::memcpy(m_buffer, &sourceBuffer[currentToEndSamples], wraparoundSamples * s_bytesPerSample);
- }
- else
- {
- ::memset(&m_buffer[m_write], 0, currentToEndSamples * s_bytesPerSample);
- ::memset(m_buffer, 0, wraparoundSamples * s_bytesPerSample);
- }
- m_write = wraparoundSamples;
- }
- return numFrames;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- size_t AddMultiTrackDataInterleaved(const void** source, size_t numFrames, size_t numChannels) override
- {
- AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
- const size_t numSamples = numFrames * numChannels;
- if (numSamples > SamplesUnused())
- {
- // Writing this many samples will cross the read index, can't proceed.
- // Consumption needs to occur first to free up room for input.
- return 0;
- }
- if (m_write + numSamples < m_size)
- {
- // write operation won't wrap the buffer
- const SampleType** sourceBufferChannels = reinterpret_cast<const SampleType**>(source);
- AZ_ErrorOnce("AudioRingBuffer", sourceBufferChannels, "AudioRingBuffer - Multi-track source buffers not found!\n");
- if (sourceBufferChannels)
- {
- for (size_t channel = 0; channel < numChannels; ++channel)
- {
- AZ_ErrorOnce("AudioRingBuffer", sourceBufferChannels[channel], "AudioRingBufffer - Multi-track source contains a null buffer at channel %zu!\n", channel);
- const SampleType* sourceBuffer = sourceBufferChannels[channel];
- if (sourceBuffer)
- {
- for (size_t frame = 0; frame < numFrames; ++frame)
- {
- m_buffer[m_write + channel + (numChannels * frame)] = *sourceBuffer++;
- }
- }
- else
- {
- for (size_t frame = 0; frame < numFrames; ++frame)
- {
- m_buffer[m_write + channel + (numChannels * frame)] = 0;
- }
- }
- }
- }
- else
- {
- ::memset(&m_buffer[m_write], 0, numSamples * s_bytesPerSample);
- }
- m_write += numSamples;
- }
- else
- {
- // split the copy operations to handle wrap-around
- size_t currentToEndSamples = m_size - m_write;
- size_t wraparoundSamples = numSamples - currentToEndSamples;
- const SampleType** sourceBufferChannels = reinterpret_cast<const SampleType**>(source);
- AZ_ErrorOnce("AudioRingBuffer", sourceBufferChannels, "AudioRingBuffer - Multi-track source buffers not found!\n");
- if (sourceBufferChannels)
- {
- size_t currentToEndFrames = (currentToEndSamples) / numChannels;
- size_t wraparoundFrames = (numFrames - currentToEndFrames);
- for (size_t channel = 0; channel < numChannels; ++channel)
- {
- AZ_ErrorOnce("AudioRingBuffer", sourceBufferChannels[channel], "AudioRingBufffer - Multi-track source contains a null buffer at channel %zu!\n", channel);
- const SampleType* sourceBuffer = sourceBufferChannels[channel];
- if (sourceBuffer)
- {
- for (size_t frame = 0; frame < currentToEndFrames; ++frame)
- {
- m_buffer[m_write + channel + (numChannels * frame)] = *sourceBuffer++;
- }
- for (size_t frame = 0; frame < wraparoundFrames; ++frame)
- {
- m_buffer[channel + (numChannels * frame)] = *sourceBuffer++;
- }
- }
- else
- {
- for (size_t frame = 0; frame < currentToEndFrames; ++frame)
- {
- m_buffer[m_write + channel + (numChannels * frame)] = 0;
- }
- for (size_t frame = 0; frame < wraparoundFrames; ++frame)
- {
- m_buffer[channel + (numChannels * frame)] = 0;
- }
- }
- }
- }
- else
- {
- ::memset(&m_buffer[m_write], 0, currentToEndSamples * s_bytesPerSample);
- ::memset(m_buffer, 0, wraparoundSamples * s_bytesPerSample);
- }
- m_write = wraparoundSamples;
- }
- return numFrames;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- size_t ConsumeData(void** dest, size_t numFrames, size_t numChannels, bool deinterleaveMultichannel) override
- {
- if (!dest)
- {
- return 0;
- }
- AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
- SampleType** destBuffers = reinterpret_cast<SampleType**>(dest);
- size_t numSamples = numFrames * numChannels;
- size_t samplesReady = SamplesReady();
- if (samplesReady == 0)
- {
- return 0;
- }
- else if (numSamples > samplesReady)
- {
- numSamples = samplesReady;
- numFrames = numSamples / numChannels; // ?? could this give incorrect number ??
- }
- if (numChannels == 2 && deinterleaveMultichannel)
- {
- if (m_write > m_read)
- {
- // do 2ch deinterleaved copy
- for (AZ::u32 frame = 0; frame < numFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- }
- }
- else
- {
- size_t currentToEndFrames = (m_size - m_read) / numChannels;
- if (currentToEndFrames > numFrames)
- {
- // do 2ch deinterleaved copy
- for (AZ::u32 frame = 0; frame < numFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- }
- }
- else
- {
- // do two partial 2ch deinterleaved copies
- AZ::u32 frame = 0;
- for (; frame < currentToEndFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- }
- m_read = 0;
- for (; frame < numFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- }
- }
- }
- }
- else if (numChannels == 6 && deinterleaveMultichannel)
- {
- if (m_write > m_read)
- {
- // do 6ch deinterleaved copy
- for (AZ::u32 frame = 0; frame < numFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- destBuffers[2][frame] = m_buffer[m_read++];
- destBuffers[3][frame] = m_buffer[m_read++];
- destBuffers[4][frame] = m_buffer[m_read++];
- destBuffers[5][frame] = m_buffer[m_read++];
- }
- }
- else
- {
- size_t currentToEndFrames = (m_size - m_read) / numChannels;
- if (currentToEndFrames > numFrames)
- {
- // do 6ch deinterleaved copy
- for (AZ::u32 frame = 0; frame < numFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- destBuffers[2][frame] = m_buffer[m_read++];
- destBuffers[3][frame] = m_buffer[m_read++];
- destBuffers[4][frame] = m_buffer[m_read++];
- destBuffers[5][frame] = m_buffer[m_read++];
- }
- }
- else
- {
- // do two partial 6ch deinterleaved copies
- AZ::u32 frame = 0;
- for (; frame < currentToEndFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- destBuffers[2][frame] = m_buffer[m_read++];
- destBuffers[3][frame] = m_buffer[m_read++];
- destBuffers[4][frame] = m_buffer[m_read++];
- destBuffers[5][frame] = m_buffer[m_read++];
- }
- m_read = 0;
- for (; frame < numFrames; ++frame)
- {
- destBuffers[0][frame] = m_buffer[m_read++];
- destBuffers[1][frame] = m_buffer[m_read++];
- destBuffers[2][frame] = m_buffer[m_read++];
- destBuffers[3][frame] = m_buffer[m_read++];
- destBuffers[4][frame] = m_buffer[m_read++];
- destBuffers[5][frame] = m_buffer[m_read++];
- }
- }
- }
- }
- else
- {
- // single channel or interleaved copy, can do straight memcpy's
- if (m_write > m_read)
- {
- ::memcpy(destBuffers[0], &m_buffer[m_read], numSamples * s_bytesPerSample);
- m_read += numSamples;
- }
- else
- {
- size_t currentToEndSamples = m_size - m_read;
- if (currentToEndSamples > numSamples)
- {
- ::memcpy(destBuffers[0], &m_buffer[m_read], numSamples * s_bytesPerSample);
- m_read += numSamples;
- }
- else
- {
- size_t wraparoundSamples = numSamples - currentToEndSamples;
- ::memcpy(destBuffers[0], &m_buffer[m_read], currentToEndSamples * s_bytesPerSample);
- ::memcpy(&destBuffers[0][currentToEndSamples], m_buffer, wraparoundSamples * s_bytesPerSample);
- m_read = wraparoundSamples;
- }
- }
- }
- return numFrames;
- }
- protected:
- ///////////////////////////////////////////////////////////////////////////////////////////
- void ResetBuffer() override
- {
- // no lock! should only be called inside api that has already locked the buffer.
- // 0xAA is used rather than 0 to intitialze the buffer to make debugging easier as samples often
- // lead with 0's, it's more obvious when and where data is being written
- ::memset(m_buffer, 0xAA, m_size * s_bytesPerSample);
- m_write = 0;
- m_read = 0;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- void AllocateData(size_t numSamples)
- {
- // no lock! should only be called inside api that has already locked the buffer.
- if (m_buffer)
- {
- DeallocateData();
- }
- m_size = numSamples;
- m_buffer = static_cast<SampleType*>(azmalloc(numSamples * s_bytesPerSample, AUDIO_ALLOCATION_ALIGNMENT, AZ::SystemAllocator));
- ResetBuffer();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- void DeallocateData()
- {
- // no lock! should only be called inside api that has already locked the buffer.
- if (m_buffer)
- {
- azfree(m_buffer, AZ::SystemAllocator);
- m_buffer = nullptr;
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Returns the number of samples in the ring buffer that are ready for consumption.
- size_t SamplesReady() const
- {
- // no lock! should only be called inside api that has already locked the buffer.
- if (m_read > m_write)
- { // read-head needs to wrap around to catch up to write-head
- return (m_write + m_size - m_read);
- }
- else
- { // read-head is behind write-head and won't wrap around
- return (m_write - m_read);
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Returns the number of samples in the ring buffer that can be filled.
- size_t SamplesUnused() const
- {
- // no lock! should only be called inside api that has already locked the buffer.
- return m_size - SamplesReady();
- }
- private:
- SampleType* m_buffer = nullptr; // sample buffer
- size_t m_write = 0; // write-head index into buffer
- size_t m_read = 0; // read-head index into buffer
- size_t m_size = 0; // total size of buffer in samples. bytes = (m_size * s_bytesPerSample)
- AZStd::mutex m_mutex; // protects buffer access
- };
- } // namespace Audio
|