iOS 设备 检测声音输出设备及耳机麦克风的处理

转载 2013年12月04日 17:31:08

1. 检测声音输入设备

- (BOOL)hasMicphone {
    return[[AVAudioSession sharedInstance] inputIsAvailable];
}

2. 检测声音输出设备

对于输出设备的检测,我们只考虑了2个情况,一种是设备自身的外放(iTouch/iPad/iPhone都有),一种是当前是否插入了带外放的耳机。iOS已经提供了相关方法用于获取当前的所有声音设备,我们只需要检查在这些设备中是否存在我们所关注的那几个就可以了。

获取当前所有声音设备:

CFStringRef route;
UInt32 propertySize = sizeof(CFStringRef);
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute,&propertySize, &route);

在iOS上所有可能的声音设备包括:

 

每一项的具体代表的设备请查考iOS文档,此处我们关注的是是否有耳机,所以只需要检查在route中是否有Headphone或Headset存在,具体方法如下:

- (BOOL)hasHeadset {
    #ifTARGET_IPHONE_SIMULATOR
       #warning *** Simulator mode: audio session code works only on adevice
       return NO;
   #else 
    CFStringRefroute;
    UInt32propertySize = sizeof(CFStringRef);
   AudioSessionGetProperty(kAudioSessionProperty_AudioRoute,&propertySize, &route);
    if((route ==NULL) || (CFStringGetLength(route) == 0)){
       // Silent Mode
       NSLog(@”AudioRoute: SILENT, do nothing!”);
    } else{
       NSString* routeStr = (NSString*)route;
       NSLog(@”AudioRoute: %@”, routeStr);
       
       NSRange headphoneRange = [routeStr rangeOfString :@"Headphone"];
       NSRange headsetRange = [routeStr rangeOfString : @"Headset"];
       if (headphoneRange.location != NSNotFound) {
           return YES;
       } else if(headsetRange.location != NSNotFound) {
           return YES;
       }
    }
    returnNO;
   #endif

}

请注意,由于获取AudioRoute的相关方法不能再simulator上运行(会直接crush),所以必须先行处理。

3. 设置声音输出设备

在我们的项目中,存在当正在播放时用户会插入或拔出耳机的情况。如果是播放时用户插入了耳机,苹果会自动将声音输出指向到耳机并自动将音量调整为合适大小;如果是在用耳机的播放过程中用户拔出了耳机,声音会自动从设备自身的外放里面播出,但是其音量并不会自动调大。
经过我们的测试,我们发现当播放时拔出耳机会有两个问题(也许对你来说不是问题,但是会影响我们的app):

  • 音乐播放自动停止
  • 声音音量大小不会自动变大,系统仍然以较小的声音(在耳机上合适的声音)来进行外放

对于第一个问题,实际上就是需要能够检测到耳机拔出的事件;而第二个问题则是需要当耳机拔出时强制设置系统输出设备修改为系统外放。

强制修改系统声音输出设备:

- (void)resetOutputTarget {
    BOOLhasHeadset = [self hasHeadset];
    NSLog(@”Will Set output target is_headset = %@ .”, hasHeadset ? @”YES” :@”NO”);
    UInt32audioRouteOverride = hasHeadset ?
       kAudioSessionOverrideAudioRoute_None:kAudioSessionOverrideAudioRoute_Speaker;
   AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute,sizeof(audioRouteOverride),&audioRouteOverride);
}

可以看到我们修改了AudioSession的属性“kAudioSessionProperty_OverrideAudioRoute”,该属性在iOS文档上的解释如下:

kAudioSessionProperty_OverrideAudioRoute
 Specifieswhether or not to override the audio session category’s normalaudio route. Can be set with one of twovalues: kAudioSessionOverrideAudioRoute_None,which specifies that you want to use the normal audio route;andkAudioSessionOverrideAudioRoute_Speaker,when sends output audio to the speaker. Awrite-only UInt32 value. 



 


Upon an audio route change (such as by plugging in or unplugging aheadset), or upon interruption, this property reverts to itsdefault value. This property can be used only withthe kAudioSessionCategory_PlayAndRecord (orthe equivalentAVAudioSessionCategoryRecord)category.

可以看到,该属性只有当category为kAudioSessionCategory_PlayAndRecord或者AVAudioSessionCategoryRecord时才能使用。所以我们还需要能够设置AudioSession的category。

4. 设置Audio工作模式(category,我当做工作模式理解的)

iOS系统中Audio支持多种工作模式(category),要实现某个功能,必须首先将AudioSession设置到支持该功能的工作模式下。所有支持的工作模式如下:

Audio Session Categories

Category identifiers for audio sessions, used as values forthe setCategory:error: method.

NSString *const AVAudioSessionCategoryAmbient;
NSString *const AVAudioSessionCategorySoloAmbient;
NSString *const AVAudioSessionCategoryPlayback;
NSString *const AVAudioSessionCategoryRecord;
NSString *const AVAudioSessionCategoryPlayAndRecord;
NSString *const AVAudioSessionCategoryAudioProcessing;

 

具体每一个category的功能请参考iOS文档,其中AVAudioSessionCategoryRecord为独立录音模式,而AVAudioSessionCategoryPlayAndRecord为支持录音盒播放的模式,而AVAudioSessionCategoryPlayback为普通播放模式。

设置category:

- (BOOL)checkAndPrepareCategoryForRecording {
    recording =YES;
    BOOLhasMicphone = [self hasMicphone];
    NSLog(@”WillSet category for recording! hasMicophone = %@”,hasMicphone?@”YES”:@”NO”);
    if(hasMicphone) {
       [[AVAudioSession sharedInstance]setCategory:AVAudioSessionCategoryPlayAndRecord
                                              error:nil];
    }
    [selfresetOutputTarget];
    returnhasMicphone;
}

- (void)resetCategory {
    if(!recording) {
       NSLog(@”Will Set category to static value =AVAudioSessionCategoryPlayback!”);
       [[AVAudioSession sharedInstance]setCategory:AVAudioSessionCategoryPlayback
                                              error:nil];
    }
}

5. 检测耳机插入/拔出事件

耳机插入拔出事件是通过监听AudioSession的RouteChange事件然后判断耳机状态实现的。实现步骤分为两步,首先注册监听函数,然后再监听函数中判断耳机状态。

注册监听函数:

AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,
                                    audioRouteChangeListenerCallback,
                                    self);

我们的需求是当耳机被插入或拔出时做出响应,而产生AouteChange事件的原因有多种,所以需要对各种类型进行处理并结合当前耳机状态进行判断。在iOS文档中,产生AouteChange事件的原因有如下几种:

Audio Session Route Change Reasons

Identifiers for the various reasons that an audio route can changewhile your iOS application is running.

enum {
   kAudioSessionRouteChangeReason_Unknown                    = 0,
   kAudioSessionRouteChangeReason_NewDeviceAvailable         = 1,
   kAudioSessionRouteChangeReason_OldDeviceUnavailable       = 2,
   kAudioSessionRouteChangeReason_CategoryChange             = 3,
   kAudioSessionRouteChangeReason_Override                   = 4,
   // this enum has no constant with a value of 5
   kAudioSessionRouteChangeReason_WakeFromSleep              = 6,
   kAudioSessionRouteChangeReason_NoSuitableRouteForCategory = 7
};

具体每个类型的含义请查阅iOS文档,其中我们关注的是kAudioSessionRouteChangeReason_NewDeviceAvailable有新设备插入、kAudioSessionRouteChangeReason_OldDeviceUnavailable原有设备被拔出以及kAudioSessionRouteChangeReason_NoSuitableRouteForCategory当前工作模式缺少合适设备

当有新设备接入时,如果检测到耳机,则判定为耳机插入事件;当原有设备移除时,如果无法检测到耳机,则判定为耳机拔出事件;当出现“当前工作模式缺少合适设备时”,直接判定为录音时拔出了麦克风。

很明显,这个判定逻辑实际上不准确,比如原来就有耳机但是插入了一个新的audio设备或者是原来就没有耳机但是拔出了一个原有的audio设备,我们的判定都会出错。但是对于我们的项目来说,其实关注的不是耳机是拔出还是插入,真正关注的是有audio设备插入/拔出时能够根据当前耳机/麦克风状态去调整设置,所以这个判定实现对我们来说是正确的。

监听函数的实现:

void audioRouteChangeListenerCallback (
                                      void                     *inUserData,
                                      AudioSessionPropertyID   inPropertyID,
                                      UInt32                   inPropertyValueSize,
                                      constvoid               *inPropertyValue
                                      ) {
    if(inPropertyID != kAudioSessionProperty_AudioRouteChange)return;
    //Determines the reason for the route change, to ensure that it isnot
   //       because of a category change.


   CFDictionaryRef   routeChangeDictionary = inPropertyValue;
    CFNumberRefrouteChangeReasonRef =
   CFDictionaryGetValue (routeChangeDictionary,
                         CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
    SInt32routeChangeReason;
   CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type,&routeChangeReason);
    NSLog(@”======================= RouteChangeReason : %d”,routeChangeReason);
    AudioHelper*_self = (AudioHelper *) inUserData;
    if(routeChangeReason ==kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
       [_self resetSettings];
       if (![_self hasHeadset]) {
           [[NSNotificationCenter defaultCenter]postNotificationName:@”ununpluggingHeadse
                                                               object:nil];
       }
    } else if(routeChangeReason ==kAudioSessionRouteChangeReason_NewDeviceAvailable) {
       [_self resetSettings];
       if (![_self hasMicphone]) {
           [[NSNotificationCenter defaultCenter]postNotificationName:@”pluggInMicrophone”
                                                               object:nil];
       }
    } else if(routeChangeReason ==kAudioSessionRouteChangeReason_NoSuitableRouteForCategory) {
       [_self resetSettings];
       [[NSNotificationCenter defaultCenter]postNotificationName:@”lostMicroPhone”
                                                           object:nil];
    }
    //else if(routeChangeReason ==kAudioSessionRouteChangeReason_CategoryChange  ){
   //   [[AVAudioSession sharedInstance]setCategory:AVAudioSessionCategoryPlayAndRecorderror:nil];       
    //}
    [_selfprintCurrentCategory];
}

当检测到相关事件后,通过NSNotificationCenter通知observers耳机(有无麦克风)拔出/插入事件拔出事件,从而触发相关操作。

6. 事件处理

对于耳机(有无麦克风)拔出/插入事件,一般需要做如下处理:

  • 强制重设系统声音输出设备(防止系统以较小声音在外放中播放)
  • 如果拔出前正在播放,则启动已经暂停的播放(当耳机拔出时,系统会自动暂停播放)
  • 当拔出前正在录音,则需要检查麦克风情况并决定是否停止录音(如果录音时从iTouch/iPad等设备上拔出了带麦克风的耳机)

相关文章推荐

ios 音频输出方式不调用系统的扬声器的解决方法

1

ios监听输出设备变化(监听耳机插拔,蓝牙设备连接断开等)的实现

在ios6以前,我们有如下的方法: #import       [[AVAudioSession sharedInstance] setDelegate:self];   Aud...

判断设备是否开启麦克风

// #import "ViewController.h" #import @interface ViewController () @end ...

IOS中录音后再播放声音太小问题解决

- (BOOL)canRecord {     __block BOOL bCanRecord = YES;     if ([[[UIDevice currentDevice] sy...
  • zttjhm
  • zttjhm
  • 2014年07月02日 11:05
  • 11336

ios 音频、VOIP相关、传输

1. http://www.csdn.net/article/2012-03-16/313194
  • Mamong
  • Mamong
  • 2014年07月13日 19:35
  • 6375

OpenAL系列之一 – iPhone上的OpenAL音频

译者注:这是我最喜欢的iPhone OpenAL教程之一,总共有好几篇文章,我会逐步翻译。   随着保密协议的解除,我们可以开始公开讨论iPhone的代码了。我觉得讨论一下我“入侵”iPh...

XAudio2获取声音输出设备信息

  • 2016年03月03日 15:51
  • 7KB
  • 下载

关于输出设备教学ppt

  • 2010年06月13日 18:27
  • 3.18MB
  • 下载

【Android Training - Multimedia】管理音频播放[Lesson 3 - 当音频输出设备突然改变]

目录(?)[+] Dealing with Audio Output Hardware [处理音频输出硬件设备] 用户在播放音乐的时候有多个选择,可以使用内置的扬声器,有线耳机或者是支...
  • CAREIT
  • CAREIT
  • 2015年02月27日 16:29
  • 385

XAudio2学习三之获取音频输出设备信息

输出设备信息包括输出输出设备支持的音频格式、设备ID、设备名称、以及扮演角色。 音频格式:通道数、采样率、有效位、音频类型等等。 设备ID:每个设备独一味二的标识, 扮演角色:用来表明音频设备的用途,...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:iOS 设备 检测声音输出设备及耳机麦克风的处理
举报原因:
原因补充:

(最多只允许输入30个字)