Cog/Audio/Chain/OutputNode.m
Christopher Snowhill 45cb841ec0 Bug Fix: Greatly improve audio buffer handling
Buffers were being treated as empty before they were actually processed,
due to races between the current node's end of stream marker and
actually feeding the output buffer.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-03-04 00:43:30 -08:00

257 lines
5.3 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 {
[self setupWithInterval:NO];
}
- (void)setupWithInterval:(BOOL)resumeInterval {
if(!resumeInterval) {
amountPlayed = 0.0;
amountPlayedInterval = 0.0;
intervalReported = NO;
}
paused = YES;
started = 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((!ret || ![ret frameCount]) && [previousNode endOfStream]) {
endOfStream = YES;
}
return ret;
} else {
return [[AudioChunk alloc] init];
}
}
}
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config {
@autoreleasepool {
[self setPreviousNode:[[controller bufferChain] finalNode]];
BOOL ret = [super peekFormat:format channelConfig:config];
if(!ret && [previousNode endOfStream]) {
endOfStream = YES;
}
return ret;
}
}
- (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;
AudioPlayer *audioPlayer = controller;
BufferChain *bufferChain = [audioPlayer 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(&currentOutputFormat, &outputFormat, sizeof(currentOutputFormat)) != 0 ||
currentOutputChannelConfig != outputChannelConfig) {
formatChanged = YES;
}
}
if(formatChanged) {
InputNode *inputNode = [bufferChain inputNode];
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