Bug Fix: Ensure robust output format changes

Output format mostly requires stopping and restarting the output device,
and this also prevents us from using the latency function properly,
which apparently always returns 0 for output devices anyway. These
changes also prevent the output callback from hanging when resets occur.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-03-07 05:37:04 -08:00
parent a3385d1af9
commit 17b8647052
2 changed files with 29 additions and 12 deletions

View file

@ -55,6 +55,7 @@ using std::atomic_long;
BOOL paused; BOOL paused;
BOOL restarted; BOOL restarted;
BOOL commandStop; BOOL commandStop;
BOOL resetting;
BOOL eqEnabled; BOOL eqEnabled;
BOOL eqInitialized; BOOL eqInitialized;

View file

@ -450,6 +450,8 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
} }
renderFormat = [[AVAudioFormat alloc] initWithStreamDescription:&deviceFormat channelLayout:[[AVAudioChannelLayout alloc] initWithLayoutTag:tag]]; renderFormat = [[AVAudioFormat alloc] initWithStreamDescription:&deviceFormat channelLayout:[[AVAudioChannelLayout alloc] initWithLayoutTag:tag]];
resetting = YES;
[_au stopHardware];
[_au.inputBusses[0] setFormat:renderFormat error:&err]; [_au.inputBusses[0] setFormat:renderFormat error:&err];
if(err != nil) if(err != nil)
return NO; return NO;
@ -459,6 +461,14 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
[outputLock lock]; [outputLock lock];
[outputBuffer reset]; [outputBuffer reset];
[outputLock unlock]; [outputLock unlock];
if(started) {
[_au startHardwareAndReturnError:&err];
if(err != nil)
return NO;
}
resetting = NO;
} }
return YES; return YES;
@ -569,16 +579,26 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
OutputCoreAudio *_self = (__bridge OutputCoreAudio *)refCon; OutputCoreAudio *_self = (__bridge OutputCoreAudio *)refCon;
int renderedSamples = 0; int renderedSamples = 0;
if(_self->resetting) {
inputData->mBuffers[0].mDataByteSize = frameCount * format->mBytesPerPacket;
bzero(inputData->mBuffers[0].mData, inputData->mBuffers[0].mDataByteSize);
inputData->mBuffers[0].mNumberChannels = channels;
return 0;
}
@autoreleasepool { @autoreleasepool {
while(renderedSamples < frameCount) { while(renderedSamples < frameCount) {
[refLock lock]; [refLock lock];
AudioChunk *chunk = [_self->outputBuffer removeSamples:frameCount - renderedSamples]; AudioChunk *chunk = nil;
if(![_self->outputBuffer isEmpty]) {
chunk = [_self->outputBuffer removeSamples:frameCount - renderedSamples];
}
[refLock unlock]; [refLock unlock];
_self->streamTimestamp = [chunk streamTimestamp]; if(chunk && [chunk frameCount]) {
_self->streamTimestamp = [chunk streamTimestamp];
size_t _frameCount = [chunk frameCount]; size_t _frameCount = [chunk frameCount];
if(_frameCount) {
NSData *sampleData = [chunk removeSamples:_frameCount]; NSData *sampleData = [chunk removeSamples:_frameCount];
float *samplePtr = (float *)[sampleData bytes]; float *samplePtr = (float *)[sampleData bytes];
size_t inputTodo = MIN(_frameCount, frameCount - renderedSamples); size_t inputTodo = MIN(_frameCount, frameCount - renderedSamples);
@ -586,7 +606,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
renderedSamples += inputTodo; renderedSamples += inputTodo;
} }
if(_self->stopping) { if(_self->stopping || _self->resetting) {
break; break;
} }
} }
@ -708,11 +728,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
} }
- (double)latency { - (double)latency {
NSTimeInterval latency = 0.0; return [outputBuffer listDuration];
if(_au) {
latency = [_au latency];
}
return [outputBuffer listDuration] + latency;
} }
- (void)start { - (void)start {