diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 01a8c15ac..7e3e2056d 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -569,6 +569,19 @@ - (void)reportScrobbleForTrack:(id)userInfo { [self sendDelegateMethod:@selector(audioPlayer:reportScrobbleForTrack:) withObject:userInfo waitUntilDone:NO]; } +- (void)schedulePlaybackStopAfterOutputLatency { + double latency = 0; + if(output) latency = [output latency]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, latency * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self stop]; + + self->bufferChain = nil; + + [self notifyPlaybackStopped:nil]; + }); +} + - (BOOL)selectNextBuffer { BOOL signalStopped = NO; BufferChain *selectedChain = nil; @@ -595,23 +608,17 @@ - (BOOL)selectNextBuffer { } while(0); if(signalStopped) { - double latency = 0; - if(output) latency = [output latency]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, latency * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self stop]; - - self->bufferChain = nil; - - [self notifyPlaybackStopped:nil]; - }); - + [self schedulePlaybackStopAfterOutputLatency]; return YES; } AudioStreamBasicDescription inputFormat = [selectedChain inputFormat]; if(![output prepareForInputFormat:inputFormat]) { ALog(@"Unable to prepare the output device for the next track format"); + [selectedChain setError:YES]; + [selectedChain setShouldContinue:NO]; + [self schedulePlaybackStopAfterOutputLatency]; + return YES; } [output setEndOfStream:NO]; diff --git a/Audio/Chain/DSP/DSPFaderNode.m b/Audio/Chain/DSP/DSPFaderNode.m index b3b6f59b1..2ac138830 100644 --- a/Audio/Chain/DSP/DSPFaderNode.m +++ b/Audio/Chain/DSP/DSPFaderNode.m @@ -194,6 +194,11 @@ - (AudioChunk *)convert { NSData *sampleData = [chunk removeSamples:frameCount]; memcpy(inBuffer, [sampleData bytes], frameCount * outputFormat.mBytesPerPacket); inputIsDoP = audioBufferIsDoP(inBuffer, outputFormat.mChannelsPerFrame, frameCount, NULL); + if(!inputIsDoP) { + // DoP mode follows the current carrier instead of remaining latched + // after playback has moved back to PCM. + doPMode = NO; + } } else { // [chunk removeSamples:frameCount]; // Only happens above, and since the samples aren't assigned, they don't need to be removed @@ -256,7 +261,9 @@ - (void)waitForReset { } - (void)setDoPMode:(BOOL)enabled { + [mutex lock]; doPMode = enabled; + [mutex unlock]; } - (float)fadeLevel { diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index f1a27dcf0..ba37bef56 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -513,6 +513,35 @@ static void convertFloatBufferToS32(int32_t *output, const float *input, size_t } } +- (BOOL)prepareOutputFloatScratchForRenderFormat:(AudioStreamBasicDescription)format { + if(format.mFormatFlags & kAudioFormatFlagIsFloat) { + return YES; + } + + const size_t maximumFrames = (size_t)_au.maximumFramesToRender; + const size_t channels = (size_t)format.mChannelsPerFrame; + if(!maximumFrames || !channels || maximumFrames > SIZE_MAX / channels) { + return NO; + } + + const size_t requiredSamples = maximumFrames * channels; + if(requiredSamples > SIZE_MAX / sizeof(float)) { + return NO; + } + if(outputFloatScratch && outputFloatScratchCapacity >= requiredSamples) { + return YES; + } + + float *scratch = (float *)realloc(outputFloatScratch, requiredSamples * sizeof(float)); + if(!scratch) { + return NO; + } + + outputFloatScratch = scratch; + outputFloatScratchCapacity = requiredSamples; + return YES; +} + - (BOOL)deviceSupportsSampleRate:(double)sampleRate { AudioObjectPropertyAddress theAddress = { .mSelector = kAudioDevicePropertyAvailableNominalSampleRates, @@ -672,8 +701,14 @@ - (BOOL)updateDeviceFormatNotifyingController:(BOOL)notifyController { [_au.inputBusses[0] setFormat:renderAVFormat error:&err]; } } - if(!renderAVFormat || err != nil) + if(!renderAVFormat || err != nil) { + resetting = NO; return NO; + } + if(![self prepareOutputFloatScratchForRenderFormat:renderFormat]) { + resetting = NO; + return NO; + } renderFormatDoPInteger = targetDoPInteger && preferDoPIntegerOutput; if(notifyController) { @@ -714,6 +749,14 @@ - (AudioStreamBasicDescription)outputFormatForInputFormat:(AudioStreamBasicDescr - (BOOL)prepareForInputFormat:(AudioStreamBasicDescription)inputFormat { if(!inputFormatUsesDoPCarrierRate(inputFormat)) { + // A pending DoP seek is only meaningful while another DoP carrier is + // expected. If playback moves to PCM before that carrier arrives, do not + // keep replacing PCM buffers with DoP silence indefinitely. + doPSeekPending = NO; + doPActive = NO; + doPMarker = 0x05; + [faderNode setDoPMode:NO]; + if(preferDoPIntegerOutput || renderFormatDoPInteger) { preferDoPIntegerOutput = NO; preferredDoPCarrierSampleRate = 0.0; @@ -730,7 +773,11 @@ - (BOOL)prepareForInputFormat:(AudioStreamBasicDescription)inputFormat { preferDoPIntegerOutput = YES; preferredDoPCarrierSampleRate = sampleRate; outputdevicechanged = YES; - return [self updateDeviceFormatNotifyingController:NO]; + BOOL prepared = [self updateDeviceFormatNotifyingController:NO]; + if(prepared) { + [faderNode setDoPMode:YES]; + } + return prepared; } - (void)updateStreamFormat { @@ -853,13 +900,8 @@ - (void)audioOutputBlock { outSamples = (float *)inputData->mBuffers[0].mData; } else { const size_t scratchSamples = (size_t)frameCount * channels; - if(_self->outputFloatScratchCapacity < scratchSamples) { - float *scratch = (float *)realloc(_self->outputFloatScratch, scratchSamples * sizeof(float)); - if(!scratch) { - return 0; - } - _self->outputFloatScratch = scratch; - _self->outputFloatScratchCapacity = scratchSamples; + if(!_self->outputFloatScratch || _self->outputFloatScratchCapacity < scratchSamples) { + return 0; } outSamples = _self->outputFloatScratch; bzero(outSamples, scratchSamples * sizeof(float));