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 <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-05 03:45:02 -08:00
parent e0e7274339
commit 637ea4efe1
17 changed files with 656 additions and 318 deletions

View File

@ -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)
{

View File

@ -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<CogDecoder>)decoder
withOutputFormat:(AudioStreamBasicDescription)outputFormat
withOutputFormatHint:(AudioStreamBasicDescription)outputFormat
withRGInfo:(NSDictionary*)rgi;
- (void)seek:(double)time;

View File

@ -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<CogDecoder>)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];

View File

@ -74,8 +74,6 @@
id __weak originalPreviousNode;
void *hdcd_decoder;
HeadphoneFilter *hFilter;
}
@property AudioStreamBasicDescription inputFormat;

View File

@ -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);

26
Audio/Chain/Downmix.h Normal file
View File

@ -0,0 +1,26 @@
//
// Downmix.h
// Cog
//
// Created by Christopher Snowhill on 2/05/22.
// Copyright 2022 __LoSnoCo__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudio.h>
#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

302
Audio/Chain/Downmix.m Normal file
View File

@ -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

View File

@ -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;

View File

@ -7,6 +7,7 @@
//
#import <Cocoa/Cocoa.h>
#import <CoreAudio/CoreAudio.h>
#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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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 = "<group>"; };
83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libsoxr.0.dylib; sourceTree = "<group>"; };
8380F2D727AE6053009183C1 /* Downmix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Downmix.m; sourceTree = "<group>"; };
8380F2D827AE6053009183C1 /* Downmix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Downmix.h; sourceTree = "<group>"; };
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>"; };
@ -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 */,

View File

@ -19,6 +19,8 @@
#import "Semaphore.h"
#import "Downmix.h"
//#define OUTPUT_LOG
#ifdef OUTPUT_LOG
#import <stdio.h>
@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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;