[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
parent
96acc738e3
commit
647c754311
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue