Visualization: Reworked buffering system
Visualization now buffers in the audio output pipeline, and uses a container system to delay multiple buffer chains from emitting visualization data over top of each other. This should stabilize display output significantly, while introducing minimal lag before DSP configuration changes take effect. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
parent
9701bd5421
commit
81b7dcfc0c
12 changed files with 444 additions and 147 deletions
|
@ -14,6 +14,7 @@
|
|||
#import "DSPFSurroundNode.h"
|
||||
#import "DSPHRTFNode.h"
|
||||
#import "DSPEqualizerNode.h"
|
||||
#import "VisualizationNode.h"
|
||||
#import "InputNode.h"
|
||||
|
||||
@interface BufferChain : NSObject {
|
||||
|
@ -23,6 +24,7 @@
|
|||
DSPFSurroundNode *fsurroundNode;
|
||||
DSPHRTFNode *hrtfNode;
|
||||
DSPEqualizerNode *equalizerNode;
|
||||
VisualizationNode *visualizationNode;
|
||||
|
||||
NSURL *streamURL;
|
||||
id userInfo;
|
||||
|
@ -87,6 +89,8 @@
|
|||
|
||||
- (DSPEqualizerNode *)equalizer;
|
||||
|
||||
- (VisualizationNode *)visualization;
|
||||
|
||||
- (double)secondsBuffered;
|
||||
|
||||
- (void)sustainHDCD;
|
||||
|
@ -97,4 +101,6 @@
|
|||
|
||||
- (void)setError:(BOOL)status;
|
||||
|
||||
- (double)getPostVisLatency;
|
||||
|
||||
@end
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
fsurroundNode = nil;
|
||||
equalizerNode = nil;
|
||||
hrtfNode = nil;
|
||||
|
||||
visualizationNode = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -46,7 +48,10 @@
|
|||
equalizerNode = [[DSPEqualizerNode alloc] initWithController:self previous:fsurroundNode latency:0.03];
|
||||
hrtfNode = [[DSPHRTFNode alloc] initWithController:self previous:equalizerNode latency:0.03];
|
||||
|
||||
finalNode = hrtfNode;
|
||||
// Approximately five frames
|
||||
visualizationNode = [[VisualizationNode alloc] initWithController:self previous:hrtfNode latency:5.0 / 60.0];
|
||||
|
||||
finalNode = visualizationNode;
|
||||
}
|
||||
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
|
@ -159,6 +164,7 @@
|
|||
[fsurroundNode launchThread];
|
||||
[equalizerNode launchThread];
|
||||
[hrtfNode launchThread];
|
||||
[visualizationNode launchThread];
|
||||
}
|
||||
|
||||
- (void)setUserInfo:(id)i {
|
||||
|
@ -185,6 +191,9 @@
|
|||
if(![inputNode threadExited])
|
||||
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
|
||||
|
||||
// Must do this here, or else the VisualizationContainer will carry a reference forever
|
||||
[visualizationNode pop];
|
||||
|
||||
DLog(@"Bufferchain dealloc");
|
||||
}
|
||||
|
||||
|
@ -230,6 +239,7 @@
|
|||
[fsurroundNode setShouldContinue:s];
|
||||
[equalizerNode setShouldContinue:s];
|
||||
[hrtfNode setShouldContinue:s];
|
||||
[visualizationNode setShouldContinue:s];
|
||||
}
|
||||
|
||||
- (BOOL)isRunning {
|
||||
|
@ -264,6 +274,10 @@
|
|||
return equalizerNode;
|
||||
}
|
||||
|
||||
- (VisualizationNode *)visualization {
|
||||
return visualizationNode;
|
||||
}
|
||||
|
||||
- (AudioStreamBasicDescription)inputFormat {
|
||||
return [inputNode nodeFormat];
|
||||
}
|
||||
|
@ -300,4 +314,17 @@
|
|||
[controller setError:status];
|
||||
}
|
||||
|
||||
- (double)getPostVisLatency {
|
||||
double latency = 0.0;
|
||||
Node *node = finalNode;
|
||||
while(node) {
|
||||
latency += [node secondsBuffered];
|
||||
if(node == visualizationNode) {
|
||||
break;
|
||||
}
|
||||
node = [node previousNode];
|
||||
}
|
||||
return latency;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
- (void)threadEntry:(id _Nullable)arg;
|
||||
|
||||
- (double)secondsBuffered;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* DSPNode_h */
|
||||
|
|
|
@ -47,4 +47,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (double)secondsBuffered {
|
||||
return [buffer listDuration];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -161,6 +161,7 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
if(shouldSeek == YES) {
|
||||
BufferChain *bufferChain = [[controller controller] bufferChain];
|
||||
ConverterNode *converter = [bufferChain converter];
|
||||
VisualizationNode *visualization = [bufferChain visualization];
|
||||
DSPRubberbandNode *rubberband = [bufferChain rubberband];
|
||||
DSPFSurroundNode *fsurround = [bufferChain fsurround];
|
||||
DSPEqualizerNode *equalizer = [bufferChain equalizer];
|
||||
|
@ -171,6 +172,7 @@ static void *kInputNodeContext = &kInputNodeContext;
|
|||
[self resetBuffer];
|
||||
[converter resetBuffer];
|
||||
[converter inputFormatDidChange:[bufferChain inputFormat] inputConfig:[bufferChain inputConfig]];
|
||||
[visualization resetBuffer];
|
||||
[rubberband resetBuffer];
|
||||
[fsurround resetBuffer];
|
||||
[equalizer resetBuffer];
|
||||
|
|
|
@ -71,4 +71,7 @@
|
|||
|
||||
- (void)restartPlaybackAtCurrentPosition;
|
||||
|
||||
- (double)getTotalLatency;
|
||||
- (double)getPostVisLatency;
|
||||
|
||||
@end
|
||||
|
|
|
@ -200,4 +200,12 @@
|
|||
return [output latency];
|
||||
}
|
||||
|
||||
- (double)getTotalLatency {
|
||||
return [[controller bufferChain] secondsBuffered] + [output latency];
|
||||
}
|
||||
|
||||
- (double)getPostVisLatency {
|
||||
return [[controller bufferChain] getPostVisLatency] + [output latency];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
34
Audio/Chain/VisualizationNode.h
Normal file
34
Audio/Chain/VisualizationNode.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// VisualizationNode.h
|
||||
// CogAudio
|
||||
//
|
||||
// Created by Christopher Snowhill on 2/12/25.
|
||||
//
|
||||
|
||||
#ifndef VisualizationNode_h
|
||||
#define VisualizationNode_h
|
||||
|
||||
#import "Node.h"
|
||||
|
||||
@interface VisualizationNode : Node {
|
||||
}
|
||||
|
||||
- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p latency:(double)latency;
|
||||
|
||||
- (void)threadEntry:(id _Nullable)arg;
|
||||
|
||||
- (BOOL)setup;
|
||||
- (void)cleanUp;
|
||||
|
||||
- (void)resetBuffer;
|
||||
|
||||
- (void)pop;
|
||||
- (void)replayPreroll;
|
||||
|
||||
- (void)process;
|
||||
|
||||
- (double)secondsBuffered;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* VisualizationNode_h */
|
344
Audio/Chain/VisualizationNode.m
Normal file
344
Audio/Chain/VisualizationNode.m
Normal file
|
@ -0,0 +1,344 @@
|
|||
//
|
||||
// VisualizationNode.m
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 2/12/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#import "Downmix.h"
|
||||
|
||||
#import <CogAudio/CogAudio-Swift.h>
|
||||
|
||||
#import "BufferChain.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
#import "rsstate.h"
|
||||
|
||||
#import "VisualizationNode.h"
|
||||
|
||||
@interface VisualizationCollection : NSObject {
|
||||
NSMutableArray *collection;
|
||||
}
|
||||
|
||||
+ (VisualizationCollection *)sharedCollection;
|
||||
|
||||
- (id)init;
|
||||
|
||||
- (BOOL)pushVisualizer:(VisualizationNode *)visualization;
|
||||
- (void)popVisualizer:(VisualizationNode *)visualization;
|
||||
|
||||
@end
|
||||
|
||||
@implementation VisualizationCollection
|
||||
|
||||
static VisualizationCollection *theCollection = nil;
|
||||
|
||||
+ (VisualizationCollection *)sharedCollection {
|
||||
@synchronized (theCollection) {
|
||||
if(!theCollection) {
|
||||
theCollection = [[VisualizationCollection alloc] init];
|
||||
}
|
||||
return theCollection;
|
||||
}
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
collection = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)pushVisualizer:(VisualizationNode *)visualization {
|
||||
@synchronized (collection) {
|
||||
[collection addObject:visualization];
|
||||
return [collection count] > 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)popVisualizer:(VisualizationNode *)visualization {
|
||||
@synchronized (collection) {
|
||||
[collection removeObject:visualization];
|
||||
if([collection count]) {
|
||||
VisualizationNode *next = [collection objectAtIndex:0];
|
||||
[next replayPreroll];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation VisualizationNode {
|
||||
void *rs;
|
||||
double lastVisRate;
|
||||
|
||||
BOOL processEntered;
|
||||
BOOL stopping;
|
||||
BOOL paused;
|
||||
BOOL replay;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription visFormat; // Mono format for vis
|
||||
|
||||
uint32_t inputChannelConfig;
|
||||
uint32_t visChannelConfig;
|
||||
|
||||
size_t resamplerRemain;
|
||||
|
||||
DownmixProcessor *downmixer;
|
||||
|
||||
VisualizationController *visController;
|
||||
|
||||
float visAudio[512];
|
||||
float resamplerInput[8192];
|
||||
float visTemp[8192];
|
||||
|
||||
BOOL registered;
|
||||
BOOL prerolling;
|
||||
NSMutableData *prerollBuffer;
|
||||
}
|
||||
|
||||
- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p latency:(double)latency {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
buffer = [[ChunkList alloc] initWithMaximumDuration:latency];
|
||||
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
accessLock = [[NSLock alloc] init];
|
||||
|
||||
initialBufferFilled = NO;
|
||||
|
||||
controller = c;
|
||||
endOfStream = NO;
|
||||
shouldContinue = YES;
|
||||
|
||||
nodeChannelConfig = 0;
|
||||
nodeLossless = NO;
|
||||
|
||||
durationPrebuffer = latency * 0.25;
|
||||
|
||||
visController = [VisualizationController sharedController];
|
||||
|
||||
registered = NO;
|
||||
prerolling = NO;
|
||||
replay = NO;
|
||||
prerollBuffer = [[NSMutableData alloc] init];
|
||||
|
||||
[self setPreviousNode:p];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
DLog(@"Visualization node dealloc");
|
||||
[self cleanUp];
|
||||
[self pop];
|
||||
}
|
||||
|
||||
- (void)pop {
|
||||
if(registered) {
|
||||
[[VisualizationCollection sharedCollection] popVisualizer:self];
|
||||
registered = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Visualization thread should be fairly high priority, too
|
||||
- (void)threadEntry:(id _Nullable)arg {
|
||||
@autoreleasepool {
|
||||
NSThread *currentThread = [NSThread currentThread];
|
||||
[currentThread setThreadPriority:0.75];
|
||||
[currentThread setQualityOfService:NSQualityOfServiceUserInitiated];
|
||||
[self process];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetBuffer {
|
||||
paused = YES;
|
||||
while(processEntered) {
|
||||
usleep(500);
|
||||
}
|
||||
[super resetBuffer];
|
||||
[self fullShutdown];
|
||||
paused = NO;
|
||||
}
|
||||
|
||||
- (double)secondsBuffered {
|
||||
return [buffer listDuration];
|
||||
}
|
||||
|
||||
- (BOOL)setup {
|
||||
if(fabs(inputFormat.mSampleRate - 44100.0) > 1e-6) {
|
||||
rs = rsstate_new(1, inputFormat.mSampleRate, 44100.0);
|
||||
if(!rs) {
|
||||
return NO;
|
||||
}
|
||||
resamplerRemain = 0;
|
||||
}
|
||||
|
||||
visFormat = inputFormat;
|
||||
visFormat.mChannelsPerFrame = 1;
|
||||
visFormat.mBytesPerFrame = sizeof(float);
|
||||
visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket;
|
||||
visChannelConfig = AudioChannelFrontCenter;
|
||||
|
||||
downmixer = [[DownmixProcessor alloc] initWithInputFormat:inputFormat inputConfig:inputChannelConfig andOutputFormat:visFormat outputConfig:visChannelConfig];
|
||||
if(!downmixer) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)cleanUp {
|
||||
stopping = YES;
|
||||
while(processEntered) {
|
||||
usleep(500);
|
||||
}
|
||||
[self fullShutdown];
|
||||
}
|
||||
|
||||
- (void)fullShutdown {
|
||||
if(rs) {
|
||||
rsstate_delete(rs);
|
||||
rs = NULL;
|
||||
}
|
||||
downmixer = nil;
|
||||
}
|
||||
|
||||
- (void)process {
|
||||
while([self shouldContinue] == YES) {
|
||||
if(paused) {
|
||||
usleep(500);
|
||||
continue;
|
||||
}
|
||||
@autoreleasepool {
|
||||
if(replay) {
|
||||
size_t length = [prerollBuffer length];
|
||||
if(length) {
|
||||
[visController postVisPCM:(const float *)[prerollBuffer bytes] amount:(length / sizeof(float))];
|
||||
[prerollBuffer replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
||||
}
|
||||
replay = NO;
|
||||
prerolling = NO;
|
||||
}
|
||||
AudioChunk *chunk = nil;
|
||||
chunk = [self readAndMergeChunksAsFloat32:512];
|
||||
if(!chunk || ![chunk duration]) {
|
||||
if([self endOfStream] == YES) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
[self processVis:[chunk copy]];
|
||||
[self writeChunk:chunk];
|
||||
chunk = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)postVisPCM:(const float *)visTemp amount:(size_t)samples {
|
||||
if(!registered) {
|
||||
prerolling = [[VisualizationCollection sharedCollection] pushVisualizer:self];
|
||||
registered = YES;
|
||||
}
|
||||
if(prerolling) {
|
||||
[prerollBuffer appendBytes:visTemp length:(samples * sizeof(float))];
|
||||
} else {
|
||||
[visController postVisPCM:visTemp amount:samples];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replayPreroll {
|
||||
paused = YES;
|
||||
while(processEntered) {
|
||||
usleep(500);
|
||||
}
|
||||
replay = YES;
|
||||
paused = NO;
|
||||
}
|
||||
|
||||
- (void)processVis:(AudioChunk *)chunk {
|
||||
processEntered = YES;
|
||||
|
||||
if(paused) {
|
||||
processEntered = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription format = [chunk format];
|
||||
uint32_t channelConfig = [chunk channelConfig];
|
||||
|
||||
[visController postSampleRate:44100.0];
|
||||
|
||||
if(!rs || !downmixer ||
|
||||
memcmp(&format, &inputFormat, sizeof(format)) != 0 ||
|
||||
channelConfig != inputChannelConfig) {
|
||||
if(rs) {
|
||||
while(!stopping) {
|
||||
int samplesFlushed;
|
||||
samplesFlushed = (int)rsstate_flush(rs, &visTemp[0], 8192);
|
||||
if(samplesFlushed > 1) {
|
||||
[self postVisPCM:visTemp amount:samplesFlushed];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
[self fullShutdown];
|
||||
inputFormat = format;
|
||||
inputChannelConfig = channelConfig;
|
||||
if(![self setup]) {
|
||||
processEntered = NO;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t frameCount = [chunk frameCount];
|
||||
NSData *sampleData = [chunk removeSamples:frameCount];
|
||||
|
||||
[downmixer process:[sampleData bytes] frameCount:frameCount output:&visAudio[0]];
|
||||
|
||||
if(rs) {
|
||||
int samplesProcessed;
|
||||
size_t totalDone = 0;
|
||||
size_t inDone = 0;
|
||||
size_t visFrameCount = frameCount;
|
||||
do {
|
||||
if(stopping) {
|
||||
break;
|
||||
}
|
||||
int visTodo = (int)MIN(visFrameCount, resamplerRemain + visFrameCount - 8192);
|
||||
if(visTodo) {
|
||||
cblas_scopy(visTodo, &visAudio[0], 1, &resamplerInput[resamplerRemain], 1);
|
||||
}
|
||||
visTodo += resamplerRemain;
|
||||
resamplerRemain = 0;
|
||||
samplesProcessed = (int)rsstate_resample(rs, &resamplerInput[0], visTodo, &inDone, &visTemp[0], 8192);
|
||||
resamplerRemain = (int)(visTodo - inDone);
|
||||
if(resamplerRemain && inDone) {
|
||||
memmove(&resamplerInput[0], &resamplerInput[inDone], resamplerRemain * sizeof(float));
|
||||
}
|
||||
if(samplesProcessed) {
|
||||
[self postVisPCM:&visTemp[0] amount:samplesProcessed];
|
||||
}
|
||||
totalDone += inDone;
|
||||
visFrameCount -= inDone;
|
||||
} while(samplesProcessed && visFrameCount);
|
||||
} else {
|
||||
[self postVisPCM:&visAudio[0] amount:frameCount];
|
||||
}
|
||||
|
||||
processEntered = NO;
|
||||
}
|
||||
|
||||
@end
|
|
@ -90,6 +90,8 @@
|
|||
839E56E82879450300DFB5F4 /* IHrtfData.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56E42879450300DFB5F4 /* IHrtfData.h */; };
|
||||
839E56EA28794F6300DFB5F4 /* HrtfTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56E928794F6300DFB5F4 /* HrtfTypes.h */; };
|
||||
839E56F7287974A100DFB5F4 /* SandboxBroker.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56F6287974A100DFB5F4 /* SandboxBroker.h */; };
|
||||
839E899E2D5DB9D500A13526 /* VisualizationNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E899D2D5DB9D500A13526 /* VisualizationNode.h */; };
|
||||
839E89A02D5DBA1700A13526 /* VisualizationNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 839E899F2D5DBA1700A13526 /* VisualizationNode.m */; };
|
||||
83A3496A2D5C3F430096D530 /* DSPRubberbandNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A349682D5C3F430096D530 /* DSPRubberbandNode.m */; };
|
||||
83A3496B2D5C3F430096D530 /* DSPRubberbandNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A349672D5C3F430096D530 /* DSPRubberbandNode.h */; };
|
||||
83A3496D2D5C40490096D530 /* DSPFSurroundNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A3496C2D5C40490096D530 /* DSPFSurroundNode.h */; };
|
||||
|
@ -214,6 +216,8 @@
|
|||
839E56E42879450300DFB5F4 /* IHrtfData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IHrtfData.h; sourceTree = "<group>"; };
|
||||
839E56E928794F6300DFB5F4 /* HrtfTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HrtfTypes.h; sourceTree = "<group>"; };
|
||||
839E56F6287974A100DFB5F4 /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../Utils/SandboxBroker.h; sourceTree = "<group>"; };
|
||||
839E899D2D5DB9D500A13526 /* VisualizationNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisualizationNode.h; sourceTree = "<group>"; };
|
||||
839E899F2D5DBA1700A13526 /* VisualizationNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VisualizationNode.m; sourceTree = "<group>"; };
|
||||
83A349672D5C3F430096D530 /* DSPRubberbandNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPRubberbandNode.h; sourceTree = "<group>"; };
|
||||
83A349682D5C3F430096D530 /* DSPRubberbandNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPRubberbandNode.m; sourceTree = "<group>"; };
|
||||
83A3496C2D5C40490096D530 /* DSPFSurroundNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPFSurroundNode.h; sourceTree = "<group>"; };
|
||||
|
@ -384,6 +388,8 @@
|
|||
17D21C7F0B8BE4BA00D1EBDE /* OutputNode.m */,
|
||||
83FFED502D5B08BC0044CCAF /* DSPNode.h */,
|
||||
83FFED522D5B09320044CCAF /* DSPNode.m */,
|
||||
839E899D2D5DB9D500A13526 /* VisualizationNode.h */,
|
||||
839E899F2D5DBA1700A13526 /* VisualizationNode.m */,
|
||||
);
|
||||
path = Chain;
|
||||
sourceTree = "<group>";
|
||||
|
@ -588,6 +594,7 @@
|
|||
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */,
|
||||
83A3496B2D5C3F430096D530 /* DSPRubberbandNode.h in Headers */,
|
||||
17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */,
|
||||
839E899E2D5DB9D500A13526 /* VisualizationNode.h in Headers */,
|
||||
8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */,
|
||||
83FFED512D5B08BC0044CCAF /* DSPNode.h in Headers */,
|
||||
17C940230B900909008627D6 /* AudioMetadataReader.h in Headers */,
|
||||
|
@ -697,6 +704,7 @@
|
|||
834FD4ED27AF91220063BC83 /* AudioChunk.m in Sources */,
|
||||
17D21CF40B8BE5EF00D1EBDE /* CogSemaphore.m in Sources */,
|
||||
839B83FA286D91ED00F529EE /* VisualizationController.swift in Sources */,
|
||||
839E89A02D5DBA1700A13526 /* VisualizationNode.m in Sources */,
|
||||
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */,
|
||||
83A3496F2D5C405E0096D530 /* DSPFSurroundNode.m in Sources */,
|
||||
17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */,
|
||||
|
|
|
@ -47,16 +47,10 @@ using std::atomic_long;
|
|||
|
||||
NSLock *outputLock;
|
||||
|
||||
double secondsLatency;
|
||||
double visPushed;
|
||||
|
||||
double streamTimestamp;
|
||||
|
||||
double lastClippedSampleRate;
|
||||
|
||||
void *rsvis;
|
||||
double lastVisRate;
|
||||
|
||||
BOOL stopInvoked;
|
||||
BOOL stopCompleted;
|
||||
BOOL running;
|
||||
|
@ -91,8 +85,6 @@ using std::atomic_long;
|
|||
AudioStreamBasicDescription realStreamFormat; // stream format pre-hrtf
|
||||
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
||||
|
||||
AudioStreamBasicDescription visFormat; // Mono format for vis
|
||||
|
||||
uint32_t deviceChannelConfig;
|
||||
uint32_t realStreamChannelConfig;
|
||||
uint32_t streamChannelConfig;
|
||||
|
@ -102,7 +94,6 @@ using std::atomic_long;
|
|||
size_t _bufferSize;
|
||||
|
||||
DownmixProcessor *downmixer;
|
||||
DownmixProcessor *downmixerForVis;
|
||||
|
||||
VisualizationController *visController;
|
||||
|
||||
|
@ -110,8 +101,6 @@ using std::atomic_long;
|
|||
|
||||
AudioChunk *chunkRemain;
|
||||
|
||||
int visResamplerRemain;
|
||||
|
||||
BOOL resetStreamFormat;
|
||||
|
||||
BOOL shouldPlayOutBuffer;
|
||||
|
@ -121,10 +110,6 @@ using std::atomic_long;
|
|||
float inputBuffer[4096 * 32]; // 4096 samples times maximum supported channel count
|
||||
float downmixBuffer[4096 * 8];
|
||||
|
||||
float visAudio[4096];
|
||||
float visResamplerInput[8192];
|
||||
float visTemp[8192];
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
FILE *_logFile;
|
||||
#endif
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#import "rsstate.h"
|
||||
|
||||
extern void scale_by_volume(float *buffer, size_t count, float volume);
|
||||
|
||||
static NSString *CogPlaybackDidBeginNotificiation = @"CogPlaybackDidBeginNotificiation";
|
||||
|
@ -58,13 +56,6 @@ static void *kOutputCoreAudioContext = &kOutputCoreAudioContext;
|
|||
realStreamChannelConfig = config;
|
||||
streamFormatStarted = YES;
|
||||
streamFormatChanged = YES;
|
||||
|
||||
visFormat = format;
|
||||
visFormat.mChannelsPerFrame = 1;
|
||||
visFormat.mBytesPerFrame = visFormat.mChannelsPerFrame * (visFormat.mBitsPerChannel / 8);
|
||||
visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket;
|
||||
|
||||
downmixerForVis = [[DownmixProcessor alloc] initWithInputFormat:origFormat inputConfig:origConfig andOutputFormat:visFormat outputConfig:AudioConfigMono];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,86 +89,6 @@ static void *kOutputCoreAudioContext = &kOutputCoreAudioContext;
|
|||
location:@"pre downmix"];
|
||||
#endif
|
||||
const float *outputPtr = (const float *)[samples bytes];
|
||||
[downmixerForVis process:outputPtr
|
||||
frameCount:frameCount
|
||||
output:&visAudio[0]];
|
||||
|
||||
[visController postSampleRate:44100.0];
|
||||
|
||||
[outputLock lock];
|
||||
if(fabs(realStreamFormat.mSampleRate - 44100.0) > 1e-5) {
|
||||
if(fabs(realStreamFormat.mSampleRate - lastVisRate) > 1e-5) {
|
||||
if(rsvis) {
|
||||
for(;;) {
|
||||
if(stopping) {
|
||||
break;
|
||||
}
|
||||
int samplesFlushed;
|
||||
samplesFlushed = (int)rsstate_flush(rsvis, &visTemp[0], 8192);
|
||||
if(samplesFlushed > 1) {
|
||||
[visController postVisPCM:visTemp amount:samplesFlushed];
|
||||
visPushed += (double)samplesFlushed / 44100.0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rsstate_delete(rsvis);
|
||||
rsvis = NULL;
|
||||
}
|
||||
lastVisRate = realStreamFormat.mSampleRate;
|
||||
rsvis = rsstate_new(1, lastVisRate, 44100.0);
|
||||
}
|
||||
if(rsvis) {
|
||||
int samplesProcessed;
|
||||
size_t totalDone = 0;
|
||||
size_t inDone = 0;
|
||||
size_t visFrameCount = frameCount;
|
||||
do {
|
||||
if(stopping) {
|
||||
break;
|
||||
}
|
||||
int visTodo = (int)MIN(visFrameCount, visResamplerRemain + visFrameCount - 8192);
|
||||
if(visTodo) {
|
||||
cblas_scopy(visTodo, &visAudio[0], 1, &visResamplerInput[visResamplerRemain], 1);
|
||||
}
|
||||
visTodo += visResamplerRemain;
|
||||
visResamplerRemain = 0;
|
||||
samplesProcessed = (int)rsstate_resample(rsvis, &visResamplerInput[0], visTodo, &inDone, &visTemp[0], 8192);
|
||||
visResamplerRemain = (int)(visTodo - inDone);
|
||||
if(visResamplerRemain && inDone) {
|
||||
memmove(&visResamplerInput[0], &visResamplerInput[inDone], visResamplerRemain * sizeof(float));
|
||||
}
|
||||
if(samplesProcessed) {
|
||||
[visController postVisPCM:&visTemp[0] amount:samplesProcessed];
|
||||
visPushed += (double)samplesProcessed / 44100.0;
|
||||
}
|
||||
totalDone += inDone;
|
||||
visFrameCount -= inDone;
|
||||
} while(samplesProcessed && visFrameCount);
|
||||
}
|
||||
} else if(rsvis) {
|
||||
for(;;) {
|
||||
if(stopping) {
|
||||
break;
|
||||
}
|
||||
int samplesFlushed;
|
||||
samplesFlushed = (int)rsstate_flush(rsvis, &visTemp[0], 8192);
|
||||
if(samplesFlushed > 1) {
|
||||
[visController postVisPCM:visTemp amount:samplesFlushed];
|
||||
visPushed += (double)samplesFlushed / 44100.0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rsstate_delete(rsvis);
|
||||
rsvis = NULL;
|
||||
[visController postVisPCM:&visAudio[0] amount:frameCount];
|
||||
visPushed += (double)frameCount / 44100.0;
|
||||
} else if(!stopping) {
|
||||
[visController postVisPCM:&visAudio[0] amount:frameCount];
|
||||
visPushed += (double)frameCount / 44100.0;
|
||||
}
|
||||
[outputLock unlock];
|
||||
|
||||
cblas_scopy((int)(frameCount * realStreamFormat.mChannelsPerFrame), outputPtr, 1, &buffer[0], 1);
|
||||
amountRead = frameCount;
|
||||
|
@ -272,7 +183,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
}
|
||||
|
||||
- (BOOL)processEndOfStream {
|
||||
if(stopping || ([outputController endOfStream] == YES && [self signalEndOfStream:secondsLatency])) {
|
||||
if(stopping || ([outputController endOfStream] == YES && [self signalEndOfStream:[outputController getTotalLatency]])) {
|
||||
stopping = YES;
|
||||
return YES;
|
||||
}
|
||||
|
@ -289,7 +200,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
running = YES;
|
||||
started = NO;
|
||||
shouldPlayOutBuffer = NO;
|
||||
secondsLatency = 1.0;
|
||||
|
||||
while(!stopping) {
|
||||
@autoreleasepool {
|
||||
|
@ -301,16 +211,9 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
if([outputController shouldReset]) {
|
||||
[outputController setShouldReset:NO];
|
||||
[outputLock lock];
|
||||
secondsLatency = 0.0;
|
||||
visPushed = 0.0;
|
||||
started = NO;
|
||||
restarted = NO;
|
||||
if(rsvis) {
|
||||
rsstate_delete(rsvis);
|
||||
rsvis = NULL;
|
||||
}
|
||||
lastClippedSampleRate = 0.0;
|
||||
lastVisRate = 0.0;
|
||||
[outputLock unlock];
|
||||
}
|
||||
|
||||
|
@ -556,11 +459,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame * (deviceFormat.mBitsPerChannel / 8);
|
||||
deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame * deviceFormat.mFramesPerPacket;
|
||||
|
||||
visFormat = deviceFormat;
|
||||
visFormat.mChannelsPerFrame = 1;
|
||||
visFormat.mBytesPerFrame = visFormat.mChannelsPerFrame * (visFormat.mBitsPerChannel / 8);
|
||||
visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket;
|
||||
|
||||
/* Set the channel layout for the audio queue */
|
||||
AudioChannelLayoutTag tag = 0;
|
||||
switch(deviceFormat.mChannelsPerFrame) {
|
||||
|
@ -791,22 +689,13 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
restarted = NO;
|
||||
|
||||
downmixer = nil;
|
||||
downmixerForVis = nil;
|
||||
|
||||
lastClippedSampleRate = 0.0;
|
||||
|
||||
rsvis = NULL;
|
||||
lastVisRate = 44100.0;
|
||||
|
||||
inputRemain = 0;
|
||||
|
||||
chunkRemain = nil;
|
||||
|
||||
visResamplerRemain = 0;
|
||||
|
||||
secondsLatency = 0;
|
||||
visPushed = 0;
|
||||
|
||||
AudioComponentDescription desc;
|
||||
NSError *err;
|
||||
|
||||
|
@ -854,14 +743,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
if(secondsPlayed > 0) {
|
||||
[outputController setAmountPlayed:streamTimestamp];
|
||||
}
|
||||
double visLatency = visPushed;
|
||||
visPushed -= secondsPlayed;
|
||||
if(visLatency < secondsPlayed || visLatency > 30.0) {
|
||||
visLatency = secondsPlayed;
|
||||
visPushed = secondsPlayed;
|
||||
}
|
||||
secondsLatency = visLatency;
|
||||
[visController postLatency:visLatency];
|
||||
[visController postLatency:[outputController getPostVisLatency]];
|
||||
}
|
||||
|
||||
- (void)setVolume:(double)v {
|
||||
|
@ -869,8 +751,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
}
|
||||
|
||||
- (double)latency {
|
||||
if(secondsLatency > 0) return secondsLatency;
|
||||
else return 0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
|
@ -920,10 +801,10 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
if(_au) {
|
||||
if(shouldPlayOutBuffer && !commandStop) {
|
||||
int compareVal = 0;
|
||||
double secondsLatency = self->secondsLatency >= 1e-5 ? self->secondsLatency : 0;
|
||||
double secondsLatency = [outputController getTotalLatency];
|
||||
int compareMax = (((1000000 / 5000) * secondsLatency) + (10000 / 5000)); // latency plus 10ms, divide by sleep intervals
|
||||
do {
|
||||
compareVal = self->secondsLatency >= 1e-5 ? self->secondsLatency : 0;
|
||||
compareVal = [outputController getTotalLatency];
|
||||
usleep(5000);
|
||||
} while(!commandStop && compareVal > 0 && compareMax-- > 0);
|
||||
}
|
||||
|
@ -936,9 +817,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
usleep(5000);
|
||||
}
|
||||
}
|
||||
if(downmixerForVis) {
|
||||
downmixerForVis = nil;
|
||||
}
|
||||
#ifdef OUTPUT_LOG
|
||||
if(_logFile) {
|
||||
fclose(_logFile);
|
||||
|
@ -950,10 +828,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
|
|||
[visController reset];
|
||||
visController = nil;
|
||||
}
|
||||
if(rsvis) {
|
||||
rsstate_delete(rsvis);
|
||||
rsvis = NULL;
|
||||
}
|
||||
stopCompleted = YES;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue