Do not reset output sample rate automatically

This was buggy as hell, and resulted in errors. Now the user should
restart playback if they change output device formats.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-07 22:02:17 -08:00
parent 4fd24838fa
commit 728c44242c
12 changed files with 34 additions and 176 deletions

View File

@ -16,9 +16,6 @@
InputNode *inputNode;
ConverterNode *converterNode;
AudioStreamBasicDescription inputFormat;
uint32_t inputChannelConfig;
NSURL *streamURL;
id userInfo;
NSDictionary *rgInfo;

View File

@ -60,7 +60,8 @@
NSDictionary *properties = [inputNode properties];
inputFormat = [inputNode nodeFormat];
AudioStreamBasicDescription inputFormat = [inputNode nodeFormat];
uint32_t inputChannelConfig = 0;
if([properties valueForKey:@"channelConfig"])
inputChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
@ -89,7 +90,8 @@
NSDictionary *properties = [inputNode properties];
inputFormat = [inputNode nodeFormat];
AudioStreamBasicDescription inputFormat = [inputNode nodeFormat];
uint32_t inputChannelConfig = 0;
if([properties valueForKey:@"channelConfig"])
inputChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
@ -123,7 +125,8 @@
DLog(@"Input Properties: %@", properties);
inputFormat = [inputNode nodeFormat];
AudioStreamBasicDescription inputFormat = [inputNode nodeFormat];
uint32_t inputChannelConfig = 0;
if([properties valueForKey:@"channelConfig"])
inputChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
@ -231,11 +234,11 @@
}
- (AudioStreamBasicDescription)inputFormat {
return inputFormat;
return [inputNode nodeFormat];
}
- (uint32_t)inputConfig {
return inputChannelConfig;
return [inputNode nodeChannelConfig];
}
- (double)secondsBuffered {

View File

@ -50,7 +50,7 @@
}
- (BOOL)isFull {
return (maxDuration - listDuration) < 0.01;
return (maxDuration - listDuration) < 0.05;
}
- (void)addChunk:(AudioChunk *)chunk {

View File

@ -15,7 +15,6 @@
#import <soxr.h>
#import "Node.h"
#import "RefillNode.h"
#import "HeadphoneFilter.h"
@ -31,7 +30,6 @@
BOOL stopping;
BOOL convertEntered;
BOOL paused;
BOOL outputFormatChanged;
BOOL skipResampler;
@ -77,13 +75,6 @@
AudioChunk *lastChunkIn;
AudioStreamBasicDescription previousOutputFormat;
uint32_t previousOutputConfig;
AudioStreamBasicDescription rememberedInputFormat;
uint32_t rememberedInputConfig;
RefillNode *refillNode;
id __weak originalPreviousNode;
void *hdcd_decoder;
HeadphoneFilter *hFilter;

View File

@ -53,13 +53,9 @@ void PrintStreamDesc(AudioStreamBasicDescription *inDesc) {
stopping = NO;
convertEntered = NO;
paused = NO;
outputFormatChanged = NO;
skipResampler = YES;
refillNode = nil;
originalPreviousNode = nil;
extrapolateBuffer = NULL;
extrapolateBufferSize = 0;
@ -68,6 +64,8 @@ void PrintStreamDesc(AudioStreamBasicDescription *inDesc) {
hdcd_decoder = NULL;
lastChunkIn = nil;
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil];
}
@ -423,22 +421,14 @@ static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes
// and returns 0 samples when it has nothing more to process at the end of stream.
while([self shouldContinue] == YES) {
int amountConverted;
while(paused) {
usleep(500);
}
@autoreleasepool {
amountConverted = [self convert:writeBuf amount:CHUNK_SIZE];
}
if(!amountConverted) {
if(paused) {
while(paused)
usleep(500);
continue;
} else if(refillNode) {
// refill node just ended, file resumes
[self setPreviousNode:originalPreviousNode];
[self setEndOfStream:NO];
[self setShouldContinue:YES];
refillNode = nil;
[self cleanUp];
[self setupWithInputFormat:rememberedInputFormat withInputConfig:rememberedInputConfig outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless];
continue;
} else if(streamFormatChanged) {
[self cleanUp];
@ -503,7 +493,7 @@ tryagain:
ssize_t bytesReadFromInput = 0;
while(bytesReadFromInput < amountToWrite && !stopping && !streamFormatChanged && [self shouldContinue] == YES && [self endOfStream] == NO) {
while(bytesReadFromInput < amountToWrite && !stopping && !paused && !streamFormatChanged && [self shouldContinue] == YES && [self endOfStream] == NO) {
AudioStreamBasicDescription inf;
uint32_t config;
if([self peekFormat:&inf channelConfig:&config]) {
@ -536,16 +526,13 @@ tryagain:
}
bytesReadFromInput += bytesRead;
if(!frameCount) {
if(refillNode)
[self setEndOfStream:YES];
else
usleep(500);
usleep(500);
}
}
// Pad end of track with input format silence
if(stopping || streamFormatChanged || [self shouldContinue] == NO || [self endOfStream] == YES) {
if(stopping || paused || streamFormatChanged || [self shouldContinue] == NO || [self endOfStream] == YES) {
if(!skipResampler && !is_postextrapolated_) {
if(dsd2pcm) {
amountToSkip = dsd2pcmLatency * inputFormat.mBytesPerPacket;
@ -989,9 +976,8 @@ static float db_to_scale(float db) {
// Move this here so process call isn't running the resampler until it's allocated
stopping = NO;
convertEntered = NO;
paused = NO;
outputFormatChanged = NO;
streamFormatChanged = NO;
paused = NO;
return YES;
}
@ -1007,46 +993,18 @@ static float db_to_scale(float db) {
- (void)setOutputFormat:(AudioStreamBasicDescription)format outputConfig:(uint32_t)outputConfig {
DLog(@"SETTING OUTPUT FORMAT!");
previousOutputFormat = outputFormat;
previousOutputConfig = outputChannelConfig;
outputFormat = format;
outputChannelConfig = outputConfig;
outputFormatChanged = YES;
}
- (void)inputFormatDidChange:(AudioStreamBasicDescription)format inputConfig:(uint32_t)inputConfig {
DLog(@"FORMAT CHANGED");
paused = YES;
[self cleanUp];
if(outputFormatChanged && ![buffer isEmpty] &&
(outputChannelConfig != previousOutputConfig ||
memcmp(&outputFormat, &previousOutputFormat, sizeof(outputFormat)) != 0)) {
// Transfer previously buffered data, remember input format
rememberedInputFormat = format;
rememberedInputConfig = inputChannelConfig;
originalPreviousNode = previousNode;
refillNode = [[RefillNode alloc] initWithController:controller previous:nil];
[self setPreviousNode:refillNode];
[refillNode setFormat:previousOutputFormat];
[refillNode setChannelConfig:previousOutputConfig];
for(;;) {
@autoreleasepool {
AudioChunk *chunk = [buffer removeSamples:16384];
size_t frameCount = [chunk frameCount];
if(frameCount) {
NSData *samples = [chunk removeSamples:frameCount];
[refillNode writeData:[samples bytes] amount:frameCount];
} else
break;
}
}
[self setupWithInputFormat:previousOutputFormat withInputConfig:[AudioChunk guessChannelConfig:previousOutputFormat.mChannelsPerFrame] outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless];
} else {
[self setupWithInputFormat:format withInputConfig:inputConfig outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless];
while(convertEntered) {
usleep(500);
}
[self cleanUp];
[self setupWithInputFormat:format withInputConfig:inputConfig outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless];
}
- (void)setRGInfo:(NSDictionary *)rgi {

View File

@ -147,13 +147,12 @@ static void downmix_to_mono(const float *inBuffer, int channels, uint32_t config
inBuffer = tempBuffer;
channels = 2;
config = AudioConfigStereo;
float invchannels = 1.0 / (float)channels;
for(size_t i = 0; i < count; ++i) {
float sample = 0;
for(int j = 0; j < channels; ++j) {
sample += inBuffer[i * channels + j];
}
outBuffer[i] = sample * invchannels;
outBuffer[i] = sample;
}
}

View File

@ -20,7 +20,6 @@
uint32_t config;
double amountPlayed;
double sampleRatio;
OutputCoreAudio *output;
BOOL paused;

View File

@ -17,7 +17,6 @@
- (void)setup {
amountPlayed = 0.0;
sampleRatio = 0.0;
paused = YES;
started = NO;
@ -100,7 +99,6 @@
config = channelConfig;
// Calculate a ratio and add to double(seconds) instead, as format may change
// double oldSampleRatio = sampleRatio;
sampleRatio = 1.0 / (format.mSampleRate * format.mBytesPerPacket);
BufferChain *bufferChain = [controller bufferChain];
if(bufferChain) {
ConverterNode *converter = [bufferChain converter];
@ -108,13 +106,16 @@
// This clears the resampler buffer, but not the input buffer
// We also have to jump the play position ahead accounting for
// the data we are flushing
#if 0
// We no longer need to do this, because outputchanged converter
// now uses the RefillNode to slap the previous samples into
// itself
if (oldSampleRatio)
amountPlayed += oldSampleRatio * [[converter buffer] bufferedLength];
#endif
amountPlayed += [[converter buffer] listDuration];
AudioStreamBasicDescription inf = [bufferChain inputFormat];
uint32_t config = [bufferChain inputConfig];
format.mChannelsPerFrame = inf.mChannelsPerFrame;
format.mBytesPerFrame = ((inf.mBitsPerChannel + 7) / 8) * format.mChannelsPerFrame;
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
channelConfig = config;
[converter setOutputFormat:format
outputConfig:channelConfig];
[converter inputFormatDidChange:[bufferChain inputFormat] inputConfig:[bufferChain inputConfig]];

View File

@ -1,27 +0,0 @@
//
// RefillNode.h
// Cog
//
// Created by Christopher SNowhill on 1/13/22.
// Copyright 2022 __LoSnoCo__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AudioUnit/AudioUnit.h>
#import <CoreAudio/AudioHardware.h>
#import "Node.h"
#import "Plugin.h"
#define INPUT_NODE_SEEK
@interface RefillNode : Node {
// This node just slaps pre-converted data into its buffer for re-buffering
}
- (void)setFormat:(AudioStreamBasicDescription)format;
- (void)setChannelConfig:(uint32_t)config;
@end

View File

@ -1,49 +0,0 @@
//
// RefillNode.m
// Cog
//
// Created by Christopher Snowhill on 1/13/22.
// Copyright 2022 __LoSnoCo__. All rights reserved.
//
#import "RefillNode.h"
#import "Plugin.h"
#import "Logging.h"
@implementation RefillNode
- (id)initWithController:(id)c previous:(id)p {
self = [super init];
if(self) {
// This special node should be able to handle up to four buffers
buffer = [[ChunkList alloc] initWithMaximumDuration:12.0];
semaphore = [[Semaphore alloc] init];
initialBufferFilled = NO;
controller = c;
endOfStream = NO;
shouldContinue = YES;
nodeLossless = NO;
[self setPreviousNode:p];
}
return self;
}
- (void)dealloc {
DLog(@"Refill Node dealloc");
}
- (void)setFormat:(AudioStreamBasicDescription)format {
nodeFormat = format;
}
- (void)setChannelConfig:(uint32_t)config {
nodeChannelConfig = config;
}
@end

View File

@ -66,8 +66,6 @@
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; };
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; };
83A44A01279119B50049B6E2 /* RefillNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A449FF279119B50049B6E2 /* RefillNode.m */; };
83A44A02279119B50049B6E2 /* RefillNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A44A00279119B50049B6E2 /* RefillNode.h */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */; settings = {ATTRIBUTES = (Public, ); }; };
8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E8D3D2E0CBAEE6E00135C1B /* AudioContainer.m */; };
@ -162,8 +160,6 @@
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; };
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
83A449FF279119B50049B6E2 /* RefillNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RefillNode.m; sourceTree = "<group>"; };
83A44A00279119B50049B6E2 /* RefillNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefillNode.h; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* CogAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CogAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioContainer.h; sourceTree = "<group>"; };
@ -298,8 +294,6 @@
834FD4EF27AF93680063BC83 /* ChunkList.m */,
834FD4F227AFA2150063BC83 /* Downmix.h */,
834FD4F327AFA2150063BC83 /* Downmix.m */,
83A44A00279119B50049B6E2 /* RefillNode.h */,
83A449FF279119B50049B6E2 /* RefillNode.m */,
17D21C760B8BE4BA00D1EBDE /* BufferChain.h */,
17D21C770B8BE4BA00D1EBDE /* BufferChain.m */,
8EC1225D0B993BD500C5B3AD /* ConverterNode.h */,
@ -457,7 +451,6 @@
839366671815923C006DD712 /* CogPluginMulti.h in Headers */,
17ADB13C0B97926D00257CA2 /* AudioSource.h in Headers */,
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */,
83A44A02279119B50049B6E2 /* RefillNode.h in Headers */,
8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */,
8384912718080FF100E7332D /* Logging.h in Headers */,
8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */,
@ -560,7 +553,6 @@
17C940240B900909008627D6 /* AudioMetadataReader.m in Sources */,
17B619310B909BC300BC003F /* AudioPropertiesReader.m in Sources */,
17ADB13D0B97926D00257CA2 /* AudioSource.m in Sources */,
83A44A01279119B50049B6E2 /* RefillNode.m in Sources */,
834FD4F127AF93680063BC83 /* ChunkList.m in Sources */,
8EC122600B993BD500C5B3AD /* ConverterNode.m in Sources */,
8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */,

View File

@ -211,16 +211,10 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
running = YES;
started = NO;
stopNext = NO;
size_t eventCount = 0;
atomic_store(&bytesRendered, 0);
NSMutableArray *delayedEvents = [[NSMutableArray alloc] init];
BOOL delayedEventsPopped = YES;
while(!stopping) {
if(++eventCount == 128) {
[self updateDeviceFormat];
eventCount = 0;
}
if([outputController shouldReset]) {
@autoreleasepool {
[[outputController buffer] reset];
@ -671,7 +665,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
#endif
inputData->mBuffers[0].mNumberChannels = channels;
return 0;
};