iOS音频播放(二):AudioSession

(本文转自码农人生

前言

在实施前一篇中所述的7个步骤步之前还必须面对一个麻烦的问题,AudioSession。

AudioSession简介

AudioSession这个玩意的主要功能包括以下几点(图片来自官方文档):

1. 确定你的app如何使用音频(是播放?还是录音?)

2. 为你的app选择合适的输入输出设备(比如输入用的麦克风,输出是耳机、手机功放或者airplay)

3. 协调你的app的音频播放和系统以及其他app行为(例如有电话时需要打断,电话结束时需要恢复,按下静音按钮时是否歌曲也要静音等)

AudioSession

AudioSession相关的类有两个:

1. AudioToolBox中的AudioSession

2. AVFoundation中的AVAudioSession

其中AudioSession在SDK 7中已经被标注为depracated,而AVAudioSession这个类虽然iOS 3开始就已经存在了,但其中很多方法和变量都是在iOS 6以后甚至是iOS 7才有的。所以各位可以依照以下标准选择:

* 如果最低版本支持iOS 5,可以使用AudioSession,也可以使用AVAudioSession;

* 如果最低版本支持iOS 6及以上,请使用AVAudioSession

下面以AudioSession类为例来讲述AudioSession相关功能的使用(很不幸我需要支持iOS 5。。T-T,使用AVAudioSession的同学可以在其头文件中寻找对应的方法使用即可,需要注意的点我会加以说明)。

注意:在使用AVAudioPlayer/AVPlayer时可以不用关心AudioSession的相关 问题,Apple已经把AudioSession的处理过程封装了,但音乐打断后的响应还是要做的(比如打断后音乐暂停了UI状态也要变化,这个应该通过 KVO就可以搞定了吧。。我没试过瞎猜的>_<)。

初始化AudioSession

使用AudioSession类首先需要调用初始化方法:

  1. extern OSStatus AudioSessionInitialize(CFRunLoopRef inRunLoop,
  2. CFStringRef inRunLoopMode,
  3. AudioSessionInterruptionListener inInterruptionListener,
  4. void *inClientData);

前两个参数一般填NULL表示AudioSession运行在主线程上(但并不代表音频的相关处理运行在主线程上,只是 AudioSession),第三个参数需要传入一个一个AudioSessionInterruptionListener类型的方法,作为 AudioSession被打断时的回调,第四个参数则是代表打断回调时需要附带的对象(即回到方法中的inClientData,如下所示,可以理解为 UIView animation中的context)。

  1. typedef void (*AudioSessionInterruptionListener)(void * inClientData, UInt32 inInterruptionState);

这才刚开始,坑就来了。这里会有两个问题:

第一,AudioSessionInitialize可以被多次执行,但AudioSessionInterruptionListener只 能被设置一次,这就意味着这个打断回调方法是一个静态方法,一旦初始化成功以后所有的打断都会回调到这个方法,即便下一次再次调用 AudioSessionInitialize并且把另一个静态方法作为参数传入,当打断到来时还是会回调到第一次设置的方法上。

这种场景并不少见,例如你的app既需要播放歌曲又需要录音,当然你不可能知道用户会先调用哪个功能,所以你必须在播放和录音的模块中都调用 AudioSessionInitialize注册打断方法,但最终打断回调只会作用在先注册的那个模块中,很蛋疼吧。。。所以对于 AudioSession的使用最好的方法是生成一个类单独进行管理,统一接收打断回调并发送自定义的打断通知,在需要用到AudioSession的模 块中接收通知并做相应的操作。

Apple也察觉到了这一点,所以在AVAudioSession中首先取消了Initialize方法,改为了单例方法 sharedInstance。在iOS 5上所有的打断都需要通过设置id<AVAudioSessionDelegate> delegate并实现回调方法来实现,这同样会有上述的问题,所以在iOS 5使用AVAudioSession下仍然需要一个单独管理AudioSession的类存在。在iOS 6以后Apple终于把打断改成了通知的形式。。这下科学了。

第二,AudioSessionInitialize方法的第四个参数inClientData,也就是回调方法的第一个参数。上面已经说了打 断回调是一个静态方法,而这个参数的目的是为了能让回调时拿到context(上下文信息),所以这个inClientData需要是一个有足够长生命周 期的对象(当然前提是你确实需要用到这个参数),如果这个对象被dealloc了,那么回调时拿到的inClientData会是一个野指针。就这一点来 说构造一个单独管理AudioSession的类也是有必要的,因为这个类的生命周期和AudioSession一样长,我们可以把context保存在 这个类中。

监听RouteChange事件

如果想要实现类似于“拔掉耳机就把歌曲暂停”的功能就需要监听RouteChange事件:

  1. extern OSStatus AudioSessionAddPropertyListener(AudioSessionPropertyID inID,
  2. AudioSessionPropertyListener inProc,
  3. void *inClientData);
  4. typedef void (*AudioSessionPropertyListener)(void * inClientData,
  5. AudioSessionPropertyID inID,
  6. UInt32 inDataSize,
  7. const void * inData);

调用上述方法,AudioSessionPropertyID参数传 kAudioSessionProperty_AudioRouteChange,AudioSessionPropertyListener参数传对应 的回调方法。inClientData参数同AudioSessionInitialize方法。

同样作为静态回调方法还是需要统一管理,接到回调时可以把第一个参数inData转换成CFDictionaryRef并从中获取 kAudioSession_AudioRouteChangeKey_Reason键值对应的value(应该是一个CFNumberRef),得到这 些信息后就可以发送自定义通知给其他模块进行相应操作(例如 kAudioSessionRouteChangeReason_OldDeviceUnavailable就可以用来做“拔掉耳机就把歌曲暂停”)。

  1. //AudioSession的AudioRouteChangeReason枚举
  2. enum {
  3. kAudioSessionRouteChangeReason_Unknown = 0,
  4. kAudioSessionRouteChangeReason_NewDeviceAvailable = 1,
  5. kAudioSessionRouteChangeReason_OldDeviceUnavailable = 2,
  6. kAudioSessionRouteChangeReason_CategoryChange = 3,
  7. kAudioSessionRouteChangeReason_Override = 4,
  8. kAudioSessionRouteChangeReason_WakeFromSleep = 6,
  9. kAudioSessionRouteChangeReason_NoSuitableRouteForCategory = 7,
  10. kAudioSessionRouteChangeReason_RouteConfigurationChange = 8
  11. };
  1. //AVAudioSession的AudioRouteChangeReason枚举
  2. typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
  3. {
  4. AVAudioSessionRouteChangeReasonUnknown = 0,
  5. AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
  6. AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
  7. AVAudioSessionRouteChangeReasonCategoryChange = 3,
  8. AVAudioSessionRouteChangeReasonOverride = 4,
  9. AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
  10. AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
  11. AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
  12. }

注意:iOS 5下如果使用了AVAudioSession由于AVAudioSessionDelegate中并没有定义相关的方法,还是需要用这个方法来实现监听。iOS 6下直接监听AVAudioSession的通知就可以了。

这里附带两个方法的实现,都是基于AudioSession类的(使用AVAudioSession的同学帮不到你们啦)。

1、判断是否插了耳机:

  1. + (BOOL)usingHeadset
  2. {
  3. #if TARGET_IPHONE_SIMULATOR
  4. return NO;
  5. #endif
  6. CFStringRef route;
  7. UInt32 propertySize = sizeof(CFStringRef);
  8. AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route);
  9. BOOL hasHeadset = NO;
  10. if((route == NULL) || (CFStringGetLength(route) == 0))
  11. {
  12. // Silent Mode
  13. }
  14. else
  15. {
  16. /* Known values of route:
  17. * "Headset"
  18. * "Headphone"
  19. * "Speaker"
  20. * "SpeakerAndMicrophone"
  21. * "HeadphonesAndMicrophone"
  22. * "HeadsetInOut"
  23. * "ReceiverAndMicrophone"
  24. * "Lineout"
  25. */
  26. NSString* routeStr = (__bridge NSString*)route;
  27. NSRange headphoneRange = [routeStr rangeOfString : @"Headphone"];
  28. NSRange headsetRange = [routeStr rangeOfString : @"Headset"];
  29. if (headphoneRange.location != NSNotFound)
  30. {
  31. hasHeadset = YES;
  32. }
  33. else if(headsetRange.location != NSNotFound)
  34. {
  35. hasHeadset = YES;
  36. }
  37. }
  38. if (route)
  39. {
  40. CFRelease(route);
  41. }
  42. return hasHeadset;
  43. }

2、判断是否开了Airplay(来自StackOverflow):

  1. + (BOOL)isAirplayActived
  2. {
  3. CFDictionaryRef currentRouteDescriptionDictionary = nil;
  4. UInt32 dataSize = sizeof(currentRouteDescriptionDictionary);
  5. AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, &currentRouteDescriptionDictionary);
  6. BOOL airplayActived = NO;
  7. if (currentRouteDescriptionDictionary)
  8. {
  9. CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);
  10. if(outputs != NULL && CFArrayGetCount(outputs) > 0)
  11. {
  12. CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);
  13. //Get the output type (will show airplay / hdmi etc
  14. CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);
  15. airplayActived = (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo);
  16. }
  17. CFRelease(currentRouteDescriptionDictionary);
  18. }
  19. return airplayActived;
  20. }

设置类别

下一步要设置AudioSession的Category,使用AudioSession时调用下面的接口

  1. extern OSStatus AudioSessionSetProperty(AudioSessionPropertyID inID,
  2. UInt32 inDataSize,
  3. const void *inData);

如果我需要的功能是播放,执行如下代码

  1. UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
  2. AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
  3. sizeof(sessionCategory),
  4. &sessionCategory);

使用AVAudioSession时调用下面的接口

  1. /* set session category */
  2. - (BOOL)setCategory:(NSString *)category error:(NSError **)outError;
  3. /* set session category with options */
  4. - (BOOL)setCategory:(NSString *)category withOptions: (AVAudioSessionCategoryOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0);

至于Category的类型在官方文档中都有介绍,我这里也只罗列一下具体就不赘述了,各位在使用时可以依照自己需要的功能设置Category。

  1. //AudioSession的AudioSessionCategory枚举
  2. enum {
  3. kAudioSessionCategory_AmbientSound               = ‘ambi‘,
  4. kAudioSessionCategory_SoloAmbientSound           = ‘solo‘,
  5. kAudioSessionCategory_MediaPlayback              = ‘medi‘,
  6. kAudioSessionCategory_RecordAudio                = ‘reca‘,
  7. kAudioSessionCategory_PlayAndRecord              = ‘plar‘,
  8. kAudioSessionCategory_AudioProcessing            = ‘proc‘
  9. };
  1. //AudioSession的AudioSessionCategory字符串
  2. /*  Use this category for background sounds such as rain, car engine noise, etc.
  3. Mixes with other music. */
  4. AVF_EXPORT NSString *const AVAudioSessionCategoryAmbient;
  5. /*  Use this category for background sounds.  Other music will stop playing. */
  6. AVF_EXPORT NSString *const AVAudioSessionCategorySoloAmbient;
  7. /* Use this category for music tracks.*/
  8. AVF_EXPORT NSString *const AVAudioSessionCategoryPlayback;
  9. /*  Use this category when recording audio. */
  10. AVF_EXPORT NSString *const AVAudioSessionCategoryRecord;
  11. /*  Use this category when recording and playing back audio. */
  12. AVF_EXPORT NSString *const AVAudioSessionCategoryPlayAndRecord;
  13. /*  Use this category when using a hardware codec or signal processor while
  14. not playing or recording audio. */
  15. AVF_EXPORT NSString *const AVAudioSessionCategoryAudioProcessing;

启用

有了Category就可以启动AudioSession了,启动方法:

  1. //AudioSession的启动方法
  2. extern OSStatus AudioSessionSetActive(Boolean active);
  3. extern OSStatus AudioSessionSetActiveWithFlags(Boolean active, UInt32 inFlags);
  4. //AVAudioSession的启动方法
  5. - (BOOL)setActive:(BOOL)active error:(NSError **)outError;
  6. - (BOOL)setActive:(BOOL)active withFlags:(NSInteger)flags error:(NSError **)outError NS_DEPRECATED_IOS(4_0, 6_0);
  7. - (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0);

启动方法调用后必须要判断是否启动成功,启动不成功的情况经常存在,例如一个前台的app正在播放,你的app正在后台想要启动AudioSession那就会返回失败。

一般情况下我们在启动和停止AudioSession调用第一个方法就可以了。但如果你正在做一个即时语音通讯app的话(类似于微信、易信) 就需要注意在deactive AudioSession的时候需要使用第二个方法,inFlags参数传入 kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation(AVAudioSession给 options参数传入AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation)。当你的 app deactive自己的AudioSession时系统会通知上一个被打断播放app打断结束(就是上面说到的打断回调),如果你的app在 deactive时传入了NotifyOthersOnDeactivation参数,那么其他app在接到打断结束回调时会多得到一个参数 kAudioSessionInterruptionType_ShouldResume否则就是 ShouldNotResume(AVAudioSessionInterruptionOptionShouldResume),根据参数的值可以决定 是否继续播放。

大概流程是这样的:

1. 一个音乐软件A正在播放;

2. 用户打开你的软件播放对话语音,AudioSession active;

3. 音乐软件A音乐被打断并收到InterruptBegin事件;

4. 对话语音播放结束,AudioSession deactive并且传入NotifyOthersOnDeactivation参数;

5. 音乐软件A收到InterruptEnd事件,查看Resume参数,如果是ShouldResume控制音频继续播放,如果是ShouldNotResume就维持打断状态;

官方文档中有一张很形象的图来阐述这个现象:

然而现在某些语音通讯软件和某些音乐软件却无视了NotifyOthersOnDeactivation和ShouldResume的正确用 法,导致我们经常接到这样的用户反馈:“你们的app在使用xx语音软件听了一段话后就不会继续播放了,但xx音乐软件可以继续播放啊。”

好吧,上面只是吐槽一下。请无视我吧。

补充:

发现即使之前已经调用过AudioSessionInitialize方法,在某些情况下被打断之后可能出现AudioSession失效的情 况,需要再次调用AudioSessionInitialize方法来重新生成AudioSession。否则调用 AudioSessionSetActive会返回560557673(其他AudioSession方法也雷同,所有方法调用前必须首先初始化 AudioSession),转换成string后为”!ini”即kAudioSessionNotInitialized,这个情况在iOS 5.1.x上尤其频繁,iOS 7.x也偶有发生具体的原因还不知晓。

所以每次在调用AudioSessionSetActive时应该判断一下错误码,如果是上述的错误码需要重新初始化一下AudioSession。

附上OSStatus转成string的方法:

  1. #import <Endian.h>
  2. NSString * OSStatusToString(OSStatus status)
  3. {
  4. size_t len = sizeof(UInt32);
  5. long addr = (unsigned long)&status;
  6. char cstring[5];
  7. len = (status >> 24) == 0 ? len - 1 : len;
  8. len = (status >> 16) == 0 ? len - 1 : len;
  9. len = (status >>  8) == 0 ? len - 1 : len;
  10. len = (status >>  0) == 0 ? len - 1 : len;
  11. addr += (4 - len);
  12. status = EndianU32_NtoB(status);        // strings are big endian
  13. strncpy(cstring, (char *)addr, len);
  14. cstring[len] = 0;
  15. return [NSString stringWithCString:(char *)cstring encoding:NSMacOSRomanStringEncoding];
  16. }

打断处理

正常启动AudioSession之后就可以播放音频了,下面要讲的是对于打断的处理。之前我们说到打断的回调在iOS 5下需要统一管理,在收到打断开始和结束时需要发送自定义的通知。

使用AudioSession时打断回调应该首先获取kAudioSessionProperty_InterruptionType,然后发送一个自定义的通知并带上对应的参数。

  1. static void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState)
  2. {
  3. AudioSessionInterruptionType interruptionType = kAudioSessionInterruptionType_ShouldNotResume;
  4. UInt32 interruptionTypeSize = sizeof(interruptionType);
  5. AudioSessionGetProperty(kAudioSessionProperty_InterruptionType,
  6. &interruptionTypeSize,
  7. &interruptionType);
  8. NSDictionary *userInfo = @{MyAudioInterruptionStateKey:@(inInterruptionState),
  9. MyAudioInterruptionTypeKey:@(interruptionType)};
  10. [[NSNotificationCenter defaultCenter] postNotificationName:MyAudioInterruptionNotification object:nil userInfo:userInfo];
  11. }

收到通知后的处理方法如下(注意ShouldResume参数):

  1. - (void)interruptionNotificationReceived:(NSNotification *)notification
  2. {
  3. UInt32 interruptionState = [notification.userInfo[MyAudioInterruptionStateKey] unsignedIntValue];
  4. AudioSessionInterruptionType interruptionType = [notification.userInfo[MyAudioInterruptionTypeKey] unsignedIntValue];
  5. [self handleAudioSessionInterruptionWithState:interruptionState type:interruptionType];
  6. }
  7. - (void)handleAudioSessionInterruptionWithState:(UInt32)interruptionState type:(AudioSessionInterruptionType)interruptionType
  8. {
  9. if (interruptionState == kAudioSessionBeginInterruption)
  10. {
  11. //控制UI,暂停播放
  12. }
  13. else if (interruptionState == kAudioSessionEndInterruption)
  14. {
  15. if (interruptionType == kAudioSessionInterruptionType_ShouldResume)
  16. {
  17. OSStatus status = AudioSessionSetActive(true);
  18. if (status == noErr)
  19. {
  20. //控制UI,继续播放
  21. }
  22. }
  23. }
  24. }

小结

关于AudioSession的话题到此结束(码字果然很累。。)。小结一下:

* 如果最低版本支持iOS 5,可以使用AudioSession也可以考虑使用AVAudioSession,需要有一个类统一管理AudioSession的所有回调,在接到回调后发送对应的自定义通知;

* 如果最低版本支持iOS 6及以上,请使用AVAudioSession,不用统一管理,接AVAudioSession的通知即可;

* 根据app的应用场景合理选择Category;

* 在deactive时需要注意app的应用场景来合理的选择是否使用NotifyOthersOnDeactivation参数;

* 在处理InterruptEnd事件时需要注意ShouldResume的值。

示例代码

这里有我自己写的AudioSession的封装,如果各位需要支持iOS 5的话可以使用一下。

下一篇将讲述如何使用AudioFileStreamer提取音频文件格式信息和分离音频帧。

参考资料

AudioSession

相关阅读:

iOS音频播放(一):概述

时间: 2024-09-30 04:26:50

iOS音频播放(二):AudioSession的相关文章

iOS音频播放 (二):AudioSession 转

原文出处 :http://msching.github.io/blog/2014/07/08/audio-in-ios-2/ 前言 本篇为<iOS音频播放>系列的第二篇. 在实施前一篇中所述的7个步骤之前还必须面对一个麻烦的问题,AudioSession. AudioSession简单介绍 AudioSession这个玩意的主要功能包含下面几点(图片来自官方文档): 确定你的app怎样使用音频(是播放?还是录音?) 为你的app选择合适的输入输出设备(比方输入用的麦克风,输出是耳机.手机功放或

IOS 音频播放

iOS音频播放 (一):概述 Audio Playback in iOS (Part 1) : Introduction 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖引玉,另一方面也是希望能帮助国内其他的iOS开发者和爱好者少走弯路(我自己就遇到了不少的坑=.=). 本篇为<iOS音频播放>系列的第一篇,主要将对iOS下实现音频播放的方法进行概述. 基础 先来简单了解一

iOS音频播放(一):概述

(本文转自码农人生) 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改,我也因此对于iOS下的音频播放实现有了一定的研究.写这个 系列的博客目的一方面希望能够抛砖引玉,另一方面也是希望能帮助国内其他的iOS开发者和爱好者少走弯路(我自己就遇到了不少的坑). 本篇为<iOS音频播放>系列的第一篇,主要将对iOS下实现音频播放的方法进行概述. 基础 先来简单了解一下一些基础的音频知识. 目前我们在计算机上进行音频播放都需要依赖于音频文件,音频文件的生成过程是将声音

iOS音频播放 (一):概述 转

今天看到非常好的介绍音频开发的文章,转载一下 原文地址:http://msching.github.io/blog/2014/07/07/audio-in-ios/ 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖引玉,另一方面也是希望能帮助国内其他的iOS开发者和爱好者少走弯路(我自己就遇到了不少的坑=.=). 本篇为<iOS音频播放>系列的第一篇,主要将对iOS下实现音

iOS音频播放 (五):AudioQueue

码农人生 ChengYin's coding life 主页 Blog 分类 Categories 归档 Archives 关于 About Weibo GitHub RSS Where there is a will, there is a way. -- Thomas Edison Aug 2nd, 2014 Audio, iOS, iOS Audio iOS音频播放 (五):AudioQueue Audio Playback in iOS (Part 5) : AudioQueue 在第三

iOS音频播放 (四):AudioFile 转

原文出处 : http://msching.github.io/blog/2014/07/19/audio-in-ios-4/ 前言 接着第三篇的AudioStreamFile这一篇要来聊一下AudioFile.和AudioStreamFile一样AudioFile是AudioToolBox framework中的一员,它也能够完成第一篇所述的第2步,读取音频格式信息和进行帧分离,但事实上它的功能远不止如此. AudioFile介绍 按照官方文档的描述: a C programming inte

iOS音频播放 (五):AudioQueue 转

原文出处 : http://msching.github.io/blog/2014/08/02/audio-in-ios-5/ 前言 在第三篇和第四篇中介绍了如何用AudioStreamFile和AudioFile解析音频数据格式.分离音频帧.下一步终于可以使用分离出来的音频帧进行播放了,本片中将来讲一讲如何使用AudioQueue播放音频数据. AudioQueue介绍 AudioQueue是AudioToolBox.framework中的一员,在官方文档中Apple这样描述AudioQueu

iOS音频播放、录音、视频播放、拍照、视频录制

随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操作都提供了多套API.在今天的文章中将会对这些内容进行一一介绍: 音频 音效 音乐 音频会话 录音 音频队列服务 视频 MPMoviePlayerController MPMoviePlayerViewController AVPlayer 摄像头 UIImagePickerController拍照

iOS音频播放之AudioQueue(一):播放本地音乐

AudioQueue简介 AudioStreamer说明 AudioQueue详解 AudioQueue工作原理 AudioQueue主要接口 AudioQueueNewOutput AudioQueueAllocateBuffer AudioQueueEnqueueBuffer AudioQueueStart Pause Stop Flush Reset Dispose AudioQueueFreeBuffer AudioQueueGetProperty AudioQueueSetProper