├── ATPAudioCompressionSession.h ├── ATPAudioCompressionSession.m ├── ATPAudioConverter+Properties.h ├── ATPAudioConverter+Properties.m ├── ATPAudioConverter+PropertiesFromDictionary.h ├── ATPAudioConverter+PropertiesFromDictionary.m ├── ATPAudioConverter.h ├── ATPAudioConverter.m ├── AudioToolboxPlus Tests ├── AudioToolboxPlus_Tests.m ├── AudioToolbox_Tests.m └── Info.plist ├── AudioToolboxPlus-OSX.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── AudioToolboxPlus-Prefix.pch ├── AudioToolboxPlus.h ├── NSError+ATPError.h ├── NSError+ATPError.m ├── NSValue+ATPAudioValueRange.h ├── NSValue+ATPAudioValueRange.m ├── TPCircularBuffer+AudioBufferList.c ├── TPCircularBuffer+AudioBufferList.h ├── TPCircularBuffer.c └── TPCircularBuffer.h /ATPAudioCompressionSession.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | #import 5 | 6 | 7 | @class ATPAudioCompressionSession; 8 | @class ATPAudioConverter; 9 | 10 | 11 | @protocol ATPAudioCompressionSessionDelegate 12 | @required 13 | 14 | - (void)audioCompressionSession:(ATPAudioCompressionSession *)compressionSession didEncodeSampleBuffer:(CMSampleBufferRef)sampleBuffer; 15 | 16 | @end 17 | 18 | 19 | @interface ATPAudioCompressionSession : NSObject 20 | 21 | - (instancetype)initWithOutputFormat:(AudioStreamBasicDescription)outputFormat; 22 | 23 | @property (nonatomic, assign, readonly) AudioStreamBasicDescription outputFormat; 24 | 25 | @property (nonatomic, strong, readonly) ATPAudioConverter *audioConverter; 26 | 27 | @property (nonatomic, weak, readonly) id delegate; 28 | @property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue; 29 | 30 | - (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue; 31 | 32 | - (BOOL)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer; 33 | //- (void)encodeAudioBufferList:(const AudioBufferList *)audioBufferList; 34 | 35 | - (BOOL)finish; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /ATPAudioCompressionSession.m: -------------------------------------------------------------------------------- 1 | #import "ATPAudioCompressionSession.h" 2 | 3 | #import "ATPAudioConverter.h" 4 | #import "ATPAudioConverter+Properties.h" 5 | 6 | #import "TPCircularBuffer.h" 7 | 8 | 9 | enum { 10 | /** 11 | * This custom error code is used when an AudioConverterFill…Buffer has no sufficent amount of data. This is not an error if there is enough data at a later time. 12 | */ 13 | kAudioConverterErr_Underrun = 'urun', 14 | }; 15 | 16 | @interface ATPAudioCompressionSession () 17 | { 18 | TPCircularBuffer circularBuffer; 19 | } 20 | 21 | @property (nonatomic, assign) UInt32 inputBytesPerFrame; 22 | 23 | @property (nonatomic, assign) AudioStreamBasicDescription outputFormat; 24 | @property (nonatomic, strong) __attribute__((NSObject)) CMAudioFormatDescriptionRef outputFormatDescription; 25 | 26 | @property (nonatomic, strong) ATPAudioConverter *converter; 27 | @property (nonatomic, strong) dispatch_queue_t converterQueue; 28 | 29 | @property (nonatomic, weak) id delegate; 30 | @property (nonatomic, strong) dispatch_queue_t delegateQueue; 31 | 32 | @property (nonatomic, assign) CMTime presentationTimeStamp; 33 | 34 | @property (nonatomic, assign) BOOL finishing; 35 | 36 | @end 37 | 38 | 39 | @implementation ATPAudioCompressionSession 40 | 41 | - (instancetype)initWithOutputFormat:(AudioStreamBasicDescription)outputFormat 42 | { 43 | self = [super init]; 44 | if(self != nil) 45 | { 46 | self.outputFormat = outputFormat; 47 | 48 | TPCircularBufferInit(&circularBuffer, 128 * 1024); 49 | } 50 | return self; 51 | } 52 | 53 | - (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue 54 | { 55 | if(queue == nil) 56 | { 57 | queue = dispatch_get_main_queue(); 58 | } 59 | 60 | self.delegateQueue = queue; 61 | self.delegate = delegate; 62 | } 63 | 64 | - (void)setupConverterWithFormatDescription:(CMFormatDescriptionRef const)formatDescription 65 | { 66 | const AudioStreamBasicDescription * const inputFormat = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription); 67 | if(inputFormat == nil) 68 | { 69 | NSLog(@"%s:%d:TODO: error", __FUNCTION__, __LINE__); 70 | return; 71 | } 72 | 73 | self.inputBytesPerFrame = inputFormat->mBytesPerFrame; 74 | 75 | AudioStreamBasicDescription outputFormat = self.outputFormat; 76 | 77 | if(outputFormat.mSampleRate != 0 && outputFormat.mSampleRate != inputFormat->mSampleRate) 78 | { 79 | NSLog(@"%s:%d:TODO: error", __FUNCTION__, __LINE__); 80 | return; 81 | } 82 | 83 | NSError *error = nil; 84 | ATPAudioConverter * const converter = [[ATPAudioConverter alloc] initWithInputFormat:inputFormat outputFormat:&outputFormat error:&error]; 85 | if (converter == nil) 86 | { 87 | NSLog(@"%s:%d:TODO: error %@", __FUNCTION__, __LINE__, error); 88 | return; 89 | } 90 | 91 | self.converter = converter; 92 | 93 | outputFormat = converter.outputFormat; 94 | self.outputFormat = outputFormat; // some fields will be filled after creating the code 95 | 96 | AudioChannelLayout channelLayout = { 0 }; 97 | channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; 98 | 99 | NSData *magicCookie = converter.magicCookie; 100 | 101 | CMAudioFormatDescriptionRef outputFormatDescription = NULL; 102 | OSStatus status = CMAudioFormatDescriptionCreate(NULL, &outputFormat, sizeof(channelLayout), &channelLayout, magicCookie.length, magicCookie.bytes, NULL, &outputFormatDescription); 103 | if (status != noErr) 104 | { 105 | NSLog(@"%s:%d:TODO: status %d", __FUNCTION__, __LINE__, status); 106 | return; 107 | } 108 | 109 | self.outputFormatDescription = outputFormatDescription; 110 | 111 | self.presentationTimeStamp = kCMTimeZero; 112 | 113 | CFRelease(outputFormatDescription); 114 | } 115 | 116 | - (TPCircularBuffer *)circularBuffer 117 | { 118 | return &circularBuffer; 119 | } 120 | 121 | static OSStatus ATPAudioCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *bufferList, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) 122 | { 123 | ATPAudioCompressionSession *self = (__bridge ATPAudioCompressionSession *)inUserData; 124 | 125 | TPCircularBuffer *circularBuffer = self.circularBuffer; 126 | 127 | const UInt32 bytesPerFrame = self.inputBytesPerFrame; 128 | 129 | UInt32 bufferLength = *ioNumberDataPackets * bytesPerFrame; 130 | 131 | int32_t availableBytes; 132 | void * const data = TPCircularBufferTail(circularBuffer, &availableBytes); 133 | 134 | if(self.finishing) 135 | { 136 | bufferLength = availableBytes; 137 | *ioNumberDataPackets = bufferLength / bytesPerFrame; 138 | } 139 | else if(availableBytes < bufferLength) 140 | { 141 | return kAudioConverterErr_Underrun; 142 | } 143 | 144 | bufferList->mNumberBuffers = 1; 145 | 146 | AudioBuffer * const buffer = &bufferList->mBuffers[0]; 147 | buffer->mNumberChannels = 2; 148 | buffer->mDataByteSize = bufferLength; 149 | buffer->mData = data; 150 | 151 | TPCircularBufferConsume(circularBuffer, bufferLength); 152 | 153 | return noErr; 154 | } 155 | 156 | - (void)encodeAudioPackets 157 | { 158 | ATPAudioConverter * const converter = self.converter; 159 | 160 | const size_t length = converter.maximumOutputPacketSize; 161 | 162 | while(1) 163 | { 164 | void *data = malloc(length); 165 | 166 | UInt32 outputDataPackets = 1; 167 | 168 | AudioBufferList outputData; 169 | outputData.mNumberBuffers = 1; 170 | outputData.mBuffers[0].mNumberChannels = 2; 171 | outputData.mBuffers[0].mDataByteSize = (UInt32)length; 172 | outputData.mBuffers[0].mData = data; 173 | 174 | AudioStreamPacketDescription outputPacketDescription; 175 | 176 | OSStatus status = AudioConverterFillComplexBuffer(converter.AudioConverter, ATPAudioCallback, (__bridge void *)self, &outputDataPackets, &outputData, &outputPacketDescription); 177 | if(status != noErr) 178 | { 179 | free(data); 180 | data = NULL; 181 | 182 | if(status == kAudioConverterErr_Underrun) 183 | { 184 | break; 185 | } 186 | 187 | break; 188 | } 189 | 190 | CMTime presentationTimeStamp = self.presentationTimeStamp; 191 | 192 | CMBlockBufferRef dataBuffer = NULL; 193 | status = CMBlockBufferCreateWithMemoryBlock(NULL, data, length, NULL, NULL, 0, outputPacketDescription.mDataByteSize, kCMBlockBufferAssureMemoryNowFlag, &dataBuffer); // data is consumed here, no more free 194 | if(status != noErr) 195 | { 196 | break; 197 | } 198 | 199 | CMSampleBufferRef sampleBuffer = NULL; 200 | CMAudioSampleBufferCreateWithPacketDescriptions(NULL, dataBuffer, true, NULL, NULL, self.outputFormatDescription, 1, presentationTimeStamp, &outputPacketDescription, &sampleBuffer); 201 | 202 | self.presentationTimeStamp = CMTimeAdd(presentationTimeStamp, CMSampleBufferGetDuration(sampleBuffer)); 203 | 204 | if(sampleBuffer != NULL) 205 | { 206 | dispatch_async(self.delegateQueue, ^{ 207 | [self.delegate audioCompressionSession:self didEncodeSampleBuffer:sampleBuffer]; 208 | 209 | CFRelease(sampleBuffer); 210 | }); 211 | } 212 | 213 | CFRelease(dataBuffer); 214 | } 215 | } 216 | 217 | - (BOOL)encodeSampleBuffer:(CMSampleBufferRef const)sampleBuffer 218 | { 219 | CMFormatDescriptionRef const formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); 220 | 221 | const CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDescription); 222 | if(mediaType != kCMMediaType_Audio) 223 | { 224 | return NO; 225 | } 226 | 227 | if(self.converter == nil) 228 | { 229 | [self setupConverterWithFormatDescription:formatDescription]; 230 | } 231 | 232 | // Fill the ring buffer 233 | { 234 | CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); 235 | 236 | size_t offset = 0; 237 | size_t length = CMBlockBufferGetDataLength(dataBuffer); 238 | 239 | while(length > 0) 240 | { 241 | char *bytes = NULL; 242 | size_t lengthAtOffset = 0; 243 | CMBlockBufferGetDataPointer(dataBuffer, offset, &lengthAtOffset, NULL, &bytes); 244 | 245 | if(lengthAtOffset > length) 246 | { 247 | lengthAtOffset = length; 248 | } 249 | 250 | if(!TPCircularBufferProduceBytes(&circularBuffer, bytes, (int32_t)lengthAtOffset)) 251 | { 252 | // the ring buffer is full ... 253 | break; 254 | } 255 | 256 | offset += lengthAtOffset; 257 | length -= lengthAtOffset; 258 | } 259 | } 260 | 261 | [self encodeAudioPackets]; 262 | 263 | return YES; 264 | } 265 | 266 | - (BOOL)finish 267 | { 268 | self.finishing = YES; 269 | 270 | [self encodeAudioPackets]; 271 | 272 | return YES; 273 | } 274 | 275 | @end 276 | -------------------------------------------------------------------------------- /ATPAudioConverter+Properties.h: -------------------------------------------------------------------------------- 1 | #import "ATPAudioConverter.h" 2 | 3 | @interface ATPAudioConverter (Properties) 4 | 5 | - (UInt32)minimumInputBufferSize; 6 | - (UInt32)minimumInputBufferSizeWithError:(NSError **)error; 7 | 8 | - (UInt32)minimumOutputBufferSize; 9 | - (UInt32)minimumOutputBufferSizeWithError:(NSError **)error; 10 | 11 | - (UInt32)maximumInputBufferSize; 12 | - (UInt32)maximumInputBufferSizeWithError:(NSError **)error; 13 | 14 | - (UInt32)maximumInputPacketSize; 15 | - (UInt32)maximumInputPacketSizeWithError:(NSError **)error; 16 | 17 | - (UInt32)maximumOutputPacketSize; 18 | - (UInt32)maximumOutputPacketSizeWithError:(NSError **)error; 19 | 20 | - (NSData *)magicCookie; 21 | - (NSData *)magicCookieWithError:(NSError **)error; 22 | 23 | - (UInt32)codecQuality; 24 | - (UInt32)codecQualityWithError:(NSError **)error; 25 | 26 | - (BOOL)setCodecQuality:(UInt32)codecQuality; 27 | - (BOOL)setCodecQuality:(UInt32)codecQuality error:(NSError **)error; 28 | 29 | - (UInt32)encodeBitRate; 30 | - (UInt32)encodeBitRateWithError:(NSError **)error; 31 | 32 | - (BOOL)setEncodeBitRate:(UInt32)encodeBitRate; 33 | - (BOOL)setEncodeBitRate:(UInt32)encodeBitRate error:(NSError **)error; 34 | 35 | - (NSArray *)applicableEncodeBitRates; 36 | - (NSArray *)applicableEncodeBitRatesWithError:(NSError **)error; 37 | 38 | - (NSArray *)availableEncodeBitRates; 39 | - (NSArray *)availableEncodeBitRatesWithError:(NSError **)error; 40 | 41 | - (AudioStreamBasicDescription)inputFormat; 42 | - (AudioStreamBasicDescription)inputFormatWithError:(NSError **)error; 43 | 44 | - (AudioStreamBasicDescription)outputFormat; 45 | - (AudioStreamBasicDescription)outputFormatWithError:(NSError **)error; 46 | 47 | - (NSArray *)channelMap; 48 | - (NSArray *)channelMapWithError:(NSError **)error; 49 | 50 | - (BOOL)setChannelMap:(NSArray *)channelMap; 51 | - (BOOL)setChannelMap:(NSArray *)channelMap error:(NSError **)error; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /ATPAudioConverter+Properties.m: -------------------------------------------------------------------------------- 1 | #import "ATPAudioConverter+Properties.h" 2 | 3 | #import "NSValue+ATPAudioValueRange.h" 4 | 5 | 6 | @implementation ATPAudioConverter (Properties) 7 | 8 | - (UInt32)minimumInputBufferSize 9 | { 10 | return [self minimumInputBufferSizeWithError:nil]; 11 | } 12 | 13 | - (UInt32)minimumInputBufferSizeWithError:(NSError **)error 14 | { 15 | UInt32 value = 0; 16 | UInt32 size = sizeof(value); 17 | 18 | [self getValue:&value size:&size forProperty:kAudioConverterPropertyMinimumInputBufferSize error:error]; 19 | 20 | return value; 21 | } 22 | 23 | - (UInt32)minimumOutputBufferSize 24 | { 25 | return [self minimumOutputBufferSizeWithError:nil]; 26 | } 27 | 28 | - (UInt32)minimumOutputBufferSizeWithError:(NSError **)error 29 | { 30 | UInt32 value = 0; 31 | UInt32 size = sizeof(value); 32 | 33 | [self getValue:&value size:&size forProperty:kAudioConverterPropertyMinimumOutputBufferSize error:error]; 34 | 35 | return value; 36 | } 37 | 38 | - (UInt32)maximumInputBufferSize 39 | { 40 | return [self maximumInputBufferSizeWithError:nil]; 41 | } 42 | 43 | - (UInt32)maximumInputBufferSizeWithError:(NSError **)error 44 | { 45 | UInt32 value = 0; 46 | UInt32 size = sizeof(value); 47 | 48 | [self getValue:&value size:&size forProperty:kAudioConverterPropertyMaximumInputBufferSize error:error]; 49 | 50 | return value; 51 | } 52 | 53 | - (UInt32)maximumInputPacketSize 54 | { 55 | return [self maximumInputPacketSizeWithError:nil]; 56 | } 57 | 58 | - (UInt32)maximumInputPacketSizeWithError:(NSError **)error 59 | { 60 | UInt32 value = 0; 61 | UInt32 size = sizeof(value); 62 | 63 | [self getValue:&value size:&size forProperty:kAudioConverterPropertyMaximumInputPacketSize error:error]; 64 | 65 | return value; 66 | } 67 | 68 | - (UInt32)maximumOutputPacketSize 69 | { 70 | return [self maximumOutputPacketSizeWithError:nil]; 71 | } 72 | 73 | - (UInt32)maximumOutputPacketSizeWithError:(NSError **)error 74 | { 75 | UInt32 value = 0; 76 | UInt32 size = sizeof(value); 77 | 78 | [self getValue:&value size:&size forProperty:kAudioConverterPropertyMaximumOutputPacketSize error:error]; 79 | 80 | return value; 81 | } 82 | 83 | - (NSData *)magicCookie 84 | { 85 | return [self magicCookieWithError:nil]; 86 | } 87 | 88 | - (NSData *)magicCookieWithError:(NSError **)error 89 | { 90 | return [self dataForProperty:kAudioConverterCompressionMagicCookie error:error]; 91 | } 92 | 93 | - (UInt32)codecQuality 94 | { 95 | return [self codecQualityWithError:nil]; 96 | } 97 | 98 | - (UInt32)codecQualityWithError:(NSError **)error 99 | { 100 | UInt32 value = { 0 }; 101 | UInt32 size = sizeof(value); 102 | 103 | [self getValue:&value size:&size forProperty:kAudioConverterCodecQuality error:error]; 104 | 105 | return value; 106 | } 107 | 108 | - (BOOL)setCodecQuality:(UInt32)codecQuality 109 | { 110 | return [self setCodecQuality:codecQuality error:nil]; 111 | } 112 | 113 | - (BOOL)setCodecQuality:(UInt32)codecQuality error:(NSError **)error 114 | { 115 | UInt32 value = codecQuality; 116 | 117 | return [self setValue:&value size:sizeof(value) forProperty:kAudioConverterCodecQuality error:error]; 118 | } 119 | 120 | - (UInt32)encodeBitRate 121 | { 122 | return [self encodeBitRateWithError:nil]; 123 | } 124 | 125 | - (UInt32)encodeBitRateWithError:(NSError **)error 126 | { 127 | UInt32 value = { 0 }; 128 | UInt32 size = sizeof(value); 129 | 130 | [self getValue:&value size:&size forProperty:kAudioConverterEncodeBitRate error:error]; 131 | 132 | return value; 133 | } 134 | 135 | - (BOOL)setEncodeBitRate:(UInt32)encodeBitRate 136 | { 137 | return [self setEncodeBitRate:encodeBitRate error:nil]; 138 | } 139 | 140 | - (BOOL)setEncodeBitRate:(UInt32)encodeBitRate error:(NSError **)error 141 | { 142 | UInt32 value = encodeBitRate; 143 | 144 | return [self setValue:&value size:sizeof(value) forProperty:kAudioConverterEncodeBitRate error:error]; 145 | } 146 | 147 | - (NSArray *)applicableEncodeBitRates 148 | { 149 | return [self applicableEncodeBitRatesWithError:nil]; 150 | } 151 | 152 | - (NSArray *)applicableEncodeBitRatesWithError:(NSError **)error 153 | { 154 | NSData *data = [self dataForProperty:kAudioConverterApplicableEncodeBitRates error:error]; 155 | 156 | const AudioValueRange *bytes = data.bytes; 157 | const NSUInteger count = data.length / sizeof(AudioValueRange); 158 | 159 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:count]; 160 | for(NSUInteger i = 0; i < count; ++i) 161 | { 162 | NSValue *value = [NSValue valueWithAudioValueRange:bytes[i]]; 163 | [values addObject:value]; 164 | } 165 | 166 | return [values copy]; 167 | } 168 | 169 | - (NSArray *)availableEncodeBitRates 170 | { 171 | return [self availableEncodeBitRatesWithError:nil]; 172 | } 173 | 174 | - (NSArray *)availableEncodeBitRatesWithError:(NSError **)error 175 | { 176 | NSData *data = [self dataForProperty:kAudioConverterAvailableEncodeBitRates error:error]; 177 | 178 | const AudioValueRange *bytes = data.bytes; 179 | const NSUInteger count = data.length / sizeof(AudioValueRange); 180 | 181 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:count]; 182 | for(NSUInteger i = 0; i < count; ++i) 183 | { 184 | NSValue *value = [NSValue valueWithAudioValueRange:bytes[i]]; 185 | [values addObject:value]; 186 | } 187 | 188 | return [values copy]; 189 | } 190 | 191 | - (AudioStreamBasicDescription)inputFormat 192 | { 193 | return [self inputFormatWithError:nil]; 194 | } 195 | 196 | - (AudioStreamBasicDescription)inputFormatWithError:(NSError **)error 197 | { 198 | AudioStreamBasicDescription value = { 0 }; 199 | UInt32 size = sizeof(value); 200 | 201 | [self getValue:&value size:&size forProperty:kAudioConverterCurrentInputStreamDescription error:error]; 202 | 203 | return value; 204 | } 205 | 206 | - (AudioStreamBasicDescription)outputFormat 207 | { 208 | return [self outputFormatWithError:nil]; 209 | } 210 | 211 | - (AudioStreamBasicDescription)outputFormatWithError:(NSError **)error 212 | { 213 | AudioStreamBasicDescription value = { 0 }; 214 | UInt32 size = sizeof(value); 215 | 216 | [self getValue:&value size:&size forProperty:kAudioConverterCurrentOutputStreamDescription error:error]; 217 | 218 | return value; 219 | } 220 | 221 | - (NSArray *)channelMap 222 | { 223 | return [self channelMapWithError:nil]; 224 | } 225 | 226 | - (NSArray *)channelMapWithError:(NSError **)error 227 | { 228 | UInt32 size = 0; 229 | if (![self getSize:&size writable:NULL forProperty:kAudioConverterChannelMap error:error]) 230 | { 231 | return nil; 232 | } 233 | 234 | SInt32 *value = malloc(size); 235 | if (![self getValue:value size:&size forProperty:kAudioConverterChannelMap error:error]) 236 | { 237 | free(value); 238 | return nil; 239 | } 240 | 241 | UInt32 count = size / sizeof(*value); 242 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:count]; 243 | 244 | for (UInt32 index = 0; index < count; ++index) 245 | { 246 | [values addObject:@(value[index])]; 247 | } 248 | 249 | free(value); 250 | 251 | return values; 252 | } 253 | 254 | - (BOOL)setChannelMap:(NSArray *)channelMap 255 | { 256 | return [self setChannelMap:channelMap error:nil]; 257 | } 258 | 259 | - (BOOL)setChannelMap:(NSArray *)channelMap error:(NSError **)error 260 | { 261 | UInt32 count = (UInt32)channelMap.count; 262 | UInt32 size = sizeof(SInt32) * count; 263 | 264 | SInt32 *value = malloc(size); 265 | 266 | UInt32 index = 0; 267 | for (NSNumber *channel in channelMap) 268 | { 269 | value[index] = (SInt32)channel.intValue; 270 | ++index; 271 | } 272 | 273 | BOOL success = [self setValue:value size:size forProperty:kAudioConverterChannelMap error:error]; 274 | 275 | free(value); 276 | 277 | return success; 278 | } 279 | 280 | @end 281 | -------------------------------------------------------------------------------- /ATPAudioConverter+PropertiesFromDictionary.h: -------------------------------------------------------------------------------- 1 | #import "ATPAudioConverter.h" 2 | 3 | 4 | @interface ATPAudioConverter (PropertiesFromDictionary) 5 | 6 | - (void)setPropertiesFromDictionary:(NSDictionary *)dictionary error:(NSError **)error; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ATPAudioConverter+PropertiesFromDictionary.m: -------------------------------------------------------------------------------- 1 | #import "ATPAudioConverter+PropertiesFromDictionary.h" 2 | 3 | #import "ATPAudioConverter+Properties.h" 4 | 5 | 6 | @implementation ATPAudioConverter (PropertiesFromDictionary) 7 | 8 | - (void)setPropertiesFromDictionary:(NSDictionary *)dictionary error:(NSError **)error 9 | { 10 | [dictionary enumerateKeysAndObjectsUsingBlock:^(NSNumber *propertyValue, id value, BOOL *stop) { 11 | UInt32 property = propertyValue.intValue; 12 | 13 | if(property == kAudioConverterEncodeBitRate) 14 | { 15 | NSNumber *encodeBitRate = value; 16 | [self setEncodeBitRate:encodeBitRate.intValue error:error]; 17 | } 18 | }]; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ATPAudioConverter.h: -------------------------------------------------------------------------------- 1 | // 2 | // CAPAudioConverter.h 3 | // CoreAudio 4 | // 5 | // Created by Maximilian Christ on 18/01/14. 6 | // Copyright (c) 2014 McZonk. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface ATPAudioConverter : NSObject 14 | 15 | - (instancetype)initWithInputFormat:(const AudioStreamBasicDescription *)inputFormat outputFormat:(const AudioStreamBasicDescription *)outputFormat error:(out NSError **)error; 16 | 17 | @property (nonatomic, assign, readonly) AudioConverterRef AudioConverter; 18 | 19 | - (BOOL)getSize:(out UInt32 *)size writable:(out BOOL *)writable forProperty:(AudioConverterPropertyID)property error:(out NSError **)error; 20 | 21 | - (BOOL)getValue:(out void *)value size:(inout UInt32 *)size forProperty:(AudioConverterPropertyID)property error:(out NSError **)error; 22 | 23 | - (NSData *)dataForProperty:(AudioConverterPropertyID)property error:(NSError **)error; 24 | 25 | - (BOOL)setValue:(const void *)value size:(UInt32)size forProperty:(AudioConverterPropertyID)property error:(out NSError **)error; 26 | 27 | - (BOOL)setData:(NSData *)data forProperty:(AudioConverterPropertyID)property error:(out NSError **)error; 28 | 29 | /** 30 | * Internally calls AudioConverterConvertComplexBuffer. 31 | */ 32 | - (BOOL)convertNumberOfPCMFrames:(UInt32)numberOfPCMFrames inputBufferList:(const AudioBufferList *)inputAudioBufferList outputBufferList:(AudioBufferList *)outputBufferList error:(out NSError **)error; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ATPAudioConverter.m: -------------------------------------------------------------------------------- 1 | // 2 | // CAPAudioConverter.m 3 | // CoreAudio 4 | // 5 | // Created by Maximilian Christ on 18/01/14. 6 | // Copyright (c) 2014 McZonk. All rights reserved. 7 | // 8 | 9 | #import "ATPAudioConverter.h" 10 | 11 | #import "NSError+ATPError.h" 12 | 13 | @interface ATPAudioConverter () { 14 | @protected 15 | AudioConverterRef audioConverter; 16 | } 17 | 18 | @end 19 | 20 | 21 | @implementation ATPAudioConverter 22 | 23 | @synthesize AudioConverter = audioConverter; 24 | 25 | - (instancetype)initWithInputFormat:(const AudioStreamBasicDescription *)inputFormat outputFormat:(const AudioStreamBasicDescription *)outputFormat error:(out NSError **)outError 26 | { 27 | self = [super init]; 28 | if(self != nil) 29 | { 30 | OSStatus status = AudioConverterNew(inputFormat, outputFormat, &audioConverter); 31 | if(status != noErr) 32 | { 33 | NSError *error = [NSError audioToolboxErrorWithStatus:status]; 34 | if(outError != nil) 35 | { 36 | *outError = error; 37 | } 38 | else 39 | { 40 | NSLog(@"%s:%d: %@", __FUNCTION__, __LINE__, error); 41 | } 42 | return nil; 43 | } 44 | } 45 | return self; 46 | } 47 | 48 | - (void)dealloc 49 | { 50 | if(audioConverter != NULL) 51 | { 52 | AudioConverterDispose(audioConverter); 53 | } 54 | } 55 | 56 | - (BOOL)getSize:(out UInt32 *)size writable:(out BOOL *)writable forProperty:(AudioConverterPropertyID)property error:(out NSError **)outError 57 | { 58 | OSStatus status = AudioConverterGetPropertyInfo(audioConverter, property, size, (Boolean *)writable); 59 | if(status != noErr) 60 | { 61 | NSError *error = [NSError audioToolboxErrorWithStatus:status]; 62 | if(outError != nil) 63 | { 64 | *outError = error; 65 | } 66 | else 67 | { 68 | NSLog(@"%s:%d: %@", __FUNCTION__, __LINE__, error); 69 | } 70 | return NO; 71 | } 72 | 73 | return YES; 74 | } 75 | 76 | - (BOOL)getValue:(out void *)value size:(inout UInt32 *)size forProperty:(AudioConverterPropertyID)property error:(out NSError **)outError 77 | { 78 | OSStatus status = AudioConverterGetProperty(audioConverter, property, size, value); 79 | if(status != noErr) 80 | { 81 | NSError *error = [NSError audioToolboxErrorWithStatus:status]; 82 | if(outError != nil) 83 | { 84 | *outError = error; 85 | } 86 | else 87 | { 88 | NSLog(@"%s:%d: %@", __FUNCTION__, __LINE__, error); 89 | } 90 | return NO; 91 | } 92 | 93 | return YES; 94 | } 95 | 96 | - (NSData *)dataForProperty:(AudioConverterPropertyID)property error:(NSError **)error 97 | { 98 | UInt32 size = 0; 99 | if(![self getSize:&size writable:NULL forProperty:property error:error]) 100 | { 101 | return nil; 102 | } 103 | 104 | void *data = malloc(size); 105 | if(![self getValue:data size:&size forProperty:property error:error]) 106 | { 107 | free(data); 108 | return nil; 109 | } 110 | 111 | return [NSData dataWithBytesNoCopy:data length:size freeWhenDone:YES]; 112 | } 113 | 114 | - (BOOL)setValue:(const void *)value size:(UInt32)size forProperty:(AudioConverterPropertyID)property error:(out NSError **)outError 115 | { 116 | OSStatus status = AudioConverterSetProperty(audioConverter, property, size, value); 117 | if(status != noErr) 118 | { 119 | NSError *error = [NSError audioToolboxErrorWithStatus:status]; 120 | if(outError != nil) 121 | { 122 | *outError = error; 123 | } 124 | else 125 | { 126 | NSLog(@"%s:%d: %@", __FUNCTION__, __LINE__, error); 127 | } 128 | return NO; 129 | } 130 | 131 | return YES; 132 | } 133 | 134 | - (BOOL)setData:(NSData *)data forProperty:(AudioConverterPropertyID)property error:(out NSError **)error 135 | { 136 | return [self setValue:data.bytes size:(UInt32)data.length forProperty:property error:error]; 137 | } 138 | 139 | - (BOOL)convertNumberOfPCMFrames:(UInt32)numberOfPCMFrames inputBufferList:(const AudioBufferList *)inputAudioBufferList outputBufferList:(AudioBufferList *)outputBufferList error:(out NSError **)outError 140 | { 141 | OSStatus status = AudioConverterConvertComplexBuffer(audioConverter, numberOfPCMFrames, inputAudioBufferList, outputBufferList); 142 | if (status != noErr) 143 | { 144 | NSError *error = [NSError audioToolboxErrorWithStatus:status]; 145 | if(outError != nil) 146 | { 147 | *outError = error; 148 | } 149 | else 150 | { 151 | NSLog(@"%s:%d: %@", __FUNCTION__, __LINE__, error); 152 | } 153 | return NO; 154 | } 155 | 156 | return YES; 157 | } 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /AudioToolboxPlus Tests/AudioToolboxPlus_Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AudioToolboxPlus_Tests.m 3 | // AudioToolboxPlus Tests 4 | // 5 | // Created by Maximilian Christ on 20/03/15. 6 | // Copyright (c) 2015 McZonk. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "ATPAudioConverter.h" 13 | #import "ATPAudioConverter+Properties.h" 14 | 15 | 16 | @interface AudioToolboxPlus_Tests : XCTestCase 17 | 18 | @end 19 | 20 | @implementation AudioToolboxPlus_Tests 21 | 22 | - (void)setUp 23 | { 24 | [super setUp]; 25 | } 26 | 27 | - (void)tearDown 28 | { 29 | [super tearDown]; 30 | } 31 | 32 | - (void)testFloatToShortPCMConvert 33 | { 34 | AudioStreamBasicDescription inASBD = { 0 }; 35 | inASBD.mSampleRate = 44100.0; 36 | inASBD.mFormatID = kAudioFormatLinearPCM; 37 | inASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 38 | inASBD.mBytesPerPacket = 4; 39 | inASBD.mFramesPerPacket = 1; 40 | inASBD.mBytesPerFrame = 4; 41 | inASBD.mChannelsPerFrame = 1; 42 | inASBD.mBitsPerChannel = 32; 43 | 44 | AudioStreamBasicDescription outASBD = { 0 }; 45 | outASBD.mSampleRate = 44100.0; 46 | outASBD.mFormatID = kAudioFormatLinearPCM; 47 | outASBD.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 48 | outASBD.mBytesPerPacket = 2; 49 | outASBD.mFramesPerPacket = 1; 50 | outASBD.mBytesPerFrame = 2; 51 | outASBD.mChannelsPerFrame = 1; 52 | outASBD.mBitsPerChannel = 16; 53 | 54 | NSError *error = nil; 55 | 56 | ATPAudioConverter *audioConverter = [[ATPAudioConverter alloc] initWithInputFormat:&inASBD outputFormat:&outASBD error:&error]; 57 | XCTAssert(audioConverter != nil); 58 | XCTAssert(error == nil, @"%@", error); 59 | 60 | float inData[2] = { 0.0, 1.0 }; 61 | short convertedData[2] = { 0, 0 }; 62 | short outData[2] = { 0, SHRT_MAX }; 63 | 64 | AudioBufferList inBufferList; 65 | inBufferList.mNumberBuffers = 1; 66 | inBufferList.mBuffers[0].mData = inData; 67 | inBufferList.mBuffers[0].mDataByteSize = sizeof(inData); 68 | inBufferList.mBuffers[0].mNumberChannels = 1; 69 | 70 | AudioBufferList outBufferList; 71 | outBufferList.mNumberBuffers = 1; 72 | outBufferList.mBuffers[0].mData = convertedData; 73 | outBufferList.mBuffers[0].mDataByteSize = sizeof(convertedData); 74 | outBufferList.mBuffers[0].mNumberChannels = 1; 75 | 76 | XCTAssert([audioConverter convertNumberOfPCMFrames:2 inputBufferList:&inBufferList outputBufferList:&outBufferList error:&error]); 77 | XCTAssert(error == nil, @"%@", error); 78 | 79 | XCTAssert(memcmp(convertedData, outData, sizeof(convertedData)) == 0); 80 | } 81 | 82 | - (void)testNonInterleavedFloatToInterleavedFloatPCMConvert 83 | { 84 | AudioStreamBasicDescription inASBD = { 0 }; 85 | inASBD.mSampleRate = 44100.0; 86 | inASBD.mFormatID = kAudioFormatLinearPCM; 87 | inASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; 88 | inASBD.mBytesPerPacket = 4; 89 | inASBD.mFramesPerPacket = 1; 90 | inASBD.mBytesPerFrame = 4; 91 | inASBD.mChannelsPerFrame = 2; 92 | inASBD.mBitsPerChannel = 32; 93 | 94 | AudioStreamBasicDescription outASBD = { 0 }; 95 | outASBD.mSampleRate = 44100.0; 96 | outASBD.mFormatID = kAudioFormatLinearPCM; 97 | outASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 98 | outASBD.mBytesPerPacket = 8; 99 | outASBD.mFramesPerPacket = 1; 100 | outASBD.mBytesPerFrame = 8; 101 | outASBD.mChannelsPerFrame = 2; 102 | outASBD.mBitsPerChannel = 32; 103 | 104 | NSError *error = nil; 105 | 106 | ATPAudioConverter *audioConverter = [[ATPAudioConverter alloc] initWithInputFormat:&inASBD outputFormat:&outASBD error:&error]; 107 | XCTAssert(audioConverter != nil); 108 | XCTAssert(error == nil, @"%@", error); 109 | 110 | float inData0[4] = { 0.0, 0.0, 0.0, 0.0 }; 111 | float inData1[4] = { 1.0, 1.0, 1.0, 1.0 }; 112 | float convertedData[8] = {}; 113 | float outData[8] = { 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 }; 114 | 115 | AudioBufferList *inBufferList = malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)); 116 | inBufferList->mNumberBuffers = 2; 117 | inBufferList->mBuffers[0].mData = inData0; 118 | inBufferList->mBuffers[0].mDataByteSize = sizeof(inData0); 119 | inBufferList->mBuffers[0].mNumberChannels = 1; 120 | inBufferList->mBuffers[1].mData = inData1; 121 | inBufferList->mBuffers[1].mDataByteSize = sizeof(inData1); 122 | inBufferList->mBuffers[1].mNumberChannels = 1; 123 | 124 | AudioBufferList outBufferList; 125 | outBufferList.mNumberBuffers = 1; 126 | outBufferList.mBuffers[0].mData = convertedData; 127 | outBufferList.mBuffers[0].mDataByteSize = sizeof(convertedData); 128 | outBufferList.mBuffers[0].mNumberChannels = 2; 129 | 130 | XCTAssert([audioConverter convertNumberOfPCMFrames:4 inputBufferList:inBufferList outputBufferList:&outBufferList error:&error]); 131 | XCTAssert(error == nil, @"%@", error); 132 | 133 | XCTAssert(memcmp(convertedData, outData, sizeof(convertedData)) == 0); 134 | 135 | free(inBufferList); 136 | } 137 | 138 | - (void)testChannelMap 139 | { 140 | AudioStreamBasicDescription inASBD = { 0 }; 141 | inASBD.mSampleRate = 44100.0; 142 | inASBD.mFormatID = kAudioFormatLinearPCM; 143 | inASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 144 | inASBD.mBytesPerPacket = 16; 145 | inASBD.mFramesPerPacket = 1; 146 | inASBD.mBytesPerFrame = 16; 147 | inASBD.mChannelsPerFrame = 4; 148 | inASBD.mBitsPerChannel = 32; 149 | 150 | AudioStreamBasicDescription outASBD = { 0 }; 151 | outASBD.mSampleRate = 44100.0; 152 | outASBD.mFormatID = kAudioFormatLinearPCM; 153 | outASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 154 | outASBD.mBytesPerPacket = 8; 155 | outASBD.mFramesPerPacket = 1; 156 | outASBD.mBytesPerFrame = 8; 157 | outASBD.mChannelsPerFrame = 2; 158 | outASBD.mBitsPerChannel = 32; 159 | 160 | NSError *error = nil; 161 | 162 | ATPAudioConverter *audioConverter = [[ATPAudioConverter alloc] initWithInputFormat:&inASBD outputFormat:&outASBD error:&error]; 163 | XCTAssert(audioConverter != nil); 164 | XCTAssert(error == nil, @"%@", error); 165 | 166 | float inData[16] = { 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0}; 167 | float convertedData[8] = {}; 168 | float outData[4] = { 1.0, 3.0, 1.0, 3.0 }; 169 | 170 | NSArray *channelMap = @[ @1, @3 ]; 171 | XCTAssert([audioConverter setChannelMap:channelMap error:&error]); 172 | XCTAssert(error == nil, @"%@", error); 173 | 174 | AudioBufferList inBufferList = { 0 }; 175 | inBufferList.mNumberBuffers = 1; 176 | inBufferList.mBuffers[0].mData = inData; 177 | inBufferList.mBuffers[0].mDataByteSize = sizeof(inData); 178 | inBufferList.mBuffers[0].mNumberChannels = 4; 179 | 180 | AudioBufferList outBufferList; 181 | outBufferList.mNumberBuffers = 1; 182 | outBufferList.mBuffers[0].mData = convertedData; 183 | outBufferList.mBuffers[0].mDataByteSize = sizeof(convertedData); 184 | outBufferList.mBuffers[0].mNumberChannels = 2; 185 | 186 | XCTAssert([audioConverter convertNumberOfPCMFrames:4 inputBufferList:&inBufferList outputBufferList:&outBufferList error:&error]); 187 | XCTAssert(error == nil, @"%@", error); 188 | 189 | XCTAssert(memcmp(convertedData, outData, sizeof(convertedData)) == 0); 190 | } 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /AudioToolboxPlus Tests/AudioToolbox_Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AudioToolboxPlus_Tests.m 3 | // AudioToolboxPlus Tests 4 | // 5 | // Created by Maximilian Christ on 20/03/15. 6 | // Copyright (c) 2015 McZonk. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | 14 | @interface AudioToolbox_Tests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation AudioToolbox_Tests 19 | 20 | - (void)setUp 21 | { 22 | [super setUp]; 23 | } 24 | 25 | - (void)tearDown 26 | { 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testFloatToShortPCMConvert 31 | { 32 | AudioStreamBasicDescription inASBD = { 0 }; 33 | inASBD.mSampleRate = 44100.0; 34 | inASBD.mFormatID = kAudioFormatLinearPCM; 35 | inASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 36 | inASBD.mBytesPerPacket = 8; 37 | inASBD.mFramesPerPacket = 1; 38 | inASBD.mBytesPerFrame = 8; 39 | inASBD.mChannelsPerFrame = 2; 40 | inASBD.mBitsPerChannel = 32; 41 | 42 | AudioStreamBasicDescription outASBD = { 0 }; 43 | outASBD.mSampleRate = 44100.0; 44 | outASBD.mFormatID = kAudioFormatLinearPCM; 45 | outASBD.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 46 | outASBD.mBytesPerPacket = 4; 47 | outASBD.mFramesPerPacket = 1; 48 | outASBD.mBytesPerFrame = 4; 49 | outASBD.mChannelsPerFrame = 2; 50 | outASBD.mBitsPerChannel = 16; 51 | 52 | AudioConverterRef audioConverter = NULL; 53 | XCTAssert(AudioConverterNew(&inASBD, &outASBD, &audioConverter) == noErr); 54 | 55 | float inData[4] = { 0.0, 0.0, 1.0, 1.0 }; 56 | short convertedData[4] = { 0, 0, 0, 0 }; 57 | short outData[4] = { 0, 0, SHRT_MAX, SHRT_MAX }; 58 | 59 | AudioBufferList inBufferList; 60 | inBufferList.mNumberBuffers = 1; 61 | inBufferList.mBuffers[0].mData = inData; 62 | inBufferList.mBuffers[0].mDataByteSize = sizeof(inData); 63 | inBufferList.mBuffers[0].mNumberChannels = 2; 64 | 65 | AudioBufferList outBufferList; 66 | outBufferList.mNumberBuffers = 1; 67 | outBufferList.mBuffers[0].mData = convertedData; 68 | outBufferList.mBuffers[0].mDataByteSize = sizeof(convertedData); 69 | outBufferList.mBuffers[0].mNumberChannels = 2; 70 | 71 | XCTAssert(AudioConverterConvertComplexBuffer(audioConverter, 2, &inBufferList, &outBufferList) == noErr); 72 | 73 | XCTAssert(memcmp(convertedData, outData, sizeof(convertedData)) == 0); 74 | 75 | AudioConverterDispose(audioConverter); 76 | } 77 | 78 | - (void)testNonInterleavedFloatToInterleavedFloatPCMConvert 79 | { 80 | AudioStreamBasicDescription inASBD = { 0 }; 81 | inASBD.mSampleRate = 44100.0; 82 | inASBD.mFormatID = kAudioFormatLinearPCM; 83 | inASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; 84 | inASBD.mBytesPerPacket = 4; 85 | inASBD.mFramesPerPacket = 1; 86 | inASBD.mBytesPerFrame = 4; 87 | inASBD.mChannelsPerFrame = 2; 88 | inASBD.mBitsPerChannel = 32; 89 | 90 | AudioStreamBasicDescription outASBD = { 0 }; 91 | outASBD.mSampleRate = 44100.0; 92 | outASBD.mFormatID = kAudioFormatLinearPCM; 93 | outASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 94 | outASBD.mBytesPerPacket = 8; 95 | outASBD.mFramesPerPacket = 1; 96 | outASBD.mBytesPerFrame = 8; 97 | outASBD.mChannelsPerFrame = 2; 98 | outASBD.mBitsPerChannel = 32; 99 | 100 | AudioConverterRef audioConverter = NULL; 101 | XCTAssert(AudioConverterNew(&inASBD, &outASBD, &audioConverter) == noErr); 102 | 103 | float inData0[4] = { 0.0, 0.0, 0.0, 0.0 }; 104 | float inData1[4] = { 1.0, 1.0, 1.0, 1.0 }; 105 | float convertedData[8] = {}; 106 | float outData[8] = { 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 }; 107 | 108 | AudioBufferList *inBufferList = malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)); 109 | inBufferList->mNumberBuffers = 2; 110 | inBufferList->mBuffers[0].mData = inData0; 111 | inBufferList->mBuffers[0].mDataByteSize = sizeof(inData0); 112 | inBufferList->mBuffers[0].mNumberChannels = 1; 113 | inBufferList->mBuffers[1].mData = inData1; 114 | inBufferList->mBuffers[1].mDataByteSize = sizeof(inData1); 115 | inBufferList->mBuffers[1].mNumberChannels = 1; 116 | 117 | AudioBufferList outBufferList; 118 | outBufferList.mNumberBuffers = 1; 119 | outBufferList.mBuffers[0].mData = convertedData; 120 | outBufferList.mBuffers[0].mDataByteSize = sizeof(convertedData); 121 | outBufferList.mBuffers[0].mNumberChannels = 2; 122 | 123 | XCTAssert(AudioConverterConvertComplexBuffer(audioConverter, 4, inBufferList, &outBufferList) == noErr); 124 | 125 | XCTAssert(memcmp(convertedData, outData, sizeof(convertedData)) == 0); 126 | 127 | AudioConverterDispose(audioConverter); 128 | 129 | free(inBufferList); 130 | } 131 | 132 | - (void)testChannelMap 133 | { 134 | AudioStreamBasicDescription inASBD = { 0 }; 135 | inASBD.mSampleRate = 44100.0; 136 | inASBD.mFormatID = kAudioFormatLinearPCM; 137 | inASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 138 | inASBD.mBytesPerPacket = 16; 139 | inASBD.mFramesPerPacket = 1; 140 | inASBD.mBytesPerFrame = 16; 141 | inASBD.mChannelsPerFrame = 4; 142 | inASBD.mBitsPerChannel = 32; 143 | 144 | AudioStreamBasicDescription outASBD = { 0 }; 145 | outASBD.mSampleRate = 44100.0; 146 | outASBD.mFormatID = kAudioFormatLinearPCM; 147 | outASBD.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; 148 | outASBD.mBytesPerPacket = 8; 149 | outASBD.mFramesPerPacket = 1; 150 | outASBD.mBytesPerFrame = 8; 151 | outASBD.mChannelsPerFrame = 2; 152 | outASBD.mBitsPerChannel = 32; 153 | 154 | AudioConverterRef audioConverter = NULL; 155 | XCTAssert(AudioConverterNew(&inASBD, &outASBD, &audioConverter) == noErr); 156 | 157 | float inData[16] = { 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0}; 158 | float convertedData[8] = {}; 159 | float outData[4] = { 1.0, 3.0, 1.0, 3.0 }; 160 | 161 | SInt32 channelMap[2] = { 1, 3 }; 162 | XCTAssert(AudioConverterSetProperty(audioConverter, kAudioConverterChannelMap, sizeof(channelMap), &channelMap) == noErr); 163 | 164 | AudioBufferList inBufferList = { 0 }; 165 | inBufferList.mNumberBuffers = 1; 166 | inBufferList.mBuffers[0].mData = inData; 167 | inBufferList.mBuffers[0].mDataByteSize = sizeof(inData); 168 | inBufferList.mBuffers[0].mNumberChannels = 4; 169 | 170 | AudioBufferList outBufferList; 171 | outBufferList.mNumberBuffers = 1; 172 | outBufferList.mBuffers[0].mData = convertedData; 173 | outBufferList.mBuffers[0].mDataByteSize = sizeof(convertedData); 174 | outBufferList.mBuffers[0].mNumberChannels = 2; 175 | 176 | XCTAssert(AudioConverterConvertComplexBuffer(audioConverter, 4, &inBufferList, &outBufferList) == noErr); 177 | 178 | XCTAssert(memcmp(convertedData, outData, sizeof(convertedData)) == 0); 179 | 180 | AudioConverterDispose(audioConverter); 181 | } 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /AudioToolboxPlus Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /AudioToolboxPlus-OSX.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5024F91318EDC6FD00B96639 /* NSValue+ATPAudioValueRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 5024F91218EDC6FD00B96639 /* NSValue+ATPAudioValueRange.m */; }; 11 | 50A0A11618EAEE2400F20799 /* ATPAudioConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A0A11118EAEE2400F20799 /* ATPAudioConverter.m */; }; 12 | 50A0A11718EAEE2400F20799 /* ATPAudioConverter+Properties.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A0A11318EAEE2400F20799 /* ATPAudioConverter+Properties.m */; }; 13 | 50A0A11818EAEE2400F20799 /* NSError+ATPError.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A0A11518EAEE2400F20799 /* NSError+ATPError.m */; }; 14 | 50A27C1D1ABC7ADB006B3986 /* AudioToolbox_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A27C1C1ABC7ADB006B3986 /* AudioToolbox_Tests.m */; }; 15 | 50A27C1E1ABC7ADB006B3986 /* libAudioToolboxPlus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 50A0A0E118EAED5A00F20799 /* libAudioToolboxPlus.a */; }; 16 | 50A27C251ABC8390006B3986 /* AudioToolboxPlus_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A27C241ABC8390006B3986 /* AudioToolboxPlus_Tests.m */; }; 17 | 50BCFE4718F5621C00227FC0 /* ATPAudioCompressionSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 50BCFE4618F5621C00227FC0 /* ATPAudioCompressionSession.m */; }; 18 | 50BCFE5018F5866000227FC0 /* TPCircularBuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 50BCFE4E18F5866000227FC0 /* TPCircularBuffer.c */; }; 19 | 50BCFE5318F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.c in Sources */ = {isa = PBXBuildFile; fileRef = 50BCFE5118F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.c */; }; 20 | 50FD86271A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 50FD86261A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 50A27C1F1ABC7ADB006B3986 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 50A0A0D918EAED5A00F20799 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 50A0A0E018EAED5A00F20799; 29 | remoteInfo = AudioToolboxPlus; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 5024F91118EDC6FD00B96639 /* NSValue+ATPAudioValueRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+ATPAudioValueRange.h"; sourceTree = ""; }; 35 | 5024F91218EDC6FD00B96639 /* NSValue+ATPAudioValueRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSValue+ATPAudioValueRange.m"; sourceTree = ""; }; 36 | 50A0A0E118EAED5A00F20799 /* libAudioToolboxPlus.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAudioToolboxPlus.a; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 50A0A10C18EAEDBE00F20799 /* AudioToolboxPlus-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AudioToolboxPlus-Prefix.pch"; sourceTree = ""; }; 38 | 50A0A10D18EAEDBE00F20799 /* AudioToolboxPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioToolboxPlus.h; sourceTree = ""; }; 39 | 50A0A11018EAEE2400F20799 /* ATPAudioConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ATPAudioConverter.h; sourceTree = ""; }; 40 | 50A0A11118EAEE2400F20799 /* ATPAudioConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ATPAudioConverter.m; sourceTree = ""; }; 41 | 50A0A11218EAEE2400F20799 /* ATPAudioConverter+Properties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ATPAudioConverter+Properties.h"; sourceTree = ""; }; 42 | 50A0A11318EAEE2400F20799 /* ATPAudioConverter+Properties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ATPAudioConverter+Properties.m"; sourceTree = ""; }; 43 | 50A0A11418EAEE2400F20799 /* NSError+ATPError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+ATPError.h"; sourceTree = ""; }; 44 | 50A0A11518EAEE2400F20799 /* NSError+ATPError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+ATPError.m"; sourceTree = ""; }; 45 | 50A27C181ABC7ADB006B3986 /* AudioToolboxPlus Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AudioToolboxPlus Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 50A27C1B1ABC7ADB006B3986 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 50A27C1C1ABC7ADB006B3986 /* AudioToolbox_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioToolbox_Tests.m; sourceTree = ""; }; 48 | 50A27C241ABC8390006B3986 /* AudioToolboxPlus_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioToolboxPlus_Tests.m; sourceTree = ""; }; 49 | 50BCFE4518F5621C00227FC0 /* ATPAudioCompressionSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ATPAudioCompressionSession.h; sourceTree = ""; }; 50 | 50BCFE4618F5621C00227FC0 /* ATPAudioCompressionSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ATPAudioCompressionSession.m; sourceTree = ""; }; 51 | 50BCFE4E18F5866000227FC0 /* TPCircularBuffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = TPCircularBuffer.c; sourceTree = ""; }; 52 | 50BCFE4F18F5866000227FC0 /* TPCircularBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPCircularBuffer.h; sourceTree = ""; }; 53 | 50BCFE5118F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "TPCircularBuffer+AudioBufferList.c"; sourceTree = ""; }; 54 | 50BCFE5218F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TPCircularBuffer+AudioBufferList.h"; sourceTree = ""; }; 55 | 50FD86251A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ATPAudioConverter+PropertiesFromDictionary.h"; sourceTree = ""; }; 56 | 50FD86261A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ATPAudioConverter+PropertiesFromDictionary.m"; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | 50A27C151ABC7ADB006B3986 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 50A27C1E1ABC7ADB006B3986 /* libAudioToolboxPlus.a in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 50A0A0D818EAED5A00F20799 = { 72 | isa = PBXGroup; 73 | children = ( 74 | 50A0A10B18EAEDB300F20799 /* AudioToolboxPlus */, 75 | 50A27C191ABC7ADB006B3986 /* AudioToolboxPlus Tests */, 76 | 50A0A0E318EAED5A00F20799 /* Frameworks */, 77 | 50A0A0E218EAED5A00F20799 /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 50A0A0E218EAED5A00F20799 /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 50A0A0E118EAED5A00F20799 /* libAudioToolboxPlus.a */, 85 | 50A27C181ABC7ADB006B3986 /* AudioToolboxPlus Tests.xctest */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 50A0A0E318EAED5A00F20799 /* Frameworks */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | ); 94 | name = Frameworks; 95 | sourceTree = ""; 96 | }; 97 | 50A0A10B18EAEDB300F20799 /* AudioToolboxPlus */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 50A0A10C18EAEDBE00F20799 /* AudioToolboxPlus-Prefix.pch */, 101 | 50A0A10D18EAEDBE00F20799 /* AudioToolboxPlus.h */, 102 | 50A0A11018EAEE2400F20799 /* ATPAudioConverter.h */, 103 | 50A0A11118EAEE2400F20799 /* ATPAudioConverter.m */, 104 | 50A0A11218EAEE2400F20799 /* ATPAudioConverter+Properties.h */, 105 | 50A0A11318EAEE2400F20799 /* ATPAudioConverter+Properties.m */, 106 | 50FD86251A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.h */, 107 | 50FD86261A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.m */, 108 | 50BCFE4518F5621C00227FC0 /* ATPAudioCompressionSession.h */, 109 | 50BCFE4618F5621C00227FC0 /* ATPAudioCompressionSession.m */, 110 | 50A0A11418EAEE2400F20799 /* NSError+ATPError.h */, 111 | 50A0A11518EAEE2400F20799 /* NSError+ATPError.m */, 112 | 5024F91118EDC6FD00B96639 /* NSValue+ATPAudioValueRange.h */, 113 | 5024F91218EDC6FD00B96639 /* NSValue+ATPAudioValueRange.m */, 114 | 50BCFE4F18F5866000227FC0 /* TPCircularBuffer.h */, 115 | 50BCFE4E18F5866000227FC0 /* TPCircularBuffer.c */, 116 | 50BCFE5218F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.h */, 117 | 50BCFE5118F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.c */, 118 | ); 119 | name = AudioToolboxPlus; 120 | sourceTree = ""; 121 | }; 122 | 50A27C191ABC7ADB006B3986 /* AudioToolboxPlus Tests */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 50A27C1C1ABC7ADB006B3986 /* AudioToolbox_Tests.m */, 126 | 50A27C241ABC8390006B3986 /* AudioToolboxPlus_Tests.m */, 127 | 50A27C1A1ABC7ADB006B3986 /* Supporting Files */, 128 | ); 129 | path = "AudioToolboxPlus Tests"; 130 | sourceTree = ""; 131 | }; 132 | 50A27C1A1ABC7ADB006B3986 /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 50A27C1B1ABC7ADB006B3986 /* Info.plist */, 136 | ); 137 | name = "Supporting Files"; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | 50A0A0E018EAED5A00F20799 /* AudioToolboxPlus */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 50A0A10518EAED5A00F20799 /* Build configuration list for PBXNativeTarget "AudioToolboxPlus" */; 146 | buildPhases = ( 147 | 50A0A0DD18EAED5A00F20799 /* Sources */, 148 | ); 149 | buildRules = ( 150 | ); 151 | dependencies = ( 152 | ); 153 | name = AudioToolboxPlus; 154 | productName = AudioToolboxPlus; 155 | productReference = 50A0A0E118EAED5A00F20799 /* libAudioToolboxPlus.a */; 156 | productType = "com.apple.product-type.library.static"; 157 | }; 158 | 50A27C171ABC7ADB006B3986 /* AudioToolboxPlus Tests */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = 50A27C231ABC7ADB006B3986 /* Build configuration list for PBXNativeTarget "AudioToolboxPlus Tests" */; 161 | buildPhases = ( 162 | 50A27C141ABC7ADB006B3986 /* Sources */, 163 | 50A27C151ABC7ADB006B3986 /* Frameworks */, 164 | 50A27C161ABC7ADB006B3986 /* Resources */, 165 | ); 166 | buildRules = ( 167 | ); 168 | dependencies = ( 169 | 50A27C201ABC7ADB006B3986 /* PBXTargetDependency */, 170 | ); 171 | name = "AudioToolboxPlus Tests"; 172 | productName = "AudioToolboxPlus Tests"; 173 | productReference = 50A27C181ABC7ADB006B3986 /* AudioToolboxPlus Tests.xctest */; 174 | productType = "com.apple.product-type.bundle.unit-test"; 175 | }; 176 | /* End PBXNativeTarget section */ 177 | 178 | /* Begin PBXProject section */ 179 | 50A0A0D918EAED5A00F20799 /* Project object */ = { 180 | isa = PBXProject; 181 | attributes = { 182 | LastUpgradeCheck = 0700; 183 | ORGANIZATIONNAME = McZonk; 184 | TargetAttributes = { 185 | 50A27C171ABC7ADB006B3986 = { 186 | CreatedOnToolsVersion = 6.1.1; 187 | }; 188 | }; 189 | }; 190 | buildConfigurationList = 50A0A0DC18EAED5A00F20799 /* Build configuration list for PBXProject "AudioToolboxPlus-OSX" */; 191 | compatibilityVersion = "Xcode 3.2"; 192 | developmentRegion = English; 193 | hasScannedForEncodings = 0; 194 | knownRegions = ( 195 | en, 196 | ); 197 | mainGroup = 50A0A0D818EAED5A00F20799; 198 | productRefGroup = 50A0A0E218EAED5A00F20799 /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | 50A0A0E018EAED5A00F20799 /* AudioToolboxPlus */, 203 | 50A27C171ABC7ADB006B3986 /* AudioToolboxPlus Tests */, 204 | ); 205 | }; 206 | /* End PBXProject section */ 207 | 208 | /* Begin PBXResourcesBuildPhase section */ 209 | 50A27C161ABC7ADB006B3986 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 50A0A0DD18EAED5A00F20799 /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 50BCFE5318F58C4400227FC0 /* TPCircularBuffer+AudioBufferList.c in Sources */, 224 | 50BCFE5018F5866000227FC0 /* TPCircularBuffer.c in Sources */, 225 | 50BCFE4718F5621C00227FC0 /* ATPAudioCompressionSession.m in Sources */, 226 | 50A0A11618EAEE2400F20799 /* ATPAudioConverter.m in Sources */, 227 | 5024F91318EDC6FD00B96639 /* NSValue+ATPAudioValueRange.m in Sources */, 228 | 50A0A11718EAEE2400F20799 /* ATPAudioConverter+Properties.m in Sources */, 229 | 50A0A11818EAEE2400F20799 /* NSError+ATPError.m in Sources */, 230 | 50FD86271A3F732B00D804AC /* ATPAudioConverter+PropertiesFromDictionary.m in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | 50A27C141ABC7ADB006B3986 /* Sources */ = { 235 | isa = PBXSourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 50A27C251ABC8390006B3986 /* AudioToolboxPlus_Tests.m in Sources */, 239 | 50A27C1D1ABC7ADB006B3986 /* AudioToolbox_Tests.m in Sources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | /* End PBXSourcesBuildPhase section */ 244 | 245 | /* Begin PBXTargetDependency section */ 246 | 50A27C201ABC7ADB006B3986 /* PBXTargetDependency */ = { 247 | isa = PBXTargetDependency; 248 | target = 50A0A0E018EAED5A00F20799 /* AudioToolboxPlus */; 249 | targetProxy = 50A27C1F1ABC7ADB006B3986 /* PBXContainerItemProxy */; 250 | }; 251 | /* End PBXTargetDependency section */ 252 | 253 | /* Begin XCBuildConfiguration section */ 254 | 50A0A10318EAED5A00F20799 /* Debug */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INT_CONVERSION = YES; 268 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | COPY_PHASE_STRIP = NO; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | MACOSX_DEPLOYMENT_TARGET = 10.9; 288 | ONLY_ACTIVE_ARCH = YES; 289 | SDKROOT = macosx; 290 | SKIP_INSTALL = YES; 291 | }; 292 | name = Debug; 293 | }; 294 | 50A0A10418EAED5A00F20799 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 299 | CLANG_CXX_LIBRARY = "libc++"; 300 | CLANG_ENABLE_MODULES = YES; 301 | CLANG_ENABLE_OBJC_ARC = YES; 302 | CLANG_WARN_BOOL_CONVERSION = YES; 303 | CLANG_WARN_CONSTANT_CONVERSION = YES; 304 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 305 | CLANG_WARN_EMPTY_BODY = YES; 306 | CLANG_WARN_ENUM_CONVERSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | COPY_PHASE_STRIP = YES; 311 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 312 | ENABLE_NS_ASSERTIONS = NO; 313 | GCC_C_LANGUAGE_STANDARD = gnu99; 314 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | MACOSX_DEPLOYMENT_TARGET = 10.9; 322 | SDKROOT = macosx; 323 | SKIP_INSTALL = YES; 324 | }; 325 | name = Release; 326 | }; 327 | 50A0A10618EAED5A00F20799 /* Debug */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | COMBINE_HIDPI_IMAGES = YES; 331 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 332 | GCC_PREFIX_HEADER = "AudioToolboxPlus-Prefix.pch"; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | }; 335 | name = Debug; 336 | }; 337 | 50A0A10718EAED5A00F20799 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | COMBINE_HIDPI_IMAGES = YES; 341 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 342 | GCC_PREFIX_HEADER = "AudioToolboxPlus-Prefix.pch"; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | }; 345 | name = Release; 346 | }; 347 | 50A27C211ABC7ADB006B3986 /* Debug */ = { 348 | isa = XCBuildConfiguration; 349 | buildSettings = { 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | COMBINE_HIDPI_IMAGES = YES; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | FRAMEWORK_SEARCH_PATHS = ( 354 | "$(DEVELOPER_FRAMEWORKS_DIR)", 355 | "$(inherited)", 356 | ); 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "DEBUG=1", 359 | "$(inherited)", 360 | ); 361 | INFOPLIST_FILE = "AudioToolboxPlus Tests/Info.plist"; 362 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 363 | MACOSX_DEPLOYMENT_TARGET = 10.10; 364 | MTL_ENABLE_DEBUG_INFO = YES; 365 | OTHER_LDFLAGS = ( 366 | "-ObjC", 367 | "-all_load", 368 | ); 369 | PRODUCT_BUNDLE_IDENTIFIER = "de.mczonk.$(PRODUCT_NAME:rfc1034identifier)"; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | }; 372 | name = Debug; 373 | }; 374 | 50A27C221ABC7ADB006B3986 /* Release */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | COMBINE_HIDPI_IMAGES = YES; 379 | ENABLE_STRICT_OBJC_MSGSEND = YES; 380 | FRAMEWORK_SEARCH_PATHS = ( 381 | "$(DEVELOPER_FRAMEWORKS_DIR)", 382 | "$(inherited)", 383 | ); 384 | INFOPLIST_FILE = "AudioToolboxPlus Tests/Info.plist"; 385 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 386 | MACOSX_DEPLOYMENT_TARGET = 10.10; 387 | MTL_ENABLE_DEBUG_INFO = NO; 388 | OTHER_LDFLAGS = ( 389 | "-ObjC", 390 | "-all_load", 391 | ); 392 | PRODUCT_BUNDLE_IDENTIFIER = "de.mczonk.$(PRODUCT_NAME:rfc1034identifier)"; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | }; 395 | name = Release; 396 | }; 397 | /* End XCBuildConfiguration section */ 398 | 399 | /* Begin XCConfigurationList section */ 400 | 50A0A0DC18EAED5A00F20799 /* Build configuration list for PBXProject "AudioToolboxPlus-OSX" */ = { 401 | isa = XCConfigurationList; 402 | buildConfigurations = ( 403 | 50A0A10318EAED5A00F20799 /* Debug */, 404 | 50A0A10418EAED5A00F20799 /* Release */, 405 | ); 406 | defaultConfigurationIsVisible = 0; 407 | defaultConfigurationName = Release; 408 | }; 409 | 50A0A10518EAED5A00F20799 /* Build configuration list for PBXNativeTarget "AudioToolboxPlus" */ = { 410 | isa = XCConfigurationList; 411 | buildConfigurations = ( 412 | 50A0A10618EAED5A00F20799 /* Debug */, 413 | 50A0A10718EAED5A00F20799 /* Release */, 414 | ); 415 | defaultConfigurationIsVisible = 0; 416 | defaultConfigurationName = Release; 417 | }; 418 | 50A27C231ABC7ADB006B3986 /* Build configuration list for PBXNativeTarget "AudioToolboxPlus Tests" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | 50A27C211ABC7ADB006B3986 /* Debug */, 422 | 50A27C221ABC7ADB006B3986 /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | /* End XCConfigurationList section */ 428 | }; 429 | rootObject = 50A0A0D918EAED5A00F20799 /* Project object */; 430 | } 431 | -------------------------------------------------------------------------------- /AudioToolboxPlus-OSX.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AudioToolboxPlus-Prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #import 4 | #endif 5 | -------------------------------------------------------------------------------- /AudioToolboxPlus.h: -------------------------------------------------------------------------------- 1 | #import "ATPAudioCompressionSession.h" 2 | 3 | #import "ATPAudioConverter.h" 4 | #import "ATPAudioConverter+Properties.h" 5 | #import "ATPAudioConverter+PropertiesFromDictionary.h" 6 | 7 | #import "NSError+ATPError.h" 8 | 9 | #import "NSValue+ATPAudioValueRange.h" 10 | 11 | #import "TPCircularBuffer.h" 12 | -------------------------------------------------------------------------------- /NSError+ATPError.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+CAPError.h 3 | // CoreAudio 4 | // 5 | // Created by Maximilian Christ on 18/01/14. 6 | // Copyright (c) 2014 McZonk. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString * const ATPErrorDomain; 12 | 13 | 14 | @interface NSError (ATPError) 15 | 16 | + (instancetype)audioToolboxErrorWithStatus:(OSStatus)status; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /NSError+ATPError.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+CAPError.m 3 | // CoreAudio 4 | // 5 | // Created by Maximilian Christ on 18/01/14. 6 | // Copyright (c) 2014 McZonk. All rights reserved. 7 | // 8 | 9 | #import "NSError+ATPError.h" 10 | 11 | NSString * const ATPErrorDomain = @"AudioToolboxError"; 12 | 13 | 14 | static NSString * DescriptionForStatus(OSStatus status) 15 | { 16 | switch(status) 17 | { 18 | 19 | default: 20 | return @"Unknown Error"; 21 | } 22 | } 23 | 24 | @implementation NSError (ATPError) 25 | 26 | + (instancetype)audioToolboxErrorWithStatus:(OSStatus)status 27 | { 28 | if(status == noErr) 29 | { 30 | return nil; 31 | } 32 | 33 | NSString *description = DescriptionForStatus(status); 34 | 35 | NSDictionary *userInfo = @{ 36 | NSLocalizedDescriptionKey: description, 37 | }; 38 | 39 | return [self errorWithDomain:ATPErrorDomain code:(NSInteger)status userInfo:userInfo]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /NSValue+ATPAudioValueRange.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | 5 | 6 | @interface NSValue (ATPAudioValueRange) 7 | 8 | + (NSValue *)valueWithAudioValueRange:(AudioValueRange)audioValueRange; 9 | 10 | - (AudioValueRange)audioValueRange; 11 | 12 | - (Float64)audioValueRangeMinimum; 13 | - (Float64)audioValueRangeMaximum; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSValue+ATPAudioValueRange.m: -------------------------------------------------------------------------------- 1 | #import "NSValue+ATPAudioValueRange.h" 2 | 3 | 4 | 5 | @implementation NSValue (ATPAudioValueRange) 6 | 7 | + (NSValue *)valueWithAudioValueRange:(AudioValueRange)audioValueRange 8 | { 9 | return [self value:&audioValueRange withObjCType:@encode(AudioValueRange)]; 10 | } 11 | 12 | - (AudioValueRange)audioValueRange 13 | { 14 | AudioValueRange audioValueRange; 15 | [self getValue:&audioValueRange]; 16 | return audioValueRange; 17 | } 18 | 19 | - (Float64)audioValueRangeMinimum 20 | { 21 | AudioValueRange audioValueRange = self.audioValueRange; 22 | return audioValueRange.mMinimum; 23 | } 24 | 25 | - (Float64)audioValueRangeMaximum 26 | { 27 | AudioValueRange audioValueRange = self.audioValueRange; 28 | return audioValueRange.mMaximum; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /TPCircularBuffer+AudioBufferList.c: -------------------------------------------------------------------------------- 1 | // 2 | // TPCircularBuffer+AudioBufferList.c 3 | // Circular/Ring buffer implementation 4 | // 5 | // https://github.com/michaeltyson/TPCircularBuffer 6 | // 7 | // Created by Michael Tyson on 20/03/2012. 8 | // 9 | // Copyright (C) 2012-2013 A Tasty Pixel 10 | // 11 | // This software is provided 'as-is', without any express or implied 12 | // warranty. In no event will the authors be held liable for any damages 13 | // arising from the use of this software. 14 | // 15 | // Permission is granted to anyone to use this software for any purpose, 16 | // including commercial applications, and to alter it and redistribute it 17 | // freely, subject to the following restrictions: 18 | // 19 | // 1. The origin of this software must not be misrepresented; you must not 20 | // claim that you wrote the original software. If you use this software 21 | // in a product, an acknowledgment in the product documentation would be 22 | // appreciated but is not required. 23 | // 24 | // 2. Altered source versions must be plainly marked as such, and must not be 25 | // misrepresented as being the original software. 26 | // 27 | // 3. This notice may not be removed or altered from any source distribution. 28 | // 29 | 30 | #include "TPCircularBuffer+AudioBufferList.h" 31 | #import 32 | 33 | static double __secondsToHostTicks = 0.0; 34 | 35 | static inline long align16byte(long val) { 36 | if ( val & (16-1) ) { 37 | return val + (16 - (val & (16-1))); 38 | } 39 | return val; 40 | } 41 | 42 | static inline long min(long a, long b) { 43 | return a > b ? b : a; 44 | } 45 | 46 | AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferList(TPCircularBuffer *buffer, int numberOfBuffers, int bytesPerBuffer, const AudioTimeStamp *inTimestamp) { 47 | int32_t availableBytes; 48 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); 49 | if ( !block || availableBytes < sizeof(TPCircularBufferABLBlockHeader)+((numberOfBuffers-1)*sizeof(AudioBuffer))+(numberOfBuffers*bytesPerBuffer) ) return NULL; 50 | 51 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 52 | 53 | if ( inTimestamp ) { 54 | memcpy(&block->timestamp, inTimestamp, sizeof(AudioTimeStamp)); 55 | } else { 56 | memset(&block->timestamp, 0, sizeof(AudioTimeStamp)); 57 | } 58 | 59 | memset(&block->bufferList, 0, sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer))); 60 | block->bufferList.mNumberBuffers = numberOfBuffers; 61 | 62 | char *dataPtr = (char*)&block->bufferList + sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer)); 63 | for ( int i=0; i availableBytes ) { 68 | return NULL; 69 | } 70 | 71 | block->bufferList.mBuffers[i].mData = dataPtr; 72 | block->bufferList.mBuffers[i].mDataByteSize = bytesPerBuffer; 73 | block->bufferList.mBuffers[i].mNumberChannels = 1; 74 | 75 | dataPtr += bytesPerBuffer; 76 | } 77 | 78 | // Make sure whole buffer (including timestamp and length value) is 16-byte aligned in length 79 | block->totalLength = (UInt32)align16byte(dataPtr - (char*)block); 80 | if ( block->totalLength > availableBytes ) { 81 | return NULL; 82 | } 83 | 84 | return &block->bufferList; 85 | } 86 | 87 | AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat, UInt32 frameCount, const AudioTimeStamp *timestamp) { 88 | return TPCircularBufferPrepareEmptyAudioBufferList(buffer, 89 | (audioFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? audioFormat->mChannelsPerFrame : 1, 90 | audioFormat->mBytesPerFrame * frameCount, 91 | timestamp); 92 | } 93 | 94 | void TPCircularBufferProduceAudioBufferList(TPCircularBuffer *buffer, const AudioTimeStamp *inTimestamp) { 95 | int32_t availableBytes; 96 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); 97 | 98 | assert(block); 99 | 100 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 101 | 102 | if ( inTimestamp ) { 103 | memcpy(&block->timestamp, inTimestamp, sizeof(AudioTimeStamp)); 104 | } 105 | 106 | UInt32 calculatedLength = (UInt32)(((char*)block->bufferList.mBuffers[block->bufferList.mNumberBuffers-1].mData + block->bufferList.mBuffers[block->bufferList.mNumberBuffers-1].mDataByteSize) - (char*)block); 107 | 108 | // Make sure whole buffer (including timestamp and length value) is 16-byte aligned in length 109 | calculatedLength = (UInt32)align16byte(calculatedLength); 110 | 111 | assert(calculatedLength <= block->totalLength && calculatedLength <= availableBytes); 112 | 113 | block->totalLength = calculatedLength; 114 | 115 | TPCircularBufferProduce(buffer, block->totalLength); 116 | } 117 | 118 | bool TPCircularBufferCopyAudioBufferList(TPCircularBuffer *buffer, const AudioBufferList *inBufferList, const AudioTimeStamp *inTimestamp, UInt32 frames, const AudioStreamBasicDescription *audioDescription) { 119 | if ( frames == 0 ) return true; 120 | 121 | int byteCount = inBufferList->mBuffers[0].mDataByteSize; 122 | if ( frames != kTPCircularBufferCopyAll ) { 123 | byteCount = frames * audioDescription->mBytesPerFrame; 124 | assert(byteCount <= inBufferList->mBuffers[0].mDataByteSize); 125 | } 126 | 127 | if ( byteCount == 0 ) return true; 128 | 129 | AudioBufferList *bufferList = TPCircularBufferPrepareEmptyAudioBufferList(buffer, inBufferList->mNumberBuffers, byteCount, inTimestamp); 130 | if ( !bufferList ) return false; 131 | 132 | for ( int i=0; imNumberBuffers; i++ ) { 133 | memcpy(bufferList->mBuffers[i].mData, inBufferList->mBuffers[i].mData, byteCount); 134 | } 135 | 136 | TPCircularBufferProduceAudioBufferList(buffer, NULL); 137 | 138 | return true; 139 | } 140 | 141 | AudioBufferList *TPCircularBufferNextBufferListAfter(TPCircularBuffer *buffer, AudioBufferList *bufferList, AudioTimeStamp *outTimestamp) { 142 | int32_t availableBytes; 143 | void *tail = TPCircularBufferTail(buffer, &availableBytes); 144 | void *end = (char*)tail + availableBytes; 145 | assert((void*)bufferList > (void*)tail && (void*)bufferList < end); 146 | 147 | TPCircularBufferABLBlockHeader *originalBlock = (TPCircularBufferABLBlockHeader*)((char*)bufferList - offsetof(TPCircularBufferABLBlockHeader, bufferList)); 148 | assert(!((unsigned long)originalBlock & 0xF) /* Beware unaligned accesses */); 149 | 150 | 151 | TPCircularBufferABLBlockHeader *nextBlock = (TPCircularBufferABLBlockHeader*)((char*)originalBlock + originalBlock->totalLength); 152 | if ( (void*)nextBlock >= end ) return NULL; 153 | assert(!((unsigned long)nextBlock & 0xF) /* Beware unaligned accesses */); 154 | 155 | if ( outTimestamp ) { 156 | memcpy(outTimestamp, &nextBlock->timestamp, sizeof(AudioTimeStamp)); 157 | } 158 | 159 | return &nextBlock->bufferList; 160 | } 161 | 162 | void TPCircularBufferConsumeNextBufferListPartial(TPCircularBuffer *buffer, int framesToConsume, const AudioStreamBasicDescription *audioFormat) { 163 | assert(framesToConsume >= 0); 164 | 165 | int32_t dontcare; 166 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); 167 | if ( !block ) return; 168 | assert(!((unsigned long)block & 0xF)); // Beware unaligned accesses 169 | 170 | int bytesToConsume = (int)min(framesToConsume * audioFormat->mBytesPerFrame, block->bufferList.mBuffers[0].mDataByteSize); 171 | 172 | if ( bytesToConsume == block->bufferList.mBuffers[0].mDataByteSize ) { 173 | TPCircularBufferConsumeNextBufferList(buffer); 174 | return; 175 | } 176 | 177 | for ( int i=0; ibufferList.mNumberBuffers; i++ ) { 178 | assert(bytesToConsume <= block->bufferList.mBuffers[i].mDataByteSize); 179 | 180 | block->bufferList.mBuffers[i].mData = (char*)block->bufferList.mBuffers[i].mData + bytesToConsume; 181 | block->bufferList.mBuffers[i].mDataByteSize -= bytesToConsume; 182 | } 183 | 184 | if ( block->timestamp.mFlags & kAudioTimeStampSampleTimeValid ) { 185 | block->timestamp.mSampleTime += framesToConsume; 186 | } 187 | if ( block->timestamp.mFlags & kAudioTimeStampHostTimeValid ) { 188 | if ( !__secondsToHostTicks ) { 189 | mach_timebase_info_data_t tinfo; 190 | mach_timebase_info(&tinfo); 191 | __secondsToHostTicks = 1.0 / (((double)tinfo.numer / tinfo.denom) * 1.0e-9); 192 | } 193 | 194 | block->timestamp.mHostTime += ((double)framesToConsume / audioFormat->mSampleRate) * __secondsToHostTicks; 195 | } 196 | 197 | // Reposition block forward, just before the audio data, ensuring 16-byte alignment 198 | TPCircularBufferABLBlockHeader *newBlock = (TPCircularBufferABLBlockHeader*)(((unsigned long)block + bytesToConsume) & ~0xFul); 199 | memmove(newBlock, block, sizeof(TPCircularBufferABLBlockHeader) + (block->bufferList.mNumberBuffers-1)*sizeof(AudioBuffer)); 200 | int32_t bytesFreed = (int32_t)newBlock - (int32_t)block; 201 | newBlock->totalLength -= bytesFreed; 202 | TPCircularBufferConsume(buffer, bytesFreed); 203 | } 204 | 205 | void TPCircularBufferDequeueBufferListFrames(TPCircularBuffer *buffer, UInt32 *ioLengthInFrames, AudioBufferList *outputBufferList, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat) { 206 | bool hasTimestamp = false; 207 | UInt32 bytesToGo = *ioLengthInFrames * audioFormat->mBytesPerFrame; 208 | UInt32 bytesCopied = 0; 209 | while ( bytesToGo > 0 ) { 210 | AudioBufferList *bufferList = TPCircularBufferNextBufferList(buffer, !hasTimestamp ? outTimestamp : NULL); 211 | if ( !bufferList ) break; 212 | 213 | hasTimestamp = true; 214 | long bytesToCopy = min(bytesToGo, bufferList->mBuffers[0].mDataByteSize); 215 | 216 | if ( outputBufferList ) { 217 | for ( int i=0; imNumberBuffers; i++ ) { 218 | assert(bytesCopied + bytesToCopy <= outputBufferList->mBuffers[i].mDataByteSize); 219 | memcpy((char*)outputBufferList->mBuffers[i].mData + bytesCopied, bufferList->mBuffers[i].mData, bytesToCopy); 220 | } 221 | } 222 | 223 | TPCircularBufferConsumeNextBufferListPartial(buffer, (int)bytesToCopy/audioFormat->mBytesPerFrame, audioFormat); 224 | 225 | bytesToGo -= bytesToCopy; 226 | bytesCopied += bytesToCopy; 227 | } 228 | 229 | *ioLengthInFrames -= bytesToGo / audioFormat->mBytesPerFrame; 230 | } 231 | 232 | static UInt32 _TPCircularBufferPeek(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime) { 233 | int32_t availableBytes; 234 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &availableBytes); 235 | if ( !block ) return 0; 236 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 237 | 238 | if ( outTimestamp ) { 239 | memcpy(outTimestamp, &block->timestamp, sizeof(AudioTimeStamp)); 240 | } 241 | 242 | void *end = (char*)block + availableBytes; 243 | 244 | UInt32 byteCount = 0; 245 | 246 | while ( 1 ) { 247 | byteCount += block->bufferList.mBuffers[0].mDataByteSize; 248 | TPCircularBufferABLBlockHeader *nextBlock = (TPCircularBufferABLBlockHeader*)((char*)block + block->totalLength); 249 | if ( (void*)nextBlock >= end || 250 | (contiguousToleranceSampleTime != UINT32_MAX 251 | && fabs(nextBlock->timestamp.mSampleTime - (block->timestamp.mSampleTime + (block->bufferList.mBuffers[0].mDataByteSize / audioFormat->mBytesPerFrame))) > contiguousToleranceSampleTime) ) { 252 | break; 253 | } 254 | assert(!((unsigned long)nextBlock & 0xF) /* Beware unaligned accesses */); 255 | block = nextBlock; 256 | } 257 | 258 | return byteCount / audioFormat->mBytesPerFrame; 259 | } 260 | 261 | UInt32 TPCircularBufferPeek(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat) { 262 | return _TPCircularBufferPeek(buffer, outTimestamp, audioFormat, UINT32_MAX); 263 | } 264 | 265 | UInt32 TPCircularBufferPeekContiguous(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime) { 266 | return _TPCircularBufferPeek(buffer, outTimestamp, audioFormat, contiguousToleranceSampleTime); 267 | } 268 | 269 | UInt32 TPCircularBufferGetAvailableSpace(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat) { 270 | // Look at buffer head; make sure there's space for the block metadata 271 | int32_t availableBytes; 272 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); 273 | if ( !block ) return 0; 274 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 275 | 276 | // Now find out how much 16-byte aligned audio we can store in the space available 277 | int numberOfBuffers = audioFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved ? audioFormat->mChannelsPerFrame : 1; 278 | char * endOfBuffer = (char*)block + availableBytes; 279 | char * dataPtr = (char*)align16byte((long)(&block->bufferList + sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer)))); 280 | if ( dataPtr >= endOfBuffer ) return 0; 281 | int32_t availableAudioBytes = (int)(endOfBuffer - dataPtr); 282 | 283 | int32_t availableAudioBytesPerBuffer = availableAudioBytes / numberOfBuffers; 284 | availableAudioBytesPerBuffer -= (availableAudioBytesPerBuffer % (16-1)); 285 | 286 | return availableAudioBytesPerBuffer > 0 ? availableAudioBytesPerBuffer / audioFormat->mBytesPerFrame : 0; 287 | } 288 | -------------------------------------------------------------------------------- /TPCircularBuffer+AudioBufferList.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPCircularBuffer+AudioBufferList.h 3 | // Circular/Ring buffer implementation 4 | // 5 | // https://github.com/michaeltyson/TPCircularBuffer 6 | // 7 | // Created by Michael Tyson on 20/03/2012. 8 | // 9 | // Copyright (C) 2012-2013 A Tasty Pixel 10 | // 11 | // This software is provided 'as-is', without any express or implied 12 | // warranty. In no event will the authors be held liable for any damages 13 | // arising from the use of this software. 14 | // 15 | // Permission is granted to anyone to use this software for any purpose, 16 | // including commercial applications, and to alter it and redistribute it 17 | // freely, subject to the following restrictions: 18 | // 19 | // 1. The origin of this software must not be misrepresented; you must not 20 | // claim that you wrote the original software. If you use this software 21 | // in a product, an acknowledgment in the product documentation would be 22 | // appreciated but is not required. 23 | // 24 | // 2. Altered source versions must be plainly marked as such, and must not be 25 | // misrepresented as being the original software. 26 | // 27 | // 3. This notice may not be removed or altered from any source distribution. 28 | // 29 | 30 | #ifndef TPCircularBuffer_AudioBufferList_h 31 | #define TPCircularBuffer_AudioBufferList_h 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | #include "TPCircularBuffer.h" 38 | #include 39 | 40 | #define kTPCircularBufferCopyAll UINT32_MAX 41 | 42 | typedef struct { 43 | AudioTimeStamp timestamp; 44 | UInt32 totalLength; 45 | AudioBufferList bufferList; 46 | } TPCircularBufferABLBlockHeader; 47 | 48 | 49 | /*! 50 | * Prepare an empty buffer list, stored on the circular buffer 51 | * 52 | * @param buffer Circular buffer 53 | * @param numberOfBuffers The number of buffers to be contained within the buffer list 54 | * @param bytesPerBuffer The number of bytes to store for each buffer 55 | * @param timestamp The timestamp associated with the buffer, or NULL. Note that you can also pass a timestamp into TPCircularBufferProduceAudioBufferList, to set it there instead. 56 | * @return The empty buffer list, or NULL if circular buffer has insufficient space 57 | */ 58 | AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferList(TPCircularBuffer *buffer, int numberOfBuffers, int bytesPerBuffer, const AudioTimeStamp *timestamp); 59 | 60 | /*! 61 | * Prepare an empty buffer list, stored on the circular buffer, using an audio description to automatically configure buffer 62 | * 63 | * @param buffer Circular buffer 64 | * @param audioFormat The kind of audio that will be stored 65 | * @param frameCount The number of frames that will be stored 66 | * @param timestamp The timestamp associated with the buffer, or NULL. Note that you can also pass a timestamp into TPCircularBufferProduceAudioBufferList, to set it there instead. 67 | * @return The empty buffer list, or NULL if circular buffer has insufficient space 68 | */ 69 | AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat, UInt32 frameCount, const AudioTimeStamp *timestamp); 70 | 71 | /*! 72 | * Mark next audio buffer list as ready for reading 73 | * 74 | * This marks the audio buffer list prepared using TPCircularBufferPrepareEmptyAudioBufferList 75 | * as ready for reading. You must not call this function without first calling 76 | * TPCircularBufferPrepareEmptyAudioBufferList. 77 | * 78 | * @param buffer Circular buffer 79 | * @param timestamp The timestamp associated with the buffer, or NULL to leave as-is. Note that you can also pass a timestamp into TPCircularBufferPrepareEmptyAudioBufferList, to set it there instead. 80 | */ 81 | void TPCircularBufferProduceAudioBufferList(TPCircularBuffer *buffer, const AudioTimeStamp *inTimestamp); 82 | 83 | /*! 84 | * Copy the audio buffer list onto the buffer 85 | * 86 | * @param buffer Circular buffer 87 | * @param bufferList Buffer list containing audio to copy to buffer 88 | * @param timestamp The timestamp associated with the buffer, or NULL 89 | * @param frames Length of audio in frames. Specify kTPCircularBufferCopyAll to copy the whole buffer (audioFormat can be NULL, in this case) 90 | * @param audioFormat The AudioStreamBasicDescription describing the audio, or NULL if you specify kTPCircularBufferCopyAll to the `frames` argument 91 | * @return YES if buffer list was successfully copied; NO if there was insufficient space 92 | */ 93 | bool TPCircularBufferCopyAudioBufferList(TPCircularBuffer *buffer, const AudioBufferList *bufferList, const AudioTimeStamp *timestamp, UInt32 frames, const AudioStreamBasicDescription *audioFormat); 94 | 95 | /*! 96 | * Get a pointer to the next stored buffer list 97 | * 98 | * @param buffer Circular buffer 99 | * @param outTimestamp On output, if not NULL, the timestamp corresponding to the buffer 100 | * @return Pointer to the next buffer list in the buffer 101 | */ 102 | static __inline__ __attribute__((always_inline)) AudioBufferList *TPCircularBufferNextBufferList(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp) { 103 | int32_t dontcare; // Length of segment is contained within buffer list, so we can ignore this 104 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); 105 | if ( !block ) { 106 | if ( outTimestamp ) { 107 | memset(outTimestamp, 0, sizeof(AudioTimeStamp)); 108 | } 109 | return NULL; 110 | } 111 | if ( outTimestamp ) { 112 | memcpy(outTimestamp, &block->timestamp, sizeof(AudioTimeStamp)); 113 | } 114 | return &block->bufferList; 115 | } 116 | 117 | /*! 118 | * Get a pointer to the next stored buffer list after the given one 119 | * 120 | * @param buffer Circular buffer 121 | * @param bufferList Preceding buffer list 122 | * @param outTimestamp On output, if not NULL, the timestamp corresponding to the buffer 123 | * @return Pointer to the next buffer list in the buffer, or NULL 124 | */ 125 | AudioBufferList *TPCircularBufferNextBufferListAfter(TPCircularBuffer *buffer, AudioBufferList *bufferList, AudioTimeStamp *outTimestamp); 126 | 127 | /*! 128 | * Consume the next buffer list 129 | * 130 | * @param buffer Circular buffer 131 | */ 132 | static __inline__ __attribute__((always_inline)) void TPCircularBufferConsumeNextBufferList(TPCircularBuffer *buffer) { 133 | int32_t dontcare; 134 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); 135 | if ( !block ) return; 136 | TPCircularBufferConsume(buffer, block->totalLength); 137 | } 138 | 139 | /*! 140 | * Consume a portion of the next buffer list 141 | * 142 | * This will also increment the sample time and host time portions of the timestamp of 143 | * the buffer list, if present. 144 | * 145 | * @param buffer Circular buffer 146 | * @param framesToConsume The number of frames to consume from the buffer list 147 | * @param audioFormat The AudioStreamBasicDescription describing the audio 148 | */ 149 | void TPCircularBufferConsumeNextBufferListPartial(TPCircularBuffer *buffer, int framesToConsume, const AudioStreamBasicDescription *audioFormat); 150 | 151 | /*! 152 | * Consume a certain number of frames from the buffer, possibly from multiple queued buffer lists 153 | * 154 | * Copies the given number of frames from the buffer into outputBufferList, of the 155 | * given audio description, then consumes the audio buffers. If an audio buffer has 156 | * not been entirely consumed, then updates the queued buffer list structure to point 157 | * to the unconsumed data only. 158 | * 159 | * @param buffer Circular buffer 160 | * @param ioLengthInFrames On input, the number of frames in the given audio format to consume; on output, the number of frames provided 161 | * @param outputBufferList The buffer list to copy audio to, or NULL to discard audio. If not NULL, the structure must be initialised properly, and the mData pointers must not be NULL. 162 | * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame returned 163 | * @param audioFormat The format of the audio stored in the buffer 164 | */ 165 | void TPCircularBufferDequeueBufferListFrames(TPCircularBuffer *buffer, UInt32 *ioLengthInFrames, AudioBufferList *outputBufferList, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat); 166 | 167 | /*! 168 | * Determine how many frames of audio are buffered 169 | * 170 | * Given the provided audio format, determines the frame count of all queued buffers 171 | * 172 | * Note: This function should only be used on the consumer thread, not the producer thread. 173 | * 174 | * @param buffer Circular buffer 175 | * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame returned 176 | * @param audioFormat The format of the audio stored in the buffer 177 | * @return The number of frames in the given audio format that are in the buffer 178 | */ 179 | UInt32 TPCircularBufferPeek(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat); 180 | 181 | /*! 182 | * Determine how many contiguous frames of audio are buffered 183 | * 184 | * Given the provided audio format, determines the frame count of all queued buffers that are contiguous, 185 | * given their corresponding timestamps (sample time). 186 | * 187 | * Note: This function should only be used on the consumer thread, not the producer thread. 188 | * 189 | * @param buffer Circular buffer 190 | * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame returned 191 | * @param audioFormat The format of the audio stored in the buffer 192 | * @param contiguousToleranceSampleTime The number of samples of discrepancy to tolerate 193 | * @return The number of frames in the given audio format that are in the buffer 194 | */ 195 | UInt32 TPCircularBufferPeekContiguous(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime); 196 | 197 | /*! 198 | * Determine how many much space there is in the buffer 199 | * 200 | * Given the provided audio format, determines the number of frames of audio that can be buffered. 201 | * 202 | * Note: This function should only be used on the producer thread, not the consumer thread. 203 | * 204 | * @param buffer Circular buffer 205 | * @param audioFormat The format of the audio stored in the buffer 206 | * @return The number of frames in the given audio format that can be stored in the buffer 207 | */ 208 | UInt32 TPCircularBufferGetAvailableSpace(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat); 209 | 210 | #ifdef __cplusplus 211 | } 212 | #endif 213 | 214 | #endif 215 | -------------------------------------------------------------------------------- /TPCircularBuffer.c: -------------------------------------------------------------------------------- 1 | // 2 | // TPCircularBuffer.c 3 | // Circular/Ring buffer implementation 4 | // 5 | // https://github.com/michaeltyson/TPCircularBuffer 6 | // 7 | // Created by Michael Tyson on 10/12/2011. 8 | // 9 | // Copyright (C) 2012-2013 A Tasty Pixel 10 | // 11 | // This software is provided 'as-is', without any express or implied 12 | // warranty. In no event will the authors be held liable for any damages 13 | // arising from the use of this software. 14 | // 15 | // Permission is granted to anyone to use this software for any purpose, 16 | // including commercial applications, and to alter it and redistribute it 17 | // freely, subject to the following restrictions: 18 | // 19 | // 1. The origin of this software must not be misrepresented; you must not 20 | // claim that you wrote the original software. If you use this software 21 | // in a product, an acknowledgment in the product documentation would be 22 | // appreciated but is not required. 23 | // 24 | // 2. Altered source versions must be plainly marked as such, and must not be 25 | // misrepresented as being the original software. 26 | // 27 | // 3. This notice may not be removed or altered from any source distribution. 28 | // 29 | 30 | #include "TPCircularBuffer.h" 31 | #include 32 | #include 33 | 34 | #define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__)) 35 | static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) { 36 | if ( result != ERR_SUCCESS ) { 37 | printf("%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length) { 44 | 45 | // Keep trying until we get our buffer, needed to handle race conditions 46 | int retries = 3; 47 | while ( true ) { 48 | 49 | buffer->length = (int32_t)round_page(length); // We need whole page sizes 50 | 51 | // Temporarily allocate twice the length, so we have the contiguous address space to 52 | // support a second instance of the buffer directly after 53 | vm_address_t bufferAddress; 54 | kern_return_t result = vm_allocate(mach_task_self(), 55 | &bufferAddress, 56 | buffer->length * 2, 57 | VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit 58 | if ( result != ERR_SUCCESS ) { 59 | if ( retries-- == 0 ) { 60 | reportResult(result, "Buffer allocation"); 61 | return false; 62 | } 63 | // Try again if we fail 64 | continue; 65 | } 66 | 67 | // Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half... 68 | result = vm_deallocate(mach_task_self(), 69 | bufferAddress + buffer->length, 70 | buffer->length); 71 | if ( result != ERR_SUCCESS ) { 72 | if ( retries-- == 0 ) { 73 | reportResult(result, "Buffer deallocation"); 74 | return false; 75 | } 76 | // If this fails somehow, deallocate the whole region and try again 77 | vm_deallocate(mach_task_self(), bufferAddress, buffer->length); 78 | continue; 79 | } 80 | 81 | // Re-map the buffer to the address space immediately after the buffer 82 | vm_address_t virtualAddress = bufferAddress + buffer->length; 83 | vm_prot_t cur_prot, max_prot; 84 | result = vm_remap(mach_task_self(), 85 | &virtualAddress, // mirror target 86 | buffer->length, // size of mirror 87 | 0, // auto alignment 88 | 0, // force remapping to virtualAddress 89 | mach_task_self(), // same task 90 | bufferAddress, // mirror source 91 | 0, // MAP READ-WRITE, NOT COPY 92 | &cur_prot, // unused protection struct 93 | &max_prot, // unused protection struct 94 | VM_INHERIT_DEFAULT); 95 | if ( result != ERR_SUCCESS ) { 96 | if ( retries-- == 0 ) { 97 | reportResult(result, "Remap buffer memory"); 98 | return false; 99 | } 100 | // If this remap failed, we hit a race condition, so deallocate and try again 101 | vm_deallocate(mach_task_self(), bufferAddress, buffer->length); 102 | continue; 103 | } 104 | 105 | if ( virtualAddress != bufferAddress+buffer->length ) { 106 | // If the memory is not contiguous, clean up both allocated buffers and try again 107 | if ( retries-- == 0 ) { 108 | printf("Couldn't map buffer memory to end of buffer\n"); 109 | return false; 110 | } 111 | 112 | vm_deallocate(mach_task_self(), virtualAddress, buffer->length); 113 | vm_deallocate(mach_task_self(), bufferAddress, buffer->length); 114 | continue; 115 | } 116 | 117 | buffer->buffer = (void*)bufferAddress; 118 | buffer->fillCount = 0; 119 | buffer->head = buffer->tail = 0; 120 | 121 | return true; 122 | } 123 | return false; 124 | } 125 | 126 | void TPCircularBufferCleanup(TPCircularBuffer *buffer) { 127 | vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); 128 | memset(buffer, 0, sizeof(TPCircularBuffer)); 129 | } 130 | 131 | void TPCircularBufferClear(TPCircularBuffer *buffer) { 132 | int32_t fillCount; 133 | if ( TPCircularBufferTail(buffer, &fillCount) ) { 134 | TPCircularBufferConsume(buffer, fillCount); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /TPCircularBuffer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPCircularBuffer.h 3 | // Circular/Ring buffer implementation 4 | // 5 | // https://github.com/michaeltyson/TPCircularBuffer 6 | // 7 | // Created by Michael Tyson on 10/12/2011. 8 | // 9 | // 10 | // This implementation makes use of a virtual memory mapping technique that inserts a virtual copy 11 | // of the buffer memory directly after the buffer's end, negating the need for any buffer wrap-around 12 | // logic. Clients can simply use the returned memory address as if it were contiguous space. 13 | // 14 | // The implementation is thread-safe in the case of a single producer and single consumer. 15 | // 16 | // Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and 17 | // adapted to Darwin by Kurt Revis (http://www.snoize.com, 18 | // http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) 19 | // 20 | // 21 | // Copyright (C) 2012-2013 A Tasty Pixel 22 | // 23 | // This software is provided 'as-is', without any express or implied 24 | // warranty. In no event will the authors be held liable for any damages 25 | // arising from the use of this software. 26 | // 27 | // Permission is granted to anyone to use this software for any purpose, 28 | // including commercial applications, and to alter it and redistribute it 29 | // freely, subject to the following restrictions: 30 | // 31 | // 1. The origin of this software must not be misrepresented; you must not 32 | // claim that you wrote the original software. If you use this software 33 | // in a product, an acknowledgment in the product documentation would be 34 | // appreciated but is not required. 35 | // 36 | // 2. Altered source versions must be plainly marked as such, and must not be 37 | // misrepresented as being the original software. 38 | // 39 | // 3. This notice may not be removed or altered from any source distribution. 40 | // 41 | 42 | #ifndef TPCircularBuffer_h 43 | #define TPCircularBuffer_h 44 | 45 | #include 46 | #include 47 | #include 48 | 49 | #ifdef __cplusplus 50 | extern "C" { 51 | #endif 52 | 53 | typedef struct { 54 | void *buffer; 55 | int32_t length; 56 | int32_t tail; 57 | int32_t head; 58 | volatile int32_t fillCount; 59 | } TPCircularBuffer; 60 | 61 | /*! 62 | * Initialise buffer 63 | * 64 | * Note that the length is advisory only: Because of the way the 65 | * memory mirroring technique works, the true buffer length will 66 | * be multiples of the device page size (e.g. 4096 bytes) 67 | * 68 | * @param buffer Circular buffer 69 | * @param length Length of buffer 70 | */ 71 | bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length); 72 | 73 | /*! 74 | * Cleanup buffer 75 | * 76 | * Releases buffer resources. 77 | */ 78 | void TPCircularBufferCleanup(TPCircularBuffer *buffer); 79 | 80 | /*! 81 | * Clear buffer 82 | * 83 | * Resets buffer to original, empty state. 84 | * 85 | * This is safe for use by consumer while producer is accessing 86 | * buffer. 87 | */ 88 | void TPCircularBufferClear(TPCircularBuffer *buffer); 89 | 90 | // Reading (consuming) 91 | 92 | /*! 93 | * Access end of buffer 94 | * 95 | * This gives you a pointer to the end of the buffer, ready 96 | * for reading, and the number of available bytes to read. 97 | * 98 | * @param buffer Circular buffer 99 | * @param availableBytes On output, the number of bytes ready for reading 100 | * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty 101 | */ 102 | static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, int32_t* availableBytes) { 103 | *availableBytes = buffer->fillCount; 104 | if ( *availableBytes == 0 ) return NULL; 105 | return (void*)((char*)buffer->buffer + buffer->tail); 106 | } 107 | 108 | /*! 109 | * Consume bytes in buffer 110 | * 111 | * This frees up the just-read bytes, ready for writing again. 112 | * 113 | * @param buffer Circular buffer 114 | * @param amount Number of bytes to consume 115 | */ 116 | static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, int32_t amount) { 117 | buffer->tail = (buffer->tail + amount) % buffer->length; 118 | OSAtomicAdd32Barrier(-amount, &buffer->fillCount); 119 | assert(buffer->fillCount >= 0); 120 | } 121 | 122 | /*! 123 | * Version of TPCircularBufferConsume without the memory barrier, for more optimal use in single-threaded contexts 124 | */ 125 | static __inline__ __attribute__((always_inline)) void TPCircularBufferConsumeNoBarrier(TPCircularBuffer *buffer, int32_t amount) { 126 | buffer->tail = (buffer->tail + amount) % buffer->length; 127 | buffer->fillCount -= amount; 128 | assert(buffer->fillCount >= 0); 129 | } 130 | 131 | /*! 132 | * Access front of buffer 133 | * 134 | * This gives you a pointer to the front of the buffer, ready 135 | * for writing, and the number of available bytes to write. 136 | * 137 | * @param buffer Circular buffer 138 | * @param availableBytes On output, the number of bytes ready for writing 139 | * @return Pointer to the first bytes ready for writing, or NULL if buffer is full 140 | */ 141 | static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, int32_t* availableBytes) { 142 | *availableBytes = (buffer->length - buffer->fillCount); 143 | if ( *availableBytes == 0 ) return NULL; 144 | return (void*)((char*)buffer->buffer + buffer->head); 145 | } 146 | 147 | // Writing (producing) 148 | 149 | /*! 150 | * Produce bytes in buffer 151 | * 152 | * This marks the given section of the buffer ready for reading. 153 | * 154 | * @param buffer Circular buffer 155 | * @param amount Number of bytes to produce 156 | */ 157 | static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, int32_t amount) { 158 | buffer->head = (buffer->head + amount) % buffer->length; 159 | OSAtomicAdd32Barrier(amount, &buffer->fillCount); 160 | assert(buffer->fillCount <= buffer->length); 161 | } 162 | 163 | /*! 164 | * Version of TPCircularBufferProduce without the memory barrier, for more optimal use in single-threaded contexts 165 | */ 166 | static __inline__ __attribute__((always_inline)) void TPCircularBufferProduceNoBarrier(TPCircularBuffer *buffer, int32_t amount) { 167 | buffer->head = (buffer->head + amount) % buffer->length; 168 | buffer->fillCount += amount; 169 | assert(buffer->fillCount <= buffer->length); 170 | } 171 | 172 | /*! 173 | * Helper routine to copy bytes to buffer 174 | * 175 | * This copies the given bytes to the buffer, and marks them ready for writing. 176 | * 177 | * @param buffer Circular buffer 178 | * @param src Source buffer 179 | * @param len Number of bytes in source buffer 180 | * @return true if bytes copied, false if there was insufficient space 181 | */ 182 | static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, int32_t len) { 183 | int32_t space; 184 | void *ptr = TPCircularBufferHead(buffer, &space); 185 | if ( space < len ) return false; 186 | memcpy(ptr, src, len); 187 | TPCircularBufferProduce(buffer, len); 188 | return true; 189 | } 190 | 191 | #ifdef __cplusplus 192 | } 193 | #endif 194 | 195 | #endif 196 | --------------------------------------------------------------------------------