Check all audio chain elements for allocation failures, and also dispose of all of the previous handles in reverse order, including nulling the final node handle so the output does not attempt to poll for audio while the chain is being rebuilt. Also set up output node to handle the new null finalNode state, and return an empty chunk to the caller. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
250 lines
5.1 KiB
Objective-C
250 lines
5.1 KiB
Objective-C
//
|
|
// OutputNode.m
|
|
// Cog
|
|
//
|
|
// Created by Vincent Spader on 8/2/05.
|
|
// Copyright 2005 Vincent Spader. All rights reserved.
|
|
//
|
|
|
|
#import "OutputNode.h"
|
|
#import "AudioPlayer.h"
|
|
#import "BufferChain.h"
|
|
#import "OutputCoreAudio.h"
|
|
|
|
#import "Logging.h"
|
|
|
|
@implementation OutputNode
|
|
|
|
- (void)setup {
|
|
amountPlayed = 0.0;
|
|
amountPlayedInterval = 0.0;
|
|
|
|
paused = YES;
|
|
started = NO;
|
|
intervalReported = NO;
|
|
|
|
output = [[OutputCoreAudio alloc] initWithController:self];
|
|
|
|
[output setup];
|
|
}
|
|
|
|
- (void)seek:(double)time {
|
|
// [output pause];
|
|
[self resetBuffer];
|
|
|
|
amountPlayed = time;
|
|
}
|
|
|
|
- (void)process {
|
|
paused = NO;
|
|
[output start];
|
|
}
|
|
|
|
- (void)pause {
|
|
paused = YES;
|
|
[output pause];
|
|
}
|
|
|
|
- (void)resume {
|
|
paused = NO;
|
|
[output resume];
|
|
}
|
|
|
|
- (void)incrementAmountPlayed:(double)seconds {
|
|
amountPlayed += seconds;
|
|
amountPlayedInterval += seconds;
|
|
if(!intervalReported && amountPlayedInterval >= 60.0) {
|
|
intervalReported = YES;
|
|
[controller reportPlayCount];
|
|
}
|
|
}
|
|
|
|
- (void)setAmountPlayed:(double)seconds {
|
|
double delta = seconds - amountPlayed;
|
|
if(delta > 0.0 && delta < 5.0) {
|
|
[self incrementAmountPlayed:delta];
|
|
} else if(delta) {
|
|
amountPlayed = seconds;
|
|
}
|
|
}
|
|
|
|
- (void)resetAmountPlayed {
|
|
amountPlayed = 0;
|
|
}
|
|
|
|
- (void)resetAmountPlayedInterval {
|
|
amountPlayedInterval = 0;
|
|
intervalReported = NO;
|
|
}
|
|
|
|
- (BOOL)selectNextBuffer {
|
|
return [controller selectNextBuffer];
|
|
}
|
|
|
|
- (void)endOfInputPlayed {
|
|
if(!intervalReported) {
|
|
intervalReported = YES;
|
|
[controller reportPlayCount];
|
|
}
|
|
[controller endOfInputPlayed];
|
|
[self resetAmountPlayedInterval];
|
|
}
|
|
|
|
- (BOOL)chainQueueHasTracks {
|
|
return [controller chainQueueHasTracks];
|
|
}
|
|
|
|
- (double)secondsBuffered {
|
|
return [buffer listDuration];
|
|
}
|
|
|
|
- (AudioChunk *)readChunk:(size_t)amount {
|
|
@autoreleasepool {
|
|
Node *finalNode = [[controller bufferChain] finalNode];
|
|
[self setPreviousNode:finalNode];
|
|
|
|
if(finalNode) {
|
|
AudioChunk *ret = [super readChunk:amount];
|
|
|
|
/* if (n == 0) {
|
|
DLog(@"Output Buffer dry!");
|
|
}
|
|
*/
|
|
return ret;
|
|
} else {
|
|
return [[AudioChunk alloc] init];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config {
|
|
@autoreleasepool {
|
|
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
|
|
|
return [super peekFormat:format channelConfig:config];
|
|
}
|
|
}
|
|
|
|
- (double)amountPlayed {
|
|
return amountPlayed;
|
|
}
|
|
|
|
- (double)amountPlayedInterval {
|
|
return amountPlayedInterval;
|
|
}
|
|
|
|
- (AudioStreamBasicDescription)format {
|
|
return format;
|
|
}
|
|
|
|
- (uint32_t)config {
|
|
return config;
|
|
}
|
|
|
|
- (AudioStreamBasicDescription)deviceFormat {
|
|
return [output deviceFormat];
|
|
}
|
|
|
|
- (uint32_t)deviceChannelConfig {
|
|
return [output deviceChannelConfig];
|
|
}
|
|
|
|
- (void)setFormat:(AudioStreamBasicDescription *)f channelConfig:(uint32_t)channelConfig {
|
|
if(!shouldContinue) return;
|
|
|
|
format = *f;
|
|
config = channelConfig;
|
|
// Calculate a ratio and add to double(seconds) instead, as format may change
|
|
// double oldSampleRatio = sampleRatio;
|
|
BufferChain *bufferChain = [controller bufferChain];
|
|
if(bufferChain) {
|
|
ConverterNode *converter = [bufferChain converter];
|
|
DSPDownmixNode *downmix = [bufferChain downmix];
|
|
AudioStreamBasicDescription outputFormat;
|
|
uint32_t outputChannelConfig;
|
|
BOOL formatChanged = NO;
|
|
if(converter) {
|
|
AudioStreamBasicDescription converterFormat = [converter nodeFormat];
|
|
if(memcmp(&converterFormat, &format, sizeof(converterFormat)) != 0) {
|
|
formatChanged = YES;
|
|
}
|
|
}
|
|
if(downmix && output && !formatChanged) {
|
|
outputFormat = [output deviceFormat];
|
|
outputChannelConfig = [output deviceChannelConfig];
|
|
AudioStreamBasicDescription currentOutputFormat = [downmix nodeFormat];
|
|
uint32_t currentOutputChannelConfig = [downmix nodeChannelConfig];
|
|
if(memcmp(¤tOutputFormat, &outputFormat, sizeof(currentOutputFormat)) != 0 ||
|
|
currentOutputChannelConfig != outputChannelConfig) {
|
|
formatChanged = YES;
|
|
}
|
|
}
|
|
if(formatChanged) {
|
|
InputNode *inputNode = [bufferChain inputNode];
|
|
if(inputNode) {
|
|
[inputNode setLastVolume:[output volume]];
|
|
[output setVolume:0.0];
|
|
}
|
|
if(converter) {
|
|
[converter setOutputFormat:format];
|
|
}
|
|
if(downmix && output) {
|
|
[downmix setOutputFormat:[output deviceFormat] withChannelConfig:[output deviceChannelConfig]];
|
|
}
|
|
if(inputNode) {
|
|
AudioStreamBasicDescription inputFormat = [inputNode nodeFormat];
|
|
[inputNode seek:(long)(amountPlayed * inputFormat.mSampleRate)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)close {
|
|
[output stop];
|
|
output = nil;
|
|
}
|
|
|
|
- (double)volume {
|
|
return [output volume];
|
|
}
|
|
|
|
- (void)setVolume:(double)v {
|
|
[output setVolume:v];
|
|
}
|
|
|
|
- (void)setShouldContinue:(BOOL)s {
|
|
[super setShouldContinue:s];
|
|
|
|
// if (s == NO)
|
|
// [output stop];
|
|
}
|
|
|
|
- (void)setShouldPlayOutBuffer:(BOOL)s {
|
|
[output setShouldPlayOutBuffer:s];
|
|
}
|
|
|
|
- (BOOL)isPaused {
|
|
return paused;
|
|
}
|
|
|
|
- (void)sustainHDCD {
|
|
[output sustainHDCD];
|
|
}
|
|
|
|
- (void)restartPlaybackAtCurrentPosition {
|
|
[controller restartPlaybackAtCurrentPosition];
|
|
}
|
|
|
|
- (double)latency {
|
|
return [output latency];
|
|
}
|
|
|
|
- (double)getTotalLatency {
|
|
return [[controller bufferChain] secondsBuffered] + [output latency];
|
|
}
|
|
|
|
- (double)getPostVisLatency {
|
|
return [[controller bufferChain] getPostVisLatency] + [output latency];
|
|
}
|
|
|
|
@end
|