Created
October 2, 2012 15:06
-
-
Save pgodino/3819907 to your computer and use it in GitHub Desktop.
Play audio with movie
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
#import <AVFoundation/AVFoundation.h> | |
#import "GPUImageOpenGLESContext.h" | |
#import "GPUImageOutput.h" | |
#import "TPCircularBuffer.h" | |
#ifndef max | |
#define max( a, b ) ( ((a) > (b)) ? (a) : (b) ) | |
#endif | |
#ifndef min | |
#define min( a, b ) ( ((a) < (b)) ? (a) : (b) ) | |
#endif | |
/** Source object for filtering movies | |
*/ | |
@interface GPUImageMovie : GPUImageOutput | |
@property (readwrite, retain) AVAsset *asset; | |
@property(readwrite, retain) NSURL *url; | |
/** This enables the benchmarking mode, which logs out instantaneous and average frame times to the console | |
*/ | |
@property(readwrite, nonatomic) BOOL runBenchmark; | |
/** This determines whether to play back a movie as fast as the frames can be processed, or if the original speed of the movie should be respected. Defaults to NO. | |
*/ | |
@property(readwrite, nonatomic) BOOL playAtActualSpeed; | |
@property(readwrite, nonatomic) AudioBuffer aBuffer; | |
@property (nonatomic) TPCircularBuffer tpCircularBuffer; | |
/// @name Initialization and teardown | |
- (id)initWithAsset:(AVAsset *)asset; | |
- (id)initWithURL:(NSURL *)url; | |
- (void)textureCacheSetup; | |
/// @name Movie processing | |
- (void)enableSynchronizedEncodingUsingMovieWriter:(GPUImageMovieWriter *)movieWriter; | |
- (void)readNextVideoFrameFromOutput:(AVAssetReaderTrackOutput *)readerVideoTrackOutput; | |
- (void)readNextAudioSampleFromOutput:(AVAssetReaderTrackOutput *)readerAudioTrackOutput; | |
- (void)startProcessing; | |
- (void)endProcessing; | |
- (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer; | |
@property(nonatomic, copy) void(^completionBlock)(void); | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "GPUImageMovie.h" | |
#import "GPUImageMovieWriter.h" | |
#import "TPCircularBuffer+AudioBufferList.h" | |
#define kOutputBus 0 | |
TPCircularBuffer tpCircularBuffer1; | |
void checkStatus(int status); | |
static OSStatus playbackCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData); | |
void checkStatus(int status) | |
{ | |
if (status) { | |
printf("Status not 0! %d\n", status); | |
// exit(1); | |
} | |
} | |
/** | |
This callback is called when the audioUnit needs new data to play through the | |
speakers. If you don't have any, just don't write anything in the buffers | |
*/ | |
static OSStatus playbackCallback(void *inRefCon, | |
AudioUnitRenderActionFlags *ioActionFlags, | |
const AudioTimeStamp *inTimeStamp, | |
UInt32 inBusNumber, | |
UInt32 inNumberFrames, | |
AudioBufferList *ioData) { | |
// GPUImageMovie* iosAudio = (__bridge GPUImageMovie *)inRefCon; | |
// Notes: ioData contains buffers (may be more than one!) | |
// Fill them up as much as you can. Remember to set the size value in each buffer to match how | |
// much data is in the buffer. | |
// &tpCircularBuffer, &audioBufferList, NULL, kTPCircularBufferCopyAll, NULL)) | |
// AudioBufferList outputBufferList; | |
// TPCircularBufferDequeueBufferListFrames(TPCircularBuffer *buffer, UInt32 *ioLengthInFrames, AudioBufferList *outputBufferList, AudioTimeStamp *outTimestamp, AudioStreamBasicDescription *audioFormat); | |
UInt32 ioLengthInFrames = inNumberFrames; | |
AudioStreamBasicDescription audioFormat; | |
audioFormat.mSampleRate = 44100.00; | |
audioFormat.mFormatID = kAudioFormatLinearPCM; | |
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; | |
audioFormat.mFramesPerPacket = 1; | |
audioFormat.mChannelsPerFrame = 2; | |
audioFormat.mBitsPerChannel = 16; | |
audioFormat.mBytesPerPacket = 4; | |
audioFormat.mBytesPerFrame = 4; | |
AudioTimeStamp outTimestamp; | |
UInt32 retVal = TPCircularBufferPeek(&tpCircularBuffer1, &outTimestamp, &audioFormat); | |
NSLog (@"Pippo %ld\n", retVal); | |
if (retVal > 0) | |
// TPCircularBuffer tpCirc = [iosAudio tpCircularBuffer]; | |
TPCircularBufferDequeueBufferListFrames(&tpCircularBuffer1, &ioLengthInFrames, ioData, &outTimestamp, &audioFormat); | |
/* | |
AudioBuffer aBuffer = outputBufferList.mBuffers[0]; | |
AudioBuffer buffer = ioData->mBuffers[0]; | |
UInt32 size = min(buffer.mDataByteSize, aBuffer.mDataByteSize); // dont copy more data then we have, or then fits | |
memcpy(buffer.mData, aBuffer.mData, size); | |
buffer.mDataByteSize = size; // indicate how much data we wrote in the buffer | |
*/ | |
/* | |
AudioBuffer *pBuffer = [iosAudio.sampleBuffer getAudioBuffer]; | |
if (pBuffer != NULL) { | |
AudioBuffer aBuffer = *pBuffer ; | |
AudioBuffer buffer = ioData->mBuffers[0]; | |
// [[iosAudio.array objectAtIndex:[iosAudio.array count]-1] getValue:&aBuffer]; | |
// [iosAudio.array removeObjectAtIndex:[iosAudio.array count]-1]; | |
UInt32 size = min(buffer.mDataByteSize, aBuffer.mDataByteSize); // dont copy more data then we have, or then fits | |
memcpy(buffer.mData, aBuffer.mData, size); | |
buffer.mDataByteSize = size; // indicate how much data we wrote in the buffer | |
} | |
*/ | |
/*for (int i=0; i < ioData->mNumberBuffers; i++) { // in practice we will only ever have 1 buffer, since audio format is mono | |
AudioBuffer buffer = ioData->mBuffers[i]; | |
// NSLog(@" Buffer %d has %d channels and wants %d bytes of data.", i, buffer.mNumberChannels, buffer.mDataByteSize); | |
// copy temporary buffer data to output buffer | |
UInt32 size = min(buffer.mDataByteSize, [iosAudio aBuffer].mDataByteSize); // dont copy more data then we have, or then fits | |
memcpy(buffer.mData, [iosAudio aBuffer].mData, size); | |
buffer.mDataByteSize = size; // indicate how much data we wrote in the buffer | |
} */ | |
return noErr; | |
} | |
@interface GPUImageMovie () | |
{ | |
BOOL audioEncodingIsFinished, videoEncodingIsFinished; | |
GPUImageMovieWriter *synchronizedMovieWriter; | |
CVOpenGLESTextureCacheRef coreVideoTextureCache; | |
AVAssetReader *reader; | |
CMTime previousFrameTime, previousAudioFrameTime; | |
CFAbsoluteTime previousActualFrameTime, previousAudioActualFrameTime; | |
AudioComponentInstance audioUnit; | |
TPCircularBuffer tpCircularBuffer; | |
} | |
- (void)processAsset; | |
@end | |
@implementation GPUImageMovie | |
@synthesize url = _url; | |
@synthesize asset = _asset; | |
@synthesize runBenchmark = _runBenchmark; | |
@synthesize playAtActualSpeed = _playAtActualSpeed; | |
@synthesize completionBlock; | |
@synthesize tpCircularBuffer; | |
#pragma mark - | |
#pragma mark Initialization and teardown | |
- (id)initWithURL:(NSURL *)url; | |
{ | |
if (!(self = [super init])) | |
{ | |
return nil; | |
} | |
[self textureCacheSetup]; | |
self.url = url; | |
self.asset = nil; | |
TPCircularBufferInit(&tpCircularBuffer1, 4096*500); | |
return self; | |
} | |
- (id)initWithAsset:(AVAsset *)asset; | |
{ | |
if (!(self = [super init])) | |
{ | |
return nil; | |
} | |
[self textureCacheSetup]; | |
self.url = nil; | |
self.asset = asset; | |
TPCircularBufferInit(&tpCircularBuffer1, 4096*500); | |
return self; | |
} | |
- (void)textureCacheSetup; | |
{ | |
if ([GPUImageOpenGLESContext supportsFastTextureUpload]) | |
{ | |
runSynchronouslyOnVideoProcessingQueue(^{ | |
[GPUImageOpenGLESContext useImageProcessingContext]; | |
#if defined(__IPHONE_6_0) | |
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, [[GPUImageOpenGLESContext sharedImageProcessingOpenGLESContext] context], NULL, &coreVideoTextureCache); | |
#else | |
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[[GPUImageOpenGLESContext sharedImageProcessingOpenGLESContext] context], NULL, &coreVideoTextureCache); | |
#endif | |
if (err) | |
{ | |
NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d", err); | |
} | |
// Need to remove the initially created texture | |
[self deleteOutputTexture]; | |
}); | |
} | |
} | |
- (void)dealloc | |
{ | |
if ([GPUImageOpenGLESContext supportsFastTextureUpload]) | |
{ | |
CFRelease(coreVideoTextureCache); | |
} | |
TPCircularBufferCleanup(&tpCircularBuffer1); | |
} | |
#pragma mark - | |
#pragma mark Movie processing | |
- (void)enableSynchronizedEncodingUsingMovieWriter:(GPUImageMovieWriter *)movieWriter; | |
{ | |
synchronizedMovieWriter = movieWriter; | |
movieWriter.encodingLiveVideo = NO; | |
} | |
- (void)startProcessing | |
{ | |
if(self.url == nil) | |
{ | |
[self processAsset]; | |
return; | |
} | |
previousFrameTime = kCMTimeZero; | |
previousActualFrameTime = CFAbsoluteTimeGetCurrent(); | |
previousAudioFrameTime = kCMTimeZero; | |
previousAudioActualFrameTime = CFAbsoluteTimeGetCurrent(); | |
NSDictionary *inputOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]; | |
AVURLAsset *inputAsset = [[AVURLAsset alloc] initWithURL:self.url options:inputOptions]; | |
[inputAsset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"tracks"] completionHandler: ^{ | |
NSError *error = nil; | |
AVKeyValueStatus tracksStatus = [inputAsset statusOfValueForKey:@"tracks" error:&error]; | |
if (!tracksStatus == AVKeyValueStatusLoaded) | |
{ | |
return; | |
} | |
self.asset = inputAsset; | |
[self setupAudio]; | |
[self processAsset]; | |
}]; | |
} | |
- (void)processAsset | |
{ | |
__unsafe_unretained GPUImageMovie *weakSelf = self; | |
NSError *error = nil; | |
reader = [AVAssetReader assetReaderWithAsset:self.asset error:&error]; | |
NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary]; | |
[outputSettings setObject: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey: (NSString*)kCVPixelBufferPixelFormatTypeKey]; | |
// Maybe set alwaysCopiesSampleData to NO on iOS 5.0 for faster video decoding | |
AVAssetReaderTrackOutput *readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings]; | |
[reader addOutput:readerVideoTrackOutput]; | |
NSArray *audioTracks = [self.asset tracksWithMediaType:AVMediaTypeAudio]; | |
// BOOL shouldRecordAudioTrack = (([audioTracks count] > 0) && (weakSelf.audioEncodingTarget != nil) ); | |
AVAssetReaderTrackOutput *readerAudioTrackOutput = nil; | |
//if (shouldRecordAudioTrack) | |
if ([audioTracks count] > 0) | |
{ | |
audioEncodingIsFinished = NO; | |
// AudioChannelLayout channelLayout; | |
// memset(&channelLayout, 0, sizeof(AudioChannelLayout)); | |
// channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; | |
NSMutableDictionary *audioOutputSettings = [NSMutableDictionary dictionary]; | |
[audioOutputSettings setObject:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey]; | |
[audioOutputSettings setObject:[NSNumber numberWithInt:44100] forKey:AVSampleRateKey]; | |
[audioOutputSettings setObject:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey]; | |
// [audioOutputSettings setObject:[NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)] forKey:AVChannelLayoutKey]; | |
[audioOutputSettings setObject:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey]; | |
[audioOutputSettings setObject:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey]; | |
[audioOutputSettings setObject:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey]; | |
[audioOutputSettings setObject:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsNonInterleaved]; | |
// This might need to be extended to handle movies with more than one audio track | |
AVAssetTrack* audioTrack = [audioTracks objectAtIndex:0]; | |
readerAudioTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:audioOutputSettings]; | |
[reader addOutput:readerAudioTrackOutput]; | |
} | |
if ([reader startReading] == NO) | |
{ | |
NSLog(@"Error reading from file at URL: %@", weakSelf.url); | |
return; | |
} | |
if (synchronizedMovieWriter != nil) | |
{ | |
[synchronizedMovieWriter setVideoInputReadyCallback:^{ | |
[weakSelf readNextVideoFrameFromOutput:readerVideoTrackOutput]; | |
}]; | |
[synchronizedMovieWriter setAudioInputReadyCallback:^{ | |
[weakSelf readNextAudioSampleFromOutput:readerAudioTrackOutput]; | |
}]; | |
[synchronizedMovieWriter enableSynchronizationCallbacks]; | |
} | |
else | |
{ | |
[self startPlay]; | |
while (reader.status == AVAssetReaderStatusReading) | |
{ | |
[weakSelf readNextVideoFrameFromOutput:readerVideoTrackOutput]; | |
// if ( (shouldRecordAudioTrack) && (!audioEncodingIsFinished) ) | |
if (!audioEncodingIsFinished) | |
{ | |
[weakSelf readNextAudioSampleFromOutput:readerAudioTrackOutput]; | |
} | |
} | |
[self stopPlay]; | |
if (reader.status == AVAssetWriterStatusCompleted) { | |
[weakSelf endProcessing]; | |
} | |
} | |
} | |
- (void)readNextVideoFrameFromOutput:(AVAssetReaderTrackOutput *)readerVideoTrackOutput; | |
{ | |
if (reader.status == AVAssetReaderStatusReading) | |
{ | |
CMSampleBufferRef sampleBufferRef = [readerVideoTrackOutput copyNextSampleBuffer]; | |
if (sampleBufferRef) | |
{ | |
if (_playAtActualSpeed) | |
{ | |
// Do this outside of the video processing queue to not slow that down while waiting | |
CMTime currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBufferRef); | |
CMTime differenceFromLastFrame = CMTimeSubtract(currentSampleTime, previousFrameTime); | |
CFAbsoluteTime currentActualTime = CFAbsoluteTimeGetCurrent(); | |
CGFloat frameTimeDifference = CMTimeGetSeconds(differenceFromLastFrame); | |
CGFloat actualTimeDifference = currentActualTime - previousActualFrameTime; | |
if (frameTimeDifference > actualTimeDifference) | |
{ | |
usleep(1000000.0 * (frameTimeDifference - actualTimeDifference)); | |
} | |
previousFrameTime = currentSampleTime; | |
previousActualFrameTime = CFAbsoluteTimeGetCurrent(); | |
} | |
__unsafe_unretained GPUImageMovie *weakSelf = self; | |
runSynchronouslyOnVideoProcessingQueue(^{ | |
[weakSelf processMovieFrame:sampleBufferRef]; | |
}); | |
CMSampleBufferInvalidate(sampleBufferRef); | |
CFRelease(sampleBufferRef); | |
} | |
else | |
{ | |
videoEncodingIsFinished = YES; | |
[self endProcessing]; | |
} | |
} | |
else if (synchronizedMovieWriter != nil) | |
{ | |
if (reader.status == AVAssetWriterStatusCompleted) | |
{ | |
[self endProcessing]; | |
} | |
} | |
} | |
- (void)readNextAudioSampleFromOutput:(AVAssetReaderTrackOutput *)readerAudioTrackOutput; | |
{ | |
if (audioEncodingIsFinished) | |
{ | |
return; | |
} | |
CMSampleBufferRef audioSampleBufferRef = [readerAudioTrackOutput copyNextSampleBuffer]; | |
if (audioSampleBufferRef) | |
{ | |
__unsafe_unretained GPUImageMovie *weakSelf = self; | |
runSynchronouslyOnVideoProcessingQueue(^{ | |
[self.audioEncodingTarget processAudioBuffer:audioSampleBufferRef]; | |
[weakSelf processAudioFrame:audioSampleBufferRef]; | |
CMSampleBufferInvalidate(audioSampleBufferRef); | |
CFRelease(audioSampleBufferRef); | |
}); | |
} | |
else | |
{ | |
audioEncodingIsFinished = YES; | |
} | |
} | |
- (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer; | |
{ | |
// CMTimeGetSeconds | |
// CMTimeSubtract | |
CMTime currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(movieSampleBuffer); | |
CVImageBufferRef movieFrame = CMSampleBufferGetImageBuffer(movieSampleBuffer); | |
int bufferHeight = CVPixelBufferGetHeight(movieFrame); | |
#if TARGET_IPHONE_SIMULATOR | |
int bufferWidth = CVPixelBufferGetBytesPerRow(movieFrame) / 4; // This works around certain movie frame types on the Simulator (see https://github.com/BradLarson/GPUImage/issues/424) | |
#else | |
int bufferWidth = CVPixelBufferGetWidth(movieFrame); | |
#endif | |
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); | |
if ([GPUImageOpenGLESContext supportsFastTextureUpload]) | |
{ | |
CVPixelBufferLockBaseAddress(movieFrame, 0); | |
[GPUImageOpenGLESContext useImageProcessingContext]; | |
CVOpenGLESTextureRef texture = NULL; | |
CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, coreVideoTextureCache, movieFrame, NULL, GL_TEXTURE_2D, GL_RGBA, bufferWidth, bufferHeight, GL_BGRA, GL_UNSIGNED_BYTE, 0, &texture); | |
if (!texture || err) { | |
NSLog(@"Movie CVOpenGLESTextureCacheCreateTextureFromImage failed (error: %d)", err); | |
return; | |
} | |
outputTexture = CVOpenGLESTextureGetName(texture); | |
// glBindTexture(CVOpenGLESTextureGetTarget(texture), outputTexture); | |
glBindTexture(GL_TEXTURE_2D, outputTexture); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
for (id<GPUImageInput> currentTarget in targets) | |
{ | |
NSInteger indexOfObject = [targets indexOfObject:currentTarget]; | |
NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; | |
[currentTarget setInputSize:CGSizeMake(bufferWidth, bufferHeight) atIndex:targetTextureIndex]; | |
[currentTarget setInputTexture:outputTexture atIndex:targetTextureIndex]; | |
[currentTarget newFrameReadyAtTime:currentSampleTime atIndex:targetTextureIndex]; | |
} | |
CVPixelBufferUnlockBaseAddress(movieFrame, 0); | |
// Flush the CVOpenGLESTexture cache and release the texture | |
CVOpenGLESTextureCacheFlush(coreVideoTextureCache, 0); | |
CFRelease(texture); | |
outputTexture = 0; | |
} | |
else | |
{ | |
// Upload to texture | |
CVPixelBufferLockBaseAddress(movieFrame, 0); | |
glBindTexture(GL_TEXTURE_2D, outputTexture); | |
// Using BGRA extension to pull in video frame data directly | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferWidth, bufferHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(movieFrame)); | |
CGSize currentSize = CGSizeMake(bufferWidth, bufferHeight); | |
for (id<GPUImageInput> currentTarget in targets) | |
{ | |
NSInteger indexOfObject = [targets indexOfObject:currentTarget]; | |
NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; | |
[currentTarget setInputSize:currentSize atIndex:targetTextureIndex]; | |
[currentTarget newFrameReadyAtTime:currentSampleTime atIndex:targetTextureIndex]; | |
} | |
CVPixelBufferUnlockBaseAddress(movieFrame, 0); | |
} | |
if (_runBenchmark) | |
{ | |
CFAbsoluteTime currentFrameTime = (CFAbsoluteTimeGetCurrent() - startTime); | |
NSLog(@"Current frame time : %f ms", 1000.0 * currentFrameTime); | |
} | |
} | |
- (void)endProcessing; | |
{ | |
for (id<GPUImageInput> currentTarget in targets) | |
{ | |
[currentTarget endProcessing]; | |
} | |
if (synchronizedMovieWriter != nil) | |
{ | |
[synchronizedMovieWriter setVideoInputReadyCallback:^{}]; | |
[synchronizedMovieWriter setAudioInputReadyCallback:^{}]; | |
} | |
if (completionBlock) | |
{ | |
completionBlock(); | |
} | |
} | |
- (void)processAudioFrame:(CMSampleBufferRef)movieSampleBuffer; | |
{ | |
// CMTime currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(movieSampleBuffer); | |
// NSLog(@"%f\n", CMTimeGetSeconds(currentSampleTime)); | |
// CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(movieSampleBuffer); | |
AudioBufferList audioBufferList; | |
CMBlockBufferRef blockBuffer; | |
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(movieSampleBuffer,NULL,&audioBufferList,sizeof(audioBufferList),NULL,NULL,kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,&blockBuffer); | |
// _audio_buffer = audioBufferList.mBuffers[0]; | |
// self.aBuffer = audioBufferList.mBuffers[0]; | |
// AudioBuffer aBuf = audioBufferList.mBuffers[0]; | |
// [sampleBuffer addAudioBuffer:&aBuf]; | |
/* AudioStreamBasicDescription audioFormat; | |
audioFormat.mSampleRate = 44100.00; | |
audioFormat.mFormatID = kAudioFormatLinearPCM; | |
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; | |
audioFormat.mFramesPerPacket = 1; | |
audioFormat.mChannelsPerFrame = 2; | |
audioFormat.mBitsPerChannel = 16; | |
audioFormat.mBytesPerPacket = 4; | |
audioFormat.mBytesPerFrame = 4; | |
*/ | |
if (!TPCircularBufferCopyAudioBufferList(&tpCircularBuffer1, &audioBufferList, NULL, kTPCircularBufferCopyAll, NULL)) | |
NSLog(@"ahhhhhhh\n"); | |
CFRelease(blockBuffer); | |
/* int j=0; | |
float* buffer111 = (float*)malloc(numSamplesInBuffer*sizeof(float)); | |
for (int i = 0; i < numSamplesInBuffer; i+=2) | |
{ | |
// buffer[i] = (float)(audioBufferList.mBuffers[0].(mData+(i*4)) + audioBufferList.mBuffers[0].mData+((i*4)+1) +audioBufferList.mBuffers[0].(mData+(i*4)+2) +audioBufferList.mBuffers[0].(mData+(i*4)+3) ); | |
// buffer[i] = (float)(*(char*)(aBuffer.mData+i) + *(char*)(aBuffer.mData+i+1) + *(char*)(aBuffer.mData+i+2) + *(char*)(aBuffer.mData+i+3)); | |
// int32_t* samples = (int32_t*)(aBuffer.mData); | |
SInt16* samples = (SInt16*)(audioBufferList.mBuffers[0].mData); | |
// buffer111[i] = (float)(*((int32_t*)aBuffer.mData+(i*sizeof(int32_t)))); | |
buffer111[j++] = (*(samples+(i*sizeof(SInt16)))) * SHRT_MAX; | |
// NSLog(@"%f\n", *(samples+(i*sizeof(float)))); | |
// buffer[i] = (float)(*(aBuffer.mData+i)); | |
} | |
// NSLog(@"%f ", buffer[0]); | |
player = [[AVBufferPlayer alloc] initWithBuffer:buffer111 frames:numSamplesInBuffer/2]; | |
free(buffer111); | |
[player play]; | |
*/ | |
} | |
- (void) setupAudio { | |
OSStatus status; | |
SInt32 ambient = kAudioSessionCategory_SoloAmbientSound; | |
if (AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (ambient), &ambient)) { | |
NSLog(@"Error setting ambient property"); | |
} | |
// Describe audio component | |
AudioComponentDescription desc; | |
desc.componentType = kAudioUnitType_Output; | |
desc.componentSubType = kAudioUnitSubType_RemoteIO; | |
desc.componentFlags = 0; | |
desc.componentFlagsMask = 0; | |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; | |
// Get component | |
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc); | |
// Get audio units | |
status = AudioComponentInstanceNew(inputComponent, &audioUnit); | |
checkStatus(status); | |
UInt32 flag = 1; | |
// Enable IO for playback | |
status = AudioUnitSetProperty(audioUnit, | |
kAudioOutputUnitProperty_EnableIO, | |
kAudioUnitScope_Output, | |
kOutputBus, | |
&flag, | |
sizeof(flag)); | |
checkStatus(status); | |
// Describe format | |
AudioStreamBasicDescription audioFormat; | |
audioFormat.mSampleRate = 44100.00; | |
audioFormat.mFormatID = kAudioFormatLinearPCM; | |
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; | |
audioFormat.mFramesPerPacket = 1; | |
audioFormat.mChannelsPerFrame = 2; | |
audioFormat.mBitsPerChannel = 16; | |
audioFormat.mBytesPerPacket = 4; | |
audioFormat.mBytesPerFrame = 4; | |
// Apply format | |
status = AudioUnitSetProperty(audioUnit, | |
kAudioUnitProperty_StreamFormat, | |
kAudioUnitScope_Input, | |
kOutputBus, | |
&audioFormat, | |
sizeof(audioFormat)); | |
checkStatus(status); | |
// Set output callback | |
AURenderCallbackStruct callbackStruct; | |
callbackStruct.inputProc = playbackCallback; | |
callbackStruct.inputProcRefCon = (__bridge void *)(self); | |
status = AudioUnitSetProperty(audioUnit, | |
kAudioUnitProperty_SetRenderCallback, | |
kAudioUnitScope_Global, | |
kOutputBus, | |
&callbackStruct, | |
sizeof(callbackStruct)); | |
checkStatus(status); | |
// Allocate our own buffers (1 channel, 16 bits per sample, thus 16 bits per frame, thus 2 bytes per frame). | |
// Practice learns the buffers used contain 512 frames, if this changes it will be fixed in processAudio. | |
//tempBuffer.mNumberChannels = 1; | |
//tempBuffer.mDataByteSize = 512 * 2; | |
//tempBuffer.mData = malloc( 512 * 2 ); | |
// Initialise | |
status = AudioUnitInitialize(audioUnit); | |
checkStatus(status); | |
} | |
- (void) startPlay { | |
OSStatus status = AudioOutputUnitStart(audioUnit); | |
checkStatus(status); | |
} | |
- (void) stopPlay { | |
OSStatus status = AudioOutputUnitStop(audioUnit); | |
checkStatus(status); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi ,
Thanks for putting this up... :)
I was tryign to use your code. But I get an error "Problem appending pixel buffer at time: 40"
This is the error message in the function newFrameReadyAtTime of GPUImageMovieWriter
Do you have any clue want it means?
Thanks,
Shruthi.