From 637ea4efe1d5533840081fba8d7dde602b0c5b17 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sat, 5 Feb 2022 03:45:02 -0800 Subject: [PATCH] Core Audio output: Rewrote major portions After all this rewriting, down or upmixing the audio is now handled with the lowest latency possible, meaning that toggling the HRIR option now takes effect immediately. Signed-off-by: Christopher Snowhill --- Audio/AudioPlayer.m | 6 +- Audio/Chain/BufferChain.h | 6 +- Audio/Chain/BufferChain.m | 32 +- Audio/Chain/ConverterNode.h | 2 - Audio/Chain/ConverterNode.m | 246 +------------- Audio/Chain/Downmix.h | 26 ++ Audio/Chain/Downmix.m | 302 ++++++++++++++++++ Audio/Chain/InputNode.m | 4 + Audio/Chain/Node.h | 6 + Audio/Chain/Node.m | 5 +- Audio/Chain/OutputNode.h | 5 + Audio/Chain/OutputNode.m | 30 +- Audio/CogAudio.xcodeproj/project.pbxproj | 8 + Audio/Output/OutputCoreAudio.h | 12 + Audio/Output/OutputCoreAudio.m | 276 ++++++++++++---- .../VirtualRingBuffer/VirtualRingBuffer.h | 2 +- .../VirtualRingBuffer/VirtualRingBuffer.m | 6 +- 17 files changed, 656 insertions(+), 318 deletions(-) create mode 100644 Audio/Chain/Downmix.h create mode 100644 Audio/Chain/Downmix.m diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 3c6497867..1a0f4694e 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -93,7 +93,7 @@ bufferChain = [[BufferChain alloc] initWithController:self]; [self notifyStreamChanged:userInfo]; - while (![bufferChain open:url withOutputFormat:[output format] withRGInfo:rgi]) + while (![bufferChain open:url withOutputFormatHint:[output format] withRGInfo:rgi]) { bufferChain = nil; @@ -401,7 +401,7 @@ && [[nextStream path] isEqualToString:[[lastChain streamURL] path]])) { if ([lastChain setTrack:nextStream] - && [newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format] withRGInfo:nextStreamRGInfo]) + && [newChain openWithInput:[lastChain inputNode] withOutputFormatHint:[output format] withRGInfo:nextStreamRGInfo]) { [newChain setStreamURL:nextStream]; [newChain setUserInfo:nextStreamUserInfo]; @@ -418,7 +418,7 @@ lastChain = nil; - while (shouldContinue && ![newChain open:nextStream withOutputFormat:[output format] withRGInfo:nextStreamRGInfo]) + while (shouldContinue && ![newChain open:nextStream withOutputFormatHint:[output format] withRGInfo:nextStreamRGInfo]) { if (nextStream == nil) { diff --git a/Audio/Chain/BufferChain.h b/Audio/Chain/BufferChain.h index 4f11b19e7..69cdc625f 100644 --- a/Audio/Chain/BufferChain.h +++ b/Audio/Chain/BufferChain.h @@ -30,14 +30,14 @@ - (id)initWithController:(id)c; - (void)buildChain; -- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi; +- (BOOL)open:(NSURL *)url withOutputFormatHint:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi; //Used when changing tracks to reuse the same decoder -- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi; +- (BOOL)openWithInput:(InputNode *)i withOutputFormatHint:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi; //Used when resetting the decoder on seek - (BOOL)openWithDecoder:(id)decoder - withOutputFormat:(AudioStreamBasicDescription)outputFormat + withOutputFormatHint:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi; - (void)seek:(double)time; diff --git a/Audio/Chain/BufferChain.m b/Audio/Chain/BufferChain.m index 29a428f72..6b17d525d 100644 --- a/Audio/Chain/BufferChain.m +++ b/Audio/Chain/BufferChain.m @@ -43,7 +43,7 @@ finalNode = converterNode; } -- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi +- (BOOL)open:(NSURL *)url withOutputFormatHint:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi { [self setStreamURL:url]; @@ -64,8 +64,14 @@ return NO; NSDictionary * properties = [inputNode properties]; + + inputFormat = [inputNode nodeFormat]; + + outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame; + outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame; + outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket; - if (![converterNode setupWithInputFormat:(inputFormat = propertiesToASBD(properties)) outputFormat:outputFormat isLossless:[[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]]) + if (![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]]) return NO; [self setRGInfo:rgi]; @@ -75,7 +81,7 @@ return YES; } -- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi +- (BOOL)openWithInput:(InputNode *)i withOutputFormatHint:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi { DLog(@"New buffer chain!"); [self buildChain]; @@ -86,7 +92,14 @@ NSDictionary * properties = [inputNode properties]; DLog(@"Input Properties: %@", properties); - if (![converterNode setupWithInputFormat:(inputFormat = propertiesToASBD(properties)) outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]]) + + inputFormat = [inputNode nodeFormat]; + + outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame; + outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame; + outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket; + + if (![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]]) return NO; [self setRGInfo:rgi]; @@ -95,7 +108,7 @@ } - (BOOL)openWithDecoder:(id)decoder - withOutputFormat:(AudioStreamBasicDescription)outputFormat + withOutputFormatHint:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi; { DLog(@"New buffer chain!"); @@ -107,7 +120,14 @@ NSDictionary * properties = [inputNode properties]; DLog(@"Input Properties: %@", properties); - if (![converterNode setupWithInputFormat:(inputFormat = propertiesToASBD(properties)) outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]]) + + inputFormat = [inputNode nodeFormat]; + + outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame; + outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame; + outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket; + + if (![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]]) return NO; [self setRGInfo:rgi]; diff --git a/Audio/Chain/ConverterNode.h b/Audio/Chain/ConverterNode.h index 514524436..e00129294 100644 --- a/Audio/Chain/ConverterNode.h +++ b/Audio/Chain/ConverterNode.h @@ -74,8 +74,6 @@ id __weak originalPreviousNode; void *hdcd_decoder; - - HeadphoneFilter *hFilter; } @property AudioStreamBasicDescription inputFormat; diff --git a/Audio/Chain/ConverterNode.m b/Audio/Chain/ConverterNode.m index 424f2cf36..0d1020cd8 100644 --- a/Audio/Chain/ConverterNode.m +++ b/Audio/Chain/ConverterNode.m @@ -72,189 +72,11 @@ void PrintStreamDesc (AudioStreamBasicDescription *inDesc) hdcd_decoder = NULL; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil]; } return self; } -static const float STEREO_DOWNMIX[8-2][8][2]={ - /*3.0*/ - { - {0.5858F,0.0F},{0.0F,0.5858F},{0.4142F,0.4142F} - }, - /*quadrophonic*/ - { - {0.4226F,0.0F},{0.0F,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F} - }, - /*5.0*/ - { - {0.651F,0.0F},{0.0F,0.651F},{0.46F,0.46F},{0.5636F,0.3254F}, - {0.3254F,0.5636F} - }, - /*5.1*/ - { - {0.529F,0.0F},{0.0F,0.529F},{0.3741F,0.3741F},{0.3741F,0.3741F},{0.4582F,0.2645F}, - {0.2645F,0.4582F} - }, - /*6.1*/ - { - {0.4553F,0.0F},{0.0F,0.4553F},{0.322F,0.322F},{0.322F,0.322F},{0.2788F,0.2788F}, - {0.3943F,0.2277F},{0.2277F,0.3943F} - }, - /*7.1*/ - { - {0.3886F,0.0F},{0.0F,0.3886F},{0.2748F,0.2748F},{0.2748F,0.2748F},{0.3366F,0.1943F}, - {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F} - } -}; - -static void downmix_to_stereo(float * buffer, int channels, size_t count) -{ - if (channels >= 3 && channels <= 8) - for (size_t i = 0; i < count; ++i) - { - float left = 0, right = 0; - for (int j = 0; j < channels; ++j) - { - left += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0]; - right += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1]; - } - buffer[i * 2 + 0] = left; - buffer[i * 2 + 1] = right; - } -} - -static void downmix_to_mono(float * buffer, int channels, size_t count) -{ - if (channels >= 3 && channels <= 8) - { - downmix_to_stereo(buffer, channels, count); - channels = 2; - } - 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 += buffer[i * channels + j]; - } - buffer[i] = sample * invchannels; - } -} - -static void upmix(float * buffer, int inchannels, int outchannels, size_t count) -{ - for (ssize_t i = count - 1; i >= 0; --i) - { - if (inchannels == 1 && outchannels == 2) - { - // upmix mono to stereo - float sample = buffer[i]; - buffer[i * 2 + 0] = sample; - buffer[i * 2 + 1] = sample; - } - else if (inchannels == 1 && outchannels == 4) - { - // upmix mono to quad - float sample = buffer[i]; - buffer[i * 4 + 0] = sample; - buffer[i * 4 + 1] = sample; - buffer[i * 4 + 2] = 0; - buffer[i * 4 + 3] = 0; - } - else if (inchannels == 1 && (outchannels == 3 || outchannels >= 5)) - { - // upmix mono to center channel - float sample = buffer[i]; - buffer[i * outchannels + 2] = sample; - for (int j = 0; j < 2; ++j) - { - buffer[i * outchannels + j] = 0; - } - for (int j = 3; j < outchannels; ++j) - { - buffer[i * outchannels + j] = 0; - } - } - else if (inchannels == 4 && outchannels >= 5) - { - float fl = buffer[i * 4 + 0]; - float fr = buffer[i * 4 + 1]; - float bl = buffer[i * 4 + 2]; - float br = buffer[i * 4 + 3]; - const int skipclfe = (outchannels == 5) ? 1 : 2; - buffer[i * outchannels + 0] = fl; - buffer[i * outchannels + 1] = fr; - buffer[i * outchannels + skipclfe + 2] = bl; - buffer[i * outchannels + skipclfe + 3] = br; - for (int j = 0; j < skipclfe; ++j) - { - buffer[i * outchannels + 2 + j] = 0; - } - for (int j = 4 + skipclfe; j < outchannels; ++j) - { - buffer[i * outchannels + j] = 0; - } - } - else if (inchannels == 5 && outchannels >= 6) - { - float fl = buffer[i * 5 + 0]; - float fr = buffer[i * 5 + 1]; - float c = buffer[i * 5 + 2]; - float bl = buffer[i * 5 + 3]; - float br = buffer[i * 5 + 4]; - buffer[i * outchannels + 0] = fl; - buffer[i * outchannels + 1] = fr; - buffer[i * outchannels + 2] = c; - buffer[i * outchannels + 3] = 0; - buffer[i * outchannels + 4] = bl; - buffer[i * outchannels + 5] = br; - for (int j = 6; j < outchannels; ++j) - { - buffer[i * outchannels + j] = 0; - } - } - else if (inchannels == 7 && outchannels == 8) - { - float fl = buffer[i * 7 + 0]; - float fr = buffer[i * 7 + 1]; - float c = buffer[i * 7 + 2]; - float lfe = buffer[i * 7 + 3]; - float sl = buffer[i * 7 + 4]; - float sr = buffer[i * 7 + 5]; - float bc = buffer[i * 7 + 6]; - buffer[i * 8 + 0] = fl; - buffer[i * 8 + 1] = fr; - buffer[i * 8 + 2] = c; - buffer[i * 8 + 3] = lfe; - buffer[i * 8 + 4] = bc; - buffer[i * 8 + 5] = bc; - buffer[i * 8 + 6] = sl; - buffer[i * 8 + 7] = sr; - } - else - { - // upmix N channels to N channels plus silence the empty channels - float samples[inchannels]; - for (int j = 0; j < inchannels; ++j) - { - samples[j] = buffer[i * inchannels + j]; - } - for (int j = 0; j < inchannels; ++j) - { - buffer[i * outchannels + j] = samples[j]; - } - for (int j = inchannels; j < outchannels; ++j) - { - buffer[i * outchannels + j] = 0; - } - } - } -} - void scale_by_volume(float * buffer, size_t count, float volume) { if ( volume != 1.0 ) @@ -1003,31 +825,6 @@ tryagain: scale_by_volume( (float*) floatBuffer, amountReadFromFC / sizeof(float), volumeScale); - if ( hFilter ) { - int samples = amountReadFromFC / floatFormat.mBytesPerFrame; - [hFilter process:floatBuffer sampleCount:samples toBuffer:floatBuffer + amountReadFromFC]; - memmove(floatBuffer, floatBuffer + amountReadFromFC, samples * sizeof(float) * 2); - amountReadFromFC = samples * sizeof(float) * 2; - } - else if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 ) - { - int samples = amountReadFromFC / floatFormat.mBytesPerFrame; - downmix_to_stereo( (float*) floatBuffer, inputFormat.mChannelsPerFrame, samples ); - amountReadFromFC = samples * sizeof(float) * 2; - } - else if ( inputFormat.mChannelsPerFrame > 1 && outputFormat.mChannelsPerFrame == 1 ) - { - int samples = amountReadFromFC / floatFormat.mBytesPerFrame; - downmix_to_mono( (float*) floatBuffer, inputFormat.mChannelsPerFrame, samples ); - amountReadFromFC = samples * sizeof(float); - } - else if ( inputFormat.mChannelsPerFrame < outputFormat.mChannelsPerFrame ) - { - int samples = amountReadFromFC / floatFormat.mBytesPerFrame; - upmix( (float*) floatBuffer, inputFormat.mChannelsPerFrame, outputFormat.mChannelsPerFrame, samples ); - amountReadFromFC = samples * sizeof(float) * outputFormat.mChannelsPerFrame; - } - floatSize = amountReadFromFC; floatOffset = 0; } @@ -1058,15 +855,6 @@ tryagain: //User reset the volume scaling option [self refreshVolumeScaling]; } - else if ([keyPath isEqualToString:@"values.headphoneVirtualization"] || - [keyPath isEqualToString:@"values.hrirPath"]) { - // Reset the converter, without rebuffering - if (outputFormat.mChannelsPerFrame == 2 && - inputFormat.mChannelsPerFrame >= 1 && - inputFormat.mChannelsPerFrame <= 8) { - [self inputFormatDidChange:inputFormat]; - } - } } static float db_to_scale(float db) @@ -1124,6 +912,8 @@ static float db_to_scale(float db) inputFormat = inf; outputFormat = outf; + nodeFormat = outputFormat; + rememberedLossless = lossless; // These are the only sample formats we support translating @@ -1175,33 +965,6 @@ static float db_to_scale(float db) dmFloatFormat.mBytesPerFrame = (32/8)*dmFloatFormat.mChannelsPerFrame; dmFloatFormat.mBytesPerPacket = dmFloatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket; - BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"]; - - if (hVirt && - outputFormat.mChannelsPerFrame == 2 && - inputFormat.mChannelsPerFrame >= 1 && - inputFormat.mChannelsPerFrame <= 8) { - NSString * userPreset = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"hrirPath"]; - - NSURL * presetUrl = nil; - - if (userPreset && ![userPreset isEqualToString:@""]) { - presetUrl = [NSURL fileURLWithPath:userPreset]; - if (![HeadphoneFilter validateImpulseFile:presetUrl]) - presetUrl = nil; - } - - if (!presetUrl) { - presetUrl = [[NSBundle mainBundle] URLForResource:@"gsx" withExtension:@"wv"]; - if (![HeadphoneFilter validateImpulseFile:presetUrl]) - presetUrl = nil; - } - - if (presetUrl) { - hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame]; - } - } - skipResampler = outputFormat.mSampleRate == floatFormat.mSampleRate; sampleRatio = (double)outputFormat.mSampleRate / (double)floatFormat.mSampleRate; @@ -1254,8 +1017,6 @@ static float db_to_scale(float db) DLog(@"Decoder dealloc"); [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeScaling"]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"]; paused = NO; [self cleanUp]; @@ -1320,9 +1081,6 @@ static float db_to_scale(float db) { usleep(500); } - if (hFilter) { - hFilter = nil; - } if (hdcd_decoder) { free(hdcd_decoder); diff --git a/Audio/Chain/Downmix.h b/Audio/Chain/Downmix.h new file mode 100644 index 000000000..3d9116837 --- /dev/null +++ b/Audio/Chain/Downmix.h @@ -0,0 +1,26 @@ +// +// Downmix.h +// Cog +// +// Created by Christopher Snowhill on 2/05/22. +// Copyright 2022 __LoSnoCo__. All rights reserved. +// + +#import +#import + +#import "HeadphoneFilter.h" + +@interface DownmixProcessor : NSObject { + HeadphoneFilter *hFilter; + + AudioStreamBasicDescription inputFormat; + AudioStreamBasicDescription outputFormat; +} + +- (id) initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf; + +- (void) process:(const void*)inBuffer frameCount:(size_t)frames output:(void *)outBuffer; + +@end + diff --git a/Audio/Chain/Downmix.m b/Audio/Chain/Downmix.m new file mode 100644 index 000000000..1348a1277 --- /dev/null +++ b/Audio/Chain/Downmix.m @@ -0,0 +1,302 @@ +// +// Downmix.m +// Cog +// +// Created by Christopher Snowhill on 2/05/22. +// Copyright 2022 __LoSnoCo__. All rights reserved. +// + +#import "Downmix.h" + +#import "Logging.h" + +static const float STEREO_DOWNMIX[8-2][8][2]={ + /*3.0*/ + { + {0.5858F,0.0F},{0.0F,0.5858F},{0.4142F,0.4142F} + }, + /*quadrophonic*/ + { + {0.4226F,0.0F},{0.0F,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F} + }, + /*5.0*/ + { + {0.651F,0.0F},{0.0F,0.651F},{0.46F,0.46F},{0.5636F,0.3254F}, + {0.3254F,0.5636F} + }, + /*5.1*/ + { + {0.529F,0.0F},{0.0F,0.529F},{0.3741F,0.3741F},{0.3741F,0.3741F},{0.4582F,0.2645F}, + {0.2645F,0.4582F} + }, + /*6.1*/ + { + {0.4553F,0.0F},{0.0F,0.4553F},{0.322F,0.322F},{0.322F,0.322F},{0.2788F,0.2788F}, + {0.3943F,0.2277F},{0.2277F,0.3943F} + }, + /*7.1*/ + { + {0.3886F,0.0F},{0.0F,0.3886F},{0.2748F,0.2748F},{0.2748F,0.2748F},{0.3366F,0.1943F}, + {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F} + } +}; + +static void downmix_to_stereo(const float * inBuffer, int channels, float * outBuffer, size_t count) +{ + if (channels >= 3 && channels <= 8) + for (size_t i = 0; i < count; ++i) + { + float left = 0, right = 0; + for (int j = 0; j < channels; ++j) + { + left += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0]; + right += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1]; + } + outBuffer[i * 2 + 0] = left; + outBuffer[i * 2 + 1] = right; + } +} + +static void downmix_to_mono(const float * inBuffer, int channels, float * outBuffer, size_t count) +{ + float tempBuffer[count * 2]; + if (channels >= 3 && channels <= 8) + { + downmix_to_stereo(inBuffer, channels, tempBuffer, count); + inBuffer = tempBuffer; + channels = 2; + } + 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; + } +} + +static void upmix(const float * inBuffer, int inchannels, float * outBuffer, int outchannels, size_t count) +{ + for (ssize_t i = 0; i < count; ++i) + { + if (inchannels == 1 && outchannels == 2) + { + // upmix mono to stereo + float sample = inBuffer[i]; + outBuffer[i * 2 + 0] = sample; + outBuffer[i * 2 + 1] = sample; + } + else if (inchannels == 1 && outchannels == 4) + { + // upmix mono to quad + float sample = inBuffer[i]; + outBuffer[i * 4 + 0] = sample; + outBuffer[i * 4 + 1] = sample; + outBuffer[i * 4 + 2] = 0; + outBuffer[i * 4 + 3] = 0; + } + else if (inchannels == 1 && (outchannels == 3 || outchannels >= 5)) + { + // upmix mono to center channel + float sample = inBuffer[i]; + outBuffer[i * outchannels + 2] = sample; + for (int j = 0; j < 2; ++j) + { + outBuffer[i * outchannels + j] = 0; + } + for (int j = 3; j < outchannels; ++j) + { + outBuffer[i * outchannels + j] = 0; + } + } + else if (inchannels == 4 && outchannels >= 5) + { + float fl = inBuffer[i * 4 + 0]; + float fr = inBuffer[i * 4 + 1]; + float bl = inBuffer[i * 4 + 2]; + float br = inBuffer[i * 4 + 3]; + const int skipclfe = (outchannels == 5) ? 1 : 2; + outBuffer[i * outchannels + 0] = fl; + outBuffer[i * outchannels + 1] = fr; + outBuffer[i * outchannels + skipclfe + 2] = bl; + outBuffer[i * outchannels + skipclfe + 3] = br; + for (int j = 0; j < skipclfe; ++j) + { + outBuffer[i * outchannels + 2 + j] = 0; + } + for (int j = 4 + skipclfe; j < outchannels; ++j) + { + outBuffer[i * outchannels + j] = 0; + } + } + else if (inchannels == 5 && outchannels >= 6) + { + float fl = inBuffer[i * 5 + 0]; + float fr = inBuffer[i * 5 + 1]; + float c = inBuffer[i * 5 + 2]; + float bl = inBuffer[i * 5 + 3]; + float br = inBuffer[i * 5 + 4]; + outBuffer[i * outchannels + 0] = fl; + outBuffer[i * outchannels + 1] = fr; + outBuffer[i * outchannels + 2] = c; + outBuffer[i * outchannels + 3] = 0; + outBuffer[i * outchannels + 4] = bl; + outBuffer[i * outchannels + 5] = br; + for (int j = 6; j < outchannels; ++j) + { + outBuffer[i * outchannels + j] = 0; + } + } + else if (inchannels == 7 && outchannels == 8) + { + float fl = inBuffer[i * 7 + 0]; + float fr = inBuffer[i * 7 + 1]; + float c = inBuffer[i * 7 + 2]; + float lfe = inBuffer[i * 7 + 3]; + float sl = inBuffer[i * 7 + 4]; + float sr = inBuffer[i * 7 + 5]; + float bc = inBuffer[i * 7 + 6]; + outBuffer[i * 8 + 0] = fl; + outBuffer[i * 8 + 1] = fr; + outBuffer[i * 8 + 2] = c; + outBuffer[i * 8 + 3] = lfe; + outBuffer[i * 8 + 4] = bc; + outBuffer[i * 8 + 5] = bc; + outBuffer[i * 8 + 6] = sl; + outBuffer[i * 8 + 7] = sr; + } + else + { + // upmix N channels to N channels plus silence the empty channels + float samples[inchannels]; + for (int j = 0; j < inchannels; ++j) + { + samples[j] = inBuffer[i * inchannels + j]; + } + for (int j = 0; j < inchannels; ++j) + { + outBuffer[i * outchannels + j] = samples[j]; + } + for (int j = inchannels; j < outchannels; ++j) + { + outBuffer[i * outchannels + j] = 0; + } + } + } +} + +@implementation DownmixProcessor + +- (id) initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf { + self = [super init]; + + if (self) { + if (inf.mFormatID != kAudioFormatLinearPCM || + (inf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked || + inf.mBitsPerChannel != 32 || + inf.mBytesPerFrame != (4 * inf.mChannelsPerFrame) || + inf.mBytesPerPacket != inf.mFramesPerPacket * inf.mBytesPerFrame) + return nil; + + if (outf.mFormatID != kAudioFormatLinearPCM || + (outf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked || + outf.mBitsPerChannel != 32 || + outf.mBytesPerFrame != (4 * outf.mChannelsPerFrame) || + outf.mBytesPerPacket != outf.mFramesPerPacket * outf.mBytesPerFrame) + return nil; + + inputFormat = inf; + outputFormat = outf; + + [self setupVirt]; + + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil]; + } + + return self; +} + +- (void) dealloc { + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"]; +} + +- (void) setupVirt { + @synchronized(hFilter) { + hFilter = nil; + } + + BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"]; + + if (hVirt && + outputFormat.mChannelsPerFrame == 2 && + inputFormat.mChannelsPerFrame >= 1 && + inputFormat.mChannelsPerFrame <= 8) { + NSString * userPreset = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"hrirPath"]; + + NSURL * presetUrl = nil; + + if (userPreset && ![userPreset isEqualToString:@""]) { + presetUrl = [NSURL fileURLWithPath:userPreset]; + if (![HeadphoneFilter validateImpulseFile:presetUrl]) + presetUrl = nil; + } + + if (!presetUrl) { + presetUrl = [[NSBundle mainBundle] URLForResource:@"gsx" withExtension:@"wv"]; + if (![HeadphoneFilter validateImpulseFile:presetUrl]) + presetUrl = nil; + } + + if (presetUrl) { + @synchronized(hFilter) { + hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame]; + } + } + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + DLog(@"SOMETHING CHANGED!"); + if ([keyPath isEqualToString:@"values.headphoneVirtualization"] || + [keyPath isEqualToString:@"values.hrirPath"]) { + // Reset the converter, without rebuffering + [self setupVirt]; + } +} + +- (void) process:(const void *)inBuffer frameCount:(size_t)frames output:(void *)outBuffer { + @synchronized (hFilter) { + if ( hFilter ) { + [hFilter process:(const float *) inBuffer sampleCount:frames toBuffer:(float *) outBuffer]; + return; + } + } + + if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 ) + { + downmix_to_stereo( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, frames ); + } + else if ( inputFormat.mChannelsPerFrame > 1 && outputFormat.mChannelsPerFrame == 1 ) + { + downmix_to_mono( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, frames ); + } + else if ( inputFormat.mChannelsPerFrame < outputFormat.mChannelsPerFrame ) + { + upmix( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, outputFormat.mChannelsPerFrame, frames ); + } + else if ( inputFormat.mChannelsPerFrame == outputFormat.mChannelsPerFrame ) + { + memcpy(outBuffer, inBuffer, frames * outputFormat.mBytesPerPacket); + } +} + +@end diff --git a/Audio/Chain/InputNode.m b/Audio/Chain/InputNode.m index cbdadaa4e..fe8d64bef 100644 --- a/Audio/Chain/InputNode.m +++ b/Audio/Chain/InputNode.m @@ -51,6 +51,8 @@ bytesPerFrame = ((bitsPerSample + 7) / 8) * channels; + nodeFormat = propertiesToASBD(properties); + shouldContinue = YES; shouldSeek = NO; @@ -68,6 +70,8 @@ bytesPerFrame = ((bitsPerSample + 7) / 8) * channels; + nodeFormat = propertiesToASBD(properties); + [self registerObservers]; shouldContinue = YES; diff --git a/Audio/Chain/Node.h b/Audio/Chain/Node.h index 65eca918f..bf327fe78 100644 --- a/Audio/Chain/Node.h +++ b/Audio/Chain/Node.h @@ -7,6 +7,7 @@ // #import +#import #import "VirtualRingBuffer.h" #import "Semaphore.h" @@ -25,7 +26,12 @@ BOOL shouldContinue; BOOL endOfStream; //All data is now in buffer BOOL initialBufferFilled; + + AudioStreamBasicDescription nodeFormat; } + +@property (readonly) AudioStreamBasicDescription nodeFormat; + - (id)initWithController:(id)c previous:(id)p; - (int)writeData:(void *)ptr amount:(int)a; diff --git a/Audio/Chain/Node.m b/Audio/Chain/Node.m index 354a6a22c..74b5500d2 100644 --- a/Audio/Chain/Node.m +++ b/Audio/Chain/Node.m @@ -13,6 +13,8 @@ @implementation Node +@synthesize nodeFormat; + - (id)initWithController:(id)c previous:(id)p { self = [super init]; @@ -41,7 +43,8 @@ while (shouldContinue == YES && amountLeft > 0) { - availOutput = [buffer lengthAvailableToWriteReturningPointer:&writePtr]; + BOOL wrapped; + availOutput = [buffer lengthAvailableToWriteReturningPointer:&writePtr bufferWrapped:&wrapped]; if (availOutput == 0) { if (initialBufferFilled == NO) { initialBufferFilled = YES; diff --git a/Audio/Chain/OutputNode.h b/Audio/Chain/OutputNode.h index 89f0a3354..5278439b8 100644 --- a/Audio/Chain/OutputNode.h +++ b/Audio/Chain/OutputNode.h @@ -24,6 +24,9 @@ BOOL paused; BOOL started; + + BOOL formatSetup; + BOOL formatChanged; } - (void)beginEqualizer:(AudioUnit)eq; @@ -62,4 +65,6 @@ - (void)sustainHDCD; +- (BOOL)formatChanged; + @end diff --git a/Audio/Chain/OutputNode.m b/Audio/Chain/OutputNode.m index 538448ede..7d85fc623 100644 --- a/Audio/Chain/OutputNode.m +++ b/Audio/Chain/OutputNode.m @@ -22,6 +22,9 @@ paused = YES; started = NO; + + formatSetup = NO; + formatChanged = NO; output = [[OutputCoreAudio alloc] initWithController:self]; @@ -84,7 +87,7 @@ @autoreleasepool { int n; [self setPreviousNode:[[controller bufferChain] finalNode]]; - + n = [super readData:ptr amount:amount]; /* if (n == 0) { @@ -128,6 +131,14 @@ if (oldSampleRatio) amountPlayed += oldSampleRatio * [[converter buffer] bufferedLength]; #endif + AudioStreamBasicDescription inf = [bufferChain inputFormat]; + + format.mChannelsPerFrame = inf.mChannelsPerFrame; + format.mBytesPerFrame = ((format.mBitsPerChannel + 7) / 8) * format.mChannelsPerFrame; + format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket; + + sampleRatio = 1.0 / (format.mSampleRate * format.mBytesPerPacket); + [converter setOutputFormat:format]; [converter inputFormatDidChange:[bufferChain inputFormat]]; } @@ -178,4 +189,21 @@ [output sustainHDCD]; } +- (BOOL)formatChanged +{ + [self setPreviousNode:[[controller bufferChain] finalNode]]; + + AudioStreamBasicDescription inf = [[self previousNode] nodeFormat]; + + if (!formatSetup || memcmp(&nodeFormat, &inf, sizeof(nodeFormat)) != 0) { + nodeFormat = inf; + formatSetup = YES; + formatChanged = YES; + } + + BOOL copyFormatChanged = formatChanged; + formatChanged = NO; + return copyFormatChanged; +} + @end diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index 0871028fd..e5d0c37dd 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -59,6 +59,8 @@ 83725A8E27AA0DE60003F694 /* libsoxr.0.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; }; 83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; }; + 8380F2D927AE6053009183C1 /* Downmix.m in Sources */ = {isa = PBXBuildFile; fileRef = 8380F2D727AE6053009183C1 /* Downmix.m */; }; + 8380F2DA27AE6053009183C1 /* Downmix.h in Headers */ = {isa = PBXBuildFile; fileRef = 8380F2D827AE6053009183C1 /* Downmix.h */; }; 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 */; }; @@ -151,6 +153,8 @@ 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 83725A8827AA0DBF0003F694 /* soxr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = soxr.h; sourceTree = ""; }; 83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libsoxr.0.dylib; sourceTree = ""; }; + 8380F2D727AE6053009183C1 /* Downmix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Downmix.m; sourceTree = ""; }; + 8380F2D827AE6053009183C1 /* Downmix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Downmix.h; sourceTree = ""; }; 8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; 839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = ""; }; 839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = ""; }; @@ -284,6 +288,8 @@ 17D21C750B8BE4BA00D1EBDE /* Chain */ = { isa = PBXGroup; children = ( + 8380F2D827AE6053009183C1 /* Downmix.h */, + 8380F2D727AE6053009183C1 /* Downmix.m */, 83A44A00279119B50049B6E2 /* RefillNode.h */, 83A449FF279119B50049B6E2 /* RefillNode.m */, 17D21C760B8BE4BA00D1EBDE /* BufferChain.h */, @@ -446,6 +452,7 @@ 8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */, 17C940230B900909008627D6 /* AudioMetadataReader.h in Headers */, 83725A8B27AA0DBF0003F694 /* soxr.h in Headers */, + 8380F2DA27AE6053009183C1 /* Downmix.h in Headers */, 17B619300B909BC300BC003F /* AudioPropertiesReader.h in Headers */, 835EDD7D279FE307001EDCCE /* HeadphoneFilter.h in Headers */, 839366671815923C006DD712 /* CogPluginMulti.h in Headers */, @@ -546,6 +553,7 @@ 8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */, 17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */, 839366681815923C006DD712 /* CogPluginMulti.m in Sources */, + 8380F2D927AE6053009183C1 /* Downmix.m in Sources */, 835C88AA2797D4D400E28EAE /* lpc.c in Sources */, 17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */, 17F94DD60B8D0F7000A34E87 /* PluginController.m in Sources */, diff --git a/Audio/Output/OutputCoreAudio.h b/Audio/Output/OutputCoreAudio.h index ef4f49b92..cc56df1a4 100644 --- a/Audio/Output/OutputCoreAudio.h +++ b/Audio/Output/OutputCoreAudio.h @@ -19,6 +19,8 @@ #import "Semaphore.h" +#import "Downmix.h" + //#define OUTPUT_LOG #ifdef OUTPUT_LOG #import @@ -42,6 +44,9 @@ BOOL eqEnabled; + BOOL streamFormatSetup; + + atomic_long bytesBuffered; atomic_long bytesRendered; atomic_long bytesHdcdSustained; @@ -54,12 +59,19 @@ AudioDeviceID outputDeviceID; AudioStreamBasicDescription deviceFormat; // info about the default device + AudioStreamBasicDescription outerStreamFormat; // this is set when the buffer changes + AudioStreamBasicDescription streamFormat; // this is set when the output callback gets it AUAudioUnit *_au; size_t _bufferSize; AudioUnit _eq; + + DownmixProcessor *downmixer; + void * savedBuffer; + size_t savedSize; + size_t savedMaxSize; #ifdef OUTPUT_LOG FILE *_logFile; #endif diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index 4d29201ff..23f235791 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -83,47 +83,134 @@ static OSStatus renderCallback( void *inRefCon, AudioUnitRenderActionFlags *ioAc return 0; } - void * readPtr; - int toRead = [[_self->outputController buffer] lengthAvailableToReadReturningPointer:&readPtr]; - - if (toRead > amountToRead) - toRead = amountToRead; - - if (toRead) { - fillBuffers(ioData, (float*)readPtr, toRead / bytesPerPacket, 0); - amountRead = toRead; - [[_self->outputController buffer] didReadLength:toRead]; - [_self->outputController incrementAmountPlayed:amountRead]; - atomic_fetch_add(&_self->bytesRendered, amountRead); + if (_self->savedSize) { + int readBytes = (int) _self->savedSize; + + const int streamchannels = _self->streamFormat.mChannelsPerFrame; + const int streamBytesPerPacket = streamchannels * sizeof(float); + + int samplesToRead = readBytes / streamBytesPerPacket; + + if (samplesToRead > (amountToRead / bytesPerPacket)) + samplesToRead = amountToRead / bytesPerPacket; + + readBytes = samplesToRead * streamBytesPerPacket; + + atomic_fetch_sub(&_self->bytesBuffered, readBytes); + + float downmixBuffer[samplesToRead * channels]; + [_self->downmixer process:_self->savedBuffer frameCount:samplesToRead output:downmixBuffer]; + fillBuffers(ioData, downmixBuffer, samplesToRead, 0); + amountRead += samplesToRead * bytesPerPacket; + [_self->outputController incrementAmountPlayed:samplesToRead * bytesPerPacket]; + atomic_fetch_add(&_self->bytesRendered, samplesToRead * bytesPerPacket); [_self->writeSemaphore signal]; - } - else - [[_self->outputController buffer] didReadLength:0]; - // Try repeatedly! Buffer wraps can cause a slight data shortage, as can - // unexpected track changes. - while ((amountRead < amountToRead) && [_self->outputController shouldContinue] == YES) - { - int amountRead2; //Use this since return type of readdata isnt known...may want to fix then can do a simple += to readdata - amountRead2 = [[_self->outputController buffer] lengthAvailableToReadReturningPointer:&readPtr]; - if (amountRead2 > (amountToRead - amountRead)) - amountRead2 = amountToRead - amountRead; - if (amountRead2) { - atomic_fetch_add(&_self->bytesRendered, amountRead2); - fillBuffers(ioData, (float*)readPtr, amountRead2 / bytesPerPacket, amountRead / bytesPerPacket); - [[_self->outputController buffer] didReadLength:amountRead2]; - - [_self->outputController incrementAmountPlayed:amountRead2]; - - amountRead += amountRead2; - [_self->writeSemaphore signal]; + if (_self->savedSize > readBytes) { + _self->savedSize -= readBytes; + memmove(_self->savedBuffer, _self->savedBuffer + readBytes, _self->savedSize); } else { - [[_self->outputController buffer] didReadLength:0]; - [_self->readSemaphore timedWait:500]; + _self->savedSize = 0; } } + while (amountRead < amountToRead && [_self->outputController shouldContinue]) + { + void * readPtr; + int toRead = 0; + do { + toRead = [[_self->outputController buffer] lengthAvailableToReadReturningPointer:&readPtr]; + if (toRead && *((uint8_t*)readPtr) == 0xFF) { + size_t toSkip = 0; + while (toRead && *((uint8_t*)readPtr) == 0xFF) { + toSkip++; + readPtr++; + toRead--; + } + [[_self->outputController buffer] didReadLength:(int)toSkip]; + toRead = 0; + } + } + while (!toRead); + + int bytesRead = 0; + + int32_t chunkId = -1; + + if (toRead >= 4) { + memcpy(&chunkId, readPtr, 4); + readPtr += 4; + toRead -= 4; + bytesRead += 4; + } + + if (chunkId == 1 && toRead >= sizeof(AudioStreamBasicDescription)) { + AudioStreamBasicDescription inf; + memcpy(&inf, readPtr, sizeof(inf)); + readPtr += sizeof(inf); + toRead -= sizeof(inf); + bytesRead += sizeof(inf); + + if (!_self->streamFormatSetup || memcmp(&inf, &_self->streamFormat, sizeof(inf)) != 0) { + _self->streamFormatSetup = YES; + _self->streamFormat = inf; + _self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:inf andOutputFormat:_self->deviceFormat]; + } + + if (toRead >= 4) { + memcpy(&chunkId, readPtr, 4); + readPtr += 4; + toRead -= 4; + bytesRead += 4; + } + else chunkId = -1; + } + + const int streamchannels = _self->streamFormat.mChannelsPerFrame; + const int streamBytesPerPacket = streamchannels * sizeof(float); + + if (chunkId == 0 && toRead >= 4) { + memcpy(&chunkId, readPtr, 4); + readPtr += 4; + bytesRead += 4; + toRead = chunkId; + } + + if (toRead) { + size_t samplesToRead = toRead / streamBytesPerPacket; + size_t saveBytes = 0; + + if (samplesToRead * bytesPerPacket > (amountToRead - amountRead)) { + size_t shortToRead = (amountToRead - amountRead) / bytesPerPacket; + saveBytes = (samplesToRead - shortToRead) * streamBytesPerPacket; + samplesToRead = shortToRead; + } + float downmixBuffer[samplesToRead * channels]; + [_self->downmixer process:readPtr frameCount:samplesToRead output:downmixBuffer]; + fillBuffers(ioData, downmixBuffer, samplesToRead, amountRead / bytesPerPacket); + amountRead += samplesToRead * bytesPerPacket; + bytesRead += toRead; + + if (saveBytes) { + if (!_self->savedBuffer || _self->savedMaxSize < saveBytes) { + _self->savedBuffer = realloc(_self->savedBuffer, _self->savedMaxSize = saveBytes * 3); + } + _self->savedSize = saveBytes; + memcpy(_self->savedBuffer, readPtr + toRead - saveBytes, saveBytes); + } + + atomic_fetch_sub(&_self->bytesBuffered, toRead - saveBytes); + + [[_self->outputController buffer] didReadLength:bytesRead]; + [_self->outputController incrementAmountPlayed:samplesToRead * bytesPerPacket]; + atomic_fetch_add(&_self->bytesRendered, samplesToRead * bytesPerPacket); + [_self->writeSemaphore signal]; + } + else + [[_self->outputController buffer] didReadLength:bytesRead]; + } + float volumeScale = 1.0; long sustained = atomic_load_explicit(&_self->bytesHdcdSustained, memory_order_relaxed); if (sustained) { @@ -138,14 +225,6 @@ static OSStatus renderCallback( void *inRefCon, AudioUnitRenderActionFlags *ioAc scaleBuffersByVolume(ioData, _self->volume * volumeScale); - if (amountRead < amountToRead) - { - // Either underrun, or no data at all. Caller output tends to just - // buffer loop if it doesn't get anything, so always produce a full - // buffer, and silence anything we couldn't supply. - clearBuffers(ioData, (amountToRead - amountRead) / bytesPerPacket, amountRead / bytesPerPacket); - } - return 0; }; @@ -165,6 +244,15 @@ static OSStatus renderCallback( void *inRefCon, AudioUnitRenderActionFlags *ioAc started = NO; stopNext = NO; + streamFormatSetup = NO; + + downmixer = nil; + + savedBuffer = NULL; + savedSize = 0; + savedMaxSize = 0; + + atomic_init(&bytesBuffered, 0); atomic_init(&bytesRendered, 0); atomic_init(&bytesHdcdSustained, 0); @@ -224,6 +312,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const if ([outputController shouldReset]) { [[outputController buffer] empty]; + atomic_store(&bytesBuffered, 0); [outputController setShouldReset:NO]; [delayedEvents removeAllObjects]; delayedEventsPopped = YES; @@ -244,13 +333,73 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const break; void *writePtr; - int toWrite = [[outputController buffer] lengthAvailableToWriteReturningPointer:&writePtr]; + BOOL wrapped = NO; + int toWrite = [[outputController buffer] lengthAvailableToWriteReturningPointer:&writePtr bufferWrapped:&wrapped]; + int bytesWritten = 0; + if (toWrite >= 4 + sizeof(AudioStreamBasicDescription)) { + if ([outputController formatChanged]) { + int32_t chunkId = 1; // ASBD + memcpy(writePtr, &chunkId, 4); + + writePtr += 4; + toWrite -= 4; + bytesWritten += 4; + + AudioStreamBasicDescription inf = [outputController nodeFormat]; + + outerStreamFormat = inf; + + memcpy(writePtr, &inf, sizeof(inf)); + + writePtr += sizeof(inf); + toWrite -= sizeof(inf); + bytesWritten += sizeof(inf); + } + } + [[outputController buffer] didWriteLength:bytesWritten]; + + toWrite = [[outputController buffer] lengthAvailableToWriteReturningPointer:&writePtr bufferWrapped:&wrapped]; int bytesRead = 0; - if (toWrite > CHUNK_SIZE) - toWrite = CHUNK_SIZE; - if (toWrite) - bytesRead = [outputController readData:writePtr amount:toWrite]; - [[outputController buffer] didWriteLength:bytesRead]; + bytesWritten = 0; + if (toWrite >= 4 + 4 + 512 * outerStreamFormat.mBytesPerPacket) { + uint8_t buffer[512 * outerStreamFormat.mBytesPerPacket]; + + bytesRead = [outputController readData:buffer amount:(int)sizeof(buffer)]; + + while (bytesRead < sizeof(buffer) && ![outputController endOfStream]) { + int bytesRead2 = [outputController readData:buffer + bytesRead amount:(int)(sizeof(buffer) - bytesRead)]; + bytesRead += bytesRead2; + } + + int32_t chunkId = 0; // audio data + memcpy(writePtr, &chunkId, 4); + writePtr += 4; + toWrite -= 4; + bytesWritten += 4; + + chunkId = bytesRead; + memcpy(writePtr, &chunkId, 4); + writePtr += 4; + toWrite -= 4; + bytesWritten += 4; + + memcpy(writePtr, buffer, bytesRead); + writePtr += bytesRead; + toWrite -= bytesRead; + bytesWritten += bytesRead; + + atomic_fetch_add(&bytesBuffered, bytesRead); + + [[outputController buffer] didWriteLength:bytesWritten]; + } + else if (wrapped && toWrite > 0) { + memset(writePtr, 0xFF, toWrite); + [[outputController buffer] didWriteLength:toWrite]; + } + else if (toWrite) { + [[outputController buffer] didWriteLength:0]; + toWrite = 0; + } if (bytesRead) { [readSemaphore signal]; continue; @@ -270,20 +419,20 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const // End of input possibly reached if (delayedEventsPopped && [outputController endOfStream] == YES) { - long bytesBuffered = [[outputController buffer] bufferedLength]; - bytesBuffered += atomic_load_explicit(&bytesRendered, memory_order_relaxed); + long _bytesBuffered = atomic_load_explicit(&bytesBuffered, memory_order_relaxed) * deviceFormat.mBytesPerPacket / outerStreamFormat.mBytesPerPacket; + _bytesBuffered += atomic_load_explicit(&bytesRendered, memory_order_relaxed); if ([outputController chainQueueHasTracks]) { - if (bytesBuffered < CHUNK_SIZE / 2) - bytesBuffered = 0; + if (_bytesBuffered < CHUNK_SIZE / 2) + _bytesBuffered = 0; else - bytesBuffered -= CHUNK_SIZE / 2; + _bytesBuffered -= CHUNK_SIZE / 2; } else { stopNext = YES; break; } - [delayedEvents addObject:[NSNumber numberWithLong:bytesBuffered]]; + [delayedEvents addObject:[NSNumber numberWithLong:_bytesBuffered]]; delayedEventsPopped = NO; if (!started) { started = YES; @@ -477,8 +626,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const NSError *err; AVAudioFormat *renderFormat; - [outputController incrementAmountPlayed:[[outputController buffer] bufferedLength]]; + [outputController incrementAmountPlayed:atomic_load_explicit(&bytesBuffered, memory_order_relaxed)]; [[outputController buffer] empty]; + atomic_store(&bytesBuffered, 0); _deviceFormat = format; deviceFormat = *(format.streamDescription); @@ -562,6 +712,12 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const paused = NO; stopNext = NO; outputDeviceID = -1; + + streamFormatSetup = NO; + + savedBuffer = NULL; + savedSize = 0; + savedMaxSize = 0; AudioComponentDescription desc; NSError *err; @@ -668,6 +824,8 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const return 0; }; + + [_au setMaximumFramesToRender:512]; UInt32 value; UInt32 size = sizeof(value); @@ -788,6 +946,14 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const _logFile = NULL; } #endif + if (savedBuffer) + { + free(savedBuffer); + savedBuffer = NULL; + savedSize = 0; + savedMaxSize = 0; + } + outputController = nil; } diff --git a/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.h b/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.h index d185394b0..790fc9ebc 100644 --- a/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.h +++ b/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.h @@ -83,7 +83,7 @@ // Write operations: // The writing thread must call this method first. -- (UInt32)lengthAvailableToWriteReturningPointer:(void **)returnedWritePointer; +- (UInt32)lengthAvailableToWriteReturningPointer:(void **)returnedWritePointer bufferWrapped:(BOOL*)wrapped; // Iff a value > 0 is returned, the writing thread may then write that much data into the returned pointer. // Afterwards, the writing thread must call didWriteLength:. - (void)didWriteLength:(UInt32)length; diff --git a/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.m b/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.m index 98bd34a0e..b7b01cc0c 100644 --- a/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.m +++ b/Audio/ThirdParty/VirtualRingBuffer/VirtualRingBuffer.m @@ -140,7 +140,7 @@ static void deallocateVirtualBuffer(void *buffer, UInt32 bufferLength); // Write operations // -- (UInt32)lengthAvailableToWriteReturningPointer:(void **)returnedWritePointer +- (UInt32)lengthAvailableToWriteReturningPointer:(void **)returnedWritePointer bufferWrapped:(BOOL *)wrapped { // Assumptions: // returnedWritePointer != NULL @@ -152,8 +152,10 @@ static void deallocateVirtualBuffer(void *buffer, UInt32 bufferLength); int localBufferFilled = atomic_load_explicit(&bufferFilled, memory_order_relaxed); length = bufferLength - localBufferFilled; - if (length > bufferLength - localWritePointer) + if (length >= bufferLength - localWritePointer) { + *wrapped = YES; length = bufferLength - localWritePointer; + } *returnedWritePointer = buffer + localWritePointer;