Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ring Buffer Documentation/Example #61

Closed
rdanbrook opened this issue Apr 28, 2019 · 7 comments
Closed

Ring Buffer Documentation/Example #61

rdanbrook opened this issue Apr 28, 2019 · 7 comments
Labels

Comments

@rdanbrook
Copy link

I'm attempting to use the built-in ring buffer rather than use my own (which was not originally designed for audio and this creates problems, since it is non-atomic), but can't seem to figure it out. Maybe I'm just low-IQ and shouldn't be programming. Either way, I think a usage example or clearer documentation on it would help out a lot.
Thanks!

@mackron
Copy link
Owner

mackron commented Apr 29, 2019

The ring buffer stuff is new and I've just not had the time to document it. I also never looked at any references or anything so I may have used a few unorthodox ideas in the API design. But the idea is that you acquire a portion of the buffer which returns a pointer for you to read/write, and then release it which moves the cursor forward. Also keep in mind that it's single producer, single consumer.

What specifically were you having trouble with?

@rdanbrook
Copy link
Author

rdanbrook commented Apr 29, 2019

ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut);
ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut);
ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut);
ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut);

Not exactly sure what to pass in to these functions. Not sure what the third argument actually is. Is it where the read/write cursor is going to be stored when the function returns (and size is stored in the second param)? Seems so when I look at the code, but after segfaulting when trying to operate on it I ended up using my own ring buffer implementation with a mutex (I'd rather use yours if it's lock-free).

If you want to see my quick and dirty solution using a mutex: https://github.com/0ldsk00l/smsplus/blob/master/shell/smsplus.c

edit: Yeah, I am only concerned with single producer/consumer for my projects which are mostly emulation-based.

@mackron
Copy link
Owner

mackron commented Apr 30, 2019

You use it like this:

size_t sizeInBytes = 1024; // Initialize this to the number of bytes you want.
void* pBuffer;
result = ma_rb_acquire_read(pRB, &sizeInBytes, &pBuffer);
if (result != MA_SUCCESS) {
    // Error
}

// At this point, sizeInBytes is set to the number of bytes _actually_ acquired. pBuffer is
// a pointer to the buffer you would read from. Note that sizeInBytes may be less than
// what you originally requested.
memcpy(pSomeBuffer, pBuffer, sizeInBytes); // <-- Do something with the data.

result = ma_rb_commit_read(pRB, sizeInBytes, pBuffer);
if (result != MA_SUCCESS) {
    // Error
}

The general flow is acquire/map a portion of the ring buffer, do something with the returned data, then commit/unmap.

If it looks like you're doing everything correct, I'd be interested to see a portion of your code if at all possible just to check if there's an underlying bug with miniaudio's ring buffer implementation since it is fairly new code.

@rdanbrook
Copy link
Author

Thanks for posting that example, it helped me make sense of everything and I've managed to get something up and running using the built-in ring buffer. Works great! I may create a sine wave example of my own that uses it and post it later in case it's useful to other people.

@acidtonic
Copy link

I too am a bit curious how this is used. In my case I am trying to keep a small buffer of past PCM frames such that if some later processing (within the next 5 seconds) decides it wants a snippet of that audio as a wav, I can seek backwards in the PCM buffer, start up a WAV encoder and pass those frames to the encoder to get a WAV from that snippet in time.

Is the ring buffer the best way to achieve this? I am still passing frames in the callback to do some FFT work and won't know if I need to go back in time to save the stream until a few seconds later. Are there any existing examples of doing something like this or storing PCM frames into a ring buffer?

@orcmid
Copy link

orcmid commented Mar 24, 2023

To have a look-behind of some fixed number of frames, using a ring buffer, you need to have a way to prevent the writer from over-taking the reader and instead be blocked at some number of frames behind the reader.

It might be better to store the read frames into a separate ring cache where new reads overlay old ones based on the size of the cache ring and how fast things are going. The size of the cache ring determines what is keeps to that predetermined depth.

That's technically not a ring buffer, because you are chasing your own tail and allowed to catch and over-run it. In this game, the oldest cache item is just in front of the moving cache filler. (This should all be on the reader's thread of course.)

The reader being cached in a ring might be obtaining its data from a ring buffer though.

MORE ON RING BUFFERS

The use of a ring buffer is usually to coordinate a reader and a writer, where the reader chases (but does not pass) the writer, and the writer chases (but does not pass) the reader.

The buffer should be large enough that the reader rarely catches the writer (and then has to wait, drop its own outputs, stall other things, etc). Similarly, if the writer catches the reader, it may end up having to drop data until the reader makes more room in the buffer.

This sort of thing can happen with any kind of buffering, of course. The ring buffer just happens to be a cool structure that works like a speedway and doesn't have to be choppy such as buffers that have to be emptied quickly and that writers can fill, with readers needing to gobble a ready buffer quickly so the writer is not blocked either.

Ring buffers can be made expandable using list structures although reducing them later can be tricky.

With regard to audio, the ring buffer provides a form of caching for smoothing operation between producer and consumer. However, there can be latency issues as a consequence. Any latency should not be so bad as having a buffer that must be filled and then must be drained, wholesale.

@mackron
Copy link
Owner

mackron commented Mar 24, 2023

@acidtonic No, I don't think a ring buffer would be appropriate for your case. The ring buffer is a very specific data structure. It's good when you have one thread writing some data (the producer) and another thread reading the data (the consumer) and you want that access to be lock-free. It's useful in audio because you'll sometimes end up in a situation where you have some capturing thread writing to the ring buffer, while at the same time on a different thread you have something that is consuming that data. It allows those two threads to be completely decoupled.

In your case I would consider just using a standard old buffer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants