From 25077277b375e4e74a471cdeac354d0d4946ce98 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 15 Feb 2022 22:45:28 -0800 Subject: [PATCH] Add bad sample cleaner for debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bad sample scanner and cleaner will point out in the log whenever a bad sample, such as infinity, or Not a Number, or even huge values over ±2.0, in case some piece of code, or a decoder, or even a bad file, has taken over the output. Signed-off-by: Christopher Snowhill --- Audio/Chain/ConverterNode.m | 36 +++++++++++++++++++++ Audio/CogAudio.xcodeproj/project.pbxproj | 8 +++++ Audio/Output/OutputCoreAudio.m | 33 ++++++++++++++++++- Audio/Utils/BadSampleCleaner.h | 16 ++++++++++ Audio/Utils/BadSampleCleaner.m | 40 ++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 Audio/Utils/BadSampleCleaner.h create mode 100644 Audio/Utils/BadSampleCleaner.m diff --git a/Audio/Chain/ConverterNode.m b/Audio/Chain/ConverterNode.m index 1affeeb03..878a7002a 100644 --- a/Audio/Chain/ConverterNode.m +++ b/Audio/Chain/ConverterNode.m @@ -18,6 +18,10 @@ #import "hdcd_decode2.h" +#ifdef _DEBUG +#import "BadSampleCleaner.h" +#endif + void PrintStreamDesc(AudioStreamBasicDescription *inDesc) { if(!inDesc) { DLog(@"Can't print a NULL desc!\n"); @@ -625,6 +629,12 @@ tryagain: isUnsigned = NO; isFloat = YES; } + +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)inputBuffer + amount:bytesReadFromInput / sizeof(float) + location:@"post int to float conversion"]; +#endif } // Extrapolate start @@ -653,6 +663,12 @@ tryagain: memmove(inputBuffer + N_samples_to_add_ * floatFormat.mBytesPerPacket, inputBuffer + bytesToSkip, bytesReadFromInput); lpc_extrapolate_bkwd(inputBuffer + _N_samples_to_add_ * floatFormat.mBytesPerPacket, samples_in_buffer, prime, floatFormat.mChannelsPerFrame, LPC_ORDER, _N_samples_to_add_, &extrapolateBuffer, &extrapolateBufferSize); +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)inputBuffer + amount:_N_samples_to_add_ * floatFormat.mChannelsPerFrame + location:@"pre-extrapolated data"]; +#endif + bytesReadFromInput += _N_samples_to_add_ * floatFormat.mBytesPerPacket; latencyEaten = N_samples_to_drop_; if(dsd2pcm) latencyEaten += (int)ceil(dsd2pcmLatency * sampleRatio); @@ -674,6 +690,11 @@ tryagain: } lpc_extrapolate_fwd(inputBuffer, samples_in_buffer, prime, floatFormat.mChannelsPerFrame, LPC_ORDER, N_samples_to_add_, &extrapolateBuffer, &extrapolateBufferSize); +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)(inputBuffer) + samples_in_buffer * floatFormat.mChannelsPerFrame + amount:N_samples_to_add_ * floatFormat.mChannelsPerFrame + location:@"post-extrapolated data"]; +#endif bytesReadFromInput += N_samples_to_add_ * floatFormat.mBytesPerPacket; latencyEatenPost = N_samples_to_drop_; is_postextrapolated_ = 2; @@ -711,7 +732,17 @@ tryagain: if(!skipResampler) { ioNumberPackets += soxr_delay(soxr); +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)(((uint8_t *)inputBuffer) + inpOffset) + amount:inputSamples * floatFormat.mChannelsPerFrame + location:@"resampler input"]; +#endif soxr_process(soxr, (float *)(((uint8_t *)inputBuffer) + inpOffset), inputSamples, &inputDone, floatBuffer, ioNumberPackets, &outputDone); +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)floatBuffer + amount:outputDone * floatFormat.mChannelsPerFrame + location:@"resampler output"]; +#endif if(latencyEatenPost) { // Post file flush @@ -719,6 +750,11 @@ tryagain: do { soxr_process(soxr, NULL, 0, &idone, floatBuffer + outputDone * floatFormat.mBytesPerPacket, ioNumberPackets - outputDone, &odone); +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)(floatBuffer + outputDone * floatFormat.mBytesPerPacket) + amount:odone * floatFormat.mChannelsPerFrame + location:@"resampler flushed output"]; +#endif outputDone += odone; } while(odone > 0); } diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index af67c20ea..537aac135 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -58,6 +58,8 @@ 835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C88B0279811A500E28EAE /* hdcd_decode2.c */; }; 835EDD7B279FE23A001EDCCE /* HeadphoneFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 835EDD7A279FE23A001EDCCE /* HeadphoneFilter.m */; }; 835EDD7D279FE307001EDCCE /* HeadphoneFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */; }; + 835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */ = {isa = PBXBuildFile; fileRef = 835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */; }; + 835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */; }; 83725A8B27AA0DBF0003F694 /* soxr.h in Headers */ = {isa = PBXBuildFile; fileRef = 83725A8827AA0DBF0003F694 /* soxr.h */; }; 83725A8C27AA0DBF0003F694 /* libsoxr.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */; }; 83725A8E27AA0DE60003F694 /* libsoxr.0.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -159,6 +161,8 @@ 835C88B0279811A500E28EAE /* hdcd_decode2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hdcd_decode2.c; sourceTree = ""; }; 835EDD7A279FE23A001EDCCE /* HeadphoneFilter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HeadphoneFilter.m; sourceTree = ""; }; 835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HeadphoneFilter.h; sourceTree = ""; }; + 835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BadSampleCleaner.h; path = Utils/BadSampleCleaner.h; sourceTree = SOURCE_ROOT; }; + 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BadSampleCleaner.m; path = Utils/BadSampleCleaner.m; sourceTree = SOURCE_ROOT; }; 83725A7B27AA0D8A0003F694 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 83725A8827AA0DBF0003F694 /* soxr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = soxr.h; sourceTree = ""; }; @@ -347,6 +351,8 @@ 17D21CDC0B8BE5B400D1EBDE /* Utils */ = { isa = PBXGroup; children = ( + 835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */, + 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */, 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */, 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */, 8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */, @@ -490,6 +496,7 @@ 8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */, 8384912718080FF100E7332D /* Logging.h in Headers */, 8377C64E27B8C54400E8BC0F /* fft.h in Headers */, + 835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */, 8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */, B0575F2D0D687A0800411D77 /* Helper.h in Headers */, 835C88AD2797DA5800E28EAE /* util.h in Headers */, @@ -580,6 +587,7 @@ 834FD4F527AFA2150063BC83 /* Downmix.m in Sources */, 17D21CC60B8BE4BA00D1EBDE /* OutputCoreAudio.m in Sources */, 835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */, + 835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */, 834FD4ED27AF91220063BC83 /* AudioChunk.m in Sources */, 17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */, 8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */, diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index 21d34fc6f..d8b827a18 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -9,6 +9,10 @@ #import "OutputCoreAudio.h" #import "OutputNode.h" +#ifdef _DEBUG +#import "BadSampleCleaner.h" +#endif + #import "Logging.h" extern void scale_by_volume(float *buffer, size_t count, float volume); @@ -94,9 +98,19 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct double chunkDuration = [chunk duration]; NSData *samples = [chunk removeSamples:frameCount]; +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)[samples bytes] + amount:frameCount * format.mChannelsPerFrame + location:@"pre downmix"]; +#endif float downmixedData[frameCount * channels]; [_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData]; +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:downmixedData + amount:frameCount * channels + location:@"post downmix"]; +#endif [_self->downmixerForVis process:[samples bytes] frameCount:frameCount output:visAudio]; visTabulated += frameCount; @@ -125,9 +139,20 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct atomic_fetch_add(&_self->bytesRendered, frameCount * bytesPerPacket); double chunkDuration = [chunk duration]; NSData *samples = [chunk removeSamples:frameCount]; +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)[samples bytes] + amount:frameCount * format.mChannelsPerFrame + location:@"pre downmix"]; +#endif + float downmixedData[frameCount * channels]; [_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData]; fillBuffers(ioData, downmixedData, frameCount, amountRead / bytesPerPacket); +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:downmixedData + amount:frameCount * channels + location:@"post downmix"]; +#endif [_self->downmixerForVis process:[samples bytes] frameCount:frameCount output:visAudio + visTabulated]; visTabulated += frameCount; @@ -711,7 +736,13 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const #endif inputData->mBuffers[0].mNumberChannels = channels; - + +#ifdef _DEBUG + [BadSampleCleaner cleanSamples:(float *)inputData->mBuffers[0].mData + amount:inputData->mBuffers[0].mDataByteSize / sizeof(float) + location:@"final output"]; +#endif + return 0; }; diff --git a/Audio/Utils/BadSampleCleaner.h b/Audio/Utils/BadSampleCleaner.h new file mode 100644 index 000000000..04cc4c278 --- /dev/null +++ b/Audio/Utils/BadSampleCleaner.h @@ -0,0 +1,16 @@ +// +// BadSampleCleaner.h +// CogAudio Framework +// +// Created by Christopher Snowhill on 2/15/22. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BadSampleCleaner : NSObject ++ (void)cleanSamples:(float *)buffer amount:(NSUInteger)amount location:(NSString *)location; +@end + +NS_ASSUME_NONNULL_END diff --git a/Audio/Utils/BadSampleCleaner.m b/Audio/Utils/BadSampleCleaner.m new file mode 100644 index 000000000..3af1c47d0 --- /dev/null +++ b/Audio/Utils/BadSampleCleaner.m @@ -0,0 +1,40 @@ +// +// BadSampleCleaner.m +// CogAudio Framework +// +// Created by Christopher Snowhill on 2/15/22. +// + +#ifdef _DEBUG +#import "BadSampleCleaner.h" + +#import "Logging.h" + +@implementation BadSampleCleaner + ++ (void)cleanSamples:(float *)buffer amount:(NSUInteger)amount location:(NSString *)location { + BOOL hadNaN = NO; + BOOL hadINF = NO; + BOOL hadHUGE = NO; + + for(NSUInteger i = 0; i < amount; ++i) { + float sample = buffer[i]; + BOOL isNaN = isnan(sample); + BOOL isINF = isinf(sample); + BOOL isHUGE = (fabs(sample) > 2.0); + hadNaN = hadNaN || isNaN; + hadINF = hadINF || isINF; + hadHUGE = hadHUGE || isHUGE; + if(isNaN || isINF || isHUGE) { + memset(&buffer[i], 0, sizeof(buffer[i])); + memset(&sample, 0, sizeof(sample)); + } + } + if(hadNaN || hadINF || hadHUGE) { + DLog(@"Sample block at %@ had NaN: %@, INF: %@, HUGE: %@", location, hadNaN ? @"yes" : @"no", hadINF ? @"yes" : @"no", hadHUGE ? @"yes" : @"no"); + } +} + +@end + +#endif