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
parent
51e8223078
commit
25077277b3
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue