[Audio Output] Greatly improve sample rate changes

Sample rate changes will now occur on exact sample boundaries, like they
are supposed to. Also, FreeSurround accounts for its output latency.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
xcode15
Christopher Snowhill 2022-07-15 03:34:10 -07:00
parent 96acc738e3
commit 647c754311
5 changed files with 93 additions and 26 deletions

View File

@ -103,6 +103,14 @@
} }
} }
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config {
@autoreleasepool {
[self setPreviousNode:[[controller bufferChain] finalNode]];
return [super peekFormat:format channelConfig:config];
}
}
- (double)amountPlayed { - (double)amountPlayed {
return amountPlayed; return amountPlayed;
} }

View File

@ -26,6 +26,7 @@
- (uint32_t)channelCount; - (uint32_t)channelCount;
- (uint32_t)channelConfig; - (uint32_t)channelConfig;
- (double)srate;
- (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count; - (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count;

View File

@ -121,6 +121,10 @@ struct freesurround_params {
return channelConfig; return channelConfig;
} }
- (double)srate {
return srate;
}
- (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count { - (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count {
freesurround_params *_params = (freesurround_params *)params; freesurround_params *_params = (freesurround_params *)params;
freesurround_decoder *_decoder = (freesurround_decoder *)decoder; freesurround_decoder *_decoder = (freesurround_decoder *)decoder;

View File

@ -111,6 +111,8 @@ using std::atomic_long;
HeadphoneFilter *hrtf; HeadphoneFilter *hrtf;
BOOL enableFSurround; BOOL enableFSurround;
BOOL FSurroundDelayRemoved;
int inputBufferLastTime;
FSurroundFilter *fsurround; FSurroundFilter *fsurround;
BOOL resetStreamFormat; BOOL resetStreamFormat;
@ -119,7 +121,7 @@ using std::atomic_long;
float tempBuffer[512 * 32]; float tempBuffer[512 * 32];
float r8bTempBuffer[4096 * 32]; float r8bTempBuffer[4096 * 32];
float inputBuffer[4096 * 32]; // 4096 samples times maximum supported channel count float inputBuffer[4096 * 32]; // 4096 samples times maximum supported channel count
float fsurroundBuffer[4096 * 6]; float fsurroundBuffer[8192 * 6];
float hrtfBuffer[4096 * 2]; float hrtfBuffer[4096 * 2];
float eqBuffer[4096 * 32]; float eqBuffer[4096 * 32];

View File

@ -69,17 +69,13 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA
return 0; return 0;
} }
AudioChunk *chunk = [outputController readChunk:amountToRead]; AudioStreamBasicDescription format;
uint32_t config;
int frameCount = (int)[chunk frameCount]; if([outputController peekFormat:&format channelConfig:&config]) {
AudioStreamBasicDescription format = [chunk format];
uint32_t config = [chunk channelConfig];
double chunkDuration = 0;
if(frameCount) {
// XXX ERROR with AirPods - Can't go higher than CD*8 surround - 192k stereo // XXX ERROR with AirPods - Can't go higher than CD*8 surround - 192k stereo
// Emits to console: [AUScotty] Initialize: invalid FFT size 16384 // Emits to console: [AUScotty] Initialize: invalid FFT size 16384
// DSD256 stereo emits: [AUScotty] Initialize: invalid FFT size 65536 // DSD256 stereo emits: [AUScotty] Initialize: invalid FFT size 65536
BOOL formatClipped = NO; BOOL formatClipped = NO;
BOOL isSurround = format.mChannelsPerFrame > 2; BOOL isSurround = format.mChannelsPerFrame > 2;
const double maxSampleRate = isSurround ? 352800.0 : 192000.0; const double maxSampleRate = isSurround ? 352800.0 : 192000.0;
@ -117,10 +113,23 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA
if(!r8bold) { if(!r8bold) {
realStreamFormat = format; realStreamFormat = format;
realStreamChannelConfig = config; realStreamChannelConfig = config;
[self updateStreamFormat]; streamFormatChanged = YES;
}
} }
} }
if(streamFormatChanged) {
return 0;
}
AudioChunk *chunk = [outputController readChunk:amountToRead];
int frameCount = (int)[chunk frameCount];
format = [chunk format];
config = [chunk channelConfig];
double chunkDuration = 0;
if(frameCount) {
chunkDuration = [chunk duration]; chunkDuration = [chunk duration];
NSData *samples = [chunk removeSamples:frameCount]; NSData *samples = [chunk removeSamples:frameCount];
@ -301,9 +310,11 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
eqPreamp = pow(10.0, preamp / 20.0); eqPreamp = pow(10.0, preamp / 20.0);
} else if([keyPath isEqualToString:@"values.enableHrtf"]) { } else if([keyPath isEqualToString:@"values.enableHrtf"]) {
enableHrtf = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"enableHrtf"]; enableHrtf = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"enableHrtf"];
if(streamFormatStarted)
resetStreamFormat = YES; resetStreamFormat = YES;
} else if([keyPath isEqualToString:@"values.enableFSurround"]) { } else if([keyPath isEqualToString:@"values.enableFSurround"]) {
enableFSurround = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"enableFSurround"]; enableFSurround = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"enableFSurround"];
if(streamFormatStarted)
resetStreamFormat = YES; resetStreamFormat = YES;
} }
} }
@ -600,18 +611,22 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
- (void)updateStreamFormat { - (void)updateStreamFormat {
/* Set the channel layout for the audio queue */ /* Set the channel layout for the audio queue */
streamFormatChanged = YES;
resetStreamFormat = NO; resetStreamFormat = NO;
uint32_t channels = realStreamFormat.mChannelsPerFrame; uint32_t channels = realStreamFormat.mChannelsPerFrame;
uint32_t channelConfig = realStreamChannelConfig; uint32_t channelConfig = realStreamChannelConfig;
if(enableFSurround && channels == 2 && channelConfig == AudioConfigStereo) { if(enableFSurround && channels == 2 && channelConfig == AudioConfigStereo) {
[currentPtsLock lock];
fsurround = [[FSurroundFilter alloc] initWithSampleRate:realStreamFormat.mSampleRate]; fsurround = [[FSurroundFilter alloc] initWithSampleRate:realStreamFormat.mSampleRate];
[currentPtsLock unlock];
channels = [fsurround channelCount]; channels = [fsurround channelCount];
channelConfig = [fsurround channelConfig]; channelConfig = [fsurround channelConfig];
FSurroundDelayRemoved = NO;
} else { } else {
[currentPtsLock lock];
fsurround = nil; fsurround = nil;
[currentPtsLock unlock];
} }
/* Apple's audio processor really only supports common 1-8 channel formats */ /* Apple's audio processor really only supports common 1-8 channel formats */
@ -742,13 +757,15 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
status = CMBlockBufferCreateEmpty(kCFAllocatorDefault, 0, 0, &blockListBuffer); status = CMBlockBufferCreateEmpty(kCFAllocatorDefault, 0, 0, &blockListBuffer);
if(status != noErr || !blockListBuffer) return 0; if(status != noErr || !blockListBuffer) return 0;
if(resetStreamFormat) { if(resetStreamFormat || streamFormatChanged) {
streamFormatChanged = NO;
[self updateStreamFormat]; [self updateStreamFormat];
} }
int inputRendered = 0; int inputRendered = inputBufferLastTime;
int bytesRendered = 0; int bytesRendered = inputRendered * newFormat.mBytesPerPacket;
do {
while(inputRendered < 4096) {
int maxToRender = MIN(4096 - inputRendered, 512); int maxToRender = MIN(4096 - inputRendered, 512);
int rendered = [self renderInput:maxToRender toBuffer:&tempBuffer[0]]; int rendered = [self renderInput:maxToRender toBuffer:&tempBuffer[0]];
if(rendered > 0) { if(rendered > 0) {
@ -758,12 +775,17 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
bytesRendered += rendered * newFormat.mBytesPerPacket; bytesRendered += rendered * newFormat.mBytesPerPacket;
if(streamFormatChanged) { if(streamFormatChanged) {
streamFormatChanged = NO; streamFormatChanged = NO;
if(rendered < maxToRender) { if(inputRendered) {
resetStreamFormat = YES;
break; break;
} else {
[self updateStreamFormat];
} }
} }
if([self processEndOfStream]) break; if([self processEndOfStream]) break;
} while(inputRendered < 4096); }
inputBufferLastTime = inputRendered;
int samplesRenderedTotal = 0; int samplesRenderedTotal = 0;
@ -793,14 +815,35 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
r8bDone = NO; r8bDone = NO;
realStreamFormat = newFormat; realStreamFormat = newFormat;
realStreamChannelConfig = newChannelConfig; realStreamChannelConfig = newChannelConfig;
[self updateStreamFormat]; streamFormatChanged = YES;
} }
} }
if(samplesRendered) { if(samplesRendered || fsurround) {
if(fsurround) { if(fsurround) {
[fsurround process:samplePtr output:&fsurroundBuffer[0] count:samplesRendered]; int countToProcess = samplesRendered;
if(countToProcess < 4096) {
bzero(samplePtr + countToProcess * 2, (4096 - countToProcess) * 2 * sizeof(float));
countToProcess = 4096;
}
[fsurround process:samplePtr output:&fsurroundBuffer[0] count:countToProcess];
samplePtr = &fsurroundBuffer[0]; samplePtr = &fsurroundBuffer[0];
if(resetStreamFormat || samplesRendered < 4096) {
bzero(&fsurroundBuffer[4096 * 6], 4096 * 2 * sizeof(float));
[fsurround process:&fsurroundBuffer[4096 * 6] output:&fsurroundBuffer[4096 * 6] count:4096];
samplesRendered += 2048;
}
if(!FSurroundDelayRemoved) {
FSurroundDelayRemoved = YES;
if(samplesRendered > 2048) {
samplePtr += 2048 * 6;
samplesRendered -= 2048;
}
}
}
if(!samplesRendered) {
break;
} }
if(hrtf) { if(hrtf) {
@ -871,11 +914,13 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
} }
if(i == 0) { if(i == 0) {
if(!samplesRendered) { samplesRenderedTotal += samplesRendered;
*blockBufferOut = blockListBuffer; if(samplesRendered) {
return samplesRenderedTotal + samplesRendered; break;
} }
++i;
} else { } else {
inputBufferLastTime = 0;
samplesRenderedTotal += samplesRendered; samplesRenderedTotal += samplesRendered;
++i; ++i;
} }
@ -898,6 +943,9 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
audioFormatDescription = NULL; audioFormatDescription = NULL;
resetStreamFormat = NO; resetStreamFormat = NO;
streamFormatChanged = NO;
inputBufferLastTime = 0;
running = NO; running = NO;
stopping = NO; stopping = NO;
@ -1031,6 +1079,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
void **r8bstate = &self->r8bstate; void **r8bstate = &self->r8bstate;
void **r8bold = &self->r8bold; void **r8bold = &self->r8bold;
void **r8bvis = &self->r8bvis; void **r8bvis = &self->r8bvis;
FSurroundFilter *const *fsurroundtest = &self->fsurround;
currentPtsObserver = [renderSynchronizer addPeriodicTimeObserverForInterval:interval currentPtsObserver = [renderSynchronizer addPeriodicTimeObserverForInterval:interval
queue:NULL queue:NULL
usingBlock:^(CMTime time) { usingBlock:^(CMTime time) {
@ -1057,6 +1106,9 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
if(*r8bvis) { if(*r8bvis) {
latencyVis = r8bstate_latency(*r8bvis); latencyVis = r8bstate_latency(*r8bvis);
} }
if(*fsurroundtest) {
latencyVis += 2048.0 / [(*fsurroundtest) srate];
}
[lock unlock]; [lock unlock];
if(latencySeconds < 0) if(latencySeconds < 0)
latencySeconds = 0; latencySeconds = 0;