Is there an event for when the headphones are unplugged? - iphone

During a test, a client noticed that video playback in the iPhone pauses when headphones are unplugged. He wanted similar functionality for audio playback, and maybe the ability to pop up a message.
Does anyone know if there's an event of some kind I could hook into to make this possible?

See Responding to Route Changes from the Audio Session Programming Guide.

This changed with iOS 7, you just need to listen to the notification named AVAudioSessionRouteChangeNotification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(audioRouteChanged:) name:AVAudioSessionRouteChangeNotification object:nil];

Swift 3.0 #snakeoil's solution:
NotificationCenter.default.addObserver(self, selector: #selector(YourViewController.yourMethodThatShouldBeCalledOnChange), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)

Here's the full implementation I eventually used for sending events when the headphones are plugged in (and unplugged).
There was a fair amount of complexity I needed to deal with to ensure things still worked after the app was returned from the background.
CVAudioSession.h file
#import <Foundation/Foundation.h>
#define kCVAudioInputChangedNotification #"kCVAudioInputChangedNotification"
#define kCVAudioInterruptionEnded #"kCVAudioInterruptionEnded"
#interface CVAudioSession : NSObject
+(void) setup;
+(void) destroy;
+(NSString*) currentAudioRoute;
+(BOOL) interrupted;
#end
CVAudioSession.m file
#import "CVAudioSession.h"
#import <AudioToolbox/AudioToolbox.h>
#implementation CVAudioSession
static BOOL _isInterrupted = NO;
+(void) setup {
NSLog(#"CVAudioSession setup");
// Set up the audio session for recording
OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, (__bridge void*)self);
if (error) NSLog(#"ERROR INITIALIZING AUDIO SESSION! %ld\n", error);
if (!error) {
UInt32 category = kAudioSessionCategory_RecordAudio; // NOTE CANT PLAY BACK WITH THIS
error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
if (error) NSLog(#"couldn't set audio category!");
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, (__bridge void*) self);
if (error) NSLog(#"ERROR ADDING AUDIO SESSION PROP LISTENER! %ld\n", error);
UInt32 inputAvailable = 0;
UInt32 size = sizeof(inputAvailable);
// we do not want to allow recording if input is not available
error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable);
if (error) NSLog(#"ERROR GETTING INPUT AVAILABILITY! %ld\n", error);
// we also need to listen to see if input availability changes
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, (__bridge void*) self);
if (error) NSLog(#"ERROR ADDING AUDIO SESSION PROP LISTENER! %ld\n", error);
error = AudioSessionSetActive(true);
if (error) NSLog(#"CVAudioSession: AudioSessionSetActive (true) failed");
}
}
+ (NSString*) currentAudioRoute {
UInt32 routeSize = sizeof (CFStringRef);
CFStringRef route;
AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
&routeSize,
&route);
NSString* routeStr = (__bridge NSString*)route;
return routeStr;
}
+(void) destroy {
NSLog(#"CVAudioSession destroy");
// Very important - remove the listeners, or we'll crash when audio routes etc change when we're no longer on screen
OSStatus stat = AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_AudioRouteChange, propListener, (__bridge void*)self);
NSLog(#".. AudioSessionRemovePropertyListener kAudioSessionProperty_AudioRouteChange returned %ld", stat);
stat = AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_AudioInputAvailable, propListener, (__bridge void*)self);
NSLog(#".. AudioSessionRemovePropertyListener kAudioSessionProperty_AudioInputAvailable returned %ld", stat);
AudioSessionSetActive(false); // disable audio session.
NSLog(#"AudioSession is now inactive");
}
+(BOOL) interrupted {
return _isInterrupted;
}
// Called when audio is interrupted for whatever reason. NOTE: doesn't always call the END one..
void interruptionListener( void * inClientData,
UInt32 inInterruptionState) {
if (inInterruptionState == kAudioSessionBeginInterruption)
{
_isInterrupted = YES;
NSLog(#"CVAudioSession: interruptionListener kAudioSessionBeginInterruption. Disable audio session..");
// Try just deactivating the audiosession..
OSStatus rc = AudioSessionSetActive(false);
if (rc) {
NSLog(#"CVAudioSession: interruptionListener kAudioSessionBeginInterruption - AudioSessionSetActive(false) returned %.ld", rc);
} else {
NSLog(#"CVAudioSession: interruptionListener kAudioSessionBeginInterruption - AudioSessionSetActive(false) ok.");
}
} else if (inInterruptionState == kAudioSessionEndInterruption) {
_isInterrupted = NO;
// Reactivate the audiosession
OSStatus rc = AudioSessionSetActive(true);
if (rc) {
NSLog(#"CVAudioSession: interruptionListener kAudioSessionEndInterruption - AudioSessionSetActive(true) returned %.ld", rc);
} else {
NSLog(#"CVAudioSession: interruptionListener kAudioSessionEndInterruption - AudioSessionSetActive(true) ok.");
}
[[NSNotificationCenter defaultCenter] postNotificationName:kCVAudioInterruptionEnded object:(__bridge NSObject*)inClientData userInfo:nil];
}
}
// This is called when microphone or other audio devices are plugged in and out. Is on the main thread
void propListener( void * inClientData,
AudioSessionPropertyID inID,
UInt32 inDataSize,
const void * inData)
{
if (inID == kAudioSessionProperty_AudioRouteChange)
{
CFDictionaryRef routeDictionary = (CFDictionaryRef)inData;
CFNumberRef reason = (CFNumberRef)CFDictionaryGetValue(routeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
SInt32 reasonVal;
CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal);
if (reasonVal != kAudioSessionRouteChangeReason_CategoryChange)
{
NSLog(#"CVAudioSession: input changed");
[[NSNotificationCenter defaultCenter] postNotificationName:kCVAudioInputChangedNotification object:(__bridge NSObject*)inClientData userInfo:nil];
}
}
else if (inID == kAudioSessionProperty_AudioInputAvailable)
{
if (inDataSize == sizeof(UInt32)) {
UInt32 isAvailable = *(UInt32*)inData;
if (isAvailable == 0) {
NSLog(#"AUDIO RECORDING IS NOT AVAILABLE");
}
}
}
}
#end

Related

Detect plugIn or unplug of headphone jack from iPhone when my app is in background mode

I want to notify the user when my headphone jack is PluggedIn or UnPlugged from my iPhone/iPod/iPad when my app is in the background mode.
Here I have the code which detects in the foreground mode.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audioSessionPropertyListener, nil);
}
BOOL isHeadsetPluggedIn()
{
UInt32 routeSize = sizeof (CFStringRef);
CFStringRef route;
OSStatus error = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
&routeSize,
&route
);
NSLog(#"%#", route);
return (!error && (route != NULL) && ([( NSString*)route rangeOfString:#"Head"].location != NSNotFound));
}
void audioSessionPropertyListener(void* inClientData, AudioSessionPropertyID inID,UInt32 inDataSize, const void* inData)
{
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
// Determines the reason for the route change, to ensure that it is not
// because of a category change.
CFDictionaryRef routeChangeDictionary = inData;
CFNumberRef routeChangeReasonRef = CFDictionaryGetValue (routeChangeDictionary,CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
SInt32 routeChangeReason;
CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);
// "Old device unavailable" indicates that a headset was unplugged, or that the
// device was removed from a dock connector that supports audio output.
// if (routeChangeReason != kAudioSessionRouteChangeReason_OldDeviceUnavailable)
// return;
if (!isHeadsetPluggedIn())
{
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
NSLog(#"With out headPhone");
}
else
{
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
NSLog(#"headPhone");
}
}
You could try this from the Apple Docs:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
I think Apple has given that data in their Docs here: Background Execution and Multitasking

Getting audio route gives empty string

I'm currently working with a piece of code to detect whether a user has plugged in/unplugged headphones with their iPhone. The method I'm using to detect it is shown below.
void audioRouteChangeListenerCallback (void *inUserData, AudioSessionPropertyID inPropertyID,UInt32 inPropertyValueSize, const void *inPropertyValue){
if (inPropertyID != kAudioSessionProperty_AudioRouteChange) return;
CFStringRef route; UInt32 routeSize = sizeof(CFStringRef);
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute,&routeSize, &route);
NSString *oldroute = (NSString*)route;
NSLog(#"Audio Route changed to: %#",oldroute);
}
My issue occurs when I go to unplug the headphones. Plugging them in works as I expect, with the Log file showing "Audio Route changed to: Headphones", however, when I unplug I get an empty string for oldroute. I was hoping that this value would be "Speaker" as it says in the Apple documents. Has anyone seen this before? Am I doing something wrong in getting the string oldroute? Thanks
I still have not found a good answer for the exact issue that I have posted as the question. Here is a workaround that may work for some people with a similar problem. This solution gives you the route that was active before the change. Ex. if you unplug your headphones you will get a string that is "Headphones" or if you put in your headphones you'll get a string that is "Speaker".
void audioRouteChangeListenerCallback (void *inUserData, AudioSessionPropertyID inPropertyID,UInt32 inPropertyValueSize, const void *inPropertyValue){
if (inPropertyID != kAudioSessionProperty_AudioRouteChange) return;
CFDictionaryRef routeChangeDictionary = (CFDictionaryRef)inPropertyValue;
CFStringRef oroute;
oroute = (CFStringRef)CFDictionaryGetValue(routeChangeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_OldRoute));
NSString *oldroute = (NSString*)oroute;
}
Hope this helps.
Edit: I'm going to accept my answer until a better one comes along
I currently use the following:
void _propertyListener( void * inClientData,
AudioSessionPropertyID inID,
UInt32 inDataSize,
const void * inData) {
if (inID != kAudioSessionProperty_AudioRouteChange) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioRouteChanged object:nil];
});
}
- (void) _audioRouteChanged:(NSNotification*)notification
{
NSLog(#"audioRouteChanged");
UInt32 routeSize = sizeof (CFStringRef);
CFStringRef route;
OSStatus error = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute, &routeSize, &route);
/* Known values of route:
* "Headset"
* "Headphone"
* "Speaker"
* "SpeakerAndMicrophone"
* "HeadphonesAndMicrophone"
* "HeadsetInOut"
* "ReceiverAndMicrophone"
* "Lineout"
*/
if (!error && (route != NULL)) {
NSString* routeStr = (__bridge NSString*)route;
NSRange headphoneRange = [routeStr rangeOfString : #"Head"];
[self.cardSwipeDelegate headphoneListener:(headphoneRange.location != NSNotFound)];
if (headphoneRange.location != NSNotFound) {
[self startSession];
} else {
[self stopSession];
}
}
}
And where ever you do your initialization add the notification:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(_audioRouteChanged:) name:kAudioRouteChanged object:nil];

openAL streaming & interruptions

I made an iphone application which uses OpenAL to play many sounds.
These sounds are in mp3, quite heavy (more than 1mn)and I stream them (2 buffers per sound) in order to use less memory.
To manage interruptions, I use this code :
In OpenALSupport.c file :
//used to disable openAL during a call
void openALInterruptionListener ( void *inClientData, UInt32 inInterruptionState)
{
if (inInterruptionState == kAudioSessionBeginInterruption)
{
alcMakeContextCurrent (NULL);
}
}
//used to restore openAL after a call
void restoreOpenAL(void* a_context)
{
alcMakeContextCurrent(a_context);
}
In my SoundManager.m file :
- (void) restoreOpenAL
{
restoreOpenAL(mContext);
}
//OPENAL initialization
- (bool) initOpenAL
{
// Initialization
mDevice = alcOpenDevice(NULL);
if (mDevice) {
...
// use the device to make a context
mContext=alcCreateContext(mDevice,NULL);
// set my context to the currently active one
alcMakeContextCurrent(mContext);
AudioSessionInitialize (NULL, NULL, openALInterruptionListener, mContext);
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
NSError *setCategoryError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error: &setCategoryError];
...
}
And finally in my AppDelegate :
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[CSoundManager getInstance] restoreOpenAL];
...
}
With this method the sounds are back after a call , but flows seem to be played randomly.
Is there a specific way to manage interruption with streaming sounds ? I don't find any article about that.
Thanks for your help.
Ok, I answer to my own question.
I solved the problem by managing error on my streaming method :
- (void) updateStream
{
ALint processed;
alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &processed);
while(processed--)
{
oldPosition = position;
NSUInteger buffer;
alSourceUnqueueBuffers(sourceID, 1, &buffer);
////////////////////
//code freshly added
ALint err = alGetError();
if (err != 0)
{
NSLog(#"Error Calling alSourceUnQueueBuffers: %d",err);
processed++;
//restore old position for the next buffer
position = oldPosition;
usleep(10000);
continue;
}
////////////////////
[self stream:buffer];
alSourceQueueBuffers(sourceID, 1, &buffer);
////////////////////
//code freshly added
err = alGetError();
if (err != 0)
{
NSLog(#"Error Calling alSourceQueueBuffers: %d",err);
processed++;
usleep(10000);
//restore old position for the next buffer
position = oldPosition;
}
///////////////////
}
}

Could not start Audio Queue Error starting recording

CFStringRef state;
UInt32 propertySize = sizeof(CFStringRef);
// AudioSessionInitialize(NULL, NULL, NULL, NULL);
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &state);
if(CFStringGetLength(state) == 0)
// if(state == 0)
{ //SILENT
NSLog(#"Silent switch is on");
// create vibrate
// AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
UInt32 audioCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty( kAudioSessionProperty_AudioCategory, sizeof(UInt32), &audioCategory);
}
else { //NOT SILENT
NSLog(#"Silent switch is off");
}
where ever i use Above code i am able to play sound file in Silent mode
but after playing recorded sound file in silent mode when i try to record voice again
I get an error
LIke
2010-12-08 13:29:56.710 VoiceRecorder[382:307] -66681
Could not start Audio Queue
Error starting recording
here is the code
// file url
[self setupAudioFormat:&recordState.dataFormat];
CFURLRef fileURL = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *) [filePath UTF8String], [filePath length], NO);
// recordState.currentPacket = 0;
// new input queue
OSStatus status;
status = AudioQueueNewInput(&recordState.dataFormat, HandleInputBuffer, &recordState, CFRunLoopGetCurrent(),kCFRunLoopCommonModes, 0, &recordState.queue);
if (status) {CFRelease(fileURL); printf("Could not establish new queue\n"); return NO;}
// create new audio file
status = AudioFileCreateWithURL(fileURL, kAudioFileAIFFType, &recordState.dataFormat, kAudioFileFlags_EraseFile, &recordState.audioFile); CFRelease(fileURL); // thanks august joki
if (status) {printf("Could not create file to record audio\n"); return NO;}
// figure out the buffer size
DeriveBufferSize(recordState.queue, recordState.dataFormat, 0.5, &recordState.bufferByteSize); // allocate those buffers and enqueue them
for(int i = 0; i < NUM_BUFFERS; i++)
{
status = AudioQueueAllocateBuffer(recordState.queue, recordState.bufferByteSize, &recordState.buffers[i]);
if (status) {printf("Error allocating buffer %d\n", i); return NO;}
status = AudioQueueEnqueueBuffer(recordState.queue, recordState.buffers[i], 0, NULL);
if (status) {printf("Error enqueuing buffer %d\n", i); return NO;}
} // enable metering
UInt32 enableMetering = YES;
status = AudioQueueSetProperty(recordState.queue, kAudioQueueProperty_EnableLevelMetering, &enableMetering,sizeof(enableMetering));
if (status) {printf("Could not enable metering\n"); return NO;}
// start recording
status = AudioQueueStart(recordState.queue, NULL); // status = 0; NSLog(#"%d",status);
if (status) {printf("Could not start Audio Queue\n"); return NO;}
recordState.currentPacket = 0;
recordState.recording = YES;
return YES;
i get an error here
I was facing similar problem in iOS 7.1. Add following in AppDelegate's didFinishLaunchingWithOptions :
AVAudioSession * audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error: nil];
[audioSession setActive:YES error: nil];
EDIT : Above code is working for me

Async CFStream networking with runloop

I am trying to implement async tcp networking with runloop.
currently I manage to connect, but when I try to send something I get that -1 bytes have been written - but CFWriteStreamCopyError returns null.
code sample below, first function connects, second send a simple message.
any help will be appreciated, including random bug spotting (I am new to objective-c and to iphone development in general).
struct header
{
uint32_t length;
uint32_t type;
} header;
- (void) connect
{
NSLog(#"Attempting to (re)connect to %#:%d", m_host, m_port);
while(TRUE)
{
CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)m_host);
if (!host)
{
NSLog(#"Error resolving host %#", m_host);
[NSThread sleepForTimeInterval:5.0];
continue;
}
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host , m_port, &m_in, &m_out);
CFRelease(host);
if (!m_in)
{
NSLog(#"Error");
}
CFStreamClientContext context = {0, self,nil,nil,nil};
if (CFReadStreamSetClient(m_in, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkReadEvent, &context))
{
CFReadStreamScheduleWithRunLoop(m_in, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
if (CFWriteStreamSetClient(m_out, kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkWriteEvent, &context))
{
CFWriteStreamScheduleWithRunLoop(m_out, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
BOOL success = CFReadStreamOpen(m_in);
CFErrorRef error = CFReadStreamCopyError(m_in);
if (!success || (error && CFErrorGetCode(error) != 0))
{
NSLog(#"Connect error %s : %d", CFErrorGetDomain(error), CFErrorGetCode(error));
[NSThread sleepForTimeInterval:5.0];
}
else
{
NSLog(#"Connected");
break;
}
}
[self startSession];
}
- (void) startSession
{
struct header hh;
hh.type = RTR_CREATE_SESSION;
hh.length = 0;
CFIndex res = CFWriteStreamWrite(self.m_out, (const UInt8*)&hh, sizeof(hh));
NSLog(#"Written %d", res);
CFErrorRef error = CFWriteStreamCopyError(self.m_out);
if (error)
{
NSLog(#"Read error %s : %d", CFErrorGetDomain(error), CFErrorGetCode(error));
CFRelease(error);
}
}
figured it out, I forgot to open the write stream as well:
CFWriteStreamOpen(m_out);