-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Real time safe recording with ring buffer stage one #7903
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
base: feature/recording-stage-one
Are you sure you want to change the base?
Real time safe recording with ring buffer stage one #7903
Conversation
|
No downloable .exe |
|
Oh yeah that's because the workflows weren't running. They're going now, so an exe should be available in the next 15-20 minutes. |
|
Ok how I test It dude? . . |
|
|
I changed the approach as it was pretty unstable. Please feel free to test the code and review it. My new approach also is supposed to fix the random segfaults and clipping audio. I also tried to fix all the naming issues with the variables for the camelCase, if some are still remaining, I will fix it in the next days. |
src/core/AudioEngine.cpp
Outdated
| delete [] buf; | ||
| f_cnt_t newSize = std::max(currentWriteBufSize * 2, requiredSize + DEFAULT_BUFFER_SIZE); | ||
|
|
||
| auto newBuffer = new SampleFrame[newSize]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this function is run in realtime context, then this line would cause an allocation, which is not realtime-safe.
src/core/AudioEngine.cpp
Outdated
| m_inputBufferFrames[i] = 0; | ||
| m_inputBufferSize[i] = initialBufferSize; | ||
|
|
||
| delete[] m_inputBuffer[i]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete[], new[] and reserve are - sadly - not realtime-safe either.
(In initialization code it is fine though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will solve any problem.
|
My most recent push has the intent to remove all the allocation I could think of to get more real time safety. I think there is still some points with non real time safety, but at least I would like to get feedback as I consider this a progress and not the final deal. |
|
Help me understand this code... Before this PR, were we using a single buffer And this PR replaces that with a ring buffer.... which is slowly filled up and copied over to the other buffer ? |
Hello, this is the explaination of the code for this PR. Before this PR, were we using a single buffer "m_inputBuffer[m_inputBufferWrite]" for recording audio which would just be scaled up 2x every time it got filled up? All of that is not real time safe as we were allocating with "new" and deallocating with "delete[]". "And this PR replaces that with a ring buffer.... which is slowly filled up and copied over to the other buffer ?"
b. I added the function "AudioEngine::processBufferedInputFrames()".
|
|
I solved the problems with the resize and reserve. Now it is supposed to be completely real time safe. |
OK, I will look later at it. But please, can you fix the indentation issues first? Thanks! |
JohannesLorenz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial code review finished, will need another one after the rework.
What I checked:
- Functionality - partially
- Style - partially
|
All changes requested will be applied. But I just have that doubt with the and statement. |
|
All the resquests were applied. |
szeli1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not test this PR. I believe I found a size_t casting bug which might cause a crash. It is similar to old AudioFileProcessor code, where size_t values were subtracted from carelessly resulting in out of index reads / writes. I may be incorrect about my finding tho.
src/core/AudioEngine.cpp
Outdated
| m_inputBuffer[i] = new SampleFrame[ DEFAULT_BUFFER_SIZE * 100 ]; | ||
| zeroSampleFrames(m_inputBuffer[i], m_inputBufferSize[i]); | ||
| m_inputBufferSize[i] = FIXED_INPUT_BUFFER_CAPACITY; | ||
| m_inputBuffer[i] = new SampleFrame[ FIXED_INPUT_BUFFER_CAPACITY ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| m_inputBuffer[i] = new SampleFrame[ FIXED_INPUT_BUFFER_CAPACITY ]; | |
| m_inputBuffer[i] = new SampleFrame[FIXED_INPUT_BUFFER_CAPACITY]; |
Maybe replace this with an std::vector?
Allocation, size is handled by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or 2 std vectors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You closed this without expressing your opinion about using std::vector
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the benefit of using an std::vector when we just need a fixed size simple array? Is the benefit on the exception safety?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The benefit is that std::vector does everything we already do, but it can destruct itself. (And it incorporates the size data, meaning we don't have to store it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using std::array would be the replacement, not std::vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
std::arraywould be the replacement, notstd::vector.
This is correct, I thought m_inputBuffer was still increased in void AudioEngine::pushInputFrames( SampleFrame* _ab, const f_cnt_t _frames )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in the recent refactor.
I've replaced the C-style arrays with std::array<std::vector<SampleFrame>, 2>.
The vectors are pre-allocated with .reserve(FIXED_INPUT_BUFFER_CAPACITY) and grow
dynamically with .resize() as frames are added (up to the reserved capacity), so:
- No manual memory management (
new/delete) - Size is tracked automatically (
.size()instead ofm_inputBufferFrames) - Automatic cleanup (RAII)
- Still real-time safe (no allocations during recording since we stay within reserved capacity)
The reason I used std::vector instead of std::array is because m_inputBuffer needs
to grow dynamically as frames are added in processBufferedInputFrames(), then gets
cleared in swapBuffers(). A fixed-size std::array wouldn't work for this use case.
@Veratil - Good point about std::array being typical for fixed-size, but in this case
the variable size behavior is needed (though bounded by the reserved capacity).
szeli1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not test this PR. What steps to follow to test this PR?
|
|
||
| if (framesInSequence == 0) return; | ||
|
|
||
| requestChangeInModel(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it really real time safe when it uses mutexes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is real-time safe. The mutex in requestChangeInModel() is NOT in the real-time
audio callback path. Here's why:
Normal playback mode:
- Uses fifoWriter (needsFifo = true by default in startProcessing())
- Audio callback only calls
nextBuffer()→m_fifo->read()(lockless, no mutex) - The fifoWriter thread (separate, non-RT thread) calls
renderNextBuffer()which includes
the mutex, but this is outside the RT path
Export/rendering mode:
- Doesn't use fifoWriter (needsFifo = false in ProjectRenderer.cpp:171)
- But there's no real-time audio callback in this mode, just writing to a file
- No RT constraints, so mutex is perfectly fine
The architecture ensures that pushInputFrames() (called from audio device callback)
only writes to the lockless ring buffer, while processBufferedInputFrames() (which has
the mutex) runs either in the fifoWriter thread or in rendering mode where RT-safety
isn't required.
src/core/AudioEngine.cpp
Outdated
| delete m_midiClient; m_midiClient = nullptr; | ||
| delete m_audioDev; m_audioDev = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe do = nullptr on new lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, it will be fixed.
include/AudioEngine.h
Outdated
|
|
||
| std::unique_ptr<LocklessRingBuffer<SampleFrame>> m_inputAudioRingBuffer; | ||
| std::unique_ptr<LocklessRingBufferReader<SampleFrame>> m_inputAudioRingBufferReader; | ||
| std::vector<SampleFrame> m_tempInputProcessingBuffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please could you leave a comment why a temporary vector is needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. After reviewing the code, I realized this temporary vector is not actually
being used. The current implementation copies directly from the ring buffer sequence
(handling the two potential halves with first_half_ptr() and second_half_ptr()) to the
destination buffer without needing an intermediate vector.
I've removed it to keep the code clean.
src/core/AudioEngine.cpp
Outdated
| SampleFrame* currentWriteBufPtr = m_inputBuffer[m_inputBufferWrite]; | ||
| f_cnt_t currentWriteBufCapacity = m_inputBufferSize[m_inputBufferWrite]; | ||
| f_cnt_t currentFramesInWriteBuf = m_inputBufferFrames[m_inputBufferWrite]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to use vectors instead of C arrays. That way the size is also known. I reviewed the code and to me, it seems like m_inputBuffer is used in about 3 functions, it shouldn't be hard to replace it with std::vector. (and also it would get rid of m_inputBufferFrames)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a reasonable request to ask you to refactor m_inputBuffer. Please would you mind doing that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I've refactored m_inputBuffer to use std::vector as you suggested.
Changes made:
Header (AudioEngine.h):
- Replaced
SampleFrame* m_inputBuffer[2],f_cnt_t m_inputBufferFrames[2], and
f_cnt_t m_inputBufferSize[2]withstd::array<std::vector<SampleFrame>, 2> m_inputBuffer - Updated
inputBuffer()to returnm_inputBuffer[m_inputBufferRead].data() - Updated
inputBufferFrames()to returnm_inputBuffer[m_inputBufferRead].size()
Implementation (AudioEngine.cpp):
- Constructor: Changed from
new SampleFrame[FIXED_INPUT_BUFFER_CAPACITY]to
m_inputBuffer[i].reserve(FIXED_INPUT_BUFFER_CAPACITY) - Destructor: Removed manual
delete[]- vectors clean up automatically processBufferedInputFrames():- Use vector references instead of raw pointers
- Call
.resize()beforememcpy()to accommodate new frames - Removed manual size tracking (now uses
.size(),.capacity())
swapBuffers(): Changed fromm_inputBufferFrames[m_inputBufferWrite] = 0to
m_inputBuffer[m_inputBufferWrite].clear()
Real-time safety maintained:
- Vectors are pre-allocated with
.reserve(FIXED_INPUT_BUFFER_CAPACITY)in constructor .resize()only increases size up to the reserved capacity, so no heap allocations
occur during recording (capacity never exceeded)- All operations remain real-time safe
Testing:
Compiled successfully and tested recording for several minutes without issues.
Thanks for the suggestion. The code is cleaner and more maintainable now.
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>
e492d48 to
52868fd
Compare
|
@steven-jaro Please refrain from rebasing feature branches after you open a PR and people have started reviewing it. It rewrites the git history and makes it harder for reviewers to see what recent changes you made. If you need to fix merge conflicts, you can merge |
I am really sorry, I wasn't aware of this. Won't happen again. |
|
You can rebase at the end before merging though. |
Co-authored-by: szeli1 <[email protected]>
Co-authored-by: szeli1 <[email protected]>




This is the first stage of the implementation of the ring buffer for real time safety when recording as this is needed to solve issue number 4 in #7786. Issue 4 says that recording is not safe with the current method. Here are the changes made to the code in this PR:
#include
#include
#include
#include
#include "LocklessRingBuffer.h"
static const f_cnt_t FIXED_INPUT_BUFFER_CAPACITY = DEFAULT_BUFFER_SIZE * 100;
How to test this PR?"
This PR just makes that recording is memmory safe, so one should look for memmory leaks, segfaults or possible reallocatio in the code.