Anonymous avatar Anonymous committed 5b3f4e3

Add DDAudioQueueReader

Comments (0)

Files changed (7)

DDAudioQueue.xcodeproj/project.pbxproj

 		55B2BCC8117C046D00CC1D9A /* DDAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 55B2B67D117AB73000CC1D9A /* DDAudioQueue.m */; };
 		55B2BCC9117C046D00CC1D9A /* DDAudioQueueBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 55B2BCC0117C042000CC1D9A /* DDAudioQueueBuffer.m */; };
 		55B2BCCA117C046D00CC1D9A /* RAAtomicList.m in Sources */ = {isa = PBXBuildFile; fileRef = 55B2B680117AB75100CC1D9A /* RAAtomicList.m */; };
+		55EFEF92117C8E2F00E2F8B6 /* DDAudioQueueReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 55EFEF91117C8E2F00E2F8B6 /* DDAudioQueueReader.m */; };
+		55EFEF93117C8E2F00E2F8B6 /* DDAudioQueueReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 55EFEF91117C8E2F00E2F8B6 /* DDAudioQueueReader.m */; };
+		55EFEFA6117CE82E00E2F8B6 /* DDAudioQueueReaderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 55EFEFA5117CE82E00E2F8B6 /* DDAudioQueueReaderTest.m */; };
 		8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
 		8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
 		8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
 		55B2BCBF117C042000CC1D9A /* DDAudioQueueBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDAudioQueueBuffer.h; sourceTree = "<group>"; };
 		55B2BCC0117C042000CC1D9A /* DDAudioQueueBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDAudioQueueBuffer.m; sourceTree = "<group>"; };
 		55B2BCF5117C05BD00CC1D9A /* DDAudioQueueDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDAudioQueueDelegate.h; sourceTree = "<group>"; };
+		55EFEF90117C8E2F00E2F8B6 /* DDAudioQueueReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDAudioQueueReader.h; sourceTree = "<group>"; };
+		55EFEF91117C8E2F00E2F8B6 /* DDAudioQueueReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDAudioQueueReader.m; sourceTree = "<group>"; };
+		55EFEFA4117CE82E00E2F8B6 /* DDAudioQueueReaderTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDAudioQueueReaderTest.h; sourceTree = "<group>"; };
+		55EFEFA5117CE82E00E2F8B6 /* DDAudioQueueReaderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDAudioQueueReaderTest.m; sourceTree = "<group>"; };
 		8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		8D1107320486CEB800E47090 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 				55B2BCF5117C05BD00CC1D9A /* DDAudioQueueDelegate.h */,
 				55B2BCBF117C042000CC1D9A /* DDAudioQueueBuffer.h */,
 				55B2BCC0117C042000CC1D9A /* DDAudioQueueBuffer.m */,
+				55EFEF90117C8E2F00E2F8B6 /* DDAudioQueueReader.h */,
+				55EFEF91117C8E2F00E2F8B6 /* DDAudioQueueReader.m */,
 				55B2B67F117AB75100CC1D9A /* RAAtomicList.h */,
 				55B2B680117AB75100CC1D9A /* RAAtomicList.m */,
 			);
 			children = (
 				55B2BCA9117C032600CC1D9A /* DDAudioQueueTest.h */,
 				55B2BCAA117C032600CC1D9A /* DDAudioQueueTest.m */,
+				55EFEFA4117CE82E00E2F8B6 /* DDAudioQueueReaderTest.h */,
+				55EFEFA5117CE82E00E2F8B6 /* DDAudioQueueReaderTest.m */,
 				55B2BC99117C029A00CC1D9A /* Info.plist */,
 			);
 			path = Tests;
 				55B2BCC8117C046D00CC1D9A /* DDAudioQueue.m in Sources */,
 				55B2BCC9117C046D00CC1D9A /* DDAudioQueueBuffer.m in Sources */,
 				55B2BCCA117C046D00CC1D9A /* RAAtomicList.m in Sources */,
+				55EFEF92117C8E2F00E2F8B6 /* DDAudioQueueReader.m in Sources */,
+				55EFEFA6117CE82E00E2F8B6 /* DDAudioQueueReaderTest.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 				55B2B67E117AB73000CC1D9A /* DDAudioQueue.m in Sources */,
 				55B2B681117AB75100CC1D9A /* RAAtomicList.m in Sources */,
 				55B2BCC1117C042000CC1D9A /* DDAudioQueueBuffer.m in Sources */,
+				55EFEF93117C8E2F00E2F8B6 /* DDAudioQueueReader.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

Source/DDAudioQueue.m

 
 - (BOOL)enqueueBuffer:(DDAudioQueueBuffer *)buffer;
 {
-    NSLog(@"enqueueBuffer: %@ %p <0x%08X>", buffer, buffer.bytes, *(uint32_t *)buffer.bytes);
+    NSAssert(buffer != nil, @"Buffer must not be nil");
+    NSLog(@"enqueueBuffer: %@ %p %u <0x%08X>", buffer, buffer.bytes, buffer.length);
     RAAtomicListInsert(&_bufferList, buffer);
     return YES;
 }

Source/DDAudioQueueReader.h

+//
+
+#import <Cocoa/Cocoa.h>
+
+@class DDAudioQueue;
+@class DDAudioQueueBuffer;
+
+@interface DDAudioQueueReader : NSObject
+{
+    DDAudioQueue * _queue;
+    DDAudioQueueBuffer * _readBuffer;
+    UInt32 _readCursor;
+}
+
+- (id)initWithAudioQueue:(DDAudioQueue *)queue;
+
+- (void)reset;
+
+@end
+
+/**
+ * Reads bytesToRead bytes into the buffer, dequeing as necessary.  If there
+ * is not enough data available, it only copies in the number of bytes
+ * read. [Maybe fill in the rest as 0 automatically?]
+ *
+ * @return The actual number of bytes dequeued and copied into the buffer
+ */
+UInt32 DDAudioQueueReaderRead(DDAudioQueueReader * reader, void * buffer, UInt32 bytesToRead);

Source/DDAudioQueueReader.m

+//
+
+#import "DDAudioQueueReader.h"
+#import "DDAudioQueue.h"
+#import "DDAudioQueueBuffer.h"
+
+
+@implementation DDAudioQueueReader
+
+- (id)initWithAudioQueue:(DDAudioQueue *)queue;
+{
+    self = [super init];
+    if (self == nil)
+        return nil;
+    
+    _queue = [queue retain];
+    
+    return self;
+}
+
+- (void)dealloc
+{
+    [_queue release];
+    [super dealloc];
+}
+
+- (void)reset;
+{
+    _readBuffer = nil;
+    _readCursor = 0;
+}
+
+UInt32 DDAudioQueueReaderPrimitiveRead(DDAudioQueueReader * reader, void * buffer, UInt32 bytesToRead)
+{
+    if (reader->_readBuffer == nil) {
+        reader->_readBuffer = DDAudioQueueDequeueBuffer(reader->_queue);
+        reader->_readCursor = 0;
+    }
+    
+    if (reader->_readBuffer == nil) {
+        return 0;
+    }
+    
+    NSUInteger bufferLength = DDAudioBufferLength(reader->_readBuffer);
+    NSUInteger bytesRemainingInReadBuffer = bufferLength - reader->_readCursor;
+    UInt32 bytesToCopy = MIN(bytesToRead, bytesRemainingInReadBuffer);
+
+    const uint8_t * readBufferBytes = DDAudioBufferBytes(reader->_readBuffer);
+    readBufferBytes += reader->_readCursor;
+    
+    memcpy(buffer, readBufferBytes, bytesToCopy);
+    reader->_readCursor += bytesToCopy;
+    
+    if (bytesToCopy == bytesRemainingInReadBuffer) {
+        NSLog(@"finished with buffer: %p", reader->_readBuffer);
+        DDAudioQueueBufferIsAvailable(reader->_queue, reader->_readBuffer);
+        reader->_readBuffer = nil;
+    }
+    
+    return bytesToCopy;
+}
+
+UInt32 DDAudioQueueReaderRead(DDAudioQueueReader * reader, void * buffer, UInt32 bytesToRead)
+{
+    UInt32 bytesRead = 0;
+    uint8_t * byteBuffer = buffer;
+    
+    while (bytesRead < bytesToRead) {
+        UInt32 bytesToReadThisIteration = bytesToRead - bytesRead;
+        UInt32 bytesReadThisIteration =
+            DDAudioQueueReaderPrimitiveRead(reader, byteBuffer, bytesToReadThisIteration);
+        
+        if (bytesReadThisIteration == 0) {
+            break;
+        }
+        bytesRead += bytesReadThisIteration;
+        byteBuffer += bytesReadThisIteration;
+    }
+    
+    return bytesRead;
+}
+
+@end

Tests/DDAudioQueueReaderTest.h

+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "DDAudioQueueDelegate.h"
+
+@class DDAudioQueue;
+@class DDAudioQueueReader;
+
+@interface DDAudioQueueReaderTest : SenTestCase <DDAudioQueueDelegate>
+{
+    DDAudioQueue * _queue;
+    DDAudioQueueReader * _reader;
+    NSMutableArray * _buffers;
+    NSMutableData * _readBuffer;
+    NSMutableArray * _availableBuffers;
+}
+
+@end

Tests/DDAudioQueueReaderTest.m

+//
+
+#import "DDAudioQueueReaderTest.h"
+#import "DDAudioQueueReader.h"
+#import "DDAudioQueue.h"
+#import "DDAudioQueueBuffer.h"
+
+
+static const NSUInteger QUEUE_BUFFER_SIZE = 10;
+static const uint8_t SCRIBBLE = 0x55;
+static const NSUInteger READ_BUFFER_SIZE = 50;
+
+@implementation DDAudioQueueReaderTest
+
+- (DDAudioQueueBuffer *)buffer:(NSUInteger)index
+{
+    return [_buffers objectAtIndex:index];
+}
+
+- (void)assertReadBufferFrom:(NSUInteger)from to:(NSUInteger)to isSetTo:(uint8_t)value
+{
+    STAssertTrue(to < [_readBuffer length], @"%u should be less than %u", to, [_readBuffer length]);
+    uint8_t * byteBuffer = [_readBuffer mutableBytes];
+    for (NSUInteger i = from; i <= to; i++) {
+        STAssertEquals(byteBuffer[i], value, @"Byte %u should be 0x%02X but was 0x%02X",
+                       i, value, byteBuffer[i]);
+    }
+}
+
+- (void)enqueueBuffer:(NSUInteger)bufferIndex withValue:(uint8_t)value length:(NSUInteger)length
+{
+    DDAudioQueueBuffer * buffer = [self buffer:bufferIndex];
+    STAssertNotNil(buffer, nil);
+    STAssertTrue(length <= buffer.capacity, nil);
+    memset([buffer bytes], value, length);
+    buffer.length = length;
+    [_queue enqueueBuffer:buffer];
+}
+
+- (UInt32)readBytes:(UInt32)bytesToRead toOffset:(NSUInteger)offset
+{
+    STAssertTrue(bytesToRead + offset <= [_readBuffer length], nil);
+    uint8_t * bytes = [_readBuffer mutableBytes];
+    bytes += offset;
+    UInt32 bytesRead = DDAudioQueueReaderRead(_reader, bytes, bytesToRead);
+    return bytesRead;
+}
+
+- (UInt32)readBytes:(UInt32)bytesToRead
+{
+    return [self readBytes:bytesToRead toOffset:0];
+}
+
+- (void)audioQueue:(DDAudioQueue *)queue bufferIsAvailable:(DDAudioQueueBuffer *)buffer;
+{
+    STAssertEquals(_queue, queue, nil);
+    NSLog(@"bufferIsAvailable: %p", buffer);
+    [_availableBuffers addObject:buffer];
+}
+
+- (void)spinRunLoop
+{
+    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
+    [runLoop runUntilDate:[NSDate date]];
+}
+
+#pragma mark -
+
+- (void)setUpBuffers
+{
+    for (int i = 0; i < 3; i++) {
+        DDAudioQueueBuffer * buffer = [_queue allocateBufferWithCapacity:QUEUE_BUFFER_SIZE error:NULL];
+        STAssertNotNil(buffer, nil);
+        [_buffers addObject:buffer];
+    }
+}
+
+- (void)setUp
+{
+    _queue = [[[DDAudioQueue alloc] initWithDelegate:self] autorelease];
+    _buffers = [NSMutableArray array];
+    [self setUpBuffers];
+    [_queue scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+
+    _reader = [[[DDAudioQueueReader alloc] initWithAudioQueue:_queue] autorelease];
+    _readBuffer = [NSMutableData dataWithLength:READ_BUFFER_SIZE];
+    memset([_readBuffer mutableBytes], SCRIBBLE, READ_BUFFER_SIZE);
+    _availableBuffers = [NSMutableArray array];
+}
+
+- (void)tearDown
+{
+    [_queue removeFromRunLoop];
+}
+
+#pragma mark -
+#pragma mark Tests
+
+- (void)testReturnsZeroReadingFromEmptyQueue
+{
+    UInt32 bytesRead = [self readBytes:READ_BUFFER_SIZE];
+
+    STAssertEquals(bytesRead, (UInt32)0, nil);
+}
+
+- (void)testDoesNotCopyAnyDataReadingFromEmptyQueue
+{
+    [self readBytes:READ_BUFFER_SIZE];
+
+    [self assertReadBufferFrom:0 to:READ_BUFFER_SIZE-1 isSetTo:SCRIBBLE];
+}
+
+- (void)testReadsProperDataWhenReadingSizeOfEnequeuedBuffer
+{
+    [self enqueueBuffer:0 withValue:0x01 length:10];
+    
+    UInt32 bytesRead = [self readBytes:10];
+    
+    STAssertEquals(bytesRead, (UInt32)10, nil);
+    [self assertReadBufferFrom:0 to:9 isSetTo:0x01];
+    [self assertReadBufferFrom:10 to:READ_BUFFER_SIZE-1 isSetTo:SCRIBBLE];
+}
+
+- (void)testReadsProperDataWhenReadDoesNotSpanWholeBuffer
+{
+    DDAudioQueueBuffer * buffer = [self buffer:0];
+    uint8_t pattern[] = {0x01,  0x01, 0x02, 0x02};
+    memcpy([buffer bytes], pattern, 4);
+    buffer.length = 4;
+    [_queue enqueueBuffer:buffer];
+    
+    UInt32 bytesRead = 0;
+    bytesRead += [self readBytes:2 toOffset:0];
+    bytesRead += [self readBytes:2 toOffset:2];
+    
+    STAssertEquals(bytesRead, (UInt32)4, nil);
+    [self assertReadBufferFrom:0 to:1 isSetTo:0x01];
+    [self assertReadBufferFrom:2 to:3 isSetTo:0x02];
+    [self assertReadBufferFrom:4 to:READ_BUFFER_SIZE-1 isSetTo:SCRIBBLE];
+}
+
+- (void)testReadsNoBytesAfterDequeingOnlyBuffer
+{
+    [self enqueueBuffer:0 withValue:0x01 length:10];
+    
+    [self readBytes:10];
+    UInt32 bytesRead = [self readBytes:10 toOffset:11];
+    
+    STAssertEquals(bytesRead, (UInt32)0, nil);
+    [self assertReadBufferFrom:0 to:9 isSetTo:0x01];
+    [self assertReadBufferFrom:10 to:READ_BUFFER_SIZE-1 isSetTo:SCRIBBLE];
+}
+
+- (void)testReadingSpansMultipleBuffers
+{
+    [self enqueueBuffer:0 withValue:0x01 length:10];
+    [self enqueueBuffer:1 withValue:0x02 length:5];
+    
+    UInt32 bytesRead = [self readBytes:READ_BUFFER_SIZE];
+    
+    STAssertEquals(bytesRead, (UInt32)15, nil);
+    [self assertReadBufferFrom:0 to:9 isSetTo:0x01];
+    [self assertReadBufferFrom:10 to:14 isSetTo:0x02];
+    [self assertReadBufferFrom:15 to:READ_BUFFER_SIZE-1 isSetTo:SCRIBBLE];
+}
+
+- (void)testDoesNotMakeReadingPartialBufferAvailable
+{
+    [self enqueueBuffer:0 withValue:0x01 length:10];
+    
+    [self readBytes:5];
+    [self spinRunLoop];
+
+    STAssertEquals([_availableBuffers count], (NSUInteger)0, nil);
+}
+
+- (void)testMakesBufferAvailableAfterReadingFullBuffer
+{
+    [self enqueueBuffer:0 withValue:0x01 length:10];
+    
+    [self readBytes:10];
+    [self spinRunLoop];
+    
+    STAssertEquals([_availableBuffers count], (NSUInteger)1, nil);
+    STAssertEquals([_availableBuffers objectAtIndex:0], [self buffer:0], nil);
+}
+
+- (void)testMakesBothBuffersAavailableAfterMultiBufferRead
+{
+    [self enqueueBuffer:0 withValue:0x01 length:10];
+    [self enqueueBuffer:1 withValue:0x02 length:10];
+    
+    [self readBytes:20];
+    [self spinRunLoop];
+    
+    STAssertEquals([_availableBuffers count], (NSUInteger)2, nil);
+    // They become available in reverse order
+    STAssertEquals([_availableBuffers objectAtIndex:0], [self buffer:1], nil);
+    STAssertEquals([_availableBuffers objectAtIndex:1], [self buffer:0], nil);
+}
+
+@end

Tests/DDAudioQueueTest.m

     [_availableBuffers addObject:buffer];
 }
 
-- (void)setUp
-{
-    _queue = [[DDAudioQueue alloc] initWithDelegate:self];
-    [_queue scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
-    _buffers = [NSMutableArray array];
-    _availableBuffers = [NSMutableArray array];
-}
-
-- (void)tearDown
-{
-    [_queue removeFromRunLoop];
-    [_queue release];
-}
-
 - (DDAudioQueueBuffer *)allocateBuffer
 {
     DDAudioQueueBuffer * buffer = [_queue allocateBufferWithCapacity:CAPACITY error:NULL];
 }
 
 #pragma mark -
+
+- (void)setUp
+{
+    _queue = [[DDAudioQueue alloc] initWithDelegate:self];
+    [_queue scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+    _buffers = [NSMutableArray array];
+    _availableBuffers = [NSMutableArray array];
+}
+
+- (void)tearDown
+{
+    [_queue removeFromRunLoop];
+    [_queue release];
+}
+
+#pragma mark -
 #pragma mark Tests
 
 - (void)testAllocatesBuffersWithCorrectInitialValues
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.