Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions Audio/AudioPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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];
Expand Down
7 changes: 7 additions & 0 deletions Audio/Chain/DSP/DSPFaderNode.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -256,7 +261,9 @@ - (void)waitForReset {
}

- (void)setDoPMode:(BOOL)enabled {
[mutex lock];
doPMode = enabled;
[mutex unlock];
}

- (float)fadeLevel {
Expand Down
60 changes: 51 additions & 9 deletions Audio/Output/OutputCoreAudio.m
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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));
Expand Down