From 45cb841ec074de7d637c246b04d94e154e63903b Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 4 Mar 2025 00:15:47 -0800 Subject: [PATCH] Bug Fix: Greatly improve audio buffer handling Buffers were being treated as empty before they were actually processed, due to races between the current node's end of stream marker and actually feeding the output buffer. Signed-off-by: Christopher Snowhill --- Audio/Chain/ConverterNode.m | 8 +++++--- Audio/Chain/DSP/DSPDownmixNode.m | 5 +++-- Audio/Chain/DSP/DSPEqualizerNode.m | 5 +++-- Audio/Chain/DSP/DSPFSurroundNode.m | 5 +++-- Audio/Chain/DSP/DSPHRTFNode.m | 5 +++-- Audio/Chain/DSP/DSPRubberbandNode.m | 4 ++-- Audio/Chain/Node.m | 6 ------ Audio/Chain/OutputNode.m | 14 +++++++++----- Audio/Chain/VisualizationNode.m | 3 ++- 9 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Audio/Chain/ConverterNode.m b/Audio/Chain/ConverterNode.m index e9520ae0d..94d2dabb4 100644 --- a/Audio/Chain/ConverterNode.m +++ b/Audio/Chain/ConverterNode.m @@ -110,7 +110,8 @@ void scale_by_volume(float *buffer, size_t count, float volume) { AudioChunk *chunk = nil; chunk = [self convert]; if(!chunk || ![chunk frameCount]) { - if([self endOfStream] == YES) { + if([previousNode endOfStream] == YES) { + endOfStream = YES; break; } if(paused || !streamFormatChanged) { @@ -127,6 +128,7 @@ void scale_by_volume(float *buffer, size_t count, float volume) { } } } + endOfStream = YES; } - (AudioChunk *)convert { @@ -163,7 +165,7 @@ void scale_by_volume(float *buffer, size_t count, float volume) { ssize_t bytesReadFromInput = 0; - while(bytesReadFromInput < amountToWrite && !stopping && !paused && !streamFormatChanged && [self shouldContinue] == YES && [self endOfStream] == NO) { + while(bytesReadFromInput < amountToWrite && !stopping && !paused && !streamFormatChanged && [self shouldContinue] == YES && !([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES)) { AudioStreamBasicDescription inf; uint32_t config; if([self peekFormat:&inf channelConfig:&config]) { @@ -203,7 +205,7 @@ void scale_by_volume(float *buffer, size_t count, float volume) { return nil; } - if(stopping || paused || streamFormatChanged || [self shouldContinue] == NO || [self endOfStream] == YES) { + if(stopping || paused || streamFormatChanged || [self shouldContinue] == NO || ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES)) { if(!skipResampler) { if(!is_postextrapolated_) { is_postextrapolated_ = 1; diff --git a/Audio/Chain/DSP/DSPDownmixNode.m b/Audio/Chain/DSP/DSPDownmixNode.m index 435b3b328..48bfea7de 100644 --- a/Audio/Chain/DSP/DSPDownmixNode.m +++ b/Audio/Chain/DSP/DSPDownmixNode.m @@ -110,7 +110,7 @@ AudioChunk *chunk = nil; chunk = [self convert]; if(!chunk || ![chunk frameCount]) { - if([self endOfStream] == YES) { + if([previousNode endOfStream] == YES) { break; } if(paused) { @@ -123,6 +123,7 @@ } } } + endOfStream = YES; } - (AudioChunk *)convert { @@ -131,7 +132,7 @@ processEntered = YES; - if(stopping || [self endOfStream] == YES || [self shouldContinue] == NO) { + if(stopping || ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) || [self shouldContinue] == NO) { processEntered = NO; return nil; } diff --git a/Audio/Chain/DSP/DSPEqualizerNode.m b/Audio/Chain/DSP/DSPEqualizerNode.m index 7b2fd8ce7..7ab7c75b3 100644 --- a/Audio/Chain/DSP/DSPEqualizerNode.m +++ b/Audio/Chain/DSP/DSPEqualizerNode.m @@ -350,7 +350,7 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA AudioChunk *chunk = nil; chunk = [self convert]; if(!chunk || ![chunk frameCount]) { - if([self endOfStream] == YES) { + if([previousNode endOfStream] == YES) { break; } if(paused) { @@ -366,6 +366,7 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA } } } + endOfStream = YES; } - (AudioChunk *)convert { @@ -374,7 +375,7 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA processEntered = YES; - if(stopping || [self endOfStream] == YES || [self shouldContinue] == NO) { + if(stopping || ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) || [self shouldContinue] == NO) { processEntered = NO; return nil; } diff --git a/Audio/Chain/DSP/DSPFSurroundNode.m b/Audio/Chain/DSP/DSPFSurroundNode.m index d506cb1dc..0d3b612a0 100644 --- a/Audio/Chain/DSP/DSPFSurroundNode.m +++ b/Audio/Chain/DSP/DSPFSurroundNode.m @@ -146,7 +146,7 @@ static void * kDSPFSurroundNodeContext = &kDSPFSurroundNodeContext; AudioChunk *chunk = nil; chunk = [self convert]; if(!chunk || ![chunk frameCount]) { - if([self endOfStream] == YES) { + if([previousNode endOfStream] == YES) { break; } if(paused) { @@ -162,6 +162,7 @@ static void * kDSPFSurroundNodeContext = &kDSPFSurroundNodeContext; } } } + endOfStream = YES; } - (AudioChunk *)convert { @@ -170,7 +171,7 @@ static void * kDSPFSurroundNodeContext = &kDSPFSurroundNodeContext; processEntered = YES; - if(stopping || [self endOfStream] == YES || [self shouldContinue] == NO) { + if(stopping || ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) || [self shouldContinue] == NO) { processEntered = NO; return nil; } diff --git a/Audio/Chain/DSP/DSPHRTFNode.m b/Audio/Chain/DSP/DSPHRTFNode.m index 2b64b01a0..5f6cc8d0c 100644 --- a/Audio/Chain/DSP/DSPHRTFNode.m +++ b/Audio/Chain/DSP/DSPHRTFNode.m @@ -271,7 +271,7 @@ static void unregisterMotionListener(void) { AudioChunk *chunk = nil; chunk = [self convert]; if(!chunk || ![chunk frameCount]) { - if([self endOfStream] == YES) { + if([previousNode endOfStream] == YES) { break; } if(paused) { @@ -287,6 +287,7 @@ static void unregisterMotionListener(void) { } } } + endOfStream = YES; } - (AudioChunk *)convert { @@ -295,7 +296,7 @@ static void unregisterMotionListener(void) { processEntered = YES; - if(stopping || [self endOfStream] == YES || [self shouldContinue] == NO) { + if(stopping || ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) || [self shouldContinue] == NO) { processEntered = NO; return nil; } diff --git a/Audio/Chain/DSP/DSPRubberbandNode.m b/Audio/Chain/DSP/DSPRubberbandNode.m index 397174807..b9dd0f7c0 100644 --- a/Audio/Chain/DSP/DSPRubberbandNode.m +++ b/Audio/Chain/DSP/DSPRubberbandNode.m @@ -361,7 +361,6 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext; chunk = [self convert]; if(!chunk || ![chunk frameCount]) { if(flushed) { - endOfStream = YES; break; } if(paused) { @@ -381,6 +380,7 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext; } } } + endOfStream = YES; } - (AudioChunk *)convert { @@ -389,7 +389,7 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext; processEntered = YES; - if(stopping || flushed || [self endOfStream] == YES || [self shouldContinue] == NO) { + if(stopping || flushed || ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) || [self shouldContinue] == NO) { processEntered = NO; return nil; } diff --git a/Audio/Chain/Node.m b/Audio/Chain/Node.m index 84eab9ff3..696753b8c 100644 --- a/Audio/Chain/Node.m +++ b/Audio/Chain/Node.m @@ -257,7 +257,6 @@ static uint64_t _Node_serial; } if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { - endOfStream = YES; [accessLock unlock]; inPeek = NO; return NO; @@ -296,7 +295,6 @@ static uint64_t _Node_serial; } if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { - endOfStream = YES; [accessLock unlock]; inPeek = NO; return NO; @@ -338,7 +336,6 @@ static uint64_t _Node_serial; } if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { - endOfStream = YES; [accessLock unlock]; inRead = NO; return [[AudioChunk alloc] init]; @@ -408,7 +405,6 @@ static uint64_t _Node_serial; } if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { - endOfStream = YES; [accessLock unlock]; inRead = NO; return [[AudioChunk alloc] init]; @@ -461,7 +457,6 @@ static uint64_t _Node_serial; [accessLock lock]; if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { - endOfStream = YES; [accessLock unlock]; inMerge = NO; return [[AudioChunk alloc] init]; @@ -519,7 +514,6 @@ static uint64_t _Node_serial; [accessLock lock]; if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { - endOfStream = YES; [accessLock unlock]; inMerge = NO; return [[AudioChunk alloc] init]; diff --git a/Audio/Chain/OutputNode.m b/Audio/Chain/OutputNode.m index 952292eb2..39a5b764c 100644 --- a/Audio/Chain/OutputNode.m +++ b/Audio/Chain/OutputNode.m @@ -112,10 +112,10 @@ if(finalNode) { AudioChunk *ret = [super readChunk:amount]; - /* if (n == 0) { - DLog(@"Output Buffer dry!"); - } - */ + if((!ret || ![ret frameCount]) && [previousNode endOfStream]) { + endOfStream = YES; + } + return ret; } else { return [[AudioChunk alloc] init]; @@ -127,7 +127,11 @@ @autoreleasepool { [self setPreviousNode:[[controller bufferChain] finalNode]]; - return [super peekFormat:format channelConfig:config]; + BOOL ret = [super peekFormat:format channelConfig:config]; + if(!ret && [previousNode endOfStream]) { + endOfStream = YES; + } + return ret; } } diff --git a/Audio/Chain/VisualizationNode.m b/Audio/Chain/VisualizationNode.m index 24515e898..ba2aad032 100644 --- a/Audio/Chain/VisualizationNode.m +++ b/Audio/Chain/VisualizationNode.m @@ -245,7 +245,7 @@ static VisualizationCollection *theCollection = nil; AudioChunk *chunk = nil; chunk = [self readAndMergeChunksAsFloat32:512]; if(!chunk || ![chunk frameCount]) { - if([self endOfStream] == YES) { + if([previousNode endOfStream] == YES) { break; } } else { @@ -255,6 +255,7 @@ static VisualizationCollection *theCollection = nil; } } } + endOfStream = YES; } - (void)postVisPCM:(const float *)visTemp amount:(size_t)samples {