I'm following the iOS "Audio Queue Programming Guide - Playing Audio". Near the end of the guide, there are calls to CFRunLoopRunInMode() in the step Start and Run an Audio Queue:
do { // 5
CFRunLoopRunInMode ( // 6
kCFRunLoopDefaultMode, // 7
0.25, // 8
false // 9
);
} while (aqData.mIsRunning);
//...
The documentation about line 6 says:
"The CFRunLoopRunInMode function runs the run loop that contains the audio queue’s thread."
But isn't that run loop executed anyways when my method returns? The code above is executed by the main thread upon pressing the play button in my app.
Now I'm having a hard time understanding what these calls to CFRunLoopRunInMode() are good for, because they have the disadvantage that my play-button does not update correctly (it looks pressed down for the whole time that the audio plays) and there is no positive effect, i.e. the audio also plays nicely if I remove the do-while-loop from my code along with the calls to CFRunLoopRunInMode() and instead directly return from this method.
Well this points to the obvious solution to simply keep these calls removed as this doesn't create a problem. Can someone explain why then this code is included in the official guide by Apple on using Audio Queues in iOS for Audio Playback?
Edit:
I'm just seeing that in Mac OS X, there exists the same audio queues API as on iOS, and the guide for iOS seems to be a copy-paste duplication of the Mac OS guide. This leads me to the suspicion that those calls to the run loop are only required in Mac OS and not anymore in iOS, e.g. because otherwise the Mac OS application would exit or something like that. Can someone please verify this or rule it out?
#bunnyhero is right, CFRunLoopRunInMode() is usually for command line examples
https://github.com/abbood/Learning-Core-Audio-Book-Code-Sample/blob/master/CH05_Player/CH05_Player/main.c
As long as your AudioQueueRef is not deallocated, you dont have to use CFRunLoopRunInMode() in IOS...
What I do is create a separate class for audio queue and as long as my class pointer and AudioQueueRef is allocated I can playback, pause, resume, stop etc....
Related to OP's question, regarding AQ blocking UI thread, to further liberate AQ user from copying that CoreAudio AQ example cited blindly,
I shall add that the example configures the AQ to run in the current runloop in the main thread, in Listing 3-11 Creating a playback audio queue:
AudioQueueNewOutput ( // 1
&aqData.mDataFormat, // 2
HandleOutputBuffer, // 3
&aqData, // 4
CFRunLoopGetCurrent (), // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
, see the parameter value CFRunLoopGetCurrent () above. The text explains
The current run loop, and the one on which the audio queue playback
callback will be invoked.
Looking at the function prototype:
OSStatus AudioQueueNewOutput(
const AudioStreamBasicDescription *inFormat, // 2
AudioQueueOutputCallback inCallbackProc, // 3
void *inUserData, // 4
CFRunLoopRef inCallbackRunLoop, // 5
CFStringRef inCallbackRunLoopMode, // 6
UInt32 inFlags, // 7
AudioQueueRef _Nullable *outAQ // 8
);
If you replace #5 with NULL, then AQ will run in a CoreAudio internal thread, making it more efficient for you app.
CFRunLoopRunInMode is needed to keep the audio queue alive while the execution of your code has ended, for example, when running a terminal app. iOS apps contain a lifecyle: To keep an audio queue alive you only need to declare AudioQueueRef as a member variable. Otherwise, if it is declared within a method scope it gets destroyed after execution of that method - and thus, it will stop - unless you keep it alive with CFRunLoopRunInMode.
To summarize, as long as you hold a member variable of AudioQueueRef - or the new AVAudioEngine - in an instantiated class that is not free'ed from memory, a CFRunLoopRunInMode is not needed.
Related
I am working on an iOS application that, say on a button click, launches several threads, each executing a piece of Open GL code. These threads either have a different EAGLContext set on them, or if they use same EAGLContext, then they are synchronised (i.e. 2 threads don't set same EAGLContext in parallel).
Now suppose the app goes into background. As per Apple's documentation, we should stop all the OpenGL calls in applicationWillResignActive: callback so that by the time applicationDidEnterBackground: is called, no further GL calls are made.
I am using dispatch_queues to create background threads. For e.g.:
__block Byte* renderedData; // some memory already allocated
dispatch_sync(glProcessingQueue, ^{
[EAGLContext setCurrentContext:_eaglContext];
glViewPort(...)
glBindFramebuffer(...)
glClear(...)
glDrawArrays(...)
glReadPixels(...) // read in renderedData
}
use renderedData for something else
My question is - how to handle applicationWillResignActive: so that any such background GL calls can be not just stopped, but also be able to resume on applicationDidBecomeActive:? Should I wait for currently running blocks to finish before returning from applicationWillResignActive:? Or should I just suspend glProcessingQueue and return?
I have also read that similar is the case when app is interrupted in other ways, like displaying an alert, a phone call, etc.
I can have multiple such threads at any point of time, invoked by possibly multiple ViewControllers, so I am looking for some scalable solution or design pattern.
The way I see it you need to either pause a thread or kill it.
If you kill it you need to ensure all resources are released which means again calling openGL most likely. In this case it might actually be better to simply wait for the block to finish execution. This means the block must not take too long to finish which is impossible to guarantee and since you have multiple contexts and threads this may realistically present an issue.
So pausing seems better. I am not sure if there is a direct API to pause a thread but you can make it wait. Maybe a s system similar to this one can help.
The linked example seems to handle exactly what you would want; it already checks the current thread and locks that one. I guess you could pack that into some tool as a static method or a C function and wherever you are confident you can pause the thread you would simply do something like:
dispatch_sync(glProcessingQueue, ^{
[EAGLContext setCurrentContext:_eaglContext];
[ThreadManager pauseCurrentThreadIfNeeded];
glViewPort(...)
glBindFramebuffer(...)
[ThreadManager pauseCurrentThreadIfNeeded];
glClear(...)
glDrawArrays(...)
glReadPixels(...) // read in renderedData
[ThreadManager pauseCurrentThreadIfNeeded];
}
You might still have an issue with main thread if it is used. You might want to skip pause on that one otherwise your system may simply never wake up again (not sure though, try it).
So now you are look at interface of your ThreadManager to be something like:
+ (void)pause {
__threadsPaused = YES;
}
+ (void)resume {
__threadsPaused = NO;
}
+ (void)pauseCurrentThreadIfNeeded {
if(__threadsPaused) {
// TODO: insert code for locking until __threadsPaused becomes false
}
}
Let us know what you find out.
I need to determine when my RemoteIO callback is changing the buffer size. Until iOS 7 we could add a session property listener using AudioSessionAddPropertyListener and then property kAudioSessionProperty_PreferredHardwareIOBufferDuration. This is now deprecated. Is there any replacement? AVAudioSession is meant to be KVO compliant, but not for the IOBufferDuration or preferredIOBufferDuration properties.
What is the replacement here?
The buffer duration is given to the RemoteIO callback in the form of the frameCount (proportional to the number of samples in the callback buffer) at a known sample rate. Any other notification would be asynchronous to this callback information, and thus possibly received at the wrong time compared to the actual change (which happens in the audio thread, not in the UI main run loop).
But your audio callback could change some visible state (global or in the parameter struct) which could be found by any other polling thread or consumer thread after the buffer duration update.
Apple's official documentation is sometimes difficult for understanding, especially for non-native speakers. This is an excerpt from Anatomy of NSRunLoop
A run loop is very much like its name sounds. It is a loop your thread enters and uses to run event handlers in response to incoming events. Your code provides the control statements used to implement the actual loop portion of the run loop—in other words, your code provides the while or for loop that drives the run loop. Within your loop, you use a run loop object to "run” the event-processing code that receives events and calls the installed handlers.
This confuses me. My code never provides while or for loops even for non-main threads. What is being meant here? Can anyone explain?
Keep reading until Using Run Loop Objects and Apple’s code samples do show control statements like while loops.
Listing 3-1
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
Listing 3-2
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
// If a source explicitly stopped the run loop, or if there are no
// sources or timers, go ahead and exit.
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
done = YES;
// Check for any other exit conditions here and set the
// done variable as needed.
}
while (!done);
The intended way to use NSRunLoop does require you to invoke the next run, again and again until a certain condition is met.
But if you start your run loop with -[NSRunLoop run], it runs indefinitely without help. That’s what the main thread does.
In case you’re wondering why Apple lets (or wants) you to control every loop, NeXTSTEP shipped in the 80s when every CPU cycle counts. Functions like -[NSRunLoop runMode:beforeDate:] lets you fine tune the frequency and behaviour of your run loops down to every run.
Oh, you do run a loop on the main thread, but you don't know.
Set a breakpoint on an action method and look at the stack trace. There will be something like:
#9 0x00007fff912eaa29 in -[NSApplication run] ()
That's the loop.
In another thread you very often do not need a instance of NSRunLoop. Its primary ability is to receive events and to dispatch them. But in an additional thread you want to process calculations straight forwarded in most cases. To have a term for it: Additional threads are usually not event-driven.
So you have a run loop (and have to run it) only rarely, especially when you have networking or file access that is dispatched using a run loop.In such a case it is a common mistake that one does not run the thread's run loop.
The problem occurs when I often stop and start audio playback and seek a lot back and forth in an AAC audio file through an ExtAudioFileRef object. In few cases, this strange behaviour is shown by ExtAudioFileRead:
Sometimes it assigns these numbers to the mDataByteSize of the only AudioBuffer of the AudioBufferList:
-51604480
-51227648
-51350528
-51440640
-51240960
In hex, these numbers have the pattern 0xFC....00.
The code:
status = ExtAudioFileRead(_file, &numberFramesRead, ioData);
printf("s=%li d=%p d.nb=%li, d.b.d=%p, d.b.dbs=%li, d.b.nc=%li\n", status, ioData, ioData->mNumberBuffers, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize, ioData->mBuffers[0].mNumberChannels);
Output:
s=0 d=0x16668bd0 d.nb=1, d.b.d=0x30de000, d.b.dbs=1024, d.b.nc=2 // good (usual)
s=0 d=0x16668bd0 d.nb=1, d.b.d=0x30de000, d.b.dbs=-51240960, d.b.nc=2 // misbehaving
The problem occurs on an iPhone 4S on iOS 7. I could not reproduce the problem in the Simulator.
The problem occurs when concurrently calling ExtAudioFileRead() and ExtAudioFileSeek() for the same ExtAudioFileRef from two different threads/queues.
The read function was called directly from the AURenderCallback, so it was executed on AudioUnit's real-time thread while the seek was done on my own serial queue.
I've modified the code of the render callback to also dispatch_sync() to the same serial queue to which the seek gets dispatched. That solved the problem.
NB: The entire code base for this project is so large that posting any meaningful amount wold render this question too localised, I have tried to distil any code down to the bare-essentials. I'm not expecting anyone to solve my problems directly but I will up vote those answers I find helpful or intriguing.
This project uses a modified version of AudioStreamer to playback audio files that are saved to locally to the device (iPhone).
The stream is set up and scheduled on the current loop using this code (unaltered from the standard AudioStreamer project as far as I know):
CFStreamClientContext context = {0, self, NULL, NULL, NULL};
CFReadStreamSetClient(
stream,
kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
ASReadStreamCallBack,
&context);
CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
The ASReadStreamCallBack calls:
- (void)handleReadFromStream:(CFReadStreamRef)aStream
eventType:(CFStreamEventType)eventType
On the AudioStreamer object, this all works fine until the stream is read using this code:
BOOL hasBytes = NO; //Added for debugging
hasBytes = CFReadStreamHasBytesAvailable(stream);
length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize);
hasBytes is YES but when CFReadStreamRead is called execution stops, the App does not crash it just stops exciting, any break points below the CFReadStreamRead call are not hit and ASReadStreamCallBack is not called again.
I am at a loss to what might cause this, my best guess is the thread is being terminated? But the hows and whys is why I'm asking SO.
Has anyone seen this behaviour before? How can I track it down and ideas on how I might solve it will be very much welcome!
Additional Info Requested via Comments
This is 100% repeatable
CFReadStreamHasBytesAvailable was added by me for debugging but removing it has no effect
First, I assume that CFReadStreamScheduleWithRunLoop() is running on the same thread as CFReadStreamRead()?
Is this thread processing its runloop? Failure to do this is my main suspicion. Do you have a call like CFRunLoopRun() or equivalent on this thread?
Typically there is no reason to spawn a separate thread for reading streams asynchronously, so I'm a little confused about your threading design. Is there really a background thread involved here? Also, typically CFReadStreamRead() would be in your client callback (when you receive the kCFStreamEventHasBytesAvailable event (which it appears to be in the linked code), but you're suggesting ASReadStreamCallBack is never called. How have you modified AudioStreamer?
It is possible that the stream pointer is just corrupt in some way. CFReadStreamRead should certainly not block if bytes are available (it certainly would never block for more than a few milliseconds for local files). Can you provide the code you use to create the stream?
Alternatively, CFReadStreams send messages asynchronously but it is possible (but not likely) that it's blocking because the runloop isn't being processed.
If you prefer, I've uploaded my AudioPlayer inspired by Matt's AudioStreamer hosted at https://code.google.com/p/audjustable/. It supports local files (as well as HTTP). I think it does what you wanted (stream files from more than just HTTP).