Skip to content

Commit

Permalink
GPUImageAudioPlayer added + better video realtime play (with drop fra…
Browse files Browse the repository at this point in the history
…mes)
  • Loading branch information
eluzix committed Sep 3, 2013
1 parent a231e2f commit 65b4740
Show file tree
Hide file tree
Showing 7 changed files with 744 additions and 44 deletions.
24 changes: 24 additions & 0 deletions framework/GPUImage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
96781B4715C3A6F1005FA0D7 /* GPUImageSoftEleganceFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 96781B4515C3A6F0005FA0D7 /* GPUImageSoftEleganceFilter.m */; };
96DD3C1415C2780500DF637E /* GPUImageLookupFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 96DD3C1215C2780500DF637E /* GPUImageLookupFilter.h */; settings = {ATTRIBUTES = (); }; };
96DD3C1515C2780500DF637E /* GPUImageLookupFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 96DD3C1315C2780500DF637E /* GPUImageLookupFilter.m */; };
9C8F77BF17D5D93D00A54EBB /* TPCircularBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8F77BD17D5D93D00A54EBB /* TPCircularBuffer.h */; };
9C8F77C017D5D93D00A54EBB /* TPCircularBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8F77BE17D5D93D00A54EBB /* TPCircularBuffer.m */; };
9C8F77C317D5D97900A54EBB /* GPUImageAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8F77C117D5D97900A54EBB /* GPUImageAudioPlayer.h */; };
9C8F77C417D5D97900A54EBB /* GPUImageAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8F77C217D5D97900A54EBB /* GPUImageAudioPlayer.m */; };
B80171E616311FCB001C8D16 /* GPUImageHueBlendFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B80171E416311FCB001C8D16 /* GPUImageHueBlendFilter.h */; settings = {ATTRIBUTES = (); }; };
B80171E716311FCB001C8D16 /* GPUImageHueBlendFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B80171E516311FCB001C8D16 /* GPUImageHueBlendFilter.m */; };
B80171FE16312800001C8D16 /* GPUImageSaturationBlendFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B80171FC16312800001C8D16 /* GPUImageSaturationBlendFilter.h */; settings = {ATTRIBUTES = (); }; };
Expand Down Expand Up @@ -395,6 +399,10 @@
96781B4515C3A6F0005FA0D7 /* GPUImageSoftEleganceFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageSoftEleganceFilter.m; path = Source/GPUImageSoftEleganceFilter.m; sourceTree = SOURCE_ROOT; };
96DD3C1215C2780500DF637E /* GPUImageLookupFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUImageLookupFilter.h; path = Source/GPUImageLookupFilter.h; sourceTree = SOURCE_ROOT; };
96DD3C1315C2780500DF637E /* GPUImageLookupFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageLookupFilter.m; path = Source/GPUImageLookupFilter.m; sourceTree = SOURCE_ROOT; };
9C8F77BD17D5D93D00A54EBB /* TPCircularBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPCircularBuffer.h; sourceTree = SOURCE_ROOT; };
9C8F77BE17D5D93D00A54EBB /* TPCircularBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPCircularBuffer.m; sourceTree = SOURCE_ROOT; };
9C8F77C117D5D97900A54EBB /* GPUImageAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUImageAudioPlayer.h; path = Source/GPUImageAudioPlayer.h; sourceTree = SOURCE_ROOT; };
9C8F77C217D5D97900A54EBB /* GPUImageAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageAudioPlayer.m; path = Source/GPUImageAudioPlayer.m; sourceTree = SOURCE_ROOT; };
B80171E416311FCB001C8D16 /* GPUImageHueBlendFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUImageHueBlendFilter.h; path = Source/GPUImageHueBlendFilter.h; sourceTree = SOURCE_ROOT; };
B80171E516311FCB001C8D16 /* GPUImageHueBlendFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUImageHueBlendFilter.m; path = Source/GPUImageHueBlendFilter.m; sourceTree = SOURCE_ROOT; };
B80171FC16312800001C8D16 /* GPUImageSaturationBlendFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUImageSaturationBlendFilter.h; path = Source/GPUImageSaturationBlendFilter.h; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -734,6 +742,17 @@
name = Pipeline;
sourceTree = "<group>";
};
9C8F77B817D5D8EE00A54EBB /* Audio */ = {
isa = PBXGroup;
children = (
9C8F77BD17D5D93D00A54EBB /* TPCircularBuffer.h */,
9C8F77BE17D5D93D00A54EBB /* TPCircularBuffer.m */,
9C8F77C117D5D97900A54EBB /* GPUImageAudioPlayer.h */,
9C8F77C217D5D97900A54EBB /* GPUImageAudioPlayer.m */,
);
name = Audio;
sourceTree = "<group>";
};
BC1B715D14F4AFFF00ACA2AB /* Color processing */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -957,6 +976,7 @@
BCB5E78114E232BC00701302 /* Sources */ = {
isa = PBXGroup;
children = (
9C8F77B817D5D8EE00A54EBB /* Audio */,
BCB5E77D14E22E4200701302 /* GPUImageOutput.h */,
BCB5E77E14E22E4200701302 /* GPUImageOutput.m */,
BCB5E76314E208D600701302 /* GPUImageVideoCamera.h */,
Expand Down Expand Up @@ -1341,6 +1361,8 @@
BCBC605716C8527C00B11741 /* GPUImageZoomBlurFilter.h in Headers */,
BC6C55401730679D00EB222D /* GPUImageLaplacianFilter.h in Headers */,
BCB030BE173400BC001A1A20 /* GPUImageThreeInputFilter.h in Headers */,
9C8F77BF17D5D93D00A54EBB /* TPCircularBuffer.h in Headers */,
9C8F77C317D5D97900A54EBB /* GPUImageAudioPlayer.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1614,6 +1636,8 @@
BCBF617B16E4F44700E2784A /* GPUImageKuwaharaRadius3Filter.m in Sources */,
BC6C55411730679D00EB222D /* GPUImageLaplacianFilter.m in Sources */,
BCB030BF173400BC001A1A20 /* GPUImageThreeInputFilter.m in Sources */,
9C8F77C017D5D93D00A54EBB /* TPCircularBuffer.m in Sources */,
9C8F77C417D5D97900A54EBB /* GPUImageAudioPlayer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
29 changes: 29 additions & 0 deletions framework/Source/GPUImageAudioPlayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// GPUImageAudioPlayer.h
// GPUImage
//
// Created by Uzi Refaeli on 03/09/2013.
// Copyright (c) 2013 Brad Larson. All rights reserved.
//

#import <Foundation/Foundation.h>
#include <AudioUnit/AudioUnit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import "TPCircularBuffer.h"


@interface GPUImageAudioPlayer : NSObject

- (void)initAudio;
- (void)startPlaying;
- (void)stopPlaying;
- (void)copyBuffer:(CMSampleBufferRef)buf;

- (TPCircularBuffer *)getBuffer;

@property(nonatomic, assign) BOOL hasBuffer;
@property(nonatomic, assign) SInt32 bufferSize;
@property(nonatomic, readonly) BOOL readyForMoreBytes;

@end
264 changes: 264 additions & 0 deletions framework/Source/GPUImageAudioPlayer.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
//
// GPUImageAudioPlayer.m
// GPUImage
//
// Created by Uzi Refaeli on 03/09/2013.
// Copyright (c) 2013 Brad Larson. All rights reserved.
//

#import "GPUImageAudioPlayer.h"
#define kOutputBus 0
#define kInputBus 1
#define SAMPLE_RATE 44100.0


#define kUnitSize sizeof(AudioSampleType)
#define kBufferUnit 655360
#define kTotalBufferSize kBufferUnit * kUnitSize
#define kRescueBufferSize kBufferUnit / 2

@interface GPUImageAudioPlayer(){
AUGraph processingGraph;
AudioUnit mixerUnit;

TPCircularBuffer circularBuffer;
BOOL firstBufferReached;

void *rescueBuffer;
UInt32 rescueBufferSize;
}

- (void)setReadyForMoreBytes;
@end

static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {


int numberOfChannels = ioData->mBuffers[0].mNumberChannels;
AudioSampleType *outSample = (AudioSampleType *)ioData->mBuffers[0].mData;

// Zero-out all the output samples first
memset(outSample, 0, ioData->mBuffers[0].mDataByteSize);

GPUImageAudioPlayer *p = (__bridge GPUImageAudioPlayer *)inRefCon;

if (p.hasBuffer){
int32_t availableBytes;
AudioSampleType *bufferTail = TPCircularBufferTail([p getBuffer], &availableBytes);

int32_t requestedBytesSize = inNumberFrames * kUnitSize * numberOfChannels;

int bytesToRead = MIN(availableBytes, requestedBytesSize);
memcpy(outSample, bufferTail, bytesToRead);
TPCircularBufferConsume([p getBuffer], bytesToRead);

if (availableBytes <= requestedBytesSize*2){
[p setReadyForMoreBytes];
}

if (availableBytes <= requestedBytesSize) {
p.hasBuffer = NO;
}

}

return noErr;
}



@implementation GPUImageAudioPlayer

- (id)init{
self = [super init];
if (self){
firstBufferReached = NO;
rescueBuffer = nil;
rescueBufferSize = 0;
_readyForMoreBytes = YES;
}

return self;
}

- (void)dealloc{
DisposeAUGraph(processingGraph);
if (rescueBuffer != nil){
free(rescueBuffer);
}

TPCircularBufferCleanup(&circularBuffer);
[self stopPlaying];
}


#pragma mark -
#pragma mark audio player methods

- (void)initAudio {

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryAmbient error:nil];
[session setActive:YES error:nil];

// create a new AUGraph
NewAUGraph(&processingGraph);

// AUNodes represent AudioUnits on the AUGraph and provide an
// easy means for connecting audioUnits together.
AUNode outputNode;
AUNode mixerNode;

// Create AudioComponentDescriptions for the AUs we want in the graph
// mixer component
AudioComponentDescription mixer_desc;
mixer_desc.componentType = kAudioUnitType_Mixer;
mixer_desc.componentSubType = kAudioUnitSubType_AU3DMixerEmbedded;
mixer_desc.componentFlags = 0;
mixer_desc.componentFlagsMask = 0;
mixer_desc.componentManufacturer = kAudioUnitManufacturer_Apple;

// output component
AudioComponentDescription output_desc;
output_desc.componentType = kAudioUnitType_Output;
output_desc.componentSubType = kAudioUnitSubType_RemoteIO;
output_desc.componentFlags = 0;
output_desc.componentFlagsMask = 0;
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;

// Add nodes to the graph to hold our AudioUnits,
// You pass in a reference to the AudioComponentDescription
// and get back an AudioUnit
AUGraphAddNode(processingGraph, &output_desc, &outputNode);
AUGraphAddNode(processingGraph, &mixer_desc, &mixerNode );

// Now we can manage connections using nodes in the graph.
// Connect the mixer node's output to the output node's input
AUGraphConnectNodeInput(processingGraph, mixerNode, 0, outputNode, 0);

// open the graph AudioUnits are open but not initialized (no resource allocation occurs here)
AUGraphOpen(processingGraph);

// Get a link to the mixer AU so we can talk to it later
AUGraphNodeInfo(processingGraph, mixerNode, NULL, &mixerUnit);

UInt32 elementCount = 1;
AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &elementCount, sizeof(elementCount));

// Set output callback
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = (__bridge void *)(self);
AUGraphSetNodeInputCallback(processingGraph, mixerNode, 0, &callbackStruct);


// Describe format
AudioStreamBasicDescription audioFormat;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagsCanonical;
audioFormat.mSampleRate = SAMPLE_RATE;
audioFormat.mReserved = 0;

audioFormat.mBytesPerPacket = 2;
audioFormat.mFramesPerPacket = 1;
audioFormat.mBytesPerFrame = 2;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;

// Apply format
AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));

//init the processing graph
AUGraphInitialize(processingGraph);

TPCircularBufferInit(&circularBuffer, kTotalBufferSize);
self.hasBuffer = NO;
}

- (void)startPlaying {
// Start playing
AUGraphStart(processingGraph);
}

- (void)stopPlaying {
// Start playing
AUGraphStop(processingGraph);
}


- (void)copyBuffer:(CMSampleBufferRef)buf {
if (!_readyForMoreBytes) return;

if (!firstBufferReached){
firstBufferReached = YES;
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(buf);
const AudioStreamBasicDescription* const asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription);

AudioStreamBasicDescription audioFormat;
UInt32 oSize = sizeof(audioFormat);
AudioUnitGetProperty(mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, &oSize);

audioFormat.mBytesPerPacket = asbd->mBytesPerPacket;
audioFormat.mFramesPerPacket = asbd->mFramesPerPacket;
audioFormat.mBytesPerFrame = asbd->mBytesPerFrame;
audioFormat.mChannelsPerFrame = asbd->mChannelsPerFrame;
audioFormat.mBitsPerChannel = asbd->mBitsPerChannel;

NSLog(@"[AudioStreamBasicDescription] updating graph uppon first buffer");
AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, oSize);
AUGraphUpdate(processingGraph, NULL);
}

AudioBufferList abl;
CMBlockBufferRef blockBuffer;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(buf, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);

UInt32 size = CMSampleBufferGetTotalSampleSize(buf);
BOOL bytesCopied = TPCircularBufferProduceBytes(&circularBuffer, abl.mBuffers[0].mData, size);

if (!bytesCopied){
// NSLog(@"TPBuffer limit reached blocking more bytes (%ld)", size);
_readyForMoreBytes = NO;

if (size > kRescueBufferSize){
NSLog(@"Unable to allocate enought space for rescue buffer, dropping audio frame");
} else {
if (rescueBuffer == nil) {
rescueBuffer = malloc(kRescueBufferSize);
}

rescueBufferSize = size;
memcpy(rescueBuffer, abl.mBuffers[0].mData, size);
}
}

CFRelease(blockBuffer);


if (!self.hasBuffer && bytesCopied > 0) {
self.hasBuffer = YES;
}
}

- (TPCircularBuffer *)getBuffer {
return &circularBuffer;
}

- (void)setReadyForMoreBytes{
if (rescueBufferSize > 0){
BOOL bytesCopied = TPCircularBufferProduceBytes(&circularBuffer, rescueBuffer, rescueBufferSize);
if (!bytesCopied){
NSLog(@"Unable to copy resuce buffer into main buffer, dropping frame");
}
rescueBufferSize = 0;
}

_readyForMoreBytes = YES;
}

@end
6 changes: 6 additions & 0 deletions framework/Source/GPUImageMovie.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
*/
@property(readwrite, nonatomic) BOOL shouldRepeat;


/** This determines whether audio should be played. Cann't be set to work with video writing. Defaults to NO.
*/
@property(readwrite, nonatomic) BOOL playSound;


/** This is used to send the delete Movie did complete playing alert
*/
@property (readwrite, nonatomic, assign) id <GPUImageMovieDelegate>delegate;
Expand Down
Loading

0 comments on commit 65b4740

Please sign in to comment.