UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
ring_buffer.cpp
Go to the documentation of this file.
1
6/*
7 * Copyright (c) 2011-2019 CESNET, z. s. p. o.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, is permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of CESNET nor the names of its contributors may be
22 * used to endorse or promote products derived from this software without
23 * specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
27 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
29 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
35 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38
39#include "utils/ring_buffer.h"
40#include <assert.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <memory>
45#include <atomic>
46
48 std::unique_ptr<char[]> data;
49 int len;
50 /* Start and end markers for the buffer.
51 *
52 * Valid values are in the range (0, 2 * ring->len). This is because in
53 * the simple implementation, where the values are in range
54 * (0, ring->len), it would be impossible to tell apart empty buffer
55 * and completely full buffer (both would have start == end). The
56 * simple workaround of always having at least one byte free would make
57 * it impossible to correctly align multi-byte elements for direct
58 * zero-copy access.
59 *
60 * When the range is doubled, full buffer has start == end in modulo
61 * ring->len, but not in modulo 2*ring->len.
62 */
63 std::atomic<int> start, end;
64};
65
66struct ring_buffer *ring_buffer_init(int size) {
67 assert(size > 0);
68 auto ring = new ring_buffer();
69
70 ring->data = std::make_unique<char[]>(size);
71 ring->len = size;
72 ring->start = 0;
73 ring->end = 0;
74 return ring;
75}
76
77void ring_buffer_destroy(struct ring_buffer *ring) {
78 if(ring) {
79 delete ring;
80 }
81}
82
83static int calculate_avail_read(int start, int end, int buf_len) {
84 int avail = (end - start + 2 * buf_len) % buf_len;
85 if(avail == 0 && (end >= buf_len) != (start >= buf_len))
86 avail += buf_len;
87
88 return avail;
89}
90
91static int calculate_avail_write(int start, int end, int buf_len) {
92 return buf_len - calculate_avail_read(start, end, buf_len);
93}
94
95
96int ring_get_read_regions(struct ring_buffer *ring, int max_len,
97 void **ptr1, int *size1,
98 void **ptr2, int *size2)
99{
100 /* end index is modified by the writer thread, use acquire order to ensure
101 * that all writes by the writer thread made before the modification are
102 * observable in this (reader) thread */
103 int end = std::atomic_load_explicit(&ring->end, std::memory_order_acquire);
104 // start index is modified only by this (reader) thread, so relaxed is enough
105 int start = std::atomic_load_explicit(&ring->start, std::memory_order_relaxed);
106
107 int read_len = calculate_avail_read(start, end, ring->len);
108 if(read_len > max_len)
109 read_len = max_len;
110
111 int start_idx = start % ring->len;
112 int to_end = ring->len - start_idx;
113 *ptr1 = ring->data.get() + start_idx;
114 if(read_len <= to_end) {
115 *size1 = read_len;
116 *ptr2 = nullptr;
117 *size2 = 0;
118 } else {
119 *size1 = to_end;
120 *ptr2 = ring->data.get();
121 *size2 = read_len - to_end;
122 }
123
124 return read_len;
125}
126
127void ring_advance_read_idx(struct ring_buffer *ring, int amount) {
128 // start index is modified only by this (reader) thread, so relaxed is enough
129 int start = std::atomic_load_explicit(&ring->start, std::memory_order_relaxed);
130
131 /* Use release order to ensure that all reads are completed (no reads
132 * or writes in the current thread can be reordered after this store).
133 */
134 std::atomic_store_explicit(&ring->start,
135 (start + amount) % (2*ring->len), std::memory_order_release);
136}
137
138int ring_buffer_read(struct ring_buffer * ring, char *out, int max_len) {
139 void *ptr1;
140 int size1;
141 void *ptr2;
142 int size2;
143
144 int read_len = ring_get_read_regions(ring, max_len, &ptr1, &size1, &ptr2, &size2);
145
146 memcpy(out, ptr1, size1);
147 if(ptr2) {
148 memcpy(out + size1, ptr2, size2);
149 }
150
151 ring_advance_read_idx(ring, read_len);
152 return read_len;
153}
154
155void ring_buffer_flush(struct ring_buffer * buf) {
156 /* This should only be called while the buffer is not being read or
157 * written. The only way to safely flush without locking is by reading
158 * all available data from the reader thread.
159 */
160 buf->start = 0;
161 buf->end = 0;
162}
163
164int ring_get_write_regions(struct ring_buffer *ring, int requested_len,
165 void **ptr1, int *size1,
166 void **ptr2, int *size2)
167{
168 *ptr1 = nullptr;
169 *size1 = 0;
170 *ptr2 = nullptr;
171 *size2 = 0;
172
173 // end index is modified only by this (writer) thread, so relaxed is enough
174 int end = std::atomic_load_explicit(&ring->end, std::memory_order_relaxed);
175
176 if(requested_len > ring->len) {
177 return 0;
178 }
179
180 int end_idx = end % ring->len;
181 int to_end = ring->len - end_idx;
182 *ptr1 = ring->data.get() + end_idx;
183 *size1 = requested_len < to_end ? requested_len : to_end;
184 if(*size1 < requested_len){
185 *ptr2 = ring->data.get();
186 *size2 = requested_len - *size1;
187 }
188
189 return *size1 + *size2;
190}
191
192bool ring_advance_write_idx(struct ring_buffer *ring, int amount) {
193 const int start = std::atomic_load_explicit(&ring->start, std::memory_order_acquire);
194 // end index is modified only by this (writer) thread, so relaxed is enough
195 const int end = std::atomic_load_explicit(&ring->end, std::memory_order_relaxed);
196
197 /* Use release order to ensure that all writes to the buffer are
198 * completed before advancing the end index (no reads or writes in the
199 * current thread can be reordered after this store).
200 */
201 std::atomic_store_explicit(&ring->end,
202 (end + amount) % (2*ring->len), std::memory_order_release);
203
204 return amount > calculate_avail_write(start, end, ring->len);
205}
206
207void ring_buffer_write(struct ring_buffer * ring, const char *in, int len) {
208 void *ptr1;
209 int size1;
210 void *ptr2;
211 int size2;
212 if(len == 0)
213 return;
214 if(!ring_get_write_regions(ring, len, &ptr1, &size1, &ptr2, &size2)){
215 fprintf(stderr, "Warning: too long write request for ring buffer (%d B)!!!\n", len);
216 return;
217 }
218
219 memcpy(ptr1, in, size1);
220 if(ptr2){
221 memcpy(ptr2, in + size1, size2);
222 }
223
224 if(ring_advance_write_idx(ring, len)) {
225 fprintf(stderr, "Warning: ring buffer overflow!!!\n");
226 }
227}
228
229int ring_get_size(struct ring_buffer * ring) {
230 return ring->len;
231}
232
233void ring_fill(struct ring_buffer *ring, int c, int size){
234 void *ptr1;
235 int size1;
236 void *ptr2;
237 int size2;
238 if(size == 0)
239 return;
240 if(!ring_get_write_regions(ring, size, &ptr1, &size1, &ptr2, &size2)){
241 fprintf(stderr, "Warning: too long write request for ring buffer (%d B)!!!\n", size);
242 return;
243 }
244
245 memset(ptr1, c, size1);
246 if(ptr2){
247 memset(ptr2, c, size2);
248 }
249
250 if(ring_advance_write_idx(ring, size)) {
251 fprintf(stderr, "Warning: ring buffer overflow!!!\n");
252 }
253}
254
255/* ring_get_current_size and ring_get_available_write_size can be called from
256 * both reader and writer threads.
257 *
258 * Writer case:
259 * If the reader modifies start index under our feet, it doesn't
260 * matter, because reader can only make the current size smaller. That
261 * means the writer may calculate less free space, but never more than
262 * really available.
263 *
264 * Reader case:
265 * If the writer modifies end index under our feet, it doesn't matter,
266 * because the writer can only make current size bigger. That means the
267 * reader may calculate less size for reading, but the read data is
268 * always valid.
269 */
271 int start = std::atomic_load_explicit(&ring->start, std::memory_order_acquire);
272 int end = std::atomic_load_explicit(&ring->end, std::memory_order_acquire);
273 return calculate_avail_read(start, end, ring->len);
274}
275
277 int start = std::atomic_load_explicit(&ring->start, std::memory_order_acquire);
278 int end = std::atomic_load_explicit(&ring->end, std::memory_order_acquire);
279 return calculate_avail_write(start, end, ring->len);
280}
281
283 (void (*)(void *)) ring_buffer_destroy,
284 (int (*)(void *, char *, int)) ring_buffer_read,
285 (void (*)(void *, const char *, int)) ring_buffer_write
286};
287
int void size_t * len
int ring_get_size(struct ring_buffer *ring)
int ring_get_current_size(struct ring_buffer *ring)
static int calculate_avail_write(int start, int end, int buf_len)
void ring_buffer_destroy(struct ring_buffer *ring)
struct audio_buffer_api ring_buffer_fns
struct ring_buffer * ring_buffer_init(int size)
bool ring_advance_write_idx(struct ring_buffer *ring, int amount)
int ring_get_write_regions(struct ring_buffer *ring, int requested_len, void **ptr1, int *size1, void **ptr2, int *size2)
static int calculate_avail_read(int start, int end, int buf_len)
void ring_advance_read_idx(struct ring_buffer *ring, int amount)
void ring_buffer_flush(struct ring_buffer *buf)
int ring_get_read_regions(struct ring_buffer *ring, int max_len, void **ptr1, int *size1, void **ptr2, int *size2)
int ring_buffer_read(struct ring_buffer *ring, char *out, int max_len)
void ring_buffer_write(struct ring_buffer *ring, const char *in, int len)
int ring_get_available_write_size(struct ring_buffer *ring)
void ring_fill(struct ring_buffer *ring, int c, int size)
static void start()
Definition sdp.c:202
std::unique_ptr< char[]> data
std::atomic< int > end
std::atomic< int > start