├── .gitignore ├── Package.swift ├── README.markdown ├── TPCircularBuffer+AudioBufferList.c ├── TPCircularBuffer+AudioBufferList.h ├── TPCircularBuffer.c ├── TPCircularBuffer.h └── TPCircularBuffer.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | .swiftpm 2 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "TPCircularBuffer", 7 | products: [ 8 | .library(name: "TPCircularBuffer", targets: ["CTPCircularBuffer"]), 9 | ], 10 | targets: [ 11 | .target(name: "CTPCircularBuffer", path: "", publicHeadersPath: ""), 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | A simple, fast circular buffer implementation for audio processing 2 | ================================================================== 3 | 4 | A simple C implementation for a circular (ring) buffer. Thread-safe with a single producer and a single consumer, using OSAtomic.h primitives, and avoids any need for buffer wrapping logic by using a virtual memory map technique to place a virtual copy of the buffer straight after the end of the real buffer. 5 | 6 | Usage 7 | ----- 8 | 9 | Initialisation and cleanup: `TPCircularBufferInit` and `TPCircularBufferCleanup` to allocate and free resources. 10 | 11 | Producing: Use `TPCircularBufferHead` to get a pointer to write to the buffer, followed by `TPCircularBufferProduce` to submit the written data. `TPCircularBufferProduceBytes` is a convenience routine for writing data straight to the buffer. 12 | 13 | Consuming: Use `TPCircularBufferTail` to get a pointer to the next data to read, followed by `TPCircularBufferConsume` to free up the space once processed. 14 | 15 | TPCircularBuffer+AudioBufferList.(c,h) contain helper functions to queue and dequeue AudioBufferList 16 | structures. These will automatically adjust the mData fields of each buffer to point to 16-byte aligned 17 | regions within the circular buffer. 18 | 19 | Thread safety 20 | ------------- 21 | 22 | As long as you restrict multithreaded access to just one producer, and just one consumer, this utility should be thread safe. 23 | 24 | Only one shared variable is used (the buffer fill count), and OSAtomic primitives are used to write to this value to ensure atomicity. 25 | 26 | License 27 | ------- 28 | 29 | Copyright (C) 2012-2013 A Tasty Pixel 30 | 31 | This software is provided 'as-is', without any express or implied 32 | warranty. In no event will the authors be held liable for any damages 33 | arising from the use of this software. 34 | 35 | Permission is granted to anyone to use this software for any purpose, 36 | including commercial applications, and to alter it and redistribute it 37 | freely, subject to the following restrictions: 38 | 39 | 1. The origin of this software must not be misrepresented; you must not 40 | claim that you wrote the original software. If you use this software 41 | in a product, an acknowledgment in the product documentation would be 42 | appreciated but is not required. 43 | 44 | 2. Altered source versions must be plainly marked as such, and must not be 45 | misrepresented as being the original software. 46 | 47 | 3. This notice may not be removed or altered from any source distribution. 48 | 49 | 50 | ----------------------------------------------------- 51 | 52 | Virtual memory technique originally proposed by [Philip Howard](http://vrb.slashusr.org/), and [adapted to Darwin](http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) by [Kurt Revis](http://www.snoize.com) 53 | 54 | See more info at [atastypixel.com](http://atastypixel.com/blog/a-simple-fast-circular-buffer-implementation-for-audio-processing/) 55 | 56 | -------------------------------------------------------------------------------- /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 unsigned long align16byte(unsigned long val) { 36 | if ( val & (16-1) ) { 37 | return val + (16 - (val & (16-1))); 38 | } 39 | return val; 40 | } 41 | 42 | static inline unsigned long min(unsigned long a, unsigned long b) { 43 | return a > b ? b : a; 44 | } 45 | 46 | AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferList(TPCircularBuffer *buffer, UInt32 numberOfBuffers, UInt32 bytesPerBuffer, const AudioTimeStamp *inTimestamp) { 47 | uint32_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 | #ifdef DEBUG 52 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 53 | #endif 54 | 55 | if ( inTimestamp ) { 56 | memcpy(&block->timestamp, inTimestamp, sizeof(AudioTimeStamp)); 57 | } else { 58 | memset(&block->timestamp, 0, sizeof(AudioTimeStamp)); 59 | } 60 | 61 | memset(&block->bufferList, 0, sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer))); 62 | block->bufferList.mNumberBuffers = numberOfBuffers; 63 | 64 | char *dataPtr = (char*)&block->bufferList + sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer)); 65 | for ( UInt32 i=0; i availableBytes ) { 70 | return NULL; 71 | } 72 | 73 | block->bufferList.mBuffers[i].mData = dataPtr; 74 | block->bufferList.mBuffers[i].mDataByteSize = bytesPerBuffer; 75 | block->bufferList.mBuffers[i].mNumberChannels = 1; 76 | 77 | dataPtr += bytesPerBuffer; 78 | } 79 | 80 | // Make sure whole buffer (including timestamp and length value) is 16-byte aligned in length 81 | block->totalLength = (UInt32)align16byte((unsigned long)(dataPtr - (char*)block)); 82 | if ( block->totalLength > availableBytes ) { 83 | return NULL; 84 | } 85 | 86 | return &block->bufferList; 87 | } 88 | 89 | AudioBufferList *TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat, UInt32 frameCount, const AudioTimeStamp *timestamp) { 90 | return TPCircularBufferPrepareEmptyAudioBufferList(buffer, 91 | (audioFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? audioFormat->mChannelsPerFrame : 1, 92 | audioFormat->mBytesPerFrame * frameCount, 93 | timestamp); 94 | } 95 | 96 | void TPCircularBufferProduceAudioBufferList(TPCircularBuffer *buffer, const AudioTimeStamp *inTimestamp) { 97 | uint32_t availableBytes; 98 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); 99 | 100 | assert(block); 101 | 102 | #ifdef DEBUG 103 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 104 | #endif 105 | 106 | assert(block->bufferList.mBuffers[0].mDataByteSize > 0); 107 | 108 | if ( inTimestamp ) { 109 | memcpy(&block->timestamp, inTimestamp, sizeof(AudioTimeStamp)); 110 | } 111 | 112 | UInt32 calculatedLength = (UInt32)(((char*)block->bufferList.mBuffers[block->bufferList.mNumberBuffers-1].mData + block->bufferList.mBuffers[block->bufferList.mNumberBuffers-1].mDataByteSize) - (char*)block); 113 | 114 | // Make sure whole buffer (including timestamp and length value) is 16-byte aligned in length 115 | calculatedLength = (UInt32)align16byte(calculatedLength); 116 | 117 | assert(calculatedLength <= block->totalLength && calculatedLength <= availableBytes); 118 | 119 | block->totalLength = calculatedLength; 120 | 121 | TPCircularBufferProduce(buffer, block->totalLength); 122 | } 123 | 124 | bool TPCircularBufferCopyAudioBufferList(TPCircularBuffer *buffer, const AudioBufferList *inBufferList, const AudioTimeStamp *inTimestamp, UInt32 frames, const AudioStreamBasicDescription *audioDescription) { 125 | if ( frames == 0 ) return true; 126 | 127 | UInt32 byteCount = inBufferList->mBuffers[0].mDataByteSize; 128 | if ( frames != kTPCircularBufferCopyAll ) { 129 | byteCount = frames * audioDescription->mBytesPerFrame; 130 | assert(byteCount <= inBufferList->mBuffers[0].mDataByteSize); 131 | } 132 | 133 | if ( byteCount == 0 ) return true; 134 | 135 | AudioBufferList *bufferList = TPCircularBufferPrepareEmptyAudioBufferList(buffer, inBufferList->mNumberBuffers, byteCount, inTimestamp); 136 | if ( !bufferList ) return false; 137 | 138 | for ( UInt32 i=0; imNumberBuffers; i++ ) { 139 | memcpy(bufferList->mBuffers[i].mData, inBufferList->mBuffers[i].mData, byteCount); 140 | } 141 | 142 | TPCircularBufferProduceAudioBufferList(buffer, NULL); 143 | 144 | return true; 145 | } 146 | 147 | AudioBufferList *TPCircularBufferNextBufferListAfter(TPCircularBuffer *buffer, const AudioBufferList *bufferList, AudioTimeStamp *outTimestamp) { 148 | uint32_t availableBytes; 149 | void *tail = TPCircularBufferTail(buffer, &availableBytes); 150 | void *end = (char*)tail + availableBytes; 151 | assert((void*)bufferList > (void*)tail && (void*)bufferList < end); 152 | 153 | TPCircularBufferABLBlockHeader *originalBlock = (TPCircularBufferABLBlockHeader*)((char*)bufferList - offsetof(TPCircularBufferABLBlockHeader, bufferList)); 154 | 155 | #ifdef DEBUG 156 | assert(!((unsigned long)originalBlock & 0xF) /* Beware unaligned accesses */); 157 | #endif 158 | 159 | TPCircularBufferABLBlockHeader *nextBlock = (TPCircularBufferABLBlockHeader*)((char*)originalBlock + originalBlock->totalLength); 160 | if ( (void*)nextBlock >= end ) return NULL; 161 | 162 | #ifdef DEBUG 163 | assert(!((unsigned long)nextBlock & 0xF) /* Beware unaligned accesses */); 164 | #endif 165 | 166 | if ( outTimestamp ) { 167 | memcpy(outTimestamp, &nextBlock->timestamp, sizeof(AudioTimeStamp)); 168 | } 169 | 170 | return &nextBlock->bufferList; 171 | } 172 | 173 | void TPCircularBufferConsumeNextBufferListPartial(TPCircularBuffer *buffer, UInt32 framesToConsume, const AudioStreamBasicDescription *audioFormat) { 174 | 175 | uint32_t dontcare; 176 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &dontcare); 177 | if ( !block ) return; 178 | 179 | #ifdef DEBUG 180 | assert(!((unsigned long)block & 0xF)); // Beware unaligned accesses 181 | #endif 182 | 183 | UInt32 bytesToConsume = (UInt32)min(framesToConsume * audioFormat->mBytesPerFrame, block->bufferList.mBuffers[0].mDataByteSize); 184 | 185 | if ( bytesToConsume == block->bufferList.mBuffers[0].mDataByteSize ) { 186 | TPCircularBufferConsumeNextBufferList(buffer); 187 | return; 188 | } 189 | 190 | for ( UInt32 i=0; ibufferList.mNumberBuffers; i++ ) { 191 | assert(bytesToConsume <= block->bufferList.mBuffers[i].mDataByteSize); 192 | 193 | block->bufferList.mBuffers[i].mData = (char*)block->bufferList.mBuffers[i].mData + bytesToConsume; 194 | block->bufferList.mBuffers[i].mDataByteSize -= bytesToConsume; 195 | } 196 | 197 | if ( block->timestamp.mFlags & kAudioTimeStampSampleTimeValid ) { 198 | block->timestamp.mSampleTime += framesToConsume; 199 | } 200 | if ( block->timestamp.mFlags & kAudioTimeStampHostTimeValid ) { 201 | if ( __secondsToHostTicks == 0.0 ) { 202 | mach_timebase_info_data_t tinfo; 203 | mach_timebase_info(&tinfo); 204 | __secondsToHostTicks = 1.0 / (((double)tinfo.numer / tinfo.denom) * 1.0e-9); 205 | } 206 | 207 | block->timestamp.mHostTime += (UInt64)(((double)framesToConsume / audioFormat->mSampleRate) * __secondsToHostTicks); 208 | } 209 | 210 | // Reposition block forward, just before the audio data, ensuring 16-byte alignment 211 | TPCircularBufferABLBlockHeader *newBlock = (TPCircularBufferABLBlockHeader*)(((unsigned long)block + bytesToConsume) & ~0xFul); 212 | memmove(newBlock, block, sizeof(TPCircularBufferABLBlockHeader) + (block->bufferList.mNumberBuffers-1)*sizeof(AudioBuffer)); 213 | UInt32 bytesFreed = (UInt32)((intptr_t)newBlock - (intptr_t)block); 214 | newBlock->totalLength -= bytesFreed; 215 | TPCircularBufferConsume(buffer, bytesFreed); 216 | } 217 | 218 | void TPCircularBufferDequeueBufferListFrames(TPCircularBuffer *buffer, UInt32 *ioLengthInFrames, const AudioBufferList *outputBufferList, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat) { 219 | bool hasTimestamp = false; 220 | UInt32 bytesToGo = *ioLengthInFrames * audioFormat->mBytesPerFrame; 221 | UInt32 bytesCopied = 0; 222 | while ( bytesToGo > 0 ) { 223 | AudioBufferList *bufferList = TPCircularBufferNextBufferList(buffer, !hasTimestamp ? outTimestamp : NULL); 224 | if ( !bufferList ) break; 225 | 226 | hasTimestamp = true; 227 | UInt32 bytesToCopy = (UInt32)min(bytesToGo, bufferList->mBuffers[0].mDataByteSize); 228 | 229 | if ( outputBufferList ) { 230 | for ( UInt32 i=0; imNumberBuffers; i++ ) { 231 | assert(bytesCopied + bytesToCopy <= outputBufferList->mBuffers[i].mDataByteSize); 232 | memcpy((char*)outputBufferList->mBuffers[i].mData + bytesCopied, bufferList->mBuffers[i].mData, bytesToCopy); 233 | } 234 | } 235 | 236 | TPCircularBufferConsumeNextBufferListPartial(buffer, bytesToCopy/audioFormat->mBytesPerFrame, audioFormat); 237 | 238 | bytesToGo -= bytesToCopy; 239 | bytesCopied += bytesToCopy; 240 | } 241 | 242 | *ioLengthInFrames -= bytesToGo / audioFormat->mBytesPerFrame; 243 | } 244 | 245 | UInt32 TPCircularBufferPeekContiguousWrapped(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime, UInt32 wrapPoint) { 246 | uint32_t availableBytes; 247 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferTail(buffer, &availableBytes); 248 | if ( !block ) return 0; 249 | 250 | #ifdef DEBUG 251 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 252 | #endif 253 | 254 | if ( outTimestamp ) { 255 | memcpy(outTimestamp, &block->timestamp, sizeof(AudioTimeStamp)); 256 | } 257 | 258 | void *end = (char*)block + availableBytes; 259 | 260 | UInt32 byteCount = 0; 261 | 262 | while ( 1 ) { 263 | byteCount += block->bufferList.mBuffers[0].mDataByteSize; 264 | TPCircularBufferABLBlockHeader *nextBlock = (TPCircularBufferABLBlockHeader*)((char*)block + block->totalLength); 265 | if ( (void*)nextBlock >= end ) { 266 | break; 267 | } 268 | 269 | if ( contiguousToleranceSampleTime != UINT32_MAX ) { 270 | UInt32 frames = block->bufferList.mBuffers[0].mDataByteSize / audioFormat->mBytesPerFrame; 271 | Float64 nextTime = block->timestamp.mSampleTime + frames; 272 | if ( wrapPoint && nextTime > wrapPoint ) nextTime = fmod(nextTime, wrapPoint); 273 | Float64 diff = fabs(nextBlock->timestamp.mSampleTime - nextTime); 274 | if ( diff > contiguousToleranceSampleTime && (!wrapPoint || fabs(diff-wrapPoint) > contiguousToleranceSampleTime) ) { 275 | break; 276 | } 277 | } 278 | 279 | #ifdef DEBUG 280 | assert(!((unsigned long)nextBlock & 0xF) /* Beware unaligned accesses */); 281 | #endif 282 | 283 | block = nextBlock; 284 | } 285 | 286 | return byteCount / audioFormat->mBytesPerFrame; 287 | } 288 | 289 | UInt32 TPCircularBufferPeek(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat) { 290 | return TPCircularBufferPeekContiguousWrapped(buffer, outTimestamp, audioFormat, UINT32_MAX, 0); 291 | } 292 | 293 | UInt32 TPCircularBufferPeekContiguous(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime) { 294 | return TPCircularBufferPeekContiguousWrapped(buffer, outTimestamp, audioFormat, contiguousToleranceSampleTime, 0); 295 | } 296 | 297 | UInt32 TPCircularBufferGetAvailableSpace(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat) { 298 | // Look at buffer head; make sure there's space for the block metadata 299 | uint32_t availableBytes; 300 | TPCircularBufferABLBlockHeader *block = (TPCircularBufferABLBlockHeader*)TPCircularBufferHead(buffer, &availableBytes); 301 | if ( !block ) return 0; 302 | 303 | #ifdef DEBUG 304 | assert(!((unsigned long)block & 0xF) /* Beware unaligned accesses */); 305 | #endif 306 | 307 | // Now find out how much 16-byte aligned audio we can store in the space available 308 | UInt32 numberOfBuffers = audioFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved ? audioFormat->mChannelsPerFrame : 1; 309 | char * endOfBuffer = (char*)block + availableBytes; 310 | char * dataPtr = (char*)align16byte((unsigned long)(&block->bufferList + sizeof(AudioBufferList)+((numberOfBuffers-1)*sizeof(AudioBuffer)))); 311 | if ( dataPtr >= endOfBuffer ) return 0; 312 | UInt32 availableAudioBytes = (UInt32)(endOfBuffer - dataPtr); 313 | 314 | UInt32 availableAudioBytesPerBuffer = availableAudioBytes / numberOfBuffers; 315 | availableAudioBytesPerBuffer -= (availableAudioBytesPerBuffer % (16-1)); 316 | 317 | return availableAudioBytesPerBuffer > 0 ? availableAudioBytesPerBuffer / audioFormat->mBytesPerFrame : 0; 318 | } 319 | -------------------------------------------------------------------------------- /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, UInt32 numberOfBuffers, UInt32 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 inTimestamp 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 | uint32_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, const 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 | uint32_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, UInt32 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, const 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 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 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 contiguous frames of audio are buffered, with wrap around 199 | * 200 | * Like TPCircularBufferPeekContiguous, determines how many contiguous frames are buffered, 201 | * but considers audio that wraps around a region of a given length as also contiguous. This 202 | * is good for audio that loops. 203 | * 204 | * Note: This function should only be used on the consumer thread, not the producer thread. 205 | * 206 | * @param buffer Circular buffer 207 | * @param outTimestamp On output, if not NULL, the timestamp corresponding to the first audio frame 208 | * @param audioFormat The format of the audio stored in the buffer 209 | * @param contiguousToleranceSampleTime The number of samples of discrepancy to tolerate 210 | * @param wrapPoint The point around which the audio may wrap and still be considered contiguous, or 0 to disable 211 | * @return The number of frames in the given audio format that are in the buffer 212 | */ 213 | UInt32 TPCircularBufferPeekContiguousWrapped(TPCircularBuffer *buffer, AudioTimeStamp *outTimestamp, const AudioStreamBasicDescription *audioFormat, UInt32 contiguousToleranceSampleTime, UInt32 wrapPoint); 214 | 215 | /*! 216 | * Determine how many much space there is in the buffer 217 | * 218 | * Given the provided audio format, determines the number of frames of audio that can be buffered. 219 | * 220 | * Note: This function should only be used on the producer thread, not the consumer thread. 221 | * 222 | * @param buffer Circular buffer 223 | * @param audioFormat The format of the audio stored in the buffer 224 | * @return The number of frames in the given audio format that can be stored in the buffer 225 | */ 226 | UInt32 TPCircularBufferGetAvailableSpace(TPCircularBuffer *buffer, const AudioStreamBasicDescription *audioFormat); 227 | 228 | #ifdef __cplusplus 229 | } 230 | #endif 231 | 232 | #endif 233 | -------------------------------------------------------------------------------- /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 | #include 34 | 35 | #define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__)) 36 | static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) { 37 | if ( result != ERR_SUCCESS ) { 38 | printf("%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | bool _TPCircularBufferInit(TPCircularBuffer *buffer, uint32_t length, size_t structSize) { 45 | 46 | assert(length > 0); 47 | 48 | if ( structSize != sizeof(TPCircularBuffer) ) { 49 | fprintf(stderr, "TPCircularBuffer: Header version mismatch. Check for old versions of TPCircularBuffer in your project\n"); 50 | abort(); 51 | } 52 | 53 | // Keep trying until we get our buffer, needed to handle race conditions 54 | int retries = 3; 55 | while ( true ) { 56 | 57 | buffer->length = (uint32_t)round_page(length); // We need whole page sizes 58 | 59 | // Temporarily allocate twice the length, so we have the contiguous address space to 60 | // support a second instance of the buffer directly after 61 | vm_address_t bufferAddress; 62 | kern_return_t result = vm_allocate(mach_task_self(), 63 | &bufferAddress, 64 | buffer->length * 2, 65 | VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit 66 | if ( result != ERR_SUCCESS ) { 67 | if ( retries-- == 0 ) { 68 | reportResult(result, "Buffer allocation"); 69 | return false; 70 | } 71 | // Try again if we fail 72 | continue; 73 | } 74 | 75 | // Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half... 76 | result = vm_deallocate(mach_task_self(), 77 | bufferAddress + buffer->length, 78 | buffer->length); 79 | if ( result != ERR_SUCCESS ) { 80 | if ( retries-- == 0 ) { 81 | reportResult(result, "Buffer deallocation"); 82 | return false; 83 | } 84 | // If this fails somehow, deallocate the whole region and try again 85 | vm_deallocate(mach_task_self(), bufferAddress, buffer->length); 86 | continue; 87 | } 88 | 89 | // Re-map the buffer to the address space immediately after the buffer 90 | vm_address_t virtualAddress = bufferAddress + buffer->length; 91 | vm_prot_t cur_prot, max_prot; 92 | result = vm_remap(mach_task_self(), 93 | &virtualAddress, // mirror target 94 | buffer->length, // size of mirror 95 | 0, // auto alignment 96 | 0, // force remapping to virtualAddress 97 | mach_task_self(), // same task 98 | bufferAddress, // mirror source 99 | 0, // MAP READ-WRITE, NOT COPY 100 | &cur_prot, // unused protection struct 101 | &max_prot, // unused protection struct 102 | VM_INHERIT_DEFAULT); 103 | if ( result != ERR_SUCCESS ) { 104 | if ( retries-- == 0 ) { 105 | reportResult(result, "Remap buffer memory"); 106 | return false; 107 | } 108 | // If this remap failed, we hit a race condition, so deallocate and try again 109 | vm_deallocate(mach_task_self(), bufferAddress, buffer->length); 110 | continue; 111 | } 112 | 113 | if ( virtualAddress != bufferAddress+buffer->length ) { 114 | // If the memory is not contiguous, clean up both allocated buffers and try again 115 | if ( retries-- == 0 ) { 116 | printf("Couldn't map buffer memory to end of buffer\n"); 117 | return false; 118 | } 119 | 120 | vm_deallocate(mach_task_self(), virtualAddress, buffer->length); 121 | vm_deallocate(mach_task_self(), bufferAddress, buffer->length); 122 | continue; 123 | } 124 | 125 | buffer->buffer = (void*)bufferAddress; 126 | buffer->fillCount = 0; 127 | buffer->head = buffer->tail = 0; 128 | buffer->atomic = true; 129 | 130 | return true; 131 | } 132 | return false; 133 | } 134 | 135 | void TPCircularBufferCleanup(TPCircularBuffer *buffer) { 136 | vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); 137 | memset(buffer, 0, sizeof(TPCircularBuffer)); 138 | } 139 | 140 | void TPCircularBufferClear(TPCircularBuffer *buffer) { 141 | uint32_t fillCount; 142 | if ( TPCircularBufferTail(buffer, &fillCount) ) { 143 | TPCircularBufferConsume(buffer, fillCount); 144 | } 145 | } 146 | 147 | void TPCircularBufferSetAtomic(TPCircularBuffer *buffer, bool atomic) { 148 | buffer->atomic = atomic; 149 | } 150 | -------------------------------------------------------------------------------- /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 | #include 52 | typedef std::atomic_int atomicInt; 53 | #define atomicFetchAdd(a,b) std::atomic_fetch_add(a,b) 54 | } 55 | #else 56 | #include 57 | typedef atomic_int atomicInt; 58 | #define atomicFetchAdd(a,b) atomic_fetch_add(a,b) 59 | #endif 60 | 61 | #ifdef __cplusplus 62 | extern "C" { 63 | #endif 64 | 65 | typedef struct { 66 | void *buffer; 67 | uint32_t length; 68 | uint32_t tail; 69 | uint32_t head; 70 | volatile atomicInt fillCount; 71 | bool atomic; 72 | } TPCircularBuffer; 73 | 74 | /*! 75 | * Initialise buffer 76 | * 77 | * Note that the length is advisory only: Because of the way the 78 | * memory mirroring technique works, the true buffer length will 79 | * be multiples of the device page size (e.g. 4096 bytes) 80 | * 81 | * If you intend to use the AudioBufferList utilities, you should 82 | * always allocate a bit more space than you need for pure audio 83 | * data, so there's room for the metadata. How much extra is required 84 | * depends on how many AudioBufferList structures are used, which is 85 | * a function of how many audio frames each buffer holds. A good rule 86 | * of thumb is to add 15%, or at least another 2048 bytes or so. 87 | * 88 | * @param buffer Circular buffer 89 | * @param length Length of buffer 90 | */ 91 | #define TPCircularBufferInit(buffer, length) \ 92 | _TPCircularBufferInit(buffer, length, sizeof(*buffer)) 93 | bool _TPCircularBufferInit(TPCircularBuffer *buffer, uint32_t length, size_t structSize); 94 | 95 | /*! 96 | * Cleanup buffer 97 | * 98 | * Releases buffer resources. 99 | */ 100 | void TPCircularBufferCleanup(TPCircularBuffer *buffer); 101 | 102 | /*! 103 | * Clear buffer 104 | * 105 | * Resets buffer to original, empty state. 106 | * 107 | * This is safe for use by consumer while producer is accessing 108 | * buffer. 109 | */ 110 | void TPCircularBufferClear(TPCircularBuffer *buffer); 111 | 112 | /*! 113 | * Set the atomicity 114 | * 115 | * If you set the atomiticy to false using this method, the buffer will 116 | * not use atomic operations. This can be used to give the compiler a little 117 | * more optimisation opportunities when the buffer is only used on one thread. 118 | * 119 | * Important note: Only set this to false if you know what you're doing! 120 | * 121 | * The default value is true (the buffer will use atomic operations) 122 | * 123 | * @param buffer Circular buffer 124 | * @param atomic Whether the buffer is atomic (default true) 125 | */ 126 | void TPCircularBufferSetAtomic(TPCircularBuffer *buffer, bool atomic); 127 | 128 | // Reading (consuming) 129 | 130 | /*! 131 | * Access end of buffer 132 | * 133 | * This gives you a pointer to the end of the buffer, ready 134 | * for reading, and the number of available bytes to read. 135 | * 136 | * @param buffer Circular buffer 137 | * @param availableBytes On output, the number of bytes ready for reading 138 | * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty 139 | */ 140 | static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, uint32_t* availableBytes) { 141 | *availableBytes = buffer->fillCount; 142 | if ( *availableBytes == 0 ) return NULL; 143 | return (void*)((char*)buffer->buffer + buffer->tail); 144 | } 145 | 146 | /*! 147 | * Consume bytes in buffer 148 | * 149 | * This frees up the just-read bytes, ready for writing again. 150 | * 151 | * @param buffer Circular buffer 152 | * @param amount Number of bytes to consume 153 | */ 154 | static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, uint32_t amount) { 155 | buffer->tail = (buffer->tail + amount) % buffer->length; 156 | if ( buffer->atomic ) { 157 | atomicFetchAdd(&buffer->fillCount, -(int)amount); 158 | } else { 159 | buffer->fillCount -= amount; 160 | } 161 | assert(buffer->fillCount >= 0); 162 | } 163 | 164 | /*! 165 | * Access front of buffer 166 | * 167 | * This gives you a pointer to the front of the buffer, ready 168 | * for writing, and the number of available bytes to write. 169 | * 170 | * @param buffer Circular buffer 171 | * @param availableBytes On output, the number of bytes ready for writing 172 | * @return Pointer to the first bytes ready for writing, or NULL if buffer is full 173 | */ 174 | static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, uint32_t* availableBytes) { 175 | *availableBytes = (buffer->length - buffer->fillCount); 176 | if ( *availableBytes == 0 ) return NULL; 177 | return (void*)((char*)buffer->buffer + buffer->head); 178 | } 179 | 180 | // Writing (producing) 181 | 182 | /*! 183 | * Produce bytes in buffer 184 | * 185 | * This marks the given section of the buffer ready for reading. 186 | * 187 | * @param buffer Circular buffer 188 | * @param amount Number of bytes to produce 189 | */ 190 | static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, uint32_t amount) { 191 | buffer->head = (buffer->head + amount) % buffer->length; 192 | if ( buffer->atomic ) { 193 | atomicFetchAdd(&buffer->fillCount, (int)amount); 194 | } else { 195 | buffer->fillCount += amount; 196 | } 197 | assert(buffer->fillCount <= buffer->length); 198 | } 199 | 200 | /*! 201 | * Helper routine to copy bytes to buffer 202 | * 203 | * This copies the given bytes to the buffer, and marks them ready for reading. 204 | * 205 | * @param buffer Circular buffer 206 | * @param src Source buffer 207 | * @param len Number of bytes in source buffer 208 | * @return true if bytes copied, false if there was insufficient space 209 | */ 210 | static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, uint32_t len) { 211 | uint32_t space; 212 | void *ptr = TPCircularBufferHead(buffer, &space); 213 | if ( space < len ) return false; 214 | memcpy(ptr, src, len); 215 | TPCircularBufferProduce(buffer, len); 216 | return true; 217 | } 218 | 219 | /*! 220 | * Deprecated method 221 | */ 222 | static __inline__ __attribute__((always_inline)) __deprecated_msg("use TPCircularBufferSetAtomic(false) and TPCircularBufferConsume instead") 223 | void TPCircularBufferConsumeNoBarrier(TPCircularBuffer *buffer, uint32_t amount) { 224 | buffer->tail = (buffer->tail + amount) % buffer->length; 225 | buffer->fillCount -= amount; 226 | assert(buffer->fillCount >= 0); 227 | } 228 | 229 | /*! 230 | * Deprecated method 231 | */ 232 | static __inline__ __attribute__((always_inline)) __deprecated_msg("use TPCircularBufferSetAtomic(false) and TPCircularBufferProduce instead") 233 | void TPCircularBufferProduceNoBarrier(TPCircularBuffer *buffer, uint32_t amount) { 234 | buffer->head = (buffer->head + amount) % buffer->length; 235 | buffer->fillCount += amount; 236 | assert(buffer->fillCount <= buffer->length); 237 | } 238 | 239 | #ifdef __cplusplus 240 | } 241 | #endif 242 | 243 | #endif 244 | -------------------------------------------------------------------------------- /TPCircularBuffer.podspec: -------------------------------------------------------------------------------- 1 | license = <<-END 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 5 | END 6 | 7 | Pod::Spec.new do |s| 8 | s.name = "TPCircularBuffer" 9 | s.version = '1.6.1' 10 | s.summary = 'A simple, fast circular buffer implementation.' 11 | s.homepage = 'https://github.com/michaeltyson/TPCircularBuffer' 12 | s.authors = { 'Michael Tyson' => 'michael@atastypixel.com' } 13 | s.license = { :type => 'MIT', :text => license } 14 | s.source = { :git => 'https://github.com/michaeltyson/TPCircularBuffer.git', :tag => '1.6.1' } 15 | s.source_files = '*.{c,h}' 16 | s.requires_arc = false 17 | s.frameworks = 'AudioToolbox' 18 | s.ios.deployment_target = '4.3' 19 | s.osx.deployment_target = '10.8' 20 | s.tvos.deployment_target = '9.0' 21 | end 22 | --------------------------------------------------------------------------------