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];
Related
I changed the code shown below into ARC compatible.
I just changed it as Xcode suggested, and it doesn't show error on Xcode. But the code crushes once the event happens. Does anybody have an idea to fix this?
I'm not sure if this crush happens because of acapela SDK, or not.
This is non ARC code, it works fine.
void MyInterruptionListener(void *inClientData, UInt32 inInterruptionState) {
AcapelaSpeech* anAcapelaSpeech = *(AcapelaSpeech**)inClientData;
if (inInterruptionState == kAudioSessionBeginInterruption) {
[anAcapelaSpeech setActive:NO];
status = AudioSessionSetActive(NO);
}
if (inInterruptionState == kAudioSessionEndInterruption) {
status = AudioSessionSetActive(YES);
[anAcapelaSpeech setActive:YES];
}
}
This is ARC compatible, but it crushes on [anAcapelaSpeech setActive:NO];.
void MyInterruptionListener(void *inClientData, UInt32 inInterruptionState) {
AcapelaSpeech* anAcapelaSpeech = (__bridge_transfer AcapelaSpeech*)inClientData;
if (inInterruptionState == kAudioSessionBeginInterruption) {
[anAcapelaSpeech setActive:NO];
AudioSessionSetActive(NO);
}
if (inInterruptionState == kAudioSessionEndInterruption) {
AudioSessionSetActive(YES);
[anAcapelaSpeech setActive:YES];
}
}
Additional info.
I'm using Acapela audio SDK, audio interruption code is shown on the 9.Interruptions of this PDF. http://www.ecometrixem.com/cms-assets/documents/44729-919017.acapela-for-iphone.pdf
This is the screenshot for the crush.
SOLVED
This code works, thanks.
void MyInterruptionListener(void *inClientData, UInt32 inInterruptionState) {
AcapelaSpeech *anAcapelaSpeech = (__bridge id) (*(void **) inClientData);
if (inInterruptionState == kAudioSessionBeginInterruption) {
[anAcapelaSpeech setActive:NO];
AudioSessionSetActive(NO);
}
if (inInterruptionState == kAudioSessionEndInterruption) {
AudioSessionSetActive(YES);
[anAcapelaSpeech setActive:YES];
}
}
You need something like this:
id asObject = (__bridge id) (*(void **) ptr);
I need to get current sound volume under my IOS5 application. The app is supposed to be used in cinemas so I want to notify the user that he/she should turn the volume down unless it is already turned down.
try this...
musicPlayer = [[MPMusicPlayerController iPodMusicPlayer];
currentVolume = musicPlayer.volume;
You can get the volume like this
-(Float32)audioVolume
{
Float32 state;
UInt32 propertySize = sizeof(CFStringRef);
OSStatus n = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume, &propertySize, &state);
if( n )
{
// something didn't work...
}
return state;
}
You can get system volume updates like this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(volumeChanged:)
name:#"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
You can work out if the phone is in silent mode if this returns an empty string (this will crash if in the simulator hence the compile time guards).
#ifndef TARGET_IPHONE_SIMULATOR
-(NSString*)audioRoute
{
CFStringRef state;
UInt32 propertySize = sizeof(CFStringRef);
OSStatus n = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &state);
if( n )
{
// something didn't work...
}
NSString *result = (NSString*)state;
[result autorelease];
return result;
}
#endif
Although apparently, this will not work in iOS 5. This post is related also.
While recording a sound using my iPad app, how can I know if the source of the sound is from the built-in microphone or headphone microphone?
Additional information: iOS ver 4.2 and above.
The way to determine this is to poll the hardware and query the current audio route.
Use the AudioSessionGetProperty object to get back the route of the audio.
This example by #TPoschel should set you on the right track.
- (void)playSound:(id) sender
{
if(player){
CFStringRef route;
UInt32 propertySize = sizeof(CFStringRef);
AudioSessionInitialize(NULL, NULL, NULL, NULL);
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route);
if((route == NULL) || (CFStringGetLength(route) == 0)){
// Silent Mode
NSLog(#"AudioRoute: SILENT");
} else {
NSString* routeStr = (NSString*)route;
NSLog(#"AudioRoute: %#", routeStr);
/* Known values of route:
* "Headset"
* "Headphone"
* "Speaker"
* "SpeakerAndMicrophone"
* "HeadphonesAndMicrophone"
* "HeadsetInOut"
* "ReceiverAndMicrophone"
* "Lineout"
*/
NSRange headphoneRange = [routeStr rangeOfString : #"Headphone"];
NSRange headsetRange = [routeStr rangeOfString : #"Headset"];
NSRange receiverRange = [routeStr rangeOfString : #"Receiver"];
NSRange speakerRange = [routeStr rangeOfString : #"Speaker"];
NSRange lineoutRange = [routeStr rangeOfString : #"Lineout"];
if (headphoneRange.location != NSNotFound) {
// Don't change the route if the headphone is plugged in.
} else if(headsetRange.location != NSNotFound) {
// Don't change the route if the headset is plugged in.
} else if (receiverRange.location != NSNotFound) {
// Change to play on the speaker
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
} else if (speakerRange.location != NSNotFound) {
// Don't change the route if the speaker is currently playing.
} else if (lineoutRange.location != NSNotFound) {
// Don't change the route if the lineout is plugged in.
} else {
NSLog(#"Unknown audio route.");
}
}
[player play];
}
}
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
This question already has answers here:
Are headphones plugged in? iOS7
(8 answers)
Closed 6 years ago.
I need to change my audio depending on whether or not headphones are plugged in. I'm aware of kAudioSessionProperty_AudioInputAvailable, which will tell me if there's a microphone, but I'd like to test for any headphones, not just headphones with a built-in microphone. Is this possible?
Here is a method of my own which is a slightly modified version of one found on this site : http://www.iphonedevsdk.com/forum/iphone-sdk-development/9982-play-record-same-time.html
- (BOOL)isHeadsetPluggedIn {
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 = (NSString*)route;
NSRange headphoneRange = [routeStr rangeOfString : #"Head"];
if (headphoneRange.location != NSNotFound) return YES;
}
return NO;
}
Here's a solution based on rob mayoff's comment:
- (BOOL)isHeadsetPluggedIn
{
AVAudioSessionRouteDescription *route = [[AVAudioSession sharedInstance] currentRoute];
BOOL headphonesLocated = NO;
for( AVAudioSessionPortDescription *portDescription in route.outputs )
{
headphonesLocated |= ( [portDescription.portType isEqualToString:AVAudioSessionPortHeadphones] );
}
return headphonesLocated;
}
Simply link to the AVFoundation framework.
Just a heads up for any future readers of this post.
Most of the AVToolbox methods have been deprecated with the release of iOS 7 without alternative so audio listeners are now largely redundancy
I started with the code given above by jpsetung, but there were a few issues with it for my use case:
No evidence of something called kAudioSessionProperty_AudioRoute in the docs
Leaks route
No audio session check
String check for headphones instead of logical awareness of categories
I was more interested in whether the iPhone was using its speakers, with "headphones" meaning "anything other than speakers". I feel that leaving out options like "bluetooth", "airplay", or "lineout" was dangerous.
This implementation broadens the check to allow for any type of specified output:
BOOL isAudioRouteAvailable(CFStringRef routeType)
{
/*
As of iOS 5:
kAudioSessionOutputRoute_LineOut;
kAudioSessionOutputRoute_Headphones;
kAudioSessionOutputRoute_BluetoothHFP;
kAudioSessionOutputRoute_BluetoothA2DP;
kAudioSessionOutputRoute_BuiltInReceiver;
kAudioSessionOutputRoute_BuiltInSpeaker;
kAudioSessionOutputRoute_USBAudio;
kAudioSessionOutputRoute_HDMI;
kAudioSessionOutputRoute_AirPlay;
*/
//Prep
BOOL foundRoute = NO;
CFDictionaryRef description = NULL;
//Session
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
AudioSessionInitialize(NULL, NULL, NULL, NULL);
});
//Property
UInt32 propertySize;
AudioSessionGetPropertySize(kAudioSessionProperty_AudioRouteDescription, &propertySize);
OSStatus error = AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &propertySize, &description);
if ( !error && description ) {
CFArrayRef outputs = CFDictionaryGetValue(description, kAudioSession_AudioRouteKey_Outputs);
CFIndex count = CFArrayGetCount(outputs);
if ( outputs && count ) {
for (CFIndex i = 0; i < count; i++) {
CFDictionaryRef route = CFArrayGetValueAtIndex(outputs, i);
CFStringRef type = CFDictionaryGetValue(route, kAudioSession_AudioRouteKey_Type);
NSLog(#"Got audio route %#", type);
//Audio route type
if ( CFStringCompare(type, routeType, 0) == kCFCompareEqualTo ) {
foundRoute = YES;
break;
}
}
}
} else if ( error ) {
NSLog(#"Audio route error %ld", error);
}
//Cleanup
if ( description ) {
CFRelease(description);
}
//Done
return foundRoute;
}
Used like so:
if ( isAudioRouteAvailable(kAudioSessionOutputRoute_BuiltInSpeaker) ) {
//Do great things...
}