I am using cocos2d v2 and experiencing a very strange behaviour.
I have a couple of audio tracks which are supposed to be played as background music one after another. But I noticed when these tracks are playing in background, any updates on screen (rendering) isn't working.
For instance I added a new sprite marker after every new track but nothing shown on screen until all the tracks are done playing. I also tried displaying track # using CCLABELBMFont but that also didn't show anything on screen until all tracks are finished playing.
Here's the code:
NSString *keyString;
CCARRAY_FOREACH([[GameManager sharedGameManager] _musicItems], keyString){
if ([[[GameManager sharedGameManager] _soundEngine] isBackgroundMusicPlaying]) {
int waitCycles = 0;
while (waitCycles < AUDIO_MAX_WAITTIME) {
[NSThread sleepForTimeInterval:0.1f];
if (![[[GameManager sharedGameManager] _soundEngine] isBackgroundMusicPlaying]) {
break;
}
waitCycles += 1;
}
}
//play sound file
CCLOG(#"Playing Sound file: %#", keyString);
[[GameManager sharedGameManager] playBackgroundTrack:keyString];
**EDIT:**
/******** changed to include dispatch: start *********/
dispatch_async(dispatch_get_main_queue(), ^{
CCLOG(#"on main thread");
CCSprite *marker = [CCSprite spriteWithSpriteFrameName:#"marker.png"];
[marker setPosition:ccp(100 * count, 200)];
[self addChild:marker z:100];
});
/***************** end **********************/
}
EDIT:
Here's implementation for audio setup
-(void)setupAudioEngine{
if(_hasAudioBeenInitialized){
return; //sound engine already initialized
}
else{
_hasAudioBeenInitialized = YES;
NSOperationQueue *queue = [[NSOperationQueue new] autorelease];
NSInvocationOperation *asyncSetupOperation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(initAudioAsync) object:nil];
[queue addOperation:asyncSetupOperation];
[asyncSetupOperation autorelease];
}
}
-(void)initAudioAsync{
//Initialize audio engine asynchronously
CCLOG(#"Audio Manager Initializing");
_managerSoundState = kAudioManagerInitializing;
//start audio engine
[CDSoundEngine setMixerSampleRate:CD_SAMPLE_RATE_HIGH];
//Init audio manager asynchronously as it can take a few seconds
//The kAMM_FxPlusMusic mode ensure only this game plays audio
[CDAudioManager initAsynchronously:kAMM_FxPlusMusic];
//wait for audio manager to initialize
while ([CDAudioManager sharedManagerState] != kAMStateInitialised) {
[NSThread sleepForTimeInterval:0.1];
}
CDAudioManager *audioManager = [CDAudioManager sharedManager];
if (audioManager.soundEngine == nil || audioManager.soundEngine.functioning == NO) {
CCLOG(#"COCOS Dension failed to init. No audio will play");
_managerSoundState = kAudioManagerFailed;
}
else{
[audioManager setResignBehavior:kAMRBStopPlay autoHandle:YES];
_soundEngine = [SimpleAudioEngine sharedEngine];
_managerSoundState = kAudioManagerReady;
CCLOG(#"COCOS Dension is ready now");
}
}
Anyone has ideas why it's happening?
Your sprites are never drawn, because you are blocking the main thread. You should dispatch asynchronously to a background queue and when you want to make changes to the UI (adding or manipulating sprites) dispatch back to the main queue.
Related
I am creating video in ARKit during session. When I press record button, camera freezes. I have written code in didUpdateFrame delegate that causes the problem. There I save scene.snapshot in an array. Also when i create video from these images, app crashes with following message in debugger:
Message from debugger: Terminated due to memory issue
-(void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
if (_recordButton.state == UIControlStateSelected)
{
currentState = Recording;
[self saveImage];
}
else if (previousState == Recording)
{
NSLog(#"Stop recording");
currentState = NotRecording;
recordTime = NULL;
self.nextButton.enabled=YES;
}
//update recording state per frame update
previousState = currentState;
}
-(void)saveImage
{
UIImage *image = self.sceneView.snapshot;
[self.bufferArray addObject:image];
image = nil;
}
Do not use ARSCNView.snapshot with implementing ARSessionDelegate.didUpdateFrame. I had same issue and solution was do not implement ARSessionDelegate.didUpdateFrame. I have used CADisplayLink with ARSCNView.snapshot and it works well.
I also tried to use ARFrame.capturedImage, but it has not contain AR objects at all. ARSCNView.snapshot contains them.
I'm building an app with multiple view controllers and I have coded an audio file to play at the start up of the app. That works fine and when I click on the button to view a different screen the audio file still plays without skipping a beat just like it's supposed to but my problem arises when I click on the button to go back to the main screen. When I click to go back to the main screen the audio file plays over itself reminding me of the song Row Row Your Boat. The app is re-reading that code that tells itself to play the audio file thus playing it all over again. My problem is that I can't figure out how to make it not do that. I have coded the app to stop the audio when clicking on the start game button, which is what I want it to do but not until then. I just need help getting the app to not play the audio file over itself when going back to the main screen. The audio file is coded to play infinitely until the "start" button is clicked. If anyone can make since out of what I'm trying to say then please help me code this thing correctly. Thanks to anyone who can make it work right.
Here my code:
-(void)viewDidLoad
{
NSString *introMusic = [[NSBundle mainBundle]pathForResource:#"invadingForces" ofType:#"mp3"];
audioPlayer0 = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:introMusic] error:NULL];
audioPlayer0.delegate = self;
audioPlayer0.numberOfLoops = -1;
[audioPlayer0 play];
}
The problem is that you start a sound in a local variable when your view is loaded, start it playing on endless repeat, and then forget about it. Then you close the view controller, leaving the now-forgotten audio player playing. Next time you invoke the view controller, it's viewDidLoad method creates another audio player and starts that one playing too, and then forgets about that one. Every time you open a new copy of that view controller, you'll start yet another sound player, adding another voice to your round of "row, row, row your boat."
The naive solution is to put the code that starts the sound player in the app delegate. Set up the AVAudioPlayer as a property of you app delegate. Create a startPlaying method and a stopPlaying method. In your didFinishLaunchingWithOptions method, call startPlaying.
It's cleaner app design not to put app functionality in your app delegate, but instead create a singleton to manage sound play. (Search on "iOS singleton design pattern" to learn more.) Create an appDidLaunch method in the singleton, and call appDidLaunch from didFinishLaunchingWithOptions to start playing your sound. That way the app delegate doesn't need to have app specific logic in it, but simply calls appDidLaunch and goes on it's way.
EDIT:
If you want to call a method in the app delegate, and your app delegate is declared as:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
Then you'd call it from another file like this:
First, import the app delegate's header:
#import "AppDelegate.h"
And the actual code to call your app delegate's stopPlaying method:
//Get a pointer to the application object.
UIApplication *theApp = [UIApplication sharedApplication];
//ask the application object for a pointer to the app delegate, and cast it
//to our custom "AppDelegate" class. If your app delegate uses a different
//class name, use that name here instead of "AppDelegate"
AppDelegate *theAppDelegate = (AppDelegate *)theApp.delegate;
[theAppDelegate stopPlaying];
Here's some example code to wrap an AVAudioPlayer in a singleton -
BWBackgroundMusic.h
#interface BWBackgroundMusic : NSObject
// singleton getter
+ (instancetype)sharedMusicPlayer;
/* public interface required to control the AVAudioPlayer instance is as follows -
start - plays from start - if playing stops and plays from start
stop - stops and returns play-head to start regardless of state
pause - stops and leaves play-head where it is - if already paused or stopped does nothing
continue - continues playing from where the play-head was left - if playing does nothing
replace audio track with new file - replaceBackgroundMusicWithFileOfName:
set background player to nil - destroyBackgroundMusic
NOTE:- change default track filename in .m #define */
// transport like methods
- (void)startBackgroundMusic;
- (void)stopBackgroundMusic;
- (void)pauseBackgroundMusic;
- (void)continueBackgroundMusic;
// audio source management
- (void)replaceBackgroundMusicWithFileOfName:(NSString*)audioFileName startPlaying:(BOOL)startPlaying;
- (void)destroyBackgroundMusic;
#end
BWBackgroundMusic.m
#import "BWBackgroundMusic.h"
#import <AVFoundation/AVFoundation.h> // must link to project first
#define DEFAULT_BACKGROUND_AUDIO_FILENAME #"invadingForces.mp3"
#interface BWBackgroundMusic ()
#property (strong, nonatomic) AVAudioPlayer *backgroundMusicPlayer;
#end
#implementation BWBackgroundMusic
#pragma mark Singleton getter
+ (instancetype)sharedMusicPlayer {
static BWBackgroundMusic *musicPlayer = nil;
static dispatch_once_t onceToken;
dispatch_once (&onceToken, ^{
musicPlayer = [[self alloc] init];
});
//NSLog(#"sample rate of file is %f",[musicPlayer currentSampleRate]);
return musicPlayer;
}
#pragma mark Initialiser
- (id)init {
//NSLog(#"sharedMusicPlayer from BWBackgroundMusic.h init called...");
if (self = [super init]) {
// self setup _backgroundMusicPlayer here...
// configure the audio player
NSURL *musicURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], DEFAULT_BACKGROUND_AUDIO_FILENAME]];
NSError *error;
if (_backgroundMusicPlayer == nil) {
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:&error];
}
if (_backgroundMusicPlayer == nil) {
NSLog(#"%#",[error description]);
} else {
[self makePlaybackInfinite];
[_backgroundMusicPlayer play];
}
}
return self;
}
#pragma mark Selfish methods
- (void)makePlaybackInfinite {
// access backing ivar directly because this is also called from init method
if (_backgroundMusicPlayer) {
_backgroundMusicPlayer.numberOfLoops = -1;
}
}
- (CGFloat)currentSampleRate {
NSDictionary *settingsDict = [self.backgroundMusicPlayer settings];
NSNumber *sampleRate = [settingsDict valueForKey:AVSampleRateKey];
return [sampleRate floatValue];
}
#pragma mark Transport like methods
- (void)startBackgroundMusic {
// plays from start - if playing stops and plays from start
if (self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer stop];
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];// this is not required as play calls this implicitly if not already prepared
[self.backgroundMusicPlayer play];
}
else {
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];
[self.backgroundMusicPlayer play];
}
}
- (void)stopBackgroundMusic {
// stops and returns play-head to start regardless of state and prepares to play
if (self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer stop];
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];
}
else {
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];
}
}
- (void)pauseBackgroundMusic {
// stops and leaves play-head where it is - if already paused or stopped does nothing
if (self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer pause];
}
}
- (void)continueBackgroundMusic {
// continues playing from where the play-head was left - if playing does nothing
if (!self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer play];
}
}
#pragma mark Content management
- (void)replaceBackgroundMusicWithFileOfName:(NSString*)audioFileName startPlaying:(BOOL)startPlaying {
// construct filepath
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], audioFileName];
// make a url from the filepath
NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
// construct player and prepare
NSError *error;
self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:&error];
[self.backgroundMusicPlayer prepareToPlay];
[self makePlaybackInfinite];
// if startplaying then play
if (startPlaying) {
[self.backgroundMusicPlayer play];
}
}
- (void)destroyBackgroundMusic {
// stop playing if playing
[self stopBackgroundMusic];
// destroy by setting background player to nil
self.backgroundMusicPlayer = nil;
}
#end
To use simply call [BWBackgroundMusic sharedMusicPlayer]; This will instantiate the singleton if not already instantiated, start the player automatically, and will loop infinitely by default.
Furthermore you can control it from any class that imports BWBackgroundMusic.h
For example to pause the player use
[[BWBackgroundMusic sharedMusicPlayer] pauseBackgroundMusic];
I have a "Loading Screen" with a "Loading bar". This loading screen gets images from my server and loads them to CCSprites, so i will be using them in other screens. While the loading screen is downloading images and creating the CCSprites, i want my Loading Bar to update it's UI. LoadingBar is a CCNode subclass.
I know I should use threading to update UI, but the problem is that (as far as I know) Cocos2D nodes are not thread safe so when i use the code below, it does update the UI but then the sprites does not load:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
// go do something asynchronous...
dispatch_async(dispatch_get_main_queue(), ^{
// update UI
});
});
I have tried many things, but i can't solve this problem. Please, help :)
Here is the code that makes my LoadingBar update:
-(void)makeStep{
int actualPersentage = 100 * (numSteps - numStepsToGo + 1) / numSteps;
int objectAtIndex = MAX(0, numSteps - numStepsToGo);
persentageLabel.string = [NSString stringWithFormat:#"%i%% %#", actualPersentage, [steps objectAtIndex:objectAtIndex]];
[frontground setTextureRect:CGRectMake(0, 0, frontground.textureRect.size.width + (200/numSteps), 40)];
frontground.position = ccp(initialPosition + (frontground.contentSize.width/2) - (background.contentSize.width/2), frontground.position.y);
numStepsToGo --;
if(numStepsToGo == 0){
[frontground setTextureRect:CGRectMake(0, 0, 200, 40)];
persentageLabel.string = [NSString stringWithFormat:#"All loaded correctly!"];
}
}
Basically what I have is a background gray rectangle as background and a green one (frontground) that "fills" the background each time makeStep is called.
Here is the code example where i parse a JSON and where my LoadingBar should update:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
...
CCSprite *ssButtonStart = [self getSpriteFromUrl:[startScreenData objectForKey:#"SSimg_button_start"]];
CCSprite *ssButtonExit = [self getSpriteFromUrl:[startScreenData objectForKey:#"SSimg_button_exit"]];
CCSprite *ssBackground = [self getSpriteFromUrl:[startScreenData objectForKey:#"SSimg_background"]];
self.gameProperties.slBackground = ssBackground;
self.gameProperties.slButtonExit = ssButtonExit;
self.gameProperties.slButtonStart = ssButtonStart;
NSLog(#"Start Screen Loading done!");
dispatch_async(dispatch_get_main_queue(), ^{
[self updateStep];
});
...
dispatch_async(dispatch_get_main_queue(), ^{
[self updateStep];
});
//Etc.
Code where i get the image and convert to CCSprite:
-(CCSprite*)getSpriteFromUrl:(NSString*)stringUrl{
__block NSData *imageData;
__block CCSprite *sprite;
imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:stringUrl]];
//NSLog(#"string imageData: %#", imageData);
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageWithData: imageData]];
dispatch_async(dispatch_get_main_queue(), ^{
CCTexture2D *texture = [[CCTexture2D alloc] initWithCGImage:imView.image.CGImage resolutionType:kCCResolutionUnknown];
if(texture != nil){
sprite = [[CCSprite alloc] init];
sprite = [[CCSprite alloc] initWithTexture:texture];
}else{
[self showError:#"Some textures didn't load correctly. Please try again!"];
}
// update UI
});
return sprite;
}
I am using iOS 7 and Cocos2D 2.0
You should update the bar on the main thread. Instead move the downloading of images to a background thread. NSData or NSURLRequest even has async methods to do so. Then when you have completed downloading the images create the CCSprites on the main thread as a last step. Sprites have to be created on the main thread anyway.
This answer from LearnCocos2D (see code in question) plus:
Solved! I made an sleep after getting every sprite in getSpriteFromUrl. The code is [NSThread sleepForTimeInterval:0.2];. Hope it helps anyone else!
From me, solved my question. Thanks!
I have a method that runs concurrently with recording a video. When the method ends it fires off a chain of other methods that continues until the recording ends. I want to be able to press a button to stop the recording prematurely that also exits the method at the same time. The way I'm currently trying to do it is with an NSTimer that checks to see if the recording is still happening, and if it isn't, it stops playing audio and should also call return to stop the method.
-(void) method
{
self.stopTimer = [NSTimer scheduledTimerWithTimeInterval:.05 target:self selector:#selector(checkRecording) userInfo:nil repeats:YES];
// Stuff happens
}
-(void) checkRecording
{
if (isRecording == NO)
{
if (player.playing == YES)
{
[player stop];
}
return;
}
}
This stops the audio immediately but the method continues to run until it's done. It doesn't call the next method in the sequence, which is a step in the right direction, but I need it to stop immediately. My only theory is that it's because I'm not calling return inside the actual method that I want to stop and instead in a different method, but even if that's the case I'm not really sure how to fix that because as far as I know timers can only point to other methods and I can't just tell it what I want it to do inside of the method that I want to stop. And if that's not the issue then I'm really not sure why this isn't working.
If a timer is valid you can invalidate it (that stops the timer).
I'm not sure if all the checking is really necessary (& the last line) but I do it currently that way:
if ( myTimer != nil && [myTimer isValid] )
{
[myTimer invalidate];
myTimer = nil;
}
EDITED:
if ( [myTimer isValid] )
{
[myTimer invalidate];
myTimer = nil;
}
My only theory is that it's because I'm not calling return inside the actual method that I want to stop and instead in a different method
Your theory is correct. return ends the function or method it is in, and none other. It pops the current function's context off the stack and returns execution to the calling function.
I'm not really sure how to fix that because as far as I know timers can only point to other methods and I can't just tell it what I want it to do inside of the method that I want to stop
We can use objects to store state and use that state to control the flow of our program. That state can be continually updated and checked. With a long-running task that needs to be cancelled in response to changes in that state, the state must be updated in parallel with the task. Since you say the timer works for stopping audio, but that the work done in method doesn't, I'm assuming that method is performing its long-running task asynchronously already.
This need to do an asynchronous long-running task (or series of tasks) in the background, with the possibility of cancellation, is nicely matched to the NSOperation and NSOperationQueue classes.
You can perform your work inside NSOperation objects, either via implementing methods or blocks. Implement your code to check if the operation has been cancelled at all appropriate times, and bail out as soon as that happens.
Below is an example that hopefully matches your use case. It was created in an iOS app 'empty application' template an everything is in the application delegate. Our app delegate keeps track of the state necessary to make the decision of whether to cancel or not, and also schedules a timer to poll for changes in that state. If it does determine that it should cancel, it delegates the actual cancellation of work to the operation queue and its operations.
#import "AppDelegate.h"
#interface AppDelegate ()
#property (nonatomic) BOOL shouldStop; // Analogous to your isRecording variable
#property (nonatomic, strong) NSOperationQueue *operationQueue; // This manages execution of the work we encapsulate into NSOperation objects
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Typical app delegate stuff
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
// Start our long running method - analogous to method in your example
[self method];
return YES;
}
- (void)method
{
// allocate operation queue and set its concurrent operation count to 1. this gives us basic ordering of
// NSOperations. More complex ordering can be done by specifying dependencies on operations.
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// We create three NSBlockOperations. They only sleep the thread a little while,
// check if they've been cancelled and should stop, and keep doing that for a few seconds.
// When they are completed (either through finishing normally or through being cancelled, they
// log a message
NSMutableArray *operations = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
// Block operations allow you to specify their work by providing a block.
// You can override NSOperation to provide your own custom implementation
// of main, or start, depending. Read the documentation for more details.
// The principle will be the same - check whether one should cancel at each
// appropriate moment and bail out if so
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
// For the "weak/strong dance" to avoid retain cycles
__weak NSBlockOperation *weakOperation = operation;
[operation addExecutionBlock:^{
// Weak/strong dance
NSBlockOperation *strongOperation = weakOperation;
// Here is where you'd be doing actual work
// Either in a block or in the main / start
// method of your own NSOperation subclass.
// Instead we sleep for some time, check if
// cancelled, bail out if so, and then sleep some more.
for (int i = 0; i < 300; i++) {
if ([strongOperation isCancelled]) {
return;
}
usleep(10000);
}
}];
// The completion block is called whether the operation is cancelled or not.
operation.completionBlock = ^{
// weak/strong dance again
NSBlockOperation *strongOperation = weakOperation;
NSLog(#"Operation completed, %# cancelled.", [strongOperation isCancelled] ? #"WAS" : #"WAS NOT");
};
[operations addObject:operation];
}
// Set up a timer that checks the status of whether we should stop.
// This timer will cancel the operations if it determines it should.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(checkShouldKeepGoing:) userInfo:nil repeats:YES];
// Use GCD to simulate a stopped recording to observe how the operations react to that.
// Comment out to see the usual case.
double delayInSeconds = 5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
self.shouldStop = YES;
});
// Add the operations to the operation queue, exeuction will start asynchronously from here.
[self.operationQueue addOperations:operations waitUntilFinished:NO];
}
// If we should stop, cancel the operations in the queue.
- (void)checkShouldKeepGoing:(NSTimer *)timer
{
if (self.shouldStop) {
NSLog(#"SHOULD STOP");
[timer invalidate];
[self.operationQueue cancelAllOperations];
}
}
#end
I have the code below that captures jpeg frames at 30fps and records the video in mp4 format. I'm trying to wrap the processFrame method in a dispatch_async call so that the recording process will not lockup the video player. The problem with this is I'm getting Memory Warning level 2 and the app ultimately crashes after a few seconds. I can see that the dispatch_async method loads the queue in memory as it tries to append each frame in the recorded video output ,and at 30fps, it doesn't have enough time to process the frame and release used memory. I tried using dispatch_after to delay execution of processFrame but it doesn't help. Any ideas? Should I be doing this differently?
This method gets called at around 30 times per second.
//Process the data sent by the server and send follow-up commands if needed
-(void)processServerData:(NSData *)data{
//render the video in the UIImage control
UIImage *image =[UIImage imageWithData:data];
imageCtrl.image = image;
//record the frame in the background
dispatch_async(recordingQueue,^{[self processFrame:image];});
}
}
processFrame method
//function for processing each frame for recording
-(void) processFrame:(UIImage *) image {
if (myRecorder.frameCounter < myRecorder.maxFrames)
{
if([myRecorder.writerInput isReadyForMoreMediaData])
{
CMTime frameTime = CMTimeMake(1, myRecorder.timeScale);
CMTime lastTime=CMTimeMake(myRecorder.frameCounter, myRecorder.timeScale);
CMTime presentTime=CMTimeAdd(lastTime, frameTime);
buffer = [Recorder pixelBufferFromCGImage:image.CGImage size:myRecorder.imageSize];
if(buffer)
{
[myRecorder.adaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
myRecorder.frameCounter++;
CVBufferRelease(buffer);
if (myRecorder.frameCounter==myRecorder.maxFrames)
{
[myRecorder finishSession];
myRecorder.frameCounter=0;
myRecorder.isRecording = NO;
}
}
else
{
NSLog(#"Buffer is empty");
}
}
else
{
NSLog(#"adaptor not ready frameCounter=%d ",myRecorder.frameCounter );
}
}
}
Solved! I discovered that I can use dispatch_async_semaphore to prevent the queue from being overloaded with too much request that it won't have enough time to release allocated resources.
Here's my updated code:
long success = dispatch_semaphore_wait(recordingSemaphore, DISPATCH_TIME_FOREVER);
if (success != 0 )
{
NSLog(#"Frame skipped");
}
else
{
dispatch_async(recordingQueue,^{
dispatch_semaphore_signal(recordingSemaphore);
[self processFrame:image];
});
}
The dispatch_semaphore created somewhere in my code. Here, I told the semaphore to accept up to only 50 request first and finish the processing before accepting anymore request.
dispatch_semaphore_t recordingSemaphore = dispatch_semaphore_create((long) 50); //so far stable at 50