iOS中声音播放的各种方法总结

welv5511 4年前
   <h3>前言</h3>    <p>这两天禁(晋)烟(嫣)的秀恩爱,身为程序员的我们又被默默的送了一把狗粮,这段时间一直在忙公司项目,两个多月都没有写过文章了,今天闲来无事想把iOS中播放音乐(包括段音效)的部分拿出来总结一下。</p>    <p>主要部分:</p>    <p>1.音效的播放</p>    <p>2.音乐的播放(本地, 网络)</p>    <p>3.音频队列服务</p>    <p>1.音效播放(AudioToolbox/AudioToolbox.h)</p>    <p>音频文件必须打包成.caf、.aif、.wav中的一种(注意这是官方文档的说法,实际测试发现一些.mp3也可以播放)</p>    <p>这个段音效播放不能大于30s,这个30s不是我说的,是苹果的API说的</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/74e78dfa32fbd802592938118a8e6cef.png"></p>    <p style="text-align:center">AudioServices_h.png</p>    <p>创建音效的ID,音效的播放和销毁都靠这个ID来执行</p>    <p>AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID)</p>    <p>播放音效</p>    <p>AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)</p>    <p>iOS9以后可以用的,带有block回调的播放</p>    <p>AudioServicesPlaySystemSoundWithCompletion(SystemSoundID inSystemSoundID, void (^__nullable inCompletionBlock)(void))</p>    <p>带有震动的播放</p>    <p>AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)</p>    <p>iOS9以后可以用的,带有block回调的播放</p>    <p>AudioServicesPlayAlertSoundWithCompletion( SystemSoundID inSystemSoundID,void (^__nullable inCompletionBlock)(void))</p>    <p>在iOS9之前,如何判断一个音效是否播放完成呢?(利用下面的方法)</p>    <p>AudioServicesAddSystemSoundCompletion(SystemSoundID inSystemSoundID,CFRunLoopRef __nullable inRunLoop, CFStringRef __nullable inRunLoopMode,AudioServicesSystemSoundCompletionProc inCompletionRoutine,void * __nullable inClientData)</p>    <p>销毁音效的播放</p>    <p>AudioServicesDisposeSystemSoundID(SystemSoundID inSystemSoundID)</p>    <p>下面对上面的方法的演示,播放一些音效, <strong>播放48s的mp3时会报错</strong></p>    <pre>  <code class="language-objectivec">static SystemSoundID soundID = 0;    - (IBAction)play:(id)sender {    //    NSString *str = [[NSBundle mainBundle] pathForResource:@"vcyber_waiting" ofType:@"wav"];      NSString *str = [[NSBundle mainBundle] pathForResource:@"28s" ofType:@"mp3"];  //    NSString *str = [[NSBundle mainBundle] pathForResource:@"48s" ofType:@"mp3"];      NSURL *url = [NSURL fileURLWithPath:str];          AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);  //  //    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallBack, NULL);  //      //    //AudioServicesPlaySystemSound(soundID);  //      //    AudioServicesPlayAlertSound(soundID);      //    AudioServicesPlaySystemSoundWithCompletion(soundID, ^{  //        NSLog(@"播放完成");  //        AudioServicesDisposeSystemSoundID(soundID);  //    });        AudioServicesPlayAlertSoundWithCompletion(soundID, ^{          NSLog(@"播放完成");      });    }    void soundCompleteCallBack(SystemSoundID soundID, void * clientDate) {      NSLog(@"播放完成");      AudioServicesDisposeSystemSoundID(soundID);  }    - (IBAction)stop:(id)sender {      AudioServicesDisposeSystemSoundID(soundID);  }</code></pre>    <p>2.本地音乐播放</p>    <p>AVAudioPlayer</p>    <p>AVAudioPlayer是播放本地音乐最常到的,这个类对于大多数人来说应该很常用,这里不多说,说一下它的基本用法和代理的用法,直接上代码,代码注释很详细</p>    <pre>  <code class="language-objectivec">@interface LocalMusicViewController ()<AVAudioPlayerDelegate>    /**   播放器   */  @property (nonatomic, strong) AVAudioPlayer *player;    /**   播放进度条   */  @property (weak, nonatomic) IBOutlet UIProgressView *progress;    /**   改变播放进度滑块   */  @property (weak, nonatomic) IBOutlet UISlider *progressSlide;    /**   改变声音滑块   */  @property (weak, nonatomic) IBOutlet UISlider *volum;    /**   改变进度条滑块显示的定时器   */  @property (nonatomic, strong) NSTimer *timer;    @end    @implementation LocalMusicViewController    - (void)viewDidLoad {      [super viewDidLoad];        NSError *err;      NSURL *url = [[NSBundle mainBundle] URLForResource:@"1" withExtension:@"mp3"];  //    初始化播放器      _player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&err];      self.volum.value = 0.5;  //    设置播放器声音      _player.volume = self.volum.value;  //    设置代理      _player.delegate = self;  //    设置播放速率      _player.rate = 1.0;  //    设置播放次数 负数代表无限循环      _player.numberOfLoops = -1;  //    准备播放      [_player prepareToPlay];      self.progress.progress = 0;      self.progressSlide.value = 0;      _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(change) userInfo:nil repeats:YES];    }    - (void)viewWillDisappear:(BOOL)animated {      [super viewWillDisappear:animated];    }    - (void)change {      self.progress.progress = _player.currentTime / _player.duration;  }    - (IBAction)progressChange:(UISlider *)sender {  //    改变当前的播放进度      _player.currentTime = sender.value * _player.duration;      self.progress.progress = sender.value;    }  - (IBAction)volumChange:(UISlider *)sender {  //    改变声音大小      _player.volume = sender.value;  }    - (IBAction)player:(id)sender {  //    开始播放      [_player play];  }    - (IBAction)stop:(id)sender {  //    暂停播放      [_player stop];  }    #pragma mark --AVAudioPlayerDelegate  /**   完成播放, 但是在打断播放和暂停、停止不会调用   */  - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {    }    /**   播放过程中解码错误时会调用   */  - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error {    }    /**   播放过程被打断     */  - (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 8_0) {    }    /**   打断结束  */  - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0) {    }    /**   打断结束     */  - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withFlags:(NSUInteger)flags NS_DEPRECATED_IOS(4_0, 6_0) {    }    /**   这个方法被上面的方法代替了   */  - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 6_0) {    }</code></pre>    <p>网络音乐播放(AVPlayer)</p>    <p>AVPlayer是播放网络音乐和网络视频最常用到的,它可以自己缓存网络数据,然后播放,AVPlayer在播放视频时必须创建一个AVPlayerLayer用来展示视频,如果播放音乐,声音就不用创建这个对象。这里简单演示一下网络播放音乐</p>    <p>1. 通过网络链接创建AVPlayerItem</p>    <p>AVPlayerItem的初始化方法很多,我这里直接用 initWithURL: 这个方法创建</p>    <pre>  <code class="language-objectivec">- (AVPlayerItem *)getItemWithIndex:(NSInteger)index {      NSURL *url = [NSURL URLWithString:self.musicArray[index]];      AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];      //KVO监听播放状态      [item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];      //KVO监听缓存大小      [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];      //通知监听item播放完毕       [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playOver:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];      return item;  }</code></pre>    <p>2.实现KVO的方法,根据keyPath来判断观察的属性是哪一个</p>    <pre>  <code class="language-objectivec">- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {        AVPlayerItem *item = object;        if ([keyPath isEqualToString:@"status"]) {          switch (self.player.status) {              case AVPlayerStatusUnknown:                  NSLog(@"未知状态,不能播放");                  break;              case AVPlayerStatusReadyToPlay:                  NSLog(@"准备完毕,可以播放");                  break;              case AVPlayerStatusFailed:                  NSLog(@"加载失败, 网络相关问题");                  break;                default:                  break;          }      }        if ([keyPath isEqualToString:@"loadedTimeRanges"]) {          NSArray *array = item.loadedTimeRanges;          //本次缓存的时间          CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];          NSTimeInterval totalBufferTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //缓存的总长度          self.bufferProgress.progress = totalBufferTime / CMTimeGetSeconds(item.duration);      }  }</code></pre>    <p>3.懒加载AVPlayer</p>    <pre>  <code class="language-objectivec">- (AVPlayer *)player {      if (!_player) {  //        根据链接数组获取第一个播放的item, 用这个item来初始化AVPlayer          AVPlayerItem *item = [self getItemWithIndex:self.currentIndex];  //        初始化AVPlayer          _player = [[AVPlayer alloc] initWithPlayerItem:item];          __weak typeof(self)weakSelf = self;  //        监听播放的进度的方法,addPeriodicTime: ObserverForInterval: usingBlock:          /*           DMTime 每到一定的时间会回调一次,包括开始和结束播放           block回调,用来获取当前播放时长           return 返回一个观察对象,当播放完毕时需要,移除这个观察           */          _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {              float current = CMTimeGetSeconds(time);              if (current) {                  [weakSelf.progressView setProgress:current / CMTimeGetSeconds(item.duration) animated:YES];                  weakSelf.progressSlide.value = current / CMTimeGetSeconds(item.duration);              }          }];      }      return _player;  }</code></pre>    <p>4.播放和暂停</p>    <pre>  <code class="language-objectivec">//  播放  - (IBAction)play:(id)sender {      [self.player play];  }    //暂停  - (IBAction)pause:(id)sender {      [self.player pause];  }</code></pre>    <p>5.下一首和上一首</p>    <pre>  <code class="language-objectivec">- (IBAction)next:(UIButton *)sender {      [self removeObserver];     self.currentIndex ++;      if (self.currentIndex >= self.musicArray.count) {          self.currentIndex = 0;      }  //  这个方法是用一个item取代当前的item      [self.player replaceCurrentItemWithPlayerItem:[self getItemWithIndex:self.currentIndex]];      [self.player play];  }    - (IBAction)last:(UIButton *)sender {      [self removeObserver];      self.currentIndex --;      if (self.currentIndex < 0) {          self.currentIndex = 0;      }  //  这个方法是用一个item取代当前的item      [self.player replaceCurrentItemWithPlayerItem:[self getItemWithIndex:self.currentIndex]];      [self.player play];  }    // 在播放另一个时,要移除当前item的观察者,还要移除item播放完成的通知  - (void)removeObserver {      [self.player.currentItem removeObserver:self forKeyPath:@"status"];      [self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];      [[NSNotificationCenter defaultCenter] removeObserver:self];  }</code></pre>    <p>6.控制播放进度,这个也有很多的方法,如果不是太精确,用 - (void)seekToTime:(CMTime)time: 这个方法就行,如果要精确的用这个 - (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter</p>    <pre>  <code class="language-objectivec">- (IBAction)changeProgress:(UISlider *)sender {      if (self.player.status == AVPlayerStatusReadyToPlay) {          [self.player seekToTime:CMTimeMake(CMTimeGetSeconds(self.player.currentItem.duration) * sender.value, 1)];      }  }</code></pre>    <p>音频队列服务(Audio Queue Services)</p>    <p>在AudioToolbox框架中的音频队列服务,是用来播放网络流媒体的一个框架,它完全可以做到 <strong> <em>音频播放和录制</em> </strong> ,一个音频服务队列有三个部分组成:</p>    <p>1.三个缓冲器Buffers:没个缓冲器都是一个存储音频数据的临时仓库。</p>    <p>2.一个缓冲队列Buffer Queue:一个包含音频缓冲器的有序队列。</p>    <p>3.一个回调CallBack:一个自定义的队列回调函数。</p>    <p>在音频播放缓冲队列中,将音频读取到缓冲器中,一旦一个缓冲器填充满之后就放到缓冲队列中,然后继续填充其他缓冲器;当开始播放时,则从第一个缓冲器中读取音频进行播放;一旦播放完之后就会触发回调函数,开始播放下一个缓冲器中的音频,同时填充第一个缓冲器放;填充满之后再次放回到缓冲队列。下面是 <a href="/misc/goto?guid=4959729346355194351" rel="nofollow,noindex">官方</a> 详细的流程:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0471e94914de35e6fa3d790dec5cd1b0.png"></p>    <p>Playback_Audio_Queues.png</p>    <p>AudioQueue的工作大致流程:</p>    <p>1.创建 AudioQueue ,创建 BufferArray 数组,用于存放 AudioQueueBufferRef</p>    <p>2.通过 AudioQueueAllocateBuffer 创建 AudioQueueBufferRef 一般2-3个,放入到 BufferArray 数组中</p>    <p>3.有数据时从 buffer 数组取出一个 buffer , memcpy 数据后用 AudioQueueEnqueueBuffer 方法把 buffer 插入 AudioQueue 中</p>    <p>4. AudioQueue 中存在 Buffer 后,调用 AudioQueueStart 播放。(具体等到填入多少 buffer 后再播放可以自己控制,只要能保证播放不间断即可)</p>    <p>5. AudioQueue 播放音乐后消耗了某个 buffer ,在另一个线程回调并送出该 buffe r,把 buffer 放回 BufferArray 供下一次使用</p>    <p>6.返回步骤3继续循环直到播放结束</p>    <p>常用API</p>    <p>创建AudioQueue</p>    <pre>  <code class="language-objectivec">第一个参数表示需要播放的音频数据格式类型,是一个AudioStreamBasicDescription对象,是使用AudioFileStream或者AudioFile解析出来的数据格式信息;  第二个参数AudioQueueOutputCallback是某块Buffer被使用之后的回调;  第三个参数为上下文对象;  第四个参数inCallbackRunLoop为AudioQueueOutputCallback需要在的哪个RunLoop上被回调,如果传入NULL的话就会再AudioQueue的内部RunLoop中被回调,所以一般传NULL就可以了;  第五个参数inCallbackRunLoopMode为RunLoop模式,如果传入NULL就相当于kCFRunLoopCommonModes,也传NULL就可以了;  第六个参数inFlags是保留字段,目前没作用,传0;  第七个参数,返回生成的AudioQueue实例;  返回值用来判断是否成功创建(OSStatus == noErr)。  extern OSStatus               AudioQueueNewOutput( const AudioStreamBasicDescription *inFormat,                        AudioQueueOutputCallback        inCallbackProc,                       void * __nullable               inUserData,                         CFRunLoopRef __nullable         inCallbackRunLoop,                         CFStringRef __nullable          inCallbackRunLoopMode,                         UInt32                          inFlags,                        AudioQueueRef __nullable * __nonnull outAQ)              参数和上面基本相同,只是把RunLoop换成了dispatch queue  AudioQueueNewOutputWithDispatchQueue(AudioQueueRef __nullable * __nonnull outAQ,                                      const AudioStreamBasicDescription *inFormat,                                      UInt32                          inFlags,                                      dispatch_queue_t                inCallbackDispatchQueue,                                      AudioQueueOutputCallbackBlock   inCallbackBlock)</code></pre>    <p>创建Buffer</p>    <pre>  <code class="language-objectivec">第一个参数方法传入AudioQueue实例  第二个参数Buffer大小  第三个传出的BufferArray实例;  extern OSStatus  AudioQueueAllocateBuffer(AudioQueueRef    inAQ,                            UInt32    inBufferByteSize,                            AudioQueueBufferRef __nullable * __nonnull outBuffer)      比上面的方法多了一个inNumberPacketDescriptions,这个参数可以指定生成的Buffer中PacketDescriptions的个数  extern OSStatus  AudioQueueAllocateBufferWithPacketDescriptions(                                      AudioQueueRef           inAQ,                                      UInt32                  inBufferByteSize,                                      UInt32                  inNumberPacketDescriptions,                                      AudioQueueBufferRef __nullable * __nonnull outBuffer)</code></pre>    <p>释放buffer</p>    <pre>  <code class="language-objectivec">第一个参数AudioQueue实例  第二个参数指定的buffer  extern OSStatus  AudioQueueFreeBuffer(               AudioQueueRef           inAQ,                                      AudioQueueBufferRef     inBuffer)</code></pre>    <p>插入buffer</p>    <pre>  <code class="language-objectivec">第一个参数AudioQueue实例  第二个参数指定的Buffer  第三个参数数据包的个数  第四个参数数据包描述  extern OSStatus  AudioQueueEnqueueBuffer(            AudioQueueRef                       inAQ,                                      AudioQueueBufferRef                 inBuffer,                                      UInt32                              inNumPacketDescs,                                      const AudioStreamPacketDescription * __nullable inPacketDescs)    上面的方法基本满足要求,这个方法对插入的buffer进行额外的更多的操作  extern OSStatus  AudioQueueEnqueueBufferWithParameters(                                      AudioQueueRef                                inAQ,                                      AudioQueueBufferRef                          inBuffer,                                      UInt32                                       inNumPacketDescs,                                      const AudioStreamPacketDescription * __nullable inPacketDescs,                                      UInt32                                       inTrimFramesAtStart,                                      UInt32                                       inTrimFramesAtEnd,                                      UInt32                                       inNumParamValues,                                      const AudioQueueParameterEvent * __nullable  inParamValues,                                      const AudioTimeStamp * __nullable            inStartTime,                                      AudioTimeStamp * __nullable                  outActualStartTime)     __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>开始播放</p>    <pre>  <code class="language-objectivec">第一个参数AudioQueue实例  第二个参数播放时间,如果直接开始播放 传NULL  extern OSStatus  AudioQueueStart(                    AudioQueueRef                     inAQ,                                      const AudioTimeStamp * __nullable inStartTime)</code></pre>    <p>解码数据,不常用,调用开始播放会自动解码</p>    <pre>  <code class="language-objectivec">extern OSStatus  AudioQueuePrime(                    AudioQueueRef           inAQ,                                      UInt32                  inNumberOfFramesToPrepare,                                      UInt32 * __nullable     outNumberOfFramesPrepared)</code></pre>    <p>停止播放</p>    <pre>  <code class="language-objectivec">第二个参数Bool值,控制是否立即停止,如果传false,会把Enqueue的所有buffer播放完成再停止  extern OSStatus  AudioQueueStop(                     AudioQueueRef           inAQ,                                      Boolean                 inImmediate)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>暂停播放</p>    <pre>  <code class="language-objectivec">extern OSStatus  AudioQueuePause(                    AudioQueueRef           inAQ)       __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>重置解码器</p>    <pre>  <code class="language-objectivec">这个方法会播放完队列中的buffer后重置解码器,防止当前的解码器影响下一段音频,比如切换歌曲的时候,如果和AudioQueueStop(AQ,false)  一起使用并不会起效,因为Stop方法的false参数也会做同样的事情。  extern OSStatus  AudioQueueFlush(                    AudioQueueRef           inAQ)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>重置AudioQueue</p>    <pre>  <code class="language-objectivec">重置AudioQueue会清除所有已经Enqueue的buffer,并触发AudioQueueOutputCallback,调用AudioQueueStop方法时同样会触发该方法。这个方法的直接调用一般在seek时使用,用来清除残留的buffer(seek时还有一种做法是先AudioQueueStop  ,等seek完成后重新start)。  extern OSStatus  AudioQueueReset(                    AudioQueueRef           inAQ)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>获取播放时间</p>    <pre>  <code class="language-objectivec">调用时传入AudioTimeStamp,从这个结构体当中获取播放时间  extern OSStatus  AudioQueueGetCurrentTime(           AudioQueueRef                    inAQ,                                      AudioQueueTimelineRef __nullable inTimeline,                                      AudioTimeStamp * __nullable      outTimeStamp,                                      Boolean * __nullable             outTimelineDiscontinuity)       __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>销毁AudioQueue</p>    <pre>  <code class="language-objectivec">参数的意义基本和AudioQueueStop一样  extern OSStatus  AudioQueueDispose(                  AudioQueueRef           inAQ,                                       Boolean                 inImmediate)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);</code></pre>    <p>AudioQueue参数</p>    <pre>  <code class="language-objectivec">AudioQueueGetParameter  AudioQueueSetParameter  参数列表  CF_ENUM(AudioQueueParameterID)  {      kAudioQueueParam_Volume         = 1,      kAudioQueueParam_PlayRate       = 2,      kAudioQueueParam_Pitch          = 3,      kAudioQueueParam_VolumeRampTime = 4,      kAudioQueueParam_Pan            = 13  };</code></pre>    <p>AudioQueue属性</p>    <pre>  <code class="language-objectivec">AudioQueueGetPropertySize  AudioQueueGetProperty  AudioQueueSetProperty  属性列表  CF_ENUM(AudioQueuePropertyID) {      kAudioQueueProperty_IsRunning               = 'aqrn',       // value is UInt32        kAudioQueueDeviceProperty_SampleRate        = 'aqsr',       // value is Float64      kAudioQueueDeviceProperty_NumberChannels    = 'aqdc',       // value is UInt32      kAudioQueueProperty_CurrentDevice           = 'aqcd',       // value is CFStringRef        kAudioQueueProperty_MagicCookie             = 'aqmc',       // value is void*      kAudioQueueProperty_MaximumOutputPacketSize = 'xops',       // value is UInt32      kAudioQueueProperty_StreamDescription       = 'aqft',       // value is AudioStreamBasicDescription        kAudioQueueProperty_ChannelLayout           = 'aqcl',       // value is AudioChannelLayout      kAudioQueueProperty_EnableLevelMetering     = 'aqme',       // value is UInt32      kAudioQueueProperty_CurrentLevelMeter       = 'aqmv',       // value is array of AudioQueueLevelMeterState, 1 per channel      kAudioQueueProperty_CurrentLevelMeterDB     = 'aqmd',       // value is array of AudioQueueLevelMeterState, 1 per channel        kAudioQueueProperty_DecodeBufferSizeFrames  = 'dcbf',       // value is UInt32      kAudioQueueProperty_ConverterError          = 'qcve',       // value is UInt32        kAudioQueueProperty_EnableTimePitch         = 'q_tp',       // value is UInt32, 0/1      kAudioQueueProperty_TimePitchAlgorithm      = 'qtpa',       // value is UInt32. See values below.      kAudioQueueProperty_TimePitchBypass         = 'qtpb',       // value is UInt32, 1=bypassed  };</code></pre>    <p>监听属相变化相关方法</p>    <pre>  <code class="language-objectivec">AudioQueueAddPropertyListener  AudioQueueRemovePropertyListener</code></pre>    <p>总结:</p>    <p>这里说的东西都比(能)较(力)基(有)础(限),其实AudioQueue的功能还有很多,如果大家想去研究比较细致的AudioQueue的使用,这里给大家推荐两个github地址,一个是 AudioStreamer ,一个是 FreeStreamer ,这里的两个播放都是使用AudioQueue实现的。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/548afbe49e67</p>    <p> </p>