Add bad sample cleaner for debugging

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 <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-15 22:45:28 -08:00
parent 51e8223078
commit 25077277b3
5 changed files with 132 additions and 1 deletions

View File

@ -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);
}

View File

@ -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 = "<group>"; };
835EDD7A279FE23A001EDCCE /* HeadphoneFilter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HeadphoneFilter.m; sourceTree = "<group>"; };
835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HeadphoneFilter.h; sourceTree = "<group>"; };
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 = "<group>"; };
@ -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 */,

View File

@ -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;
};

View File

@ -0,0 +1,16 @@
//
// BadSampleCleaner.h
// CogAudio Framework
//
// Created by Christopher Snowhill on 2/15/22.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface BadSampleCleaner : NSObject
+ (void)cleanSamples:(float *)buffer amount:(NSUInteger)amount location:(NSString *)location;
@end
NS_ASSUME_NONNULL_END

View File

@ -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