Audacity 3.2.0
RingBuffer.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 RingBuffer.cpp
6
7 Dominic Mazzoni
8 Paul Licameli
9
10*******************************************************************//*******************************************************************/
26
27
28#include "RingBuffer.h"
29#include "Dither.h"
30#include <cstring>
31
33 : mBufferSize{ std::max<size_t>(size, 64) }
34 , mFormat{ format }
35 , mBuffer{ mBufferSize, mFormat }
36{
37}
38
40{
41}
42
43// Calculations of free and filled space, given snapshots taken of the start
44// and end values
45
46size_t RingBuffer::Filled(size_t start, size_t end) const
47{
48 return (end + mBufferSize - start) % mBufferSize;
49}
50
51size_t RingBuffer::Free(size_t start, size_t end) const
52{
53 return std::max<size_t>(mBufferSize - Filled( start, end ), 4) - 4;
54}
55
56//
57// For the writer only:
58// Only writer reads or writes mWritten
59// Only writer writes the end, so it can read it again relaxed
60// And it reads the start written by reader, with acquire order,
61// so that any reading done in Get() happens-before any reuse of the space.
62//
63
65{
66 auto start = mStart.load( std::memory_order_relaxed );
67 return Free( start, mWritten );
68
69 // Reader might increase the available free space after return, but will
70 // never decrease it, so writer can safely assume this much at least
71}
72
74{
75 auto start = mStart.load( std::memory_order_relaxed );
76 return Filled( start, mWritten );
77}
78
80 size_t samplesToCopy, size_t padding)
81{
82 mLastPadding = padding;
83 auto start = mStart.load( std::memory_order_acquire );
84 auto end = mWritten;
85 const auto free = Free( start, end );
86 samplesToCopy = std::min( samplesToCopy, free );
87 padding = std::min( padding, free - samplesToCopy );
88 auto src = buffer;
89 size_t copied = 0;
90 auto pos = end;
91
92 while ( samplesToCopy ) {
93 auto block = std::min( samplesToCopy, mBufferSize - pos );
94
97 block, DitherType::none);
98
99 src += block * SAMPLE_SIZE(format);
100 pos = (pos + block) % mBufferSize;
101 samplesToCopy -= block;
102 copied += block;
103 }
104
105 while ( padding ) {
106 const auto block = std::min( padding, mBufferSize - pos );
107 ClearSamples( mBuffer.ptr(), mFormat, pos, block );
108 pos = (pos + block) % mBufferSize;
109 padding -= block;
110 copied += block;
111 }
112
113 mWritten = pos;
114 return copied;
115}
116
118{
119 const auto sampleSize = SAMPLE_SIZE(mFormat);
120 const auto buffer = mBuffer.ptr();
121
122 // un-put some of the un-flushed data which is from mEnd to mWritten
123 // bound the result
124 auto end = mEnd.load(std::memory_order_relaxed);
126 const auto result = size;
127
128 // First memmove
129 auto limit = end < mWritten ? mWritten : mBufferSize;
130 // Source offset for move
131 auto source = std::min(end + size, limit);
132 // How many to move
133 auto count = limit - source;
134 auto pDst = buffer + end * sampleSize;
135 auto pSrc = buffer + source * sampleSize;
136 memmove(pDst, pSrc, count * sampleSize);
137 // Discount how many really discarded
138 size -= (source - end);
139
140 if (end >= mWritten) {
141 // The unflushed data were wrapped around, not contiguous
142 end += count;
143 auto pDst = buffer + end * sampleSize;
144 // Rotate some samples from start of buffer, but discarding
145 // any remaining number that must be un-put
146 // Then shift samples near the start of buffer
147 pSrc = buffer + size * sampleSize;
148 auto toMove = mWritten - size;
149 auto toMove1 = std::min(toMove, mBufferSize - end);
150 auto toMove2 = toMove - toMove1;
151 memmove(pDst, pSrc, toMove1 * sampleSize);
152 memmove(buffer, pSrc + toMove1 * sampleSize, toMove2 * sampleSize);
153 }
154
155 // Move mWritten backwards by result
156 mWritten = (mWritten + (mBufferSize - result)) % mBufferSize;
157
158 // Adjust mLastPadding
160
161 return result;
162}
163
164size_t RingBuffer::Clear(sampleFormat format, size_t samplesToClear)
165{
166 auto start = mStart.load( std::memory_order_acquire );
167 auto end = mWritten;
168 samplesToClear = std::min( samplesToClear, Free( start, end ) );
169 size_t cleared = 0;
170 auto pos = end;
171
172 while(samplesToClear) {
173 auto block = std::min( samplesToClear, mBufferSize - pos );
174
175 ClearSamples(mBuffer.ptr(), format, pos, block);
176
177 pos = (pos + block) % mBufferSize;
178 samplesToClear -= block;
179 cleared += block;
180 }
181
182 mWritten = pos;
183
184 return cleared;
185}
186
187std::pair<samplePtr, size_t> RingBuffer::GetUnflushed(unsigned iBlock)
188{
189 // This function is called by the writer
190
191 // Find total number of samples unflushed:
192 auto end = mEnd.load(std::memory_order_relaxed);
193 const size_t size = Filled(end, mWritten) - mLastPadding;
194
195 // How many in the first part:
196 const size_t size0 = std::min(size, mBufferSize - end);
197 // How many wrap around the ring buffer:
198 const size_t size1 = size - size0;
199
200 if (iBlock == 0)
201 return {
202 size0 ? mBuffer.ptr() + end * SAMPLE_SIZE(mFormat) : nullptr,
203 size0 };
204 else
205 return {
206 size1 ? mBuffer.ptr() : nullptr,
207 size1 };
208}
209
211{
212 // Atomically update the end pointer with release, so the nonatomic writes
213 // just done to the buffer don't get reordered after
214 mEnd.store(mWritten, std::memory_order_release);
215 mLastPadding = 0;
216}
217
218//
219// For the reader only:
220// Only reader writes the start, so it can read it again relaxed
221// But it reads the end written by the writer, who also sends sample data
222// with the changes of end; therefore that must be read with acquire order
223// if we do more than merely query the size or throw samples away
224//
225
227{
228 auto end = mEnd.load( std::memory_order_relaxed ); // get away with it here
229 auto start = mStart.load( std::memory_order_relaxed );
230 return Filled( start, end );
231
232 // Writer might increase the available samples after return, but will
233 // never decrease them, so reader can safely assume this much at least
234}
235
237 size_t samplesToCopy)
238{
239 // Must match the writer's release with acquire for well defined reads of
240 // the buffer
241 auto end = mEnd.load( std::memory_order_acquire );
242 auto start = mStart.load( std::memory_order_relaxed );
243 samplesToCopy = std::min( samplesToCopy, Filled( start, end ) );
244 auto dest = buffer;
245 size_t copied = 0;
246
247 while(samplesToCopy) {
248 auto block = std::min( samplesToCopy, mBufferSize - start );
249
251 dest, format,
252 block, DitherType::none);
253
254 dest += block * SAMPLE_SIZE(format);
255 start = (start + block) % mBufferSize;
256 samplesToCopy -= block;
257 copied += block;
258 }
259
260 // Communicate to writer that we have consumed some data,
261 // with nonrelaxed ordering
262 mStart.store( start, std::memory_order_release );
263
264 return copied;
265}
266
267size_t RingBuffer::Discard(size_t samplesToDiscard)
268{
269 auto end = mEnd.load( std::memory_order_relaxed ); // get away with it here
270 auto start = mStart.load( std::memory_order_relaxed );
271 samplesToDiscard = std::min( samplesToDiscard, Filled( start, end ) );
272
273 // Communicate to writer that we have skipped some data, and that's all
274 mStart.store((start + samplesToDiscard) % mBufferSize,
275 std::memory_order_relaxed);
276
277 return samplesToDiscard;
278}
int min(int a, int b)
@ none
Definition: Dither.h:20
void ClearSamples(samplePtr dst, sampleFormat format, size_t start, size_t len)
void CopySamples(constSamplePtr src, sampleFormat srcFormat, samplePtr dst, sampleFormat dstFormat, size_t len, DitherType ditherType, unsigned int srcStride, unsigned int dstStride)
Copy samples from any format to any other format; apply dithering only if narrowing the format.
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
char * samplePtr
Definition: SampleFormat.h:57
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const char * constSamplePtr
Definition: SampleFormat.h:58
size_t WrittenForGet() const
Reader may concurrently cause a decrease of what this returns.
Definition: RingBuffer.cpp:73
size_t AvailForPut() const
Definition: RingBuffer.cpp:64
size_t Unput(size_t size)
Remove an initial segment of data that has been Put but not Flushed yet.
Definition: RingBuffer.cpp:117
size_t mLastPadding
Definition: RingBuffer.h:59
size_t Put(constSamplePtr buffer, sampleFormat format, size_t samples, size_t padding=0)
Does not apply dithering.
Definition: RingBuffer.cpp:79
size_t Free(size_t start, size_t end) const
Definition: RingBuffer.cpp:51
const size_t mBufferSize
Definition: RingBuffer.h:64
std::pair< samplePtr, size_t > GetUnflushed(unsigned iBlock)
Definition: RingBuffer.cpp:187
NonInterfering< std::atomic< size_t > > mEnd
Definition: RingBuffer.h:62
size_t Filled(size_t start, size_t end) const
Definition: RingBuffer.cpp:46
void Flush()
Flush after a sequence of Put (and/or Clear) calls to let consumer see.
Definition: RingBuffer.cpp:210
size_t Clear(sampleFormat format, size_t samples)
Definition: RingBuffer.cpp:164
size_t Get(samplePtr buffer, sampleFormat format, size_t samples)
Does not apply dithering.
Definition: RingBuffer.cpp:236
const sampleFormat mFormat
Definition: RingBuffer.h:66
size_t mWritten
Definition: RingBuffer.h:58
NonInterfering< std::atomic< size_t > > mStart
Definition: RingBuffer.h:62
const SampleBuffer mBuffer
Definition: RingBuffer.h:67
size_t Discard(size_t samples)
Definition: RingBuffer.cpp:267
RingBuffer(sampleFormat format, size_t size)
Definition: RingBuffer.cpp:32
size_t AvailForGet() const
Definition: RingBuffer.cpp:226
samplePtr ptr() const
Definition: SampleFormat.h:165
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
void free(void *ptr)
Definition: VectorOps.h:34
STL namespace.