<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[码农人生]]></title>
  <link href="http://msching.github.io/atom.xml" rel="self"/>
  <link href="http://msching.github.io/"/>
  <updated>2016-11-25T10:13:57+08:00</updated>
  <id>http://msching.github.io/</id>
  <author>
    <name><![CDATA[ChengYinZju]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (九)：边播边缓存]]></title>
    <link href="http://msching.github.io/blog/2016/05/24/audio-in-ios-9/"/>
    <updated>2016-05-24T09:46:48+08:00</updated>
    <id>http://msching.github.io/blog/2016/05/24/audio-in-ios-9</id>
    <content type="html"><![CDATA[<p>好久没写过博客了，在这期间有很多同学通过博客评论、微博私信、邮件和我交流iOS音频方面的相关问题，其中被问到最多的是如何实现“边播边缓存”，这篇就来说一说这个话题。顺便一提，本文的题目虽然为“iOS音频播放”，但其中所涉及的部分技术方案在OSX平台或者在流播放视频下同样适用。</p>

<!--more-->


<hr />

<h1>我能想到的方案</h1>

<p>这类的技术方案其实有不少（其实在<a href="http://msching.github.io/blog/2014/07/07/audio-in-ios/">第一篇</a>的末尾也略微有所涉及）：</p>

<p>思路1. 最直接的方式，自行实现音频数据的请求在请求的过程中把数据缓存到磁盘，然后基于磁盘的数据自己实现解码、播放等功能；这个方法作为直接也最为复杂，开发者需要对音频播放的原理、操作系统等知识有一定程度的理解。如果能够实现这种方式所达到的效果也将会是最好的，整个过程都由开发者掌控，出现问题也可以对症下药。开源播放器<a href="https://github.com/muhku/FreeStreamer">FreeStreamer</a>就是一个很好的例子，使用带有cache功能开源播放器或在其基础上进行二次开发也是不错的选择；</p>

<p>思路2. 请求拦截的方式，首先你需要一个能够进行流播放的播放器（如Apple提供的<a href="https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVPlayer_Class/">AVPlayer</a>），通过拦截播放器发送的请求可以知道需要下载哪一段数据，于是就可以根据本地缓存文件的情况分段为播放器提供数据，如遇到已缓存的数据段直接从缓存中获取数据塞回给播放器，如遇到未缓存的数据段就发送请求获取数据，得到response和数据后保存到磁盘同时塞回给播放器。这种思路下有三个分支：</p>

<p>思路2.1 流播放器 + LocalServer，首先在搭建一个LocalServer（例如使用<a href="https://github.com/swisspol/GCDWebServer">GCDWebServer</a>），然后将URL组织成类似这种形式：</p>

<blockquote><p><a href="http://localhost:port?url=urlEncode">http://localhost:port?url=urlEncode</a>(audioUrl)</p></blockquote>

<p>把组织好的URL交给播放器播放，播放器把请求发送到LocalServer上，LocalServer解析到实际的音频地址后发送请求或者读取已缓存的数据。</p>

<p>思路2.2 流播放器 + NSURLProtocol，大家都知道<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLProtocol_Class/">NSURLProtocol</a>可以拦截Cocoa平台下<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html">URL Loading System</a>中的请求，如果播放器的请求是运行在<code>URL Loading System</code>下的话使用这个方法可以轻松的拦截到播放器所发送的请求然后自己再进行请求或者读取缓存数据。这里需要注意如果使用AVPlayer作为播放器的话这种方法只在模拟器上才work，真机上并不能拦截到任何请求。这也证明AVPlayer在真机上并没有运行在<code>URL Loading System</code>下，但模拟器上却是（不知道在OSX下是否能work，有兴趣的同学可以尝试一下）。</p>

<p>注：如果播放器使用的是CFNetwork，也可以尝试拦截，例如使用FB的<a href="https://github.com/facebook/fishhook">fishhook</a>，这hook方法应该会遇上不少坑，请做好心理准备。。</p>

<p>思路2.3 AVPlayer + AVAssetResourceLoader，<a href="https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAssetResourceLoader_Class/">AVAssetResourceLoader</a>是iOS 6之后添加的类其主要作用是让开发者能够掌控<code>AVURLAsset</code>加载数据的整个过程。这正好符合我们的需求，<code>AVAssetResourceLoader</code>会通过delegate把<code>AVAssetResourceLoadingRequest</code>对象传递给开发者，开发者可以根据其中的一些属性得知需要加载的数据段。在得到数据后也可以通过<code>AVAssetResourceLoadingRequest</code>向AVPlayer传递response和数据。</p>

<p>思路3. 取巧的方式，自行实现音频数据的请求在请求的过程中把数据缓存到磁盘，然后使用系统提供的播放器（如AVAudioPlayer、AVPlayer进行播放）。这种实现方式中需要注意的是要播放的音频文件需要预先缓存一定量之后才能够播放，具体缓存多少完全频个人感觉，并且有可能会产生播放失败或者播放错误。这种方式的另一个缺点是无法进行任意的seek；</p>

<hr />

<h1>方案的选择</h1>

<p>上面提到了3种思路共5个方案，那么在实际开发过程中开发者应该可以根据各个方案的优劣结合自己的实际情况选择最适合自己的方案。</p>

<p>思路1：优点在于整个播放过程可控，出现问题可调试，但开发复杂度较高，故选择有对应功能的开源播放器是一个比较好途径。在使用开源播放器之前最好能阅读其代码，掌握整个播放流程，出了问题才能迅速定位。推荐以播放为核心功能的app使用此方案；</p>

<p>思路2：优点在于开发者不必关心播放的整个过程，对音频播放的相关知识也不必有太多的了解，整个开发过程只要关心请求的解析、缓存数据的读取和保存以及数据的回填即可；至于缺点，首先你的有一个靠谱的流播放器，如果使用AVPlayer那么请做踩坑准备；</p>

<p>思路2.1：各类流播放器通吃，如果方案2.2和2.3不管用2.1是最好的选择；</p>

<p>思路2.2：需要播放器有指定的请求方式，如运行在<code>URL Loading System</code>下；</p>

<p>思路2.3：如果你用的就是AVPlayer那么可以尝试使用这个思路，但对于播放列表形式(M3U8)的音频这种方式是无效的；</p>

<p>思路3：如果你选择这条路，那说明你真的懒得不行。。。</p>

<hr />

<h1>思路2缓存和数据读取细节</h1>

<p>一般音频流或者视频流都会支持HTTP协议中的<a href="https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header">Range request header</a>，所以大多数的流播放器都会对<code>Range header</code>进行支持，在数据源支持Range的情况下拦截到请求时有必要注意播放器所请求的数据段并根据当前数据缓存的状态进行分段处理。</p>

<p>举个例子，播放器请求bytes=0-100，其中10-20、50-60已经被缓存，那么这个请求就应该被分为下面几段来处理：</p>

<ol>
<li>0-10，网络请求</li>
<li>10-20，本地缓存</li>
<li>20-50，网络请求</li>
<li>50-60，本地缓存</li>
<li>60-100，网络请求</li>
</ol>


<p>以上几段数据请求按顺序执行并进行数据回填，其中通过网络请求的数据在收到之后加入缓存以便下一次请求再次使用。另外要注意的是由于播放器本身只发送了一个请求所以response还是只有一个并且Content-Range还是应该为<code>0-100/FileLength</code>。</p>

<hr />

<h1>AVAssetResourceLoader踩坑</h1>

<div class="github-card" data-github="msching/AVPlayerCacheSupport" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<p><a href="https://github.com/msching/AVPlayerCacheSupport">AVPlayerCacheSupport</a>是我使用<code>AVAssetResourceLoader</code>进行实践后实现的一个开源项目，在开发的过程中踩到的坑也在这里分享给大家。</p>

<h3>shceme必须自定义</h3>

<p>非自定义的URL Scheme不会触发AVAssetResourceLoader的delegate方法。这一点并不难发现，Stackoverflow上和github上都有提到这一点。所以在构造AVPlayItem时必须使用自定义Scheme的URL才行，这里我是在原有的Scheme后加上了<code>-streaming</code>，在收到AVAssetResourceLoader的回调之后实际发送请求时再把<code>-streaming</code>后缀去掉。</p>

<h3>AVURLAsset.resourceLoader的delegate必须在AVPlayerItem生成前赋值</h3>

<p>看代码感受一下吧，这样写能接到回调：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AVURLAsset</span> <span class="o">*</span><span class="n">asset</span> <span class="o">=</span> <span class="p">[</span><span class="n">AVURLAsset</span> <span class="nl">URLAssetWithURL:</span><span class="n">url</span><span class="p">]</span> <span class="nl">options:</span><span class="n">options</span><span class="p">];</span>
</span><span class='line'><span class="p">[</span><span class="n">asset</span><span class="p">.</span><span class="n">resourceLoader</span> <span class="nl">setDelegate:</span><span class="n">self</span> <span class="nl">queue:</span><span class="n">dispatch_get_main_queue</span><span class="p">()];</span>
</span><span class='line'><span class="n">AVPlayerItem</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="p">[</span><span class="n">self</span> <span class="nl">playerItemWithAsset:</span><span class="n">asset</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>下面这种写法是无法接到回调的：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AVURLAsset</span> <span class="o">*</span><span class="n">asset</span> <span class="o">=</span> <span class="p">[</span><span class="n">AVURLAsset</span> <span class="nl">URLAssetWithURL:</span><span class="n">url</span><span class="p">]</span> <span class="nl">options:</span><span class="n">options</span><span class="p">];</span>
</span><span class='line'><span class="n">AVPlayerItem</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="p">[</span><span class="n">self</span> <span class="nl">playerItemWithAsset:</span><span class="n">asset</span><span class="p">];</span>
</span><span class='line'><span class="p">[[(</span><span class="n">AVURLAsset</span> <span class="o">*</span><span class="p">)</span><span class="n">item</span><span class="p">.</span><span class="n">asset</span> <span class="n">resourceLoader</span><span class="p">]</span> <span class="nl">setDelegate:</span><span class="n">self</span> <span class="nl">queue:</span><span class="n">dispatch_get_main_queue</span><span class="p">()];</span>
</span></code></pre></td></tr></table></div></figure>


<h3>不支持Playlist类型的播放</h3>

<p>AVAssetResourceLoader不支持类似M3U和M3U8这类播放列表类型的流，这个问题的回答来自<a href="http://stackoverflow.com/a/30239876">SO链接</a>，<a href="https://developer.apple.com/library/ios/technotes/tn2232/_index.html#//apple_ref/doc/uid/DTS40012884-CH1-SECHTTPLIVESTREAMING">官方文档</a>中关于HTTP Live Streaming的一段话也印证了这一点。</p>

<p>在搜索相关问题之前，我尝试了使用AVAssetResourceLoader去加载M3U8播放列表，其中M3U8文件可以获取到，但并非获取了之后直接存储就完事了，还需要进行一些处理：</p>

<p>M3U8中一般有两种类型的URL：相对地址的URL和绝对地址的URL，其中相对地址的URL不需要处理AVPlayer会根据原先的host（也就是带了-streaming后缀的host）进行请求，这样的请求还是会被AVAssetResourceLoader拦截到。而绝对地址的URL则需要对其中的scheme进行处理使其能够被AVAssetResourceLoader拦截。</p>

<p>处理完所有的URL以后才能把M3U8文件进行保存。</p>

<p>M3U8处理完成之后，就尝试处理其中的一些媒体文件地址，例如ts格式的视频，但经过尝试后发现这类ts的链接并不能被AVAssetResourceLoader拦截到，这才去搜索相关内容后找到了上述的SO链接和官方文档。</p>

<h3>AVAssetResourceLoadingContentInformationRequest的contentLength和contentType</h3>

<p><code>AVAssetResourceLoadingContentInformationRequest</code>是<code>AVAssetResourceLoadingRequest</code>的一个属性</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cm">/*! </span>
</span><span class='line'><span class="cm"> @property       contentInformationRequest</span>
</span><span class='line'><span class="cm"> @abstract       An instance of AVAssetResourceLoadingContentInformationRequest that you should populate with information about the resource. The value of this property will be nil if no such information is being requested.</span>
</span><span class='line'><span class="cm">*/</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">readonly</span><span class="p">,</span> <span class="n">nullable</span><span class="p">)</span> <span class="n">AVAssetResourceLoadingContentInformationRequest</span> <span class="o">*</span><span class="n">contentInformationRequest</span> <span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="mi">10</span><span class="n">_9</span><span class="p">,</span> <span class="mi">7</span><span class="n">_0</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>其作用是告诉AVPlayer当前加载的资源类型、文件大小等信息。</p>

<p><code>AVAssetResourceLoadingContentInformationRequest</code>有这样一个属性：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cm">/*! </span>
</span><span class='line'><span class="cm"> @property       contentLength</span>
</span><span class='line'><span class="cm"> @abstract       Indicates the length of the requested resource, in bytes.</span>
</span><span class='line'><span class="cm"> @discussion Before you finish loading an AVAssetResourceLoadingRequest, if its contentInformationRequest is not nil, you should set the value of this property to the number of bytes contained by the requested resource.</span>
</span><span class='line'><span class="cm">*/</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">)</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">contentLength</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>乍看上去可以把当前所请求数据的<code>Content-Length</code>直接赋给这个属性，例如请求range=0-100的那么其<code>Content-Length</code>就是100。如果当前数据无缓存的话，就直接把<code>NSURLResponse</code>的<code>expectedContentLength</code>属性值赋值给了contentLength。</p>

<p>但经过实践发现上面的做法并不正确。对于支持<code>Range</code>的请求，如range=0-100，<code>NSURLResponse</code>的<code>expectedContentLength</code>属性值为100，但这里需要填入的是文件的总长。所以对于response header中包含<code>Content-Range</code>的请求，需要解析出其中的文件总长再赋值给<code>AVAssetResourceLoadingContentInformationRequest</code>的<code>contentLength</code>属性。</p>

<p>接下来是<code>contentType</code>：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cm">/*! </span>
</span><span class='line'><span class="cm"> @property       contentType</span>
</span><span class='line'><span class="cm"> @abstract       A UTI that indicates the type of data contained by the requested resource.</span>
</span><span class='line'><span class="cm"> @discussion Before you finish loading an AVAssetResourceLoadingRequest, if its contentInformationRequest is not nil, you should set the value of this property to a UTI indicating the type of data contained by the requested resource.</span>
</span><span class='line'><span class="cm">*/</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">copy</span><span class="p">,</span> <span class="n">nullable</span><span class="p">)</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">contentType</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里的<code>contentType</code>是UTI，和<code>NSURLResponse</code>的<code>MIMEType</code>并不相同。需要进行转换：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">NSString</span> <span class="o">*</span><span class="n">mimeType</span> <span class="o">=</span> <span class="p">[</span><span class="n">response</span> <span class="n">MIMEType</span><span class="p">];</span>
</span><span class='line'><span class="n">CFStringRef</span> <span class="n">contentType</span> <span class="o">=</span> <span class="n">UTTypeCreatePreferredIdentifierForTag</span><span class="p">(</span><span class="n">kUTTagClassMIMEType</span><span class="p">,</span> <span class="p">(</span><span class="n">__bridge</span> <span class="n">CFStringRef</span><span class="p">)(</span><span class="n">mimeType</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
</span><span class='line'><span class="n">loadingRequest</span><span class="p">.</span><span class="n">contentInformationRequest</span><span class="p">.</span><span class="n">contentType</span> <span class="o">=</span> <span class="n">CFBridgingRelease</span><span class="p">(</span><span class="n">contentType</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>总结</h1>

<p>要说的就这么多，希望能帮到大家 =)。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (八)：NowPlayingCenter和RemoteControl]]></title>
    <link href="http://msching.github.io/blog/2014/11/06/audio-in-ios-8/"/>
    <updated>2014-11-06T13:26:28+08:00</updated>
    <id>http://msching.github.io/blog/2014/11/06/audio-in-ios-8</id>
    <content type="html"><![CDATA[<p>距离<a href="http://msching.github.io/blog/2014/09/07/audio-in-ios-7/">上一篇</a>博文发布已经有一个月多的时间了，在这其间我一直忙于筹办婚礼以至于这篇博文一直拖到了现在。</p>

<p>在之前<a href="http://msching.github.io/blog/categories/ios-audio/">一到六篇</a>中我对iOS下的音频播放流程进行了阐述，在<a href="http://msching.github.io/blog/2014/09/07/audio-in-ios-7/">第七篇</a>中介绍了如何播放iPod Lib中的歌曲，至此有关音频播放的话题就已经完结了，在这篇里我将会讲到的<code>NowPlayingCenter</code>和<code>RemoteControl</code>这两个玩意本身和整个播放流程并没有什么关系，但它们可以让音频播放在iOS系统上获得更加好的用户体验。</p>

<!--more-->


<hr />

<h1>NowPlayingCenter</h1>

<p><code>NowPlayingCenter</code>能够显示当前正在播放的歌曲信息，它可以控制的范围包括：</p>

<ul>
<li>锁频界面上所显示的歌曲播放信息和图片</li>
<li>iOS7之后控制中心上显示的歌曲播放信息</li>
<li>iOS7之前双击home键后出现的进程中向左滑动出现的歌曲播放信息</li>
<li>AppleTV，AirPlay中显示的播放信息</li>
<li>车载系统中显示的播放信息</li>
</ul>


<p>这些信息的显示都由<code>MPNowPlayingInfoCenter</code>类来控制，这个类的定义非常简单：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">MP_EXTERN_CLASS_AVAILABLE</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">)</span> <span class="k">@interface</span> <span class="nc">MPNowPlayingInfoCenter</span> : <span class="nc">NSObject</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Returns the default now playing info center.</span>
</span><span class='line'><span class="c1">// The default center holds now playing info about the current application.</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPNowPlayingInfoCenter</span> <span class="o">*</span><span class="p">)</span><span class="nf">defaultCenter</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The current now playing info for the center.</span>
</span><span class='line'><span class="c1">// Setting the info to nil will clear it.</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">copy</span><span class="p">)</span> <span class="n">NSDictionary</span> <span class="o">*</span><span class="n">nowPlayingInfo</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用也同样简单，首先<code>#import &lt;MediaPlayer/MPNowPlayingInfoCenter.h&gt;</code>然后调用<code>MPNowPlayingInfoCenter</code>的单例方法获取实例，再把需要显示的信息组织成Dictionary并赋值给<code>nowPlayingInfo</code>属性就完成了。</p>

<p><code>nowPlayingInfo</code>中一些常用属性被定义在<code>&lt;MediaPlayer/MPMediaItem.h&gt;</code>中</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">MPMediaItemPropertyAlbumTitle</span>              <span class="c1">//NSString</span>
</span><span class='line'><span class="n">MPMediaItemPropertyAlbumTrackCount</span>         <span class="c1">//NSNumber of NSUInteger</span>
</span><span class='line'><span class="n">MPMediaItemPropertyAlbumTrackNumber</span>        <span class="c1">//NSNumber of NSUInteger</span>
</span><span class='line'><span class="n">MPMediaItemPropertyArtist</span>                  <span class="c1">//NSString</span>
</span><span class='line'><span class="n">MPMediaItemPropertyArtwork</span>                 <span class="c1">//MPMediaItemArtwork</span>
</span><span class='line'><span class="n">MPMediaItemPropertyComposer</span>                <span class="c1">//NSString</span>
</span><span class='line'><span class="n">MPMediaItemPropertyDiscCount</span>               <span class="c1">//NSNumber of NSUInteger</span>
</span><span class='line'><span class="n">MPMediaItemPropertyDiscNumber</span>              <span class="c1">//NSNumber of NSUInteger</span>
</span><span class='line'><span class="n">MPMediaItemPropertyGenre</span>                   <span class="c1">//NSString</span>
</span><span class='line'><span class="n">MPMediaItemPropertyPersistentID</span>            <span class="c1">//NSNumber of uint64_t</span>
</span><span class='line'><span class="n">MPMediaItemPropertyPlaybackDuration</span>        <span class="c1">//NSNumber of NSTimeInterval</span>
</span><span class='line'><span class="n">MPMediaItemPropertyTitle</span>                   <span class="c1">//NSString</span>
</span></code></pre></td></tr></table></div></figure>


<p>上面这些属性大多比较浅显易懂，基本上按照字面上的意思去理解就可以了，需要稍微解释以下的是<code>MPMediaItemPropertyArtwork</code>。这个属性表示的是锁屏界面或者AirPlay中显示的歌曲封面图，<code>MPMediaItemArtwork</code>类可以由<code>UIImage</code>类进行初始化。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">MP_EXTERN_CLASS_AVAILABLE</span><span class="p">(</span><span class="mi">3</span><span class="n">_0</span><span class="p">)</span> <span class="k">@interface</span> <span class="nc">MPMediaItemArtwork</span> : <span class="nc">NSObject</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Initializes an MPMediaItemArtwork instance with the given full-size image.</span>
</span><span class='line'><span class="c1">// The crop rect of the image is assumed to be equal to the bounds of the </span>
</span><span class='line'><span class="c1">// image as defined by the image&#39;s size in points, i.e. tightly cropped.</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">instancetype</span><span class="p">)</span><span class="nf">initWithImage:</span><span class="p">(</span><span class="n">UIImage</span> <span class="o">*</span><span class="p">)</span><span class="nv">image</span> <span class="n">NS_DESIGNATED_INITIALIZER</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Returns the artwork image for an item at a given size (in points).</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">UIImage</span> <span class="o">*</span><span class="p">)</span><span class="nf">imageWithSize:</span><span class="p">(</span><span class="n">CGSize</span><span class="p">)</span><span class="nv">size</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">readonly</span><span class="p">)</span> <span class="n">CGRect</span> <span class="n">bounds</span><span class="p">;</span> <span class="c1">// The bounds of the full size image (in points).</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">readonly</span><span class="p">)</span> <span class="n">CGRect</span> <span class="n">imageCropRect</span><span class="p">;</span> <span class="c1">// The actual content area of the artwork, in the bounds of the full size image (in points).</span>
</span><span class='line'>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>另外一些附加属性被定义在<code>&lt;MediaPlayer/MPNowPlayingInfoCenter.h&gt;</code>中</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// The elapsed time of the now playing item, in seconds.</span>
</span><span class='line'><span class="c1">// Note the elapsed time will be automatically extrapolated from the previously </span>
</span><span class='line'><span class="c1">// provided elapsed time and playback rate, so updating this property frequently</span>
</span><span class='line'><span class="c1">// is not required (or recommended.)</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyElapsedPlaybackTime</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (double)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The playback rate of the now playing item, with 1.0 representing normal </span>
</span><span class='line'><span class="c1">// playback. For example, 2.0 would represent playback at twice the normal rate.</span>
</span><span class='line'><span class="c1">// If not specified, assumed to be 1.0.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyPlaybackRate</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (double)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The &quot;default&quot; playback rate of the now playing item. You should set this</span>
</span><span class='line'><span class="c1">// property if your app is playing a media item at a rate other than 1.0 in a</span>
</span><span class='line'><span class="c1">// default playback state. e.g., if you are playing back content at a rate of</span>
</span><span class='line'><span class="c1">// 2.0 and your playback state is not fast-forwarding, then the default</span>
</span><span class='line'><span class="c1">// playback rate should also be 2.0. Conversely, if you are playing back content</span>
</span><span class='line'><span class="c1">// at a normal rate (1.0) but the user is fast-forwarding your content at a rate</span>
</span><span class='line'><span class="c1">// greater than 1.0, then the default playback rate should be set to 1.0.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyDefaultPlaybackRate</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">8</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (double)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The index of the now playing item in the application&#39;s playback queue.</span>
</span><span class='line'><span class="c1">// Note that the queue uses zero-based indexing, so the index of the first item </span>
</span><span class='line'><span class="c1">// would be 0 if the item should be displayed as &quot;item 1 of 10&quot;.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyPlaybackQueueIndex</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (NSUInteger)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The total number of items in the application&#39;s playback queue.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyPlaybackQueueCount</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (NSUInteger)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The chapter currently being played. Note that this is zero-based.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyChapterNumber</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (NSUInteger)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// The total number of chapters in the now playing item.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPNowPlayingInfoPropertyChapterCount</span> <span class="nf">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">5</span><span class="n">_0</span><span class="p">);</span> <span class="c1">// NSNumber (NSUInteger)</span>
</span></code></pre></td></tr></table></div></figure>


<p>其中常用的是<code>MPNowPlayingInfoPropertyElapsedPlaybackTime</code>和<code>MPNowPlayingInfoPropertyPlaybackRate</code>：</p>

<ul>
<li><code>MPNowPlayingInfoPropertyElapsedPlaybackTime</code>表示已经播放的时间，用这个属性可以让<code>NowPlayingCenter</code>显示播放进度；</li>
<li><code>MPNowPlayingInfoPropertyPlaybackRate</code>表示播放速率。通常情况下播放速率为1.0，即真是时间的1秒对应播放时间中的1秒；</li>
</ul>


<p>这里需要解释的是，<code>NowPlayingCenter</code>中的进度刷新并不是由app不停的更新<code>nowPlayingInfo</code>来做的，而是根据app传入的<code>ElapsedPlaybackTime</code>和<code>PlaybackRate</code>进行自动刷新。例如传入ElapsedPlaybackTime=120s，PlaybackRate=1.0，那么<code>NowPlayingCenter</code>会显示2:00并且在接下来的时间中每一秒把进度加1秒并刷新显示。如果需要暂停进度，传入PlaybackRate=0.0即可。</p>

<p>所以每次播放暂停和继续都需要更新<code>NowPlayingCenter</code>并正确设置<code>ElapsedPlaybackTime</code>和<code>PlaybackRate</code>否则<code>NowPlayingCenter</code>中的播放进度无法正常显示。</p>

<h3>NowPlayingCenter的刷新时机</h3>

<p>频繁的刷新<code>NowPlayingCenter</code>并不可取，特别是在有Artwork的情况下。所以需要在合适的时候进行刷新。</p>

<p>依照我自己的经验下面几个情况下刷新<code>NowPlayingCenter</code>比较合适：</p>

<ul>
<li>当前播放歌曲进度被拖动时</li>
<li>当前播放的歌曲变化时</li>
<li>播放暂停或者恢复时</li>
<li>当前播放歌曲的信息发生变化时（例如Artwork，duration等）</li>
</ul>


<p>在刷新时可以适当的通过判断app是否active来决定是否必须刷新以减少刷新次数。</p>

<h3>MPMediaItemPropertyArtwork</h3>

<p>这是一个非常有用的属性，我们可以利用歌曲的封面图来合成一些图片借此达到美化锁屏界面或者显示锁屏歌词。</p>

<hr />

<h1>RemoteControl</h1>

<p><code>RemoteComtrol</code>可以用来在不打开app的情况下控制app中的多媒体播放行为，涉及的内容主要包括：</p>

<ul>
<li>锁屏界面双击Home键后出现的播放操作区域</li>
<li>iOS7之后控制中心的播放操作区域</li>
<li>iOS7之前双击home键后出现的进程中向左滑动出现的播放操作区域</li>
<li>AppleTV，AirPlay中显示的播放操作区域</li>
<li>耳机线控</li>
<li>车载系统的设置</li>
<li>&hellip;</li>
</ul>


<h3>iOS 7.1之后如何处理RemoteControl</h3>

<p>iOS 7.1之后Apple提供了<code>MPRemoteCommandCenter</code>类来统一管理RemoteControl，并且在MPRemoteCommandCenter定义了比之前更多的RemoteControl操作，我们除了可以操作播放还可以进行一些其他的交互操作，比如收藏、评分等等。</p>

<p>使用过程中我们只要为其中需要用到的command添加一个方法就可以实现RemoteControl。</p>

<p>例如对于播放和暂停来说只要进行如下设置就可以了：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="p">[[</span><span class="n">UIApplication</span> <span class="n">sharedApplication</span><span class="p">]</span> <span class="n">beginReceivingRemoteControlEvents</span><span class="p">]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//返回值根据需要返回，一般情况下返回MPRemoteCommandHandlerStatusSuccess即可</span>
</span><span class='line'><span class="p">[[</span><span class="n">MPRemoteCommandCenter</span> <span class="n">sharedCommandCenter</span><span class="p">].</span><span class="n">playCommand</span> <span class="nl">addTargetWithHandler:</span><span class="o">^</span><span class="n">MPRemoteCommandHandlerStatus</span><span class="p">(</span><span class="n">MPRemoteCommandEvent</span> <span class="o">*</span> <span class="n">_Nonnull</span> <span class="n">event</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// play</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">MPRemoteCommandHandlerStatusSuccess</span><span class="p">;</span>
</span><span class='line'><span class="p">}];</span>
</span><span class='line'>
</span><span class='line'><span class="p">[[</span><span class="n">MPRemoteCommandCenter</span> <span class="n">sharedCommandCenter</span><span class="p">].</span><span class="n">pauseCommand</span> <span class="nl">addTargetWithHandler:</span><span class="o">^</span><span class="n">MPRemoteCommandHandlerStatus</span><span class="p">(</span><span class="n">MPRemoteCommandEvent</span> <span class="o">*</span> <span class="n">_Nonnull</span> <span class="n">event</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// pause</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">MPRemoteCommandHandlerStatusSuccess</span><span class="p">;</span>
</span><span class='line'><span class="p">}];</span>
</span><span class='line'>
</span><span class='line'><span class="p">[[</span><span class="n">MPRemoteCommandCenter</span> <span class="n">sharedCommandCenter</span><span class="p">].</span><span class="n">togglePlayPauseCommand</span> <span class="nl">addTargetWithHandler:</span><span class="o">^</span><span class="n">MPRemoteCommandHandlerStatus</span><span class="p">(</span><span class="n">MPRemoteCommandEvent</span> <span class="o">*</span> <span class="n">_Nonnull</span> <span class="n">event</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// play or pause</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">MPRemoteCommandHandlerStatusSuccess</span><span class="p">;</span>
</span><span class='line'><span class="p">}];</span>
</span></code></pre></td></tr></table></div></figure>


<p>或者</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="p">[[</span><span class="n">UIApplication</span> <span class="n">sharedApplication</span><span class="p">]</span> <span class="n">beginReceivingRemoteControlEvents</span><span class="p">]</span>
</span><span class='line'>
</span><span class='line'><span class="p">[[</span><span class="n">MPRemoteCommandCenter</span> <span class="n">sharedCommandCenter</span><span class="p">].</span><span class="n">playCommand</span> <span class="nl">addTarget:</span><span class="n">self</span> <span class="nl">action:</span><span class="k">@selector</span><span class="p">(</span><span class="n">play</span><span class="p">)];</span>
</span><span class='line'><span class="p">[[</span><span class="n">MPRemoteCommandCenter</span> <span class="n">sharedCommandCenter</span><span class="p">].</span><span class="n">pauseCommand</span> <span class="nl">addTarget:</span><span class="n">self</span> <span class="nl">action:</span><span class="k">@selector</span><span class="p">(</span><span class="n">pause</span><span class="p">)];</span>
</span><span class='line'><span class="p">[[</span><span class="n">MPRemoteCommandCenter</span> <span class="n">sharedCommandCenter</span><span class="p">].</span><span class="n">togglePlayPauseCommand</span> <span class="nl">addTarget:</span><span class="n">self</span> <span class="nl">action:</span><span class="k">@selector</span><span class="p">(</span><span class="n">playOrPause</span><span class="p">)];</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果你只是临时使用的话记得在使用完成后removeTarget并调用endReceivingRemoteControlEvents，removeTarget的方法根据addTarget的方式不同而不同：</p>

<ol>
<li>如果是<code>-addTargetWithHandler:</code>添加的，需要把返回的target记下来后调用<code>-removeTarget:</code>移除</li>
<li>如果是<code>-addTarget:action:</code>添加的，使用<code>-removeTarget:action:</code>移除</li>
</ol>


<p>对于播放控制之外的收藏、评分等操作需要注意command的相关属性，在必要的时候使用这些属性以达到需要的效果。例如<code>MPFeedbackCommand</code>的<code>active</code>属性。</p>

<p><del>另外注意<code>MPChangePlaybackPositionCommand</code>是个坑，目前（iOS 9.x）看来还不能得到应用，无法通过公开的接口使用，必须调用私有接口后才能让锁屏界面和控制中心的进度条能够拖动，不知道iOS 10会不会开放。</del></p>

<p>SDK8编译在iOS 10.1之后已经可以使用<code>MPChangePlaybackPositionCommand</code>了，而且不知道为什么iOS 8.4也是可以的，其他iOS版本依然不能应用。</p>

<p>SDK8中还出现了两个新的Command，<code>MPChangeRepeatModeCommand</code>和<code>MPChangeShuffleModeCommand</code>，虽然SDK8中才出现却没有标记版本，说明是iOS7.1就开始有了？这个我不确信。这两个Command在锁屏界面和控制中心中没有UI对应，根据台湾<a href="https://medium.com/@zonble/the-issue-about-using-mpchangerepeatmodecommand-811840a39e93#.153icy7qb">KKBOX公司的实践</a>，这两个Command是被用在CarPlay中，但使用之后出现了一些兼容性的问题，故不推荐大家使用。</p>

<p>PS：macOS 10.12.1之后macOS也有RemoteControl和NowPlayingInfoCenter了，可以在2016款MBP的TouchBar上进行歌曲信息的展示和操作，其中RemoteControl可以接受耳机线控操作和键盘多媒体键操作（之前是要通过<a href="https://github.com/nevyn/SPMediaKeyTap">SPMediaKeyTap</a>和<a href="https://github.com/Daij-Djan/DDHidLib/blob/master/lib/DDHidAppleMikey.m">DDHidAppleMikey</a>实现的），耳机线控操作也不再唤起iTunes了，不过似乎只能在非沙箱环境下有效。</p>

<h3>iOS 7.1之前如何处理RemoteComtrol</h3>

<h4>在何处处理RemoteComtrol</h4>

<p>根据<a href="https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html">官方文档</a>的描述：</p>

<p><code>If your app plays audio or video content, you might want it to respond to remote control events that originate from either transport controls or external accessories. (External accessories must conform to Apple-provided specifications.) iOS converts commands into UIEvent objects and delivers the events to an app. The app sends them to the first responder and, if the first responder doesn’t handle them, they travel up the responder chain.</code></p>

<p>当<code>RemoteComtrol</code>事件产生时，iOS会以<code>UIEvent</code>的形式发送给app，app会首先转发到first responder，如果first responder不处理这个事件的话那么事件就会沿着responder chain继续转发。关于responder chain的相关内容可以查看<a href="https://developer.apple.com/library/IOs/documentation/General/Conceptual/Devpedia-CocoaApp/Responder.html">这里</a>。</p>

<p>从responder chain文档看来如果之前的所有responder全部不响应<code>RemoteComtrol</code>事件的话，最终事件会被转发给Application（如图）。所以我们知道作为responder chain的最末端，在<code>UIApplication</code>中实现<code>RemoteComtrol</code>的处理是最为合理的，而并非在UIWindow中或者AppDelegate中。</p>

<p><img src="http://msching.github.io/images/iOS-audio/responder%20chain.jpg" alt="" /></p>

<h4>实现自己的UIApplication</h4>

<p>首先新建一个<code>UIApplication</code>的子类</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cp">#import &lt;UIKit/UIKit.h&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@interface</span> <span class="nc">MyApplication</span> : <span class="nc">UIApplication</span>
</span><span class='line'>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>然后找到工程中的<code>main.m</code>，可以看到代码如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="err">@</span><span class="n">autoreleasepool</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="n">UIApplicationMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="n">nil</span><span class="p">,</span> <span class="n">NSStringFromClass</span><span class="p">([</span><span class="n">AppDelegate</span> <span class="n">class</span><span class="p">]));</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在main中调用了<code>UIApplicationMain</code>方法</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no</span>
</span><span class='line'><span class="c1">// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.</span>
</span><span class='line'><span class="n">UIKIT_EXTERN</span> <span class="kt">int</span> <span class="nf">UIApplicationMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[],</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">principalClassName</span><span class="p">,</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">delegateClassName</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们需要做的就是给<code>UIApplicationMain</code>方法的第三个参数传入我们的application类名，如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="cp">#import &lt;UIKit/UIKit.h&gt;</span>
</span><span class='line'><span class="cp">#import &quot;AppDelegate.h&quot;</span>
</span><span class='line'><span class="cp">#import &quot;MyApplication.h&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="err">@</span><span class="n">autoreleasepool</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="n">UIApplicationMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="n">NSStringFromClass</span><span class="p">([</span><span class="n">MyApplication</span> <span class="n">class</span><span class="p">]),</span> <span class="n">NSStringFromClass</span><span class="p">([</span><span class="n">AppDelegate</span> <span class="n">class</span><span class="p">]));</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>这样就成功实现了自己的<code>UIApplication</code>.</p>

<h4>处理RemoteComtrol</h4>

<p>了解了应该在何处处理<code>RemoteComtrol</code>事件之后，再来看下<a href="https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html">官方文档</a>中描述的三个必要条件：</p>

<ul>
<li>接受者必须能够成为first responder</li>
<li>必须显示地声明接收<code>RemoteComtrol</code>事件</li>
<li>你的app必须是<code>Now Playing</code>app</li>
</ul>


<p>对于第一条就是要在自己的<code>UIApplication</code>中实现<code>canBecomeFirstResponder</code>方法:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cp">#import &quot;MyApplication.h&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@implementation</span> <span class="nc">MyApplication</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">canBecomeFirstResponder</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>第二条是要求显示地调用<code>[[UIApplication sharedApplication] beginReceivingRemoteControlEvents]</code>，调用的实际一般是在播放开始时；</p>

<p>第三条就是要求占据NowPlayingCenter，这个之前已经提到过了。</p>

<p>满足三个条件后可以在<code>UIApplication</code>中实现处理<code>RemoteComtrol</code>事件的方法，根据不同的事件实现不同的操作即可。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cp">#import &quot;MyApplication.h&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@implementation</span> <span class="nc">MyApplication</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">canBecomeFirstResponder</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">remoteControlReceivedWithEvent:</span><span class="p">(</span><span class="n">UIEvent</span> <span class="o">*</span><span class="p">)</span><span class="nv">event</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">switch</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="n">subtype</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">case</span> <span class="nl">UIEventSubtypeRemoteControlPlay:</span>
</span><span class='line'>            <span class="c1">//play</span>
</span><span class='line'>            <span class="k">break</span><span class="p">;</span>
</span><span class='line'>        <span class="k">case</span> <span class="nl">UIEventSubtypeRemoteControlPause:</span>
</span><span class='line'>            <span class="c1">//pause</span>
</span><span class='line'>            <span class="k">break</span><span class="p">;</span>
</span><span class='line'>        <span class="k">case</span> <span class="nl">UIEventSubtypeRemoteControlStop:</span>
</span><span class='line'>            <span class="c1">//stop</span>
</span><span class='line'>            <span class="k">break</span><span class="p">;</span>
</span><span class='line'>        <span class="k">default</span><span class="o">:</span>
</span><span class='line'>            <span class="k">break</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>关于iOS 7之前的RemoteControl处理，git上有一个关于remotecontrol的小工程供大家参考<a href="https://github.com/MosheBerman/ios-audio-remote-control">ios-audio-remote-control</a></p>

<div class="github-card" data-github="MosheBerman/ios-audio-remote-control" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<hr />

<h1>后记</h1>

<p>到本篇为止iOS的音频播放话题基本上算是完结了。接下来我会在空余时间去研究一下iOS 8中新加入的<code>AVAudioEngine</code>，其功能涵盖播放、录音、混音、音效处理，看上去十分强大，从接口的定义上看像是对<code>AudioUnit</code>的高层封装，当研究有了一定的成果之后也会以博文的形式分享出来。</p>

<hr />

<h1>参考资料</h1>

<p><a href="https://encrypted.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;ved=0CC4QFjAA&amp;url=https%3A%2F%2Fdeveloper.apple.com%2FLibrary%2Fios%2Fdocumentation%2FMediaPlayer%2FReference%2FMPNowPlayingInfoCenter_Class%2Findex.html&amp;ei=8bBhVO_RF4HKmwXBiILIDA&amp;usg=AFQjCNFOziF2zKft-wGQ3ew_cHy7Ivxrvg">MPNowPlayingInfoCenter</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html">Remote Control Events</a></p>

<p><a href="https://developer.apple.com/library/IOs/documentation/General/Conceptual/Devpedia-CocoaApp/Responder.html">Cocoa Responder Chain</a></p>

<p><a href="https://github.com/MosheBerman/ios-audio-remote-control">ios-audio-remote-control</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (七)：播放iPod Library中的歌曲]]></title>
    <link href="http://msching.github.io/blog/2014/09/07/audio-in-ios-7/"/>
    <updated>2014-09-07T15:45:47+08:00</updated>
    <id>http://msching.github.io/blog/2014/09/07/audio-in-ios-7</id>
    <content type="html"><![CDATA[<p>由于最近工作量非常饱和，所以这第七篇来的有点晚（创建时间是9月7日。。说出来都是泪）。</p>

<p>现在市面上的音乐播放器都支持iPod Library歌曲（俗称iPod音乐或者本地音乐）的播放，用户对于iPod音乐播放的需求也一直十分强烈。这篇要讲的是如何来播放iPod Library的歌曲。</p>

<!--more-->


<hr />

<h1>概述</h1>

<p>根据<a href="https://developer.apple.com/library/ios/documentation/audiovideo/conceptual/multimediapg/usingaudio/usingaudio.html#//apple_ref/doc/uid/TP40009767-CH2-SW43">官方文档</a>描述Apple从iOS 3.0开始允许开发者访问用户的iPod library来获取用户放在其中的歌曲等多媒体内容。</p>

<p>为此Apple提供了多种方法来访问和播放iPod中的音乐，下面我们来分别列举一下这些方法。</p>

<hr />

<h1>访问MediaLibrary</h1>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/AboutiPodLibraryAccess/AboutiPodLibraryAccess.html#//apple_ref/doc/uid/TP40008765-CH103-SW9">官方文档</a>访问iPod Library的方法有两种，分别是MediaPicker和MediaQuery。</p>

<p><img src="http://msching.github.io/images/iOS-audio/iPodLibraryAccessOverview.jpg" alt="" /></p>

<h3>MediaPicker</h3>

<p>MediaPicker是一个高度封装的iPod Library访问方式，通过使用<code>MPMediaPickerController</code>类来访问iPod Library。这是一个UI控件，用户可以根据需要选择其中的音乐。这个类使用时非常方便，只需要生成一个&#8220;的实例，设置一下属性和delegate后present出来，接下来只要等待回调即可，在回调时需要手动dismiss picker。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">MPMediaPickerController</span> <span class="o">*</span><span class="n">picker</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MPMediaPickerController</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithMediaTypes:</span><span class="n">MPMediaTypeAnyAudio</span><span class="p">];</span>
</span><span class='line'><span class="n">picker</span><span class="p">.</span><span class="n">prompt</span> <span class="o">=</span> <span class="s">@&quot;请选择需要播放的歌曲&quot;</span><span class="p">;</span>
</span><span class='line'><span class="n">picker</span><span class="p">.</span><span class="n">showsCloudItems</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'><span class="n">picker</span><span class="p">.</span><span class="n">allowsPickingMultipleItems</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'><span class="n">picker</span><span class="p">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span><span class='line'><span class="p">[</span><span class="n">self</span> <span class="nl">presentViewController:</span><span class="n">picker</span> <span class="nl">animated:</span><span class="n">YES</span> <span class="nl">completion:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">mediaPickerDidCancel:</span><span class="p">(</span><span class="n">MPMediaPickerController</span> <span class="o">*</span><span class="p">)</span><span class="nv">mediaPicker</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">mediaPicker</span> <span class="nl">dismissViewControllerAnimated:</span><span class="n">YES</span> <span class="nl">completion:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">mediaPicker:</span><span class="p">(</span><span class="n">MPMediaPickerController</span> <span class="o">*</span><span class="p">)</span><span class="nv">mediaPicker</span> <span class="nf">didPickMediaItems:</span><span class="p">(</span><span class="n">MPMediaItemCollection</span> <span class="o">*</span><span class="p">)</span><span class="nv">mediaItemCollection</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">mediaPicker</span> <span class="nl">dismissViewControllerAnimated:</span><span class="n">YES</span> <span class="nl">completion:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>    <span class="c1">//do something</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>上面的代码将会得到如下的效果：</p>

<p><img src="http://msching.github.io/images/iOS-audio/MediaPicker.jpg" alt="" /></p>

<p>通过MediaPicker最终可以得到<code>MPMediaItemCollection</code>，其中存放着所有在Picker中选中的歌曲，每一个歌曲使用一个<code>MPMediaItem</code>对象表示。对于MediaPicker的使用也可以参考<a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/UsingtheMediaItemPicker/UsingtheMediaItemPicker.html#//apple_ref/doc/uid/TP40008765-CH104-SW1">官方文档</a>。</p>

<h3>MediaQuery</h3>

<p>如果你觉得MeidaPicker的功能或者UI不能满足你的要求那么可以使用MediaQuery。MediaQuery可以直接访问iPod Library的DB，并根据需要获取数据。<a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/UsingTheiPodLibrary/UsingTheiPodLibrary.html#//apple_ref/doc/uid/TP40008765-CH101-SW1">官方文档</a>给出了MediaQuery的示意图。</p>

<p><img src="http://msching.github.io/images/iOS-audio/database_access_classes.jpg" alt="" /></p>

<p>MediaQuery功能十分强大，它可以根据一个或多个条件查询满足需要的MediaItem。</p>

<p>你可以使用<code>MPMediaQuery</code>的类方法来生成一些已经预置了条件的Query</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// Base queries which can be used directly or as the basis for custom queries.</span>
</span><span class='line'><span class="c1">// The groupingType for these queries is preset to the appropriate type for the query.</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">albumsQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">artistsQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">songsQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">playlistsQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">podcastsQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">audiobooksQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">compilationsQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">composersQuery</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nf">genresQuery</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>也可以自己生成<code>MPMediaPredicate</code>设置条件，并把它加到Query中，最后通过items和collections访问查询到的结果，例如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">MPMediaPropertyPredicate</span> <span class="o">*</span><span class="n">artistNamePredicate</span> <span class="o">=</span>
</span><span class='line'><span class="p">[</span><span class="n">MPMediaPropertyPredicate</span> <span class="nl">predicateWithValue:</span><span class="s">@&quot;Happy the Clown&quot;</span>
</span><span class='line'>                                 <span class="nl">forProperty:</span><span class="n">MPMediaItemPropertyArtist</span>
</span><span class='line'>                              <span class="nl">comparisonType:</span><span class="n">MPMediaPredicateComparisonEqualTo</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="n">quert</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MPMediaQuery</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
</span><span class='line'><span class="p">[</span><span class="n">quert</span> <span class="nl">addFilterPredicate:</span> <span class="n">artistNamePredicate</span><span class="p">];</span>
</span><span class='line'><span class="n">quert</span><span class="p">.</span><span class="n">groupingType</span> <span class="o">=</span> <span class="n">MPMediaGroupingArtist</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="n">NSArray</span> <span class="o">*</span><span class="n">itemsFromArtistQuery</span> <span class="o">=</span> <span class="p">[</span><span class="n">quert</span> <span class="n">items</span><span class="p">];</span>
</span><span class='line'><span class="n">NSArray</span> <span class="o">*</span><span class="n">collectionsFromArtistQuery</span> <span class="o">=</span> <span class="p">[</span><span class="n">quert</span> <span class="n">collections</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>这一过程可以表示为（图来自<a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/AboutiPodLibraryAccess/AboutiPodLibraryAccess.html#//apple_ref/doc/uid/TP40008765-CH103-SW9">官方文档</a>）：</p>

<p><img src="http://msching.github.io/images/iOS-audio/mediaQuery.jpg" alt="" /></p>

<p>这里对于MediaQuery的用法就不再继续展开，关于这块内容并没有什么晦涩难懂的地方需要解释，大家可以通过阅读<a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/UsingTheiPodLibrary/UsingTheiPodLibrary.html#//apple_ref/doc/uid/TP40008765-CH101-SW1">官方文档</a>来详细了解其用法。</p>

<h3>MediaCollection</h3>

<p><code>MPMediaCollection</code>是MediaItem的合集，可以通过访问它的items属性来访问所有的MediaItem。</p>

<p><code>MPMediaPlaylist</code>是一个特殊的<code>MPMediaCollection</code>代表用户创建的播放列表，它会比MediaCollection包含更多的信息，比如播放列表的名字等等。这些属性可以通过<code>MPMediaEntity</code>的方法访问（MPMediaCollection是MPMediaEntity的子类，MPMediaItem也是）。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// Returns the value for the given entity property.</span>
</span><span class='line'><span class="c1">// MPMediaItem and MPMediaPlaylist have their own properties</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">valueForProperty:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">property</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Executes a provided block with the fetched values for the given item properties, or nil if no value is available for a property.</span>
</span><span class='line'><span class="c1">// In some cases, enumerating the values for multiple properties can be more efficient than fetching each individual property with -valueForProperty:.</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">enumerateValuesForProperties:</span><span class="p">(</span><span class="n">NSSet</span> <span class="o">*</span><span class="p">)</span><span class="nv">properties</span> <span class="nf">usingBlock:</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="p">)(</span><span class="n">NSString</span> <span class="o">*</span><span class="n">property</span><span class="p">,</span> <span class="kt">id</span> <span class="n">value</span><span class="p">,</span> <span class="kt">BOOL</span> <span class="o">*</span><span class="n">stop</span><span class="p">))</span><span class="nv">block</span> <span class="n">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">4</span><span class="n">_0</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<h3>MediaItem</h3>

<p>通过MediaPicker和MediaQuery最终都会得到<code>MPMediaItem</code>，这个item中包含了许多信息。这些信息都可以通过<code>MPMediaEntity</code>的方法访问，其中参数非常多就不列举了具体可以参照MPMediaItem.h。</p>

<hr />

<h1>使用MPMusicPlayerController</h1>

<p>拿到iPod Library中的歌曲后就可以开始播放了。播放的方式有很多种，先介绍一下<code>MediaPlayer framework</code>中的<code>MPMusicPlayerController</code>类。</p>

<p>通过<code>MPMusicPlayerController</code>的类方法可以生成两种播放器，生成方法如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// Playing media items with the applicationMusicPlayer will restore the user&#39;s iPod state after the application quits.</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMusicPlayerController</span> <span class="o">*</span><span class="p">)</span><span class="nf">applicationMusicPlayer</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Playing media items with the iPodMusicPlayer will replace the user&#39;s current iPod state.</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">MPMusicPlayerController</span> <span class="o">*</span><span class="p">)</span><span class="nf">iPodMusicPlayer</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>这两个方法看似生成了一样的对象，但它们的行为却有很大不同。从Apple写的注释上我们可以很清楚的发现它们的区别。<code>+applicationMusicPlayer</code>不会继承来自iOS系统自带的iPod应用中的播放状态，同时也不会覆盖iPod的播放状态。而<code>+iPodMusicPlayer</code>完全继承iPod应用的播放状态（甚至是播放时间），对其实例的任何操作也会覆盖到iPod应用。对<code>+iPodMusicPlayer</code>方法command+点击后可以看到更详细的注释。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">The</span> <span class="n">iPod</span> <span class="n">music</span> <span class="n">player</span> <span class="n">employs</span> <span class="n">the</span> <span class="n">iPod</span> <span class="n">app</span> <span class="n">on</span> <span class="n">your</span> <span class="n">behalf</span><span class="p">.</span> <span class="n">On</span> <span class="n">instantiation</span><span class="p">,</span> <span class="n">it</span> <span class="n">takes</span> <span class="n">on</span> <span class="n">the</span> <span class="n">current</span> <span class="n">iPod</span> <span class="n">app</span> <span class="n">state</span> <span class="n">and</span> <span class="n">controls</span> <span class="n">that</span> <span class="n">state</span> <span class="n">as</span> <span class="n">your</span> <span class="n">app</span> <span class="n">runs</span><span class="p">.</span> <span class="n">Specifically</span><span class="p">,</span> <span class="n">the</span> <span class="n">shared</span> <span class="n">state</span> <span class="n">includes</span> <span class="n">the</span> <span class="nl">following:</span>
</span><span class='line'><span class="n">Repeat</span> <span class="n">mode</span> <span class="p">(</span><span class="n">see</span> <span class="err">“</span><span class="n">Repeat</span> <span class="n">Modes</span><span class="err">”</span><span class="p">)</span>
</span><span class='line'><span class="n">Shuffle</span> <span class="n">mode</span> <span class="p">(</span><span class="n">see</span> <span class="err">“</span><span class="n">Shuffle</span> <span class="n">Modes</span><span class="err">”</span>
</span><span class='line'><span class="n">Now</span><span class="o">-</span><span class="n">playing</span> <span class="n">item</span> <span class="p">(</span><span class="n">see</span> <span class="n">nowPlayingItem</span><span class="p">)</span>
</span><span class='line'><span class="n">Playback</span> <span class="n">state</span> <span class="p">(</span><span class="n">see</span> <span class="n">playbackState</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="n">Other</span> <span class="n">aspects</span> <span class="n">of</span> <span class="n">iPod</span> <span class="n">state</span><span class="p">,</span> <span class="n">such</span> <span class="n">as</span> <span class="n">the</span> <span class="n">on</span><span class="o">-</span><span class="n">the</span><span class="o">-</span><span class="n">go</span> <span class="n">playlist</span><span class="p">,</span> <span class="n">are</span> <span class="n">not</span> <span class="n">shared</span><span class="p">.</span> <span class="n">Music</span> <span class="n">that</span> <span class="n">is</span> <span class="n">playing</span> <span class="n">continues</span> <span class="n">to</span> <span class="n">play</span> <span class="n">when</span> <span class="n">your</span> <span class="n">app</span> <span class="n">moves</span> <span class="n">to</span> <span class="n">the</span> <span class="n">background</span><span class="p">.</span>
</span></code></pre></td></tr></table></div></figure>


<p>说白了，当在使用iPodMusicPlayerv其实并不是你的程序在播放音频，而是你的程序在操纵iPod应用播放音频，即使你的程序crash了或者被kill了，音乐也不会因此停止。</p>

<p>而对于<code>+applicationMusicPlayer</code>通过command+点击可以看到：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">The</span> <span class="n">application</span> <span class="n">music</span> <span class="n">player</span> <span class="n">plays</span> <span class="n">music</span> <span class="n">locally</span> <span class="n">within</span> <span class="n">your</span> <span class="n">app</span><span class="p">.</span> <span class="n">It</span> <span class="n">does</span> <span class="n">not</span> <span class="n">affect</span> <span class="n">the</span> <span class="n">iPod</span> <span class="n">state</span><span class="p">.</span>
</span><span class='line'><span class="n">When</span> <span class="n">your</span> <span class="n">app</span> <span class="n">moves</span> <span class="n">to</span> <span class="n">the</span> <span class="n">background</span><span class="p">,</span> <span class="n">the</span> <span class="n">music</span> <span class="n">player</span> <span class="n">stops</span> <span class="k">if</span> <span class="n">it</span> <span class="n">was</span> <span class="n">playing</span><span class="p">.</span>
</span></code></pre></td></tr></table></div></figure>


<p>从注释中可以知道这个方法返回的对象虽然不是调用iPod应用播放的也不会影响到iPod应用，但它有个很大的缺点：无法后台播放，即使你在active了audioSession并且在app的设置中设置了Background Audio同样不会奏效。</p>

<p>综上所述，一般在开发音乐软件时很少用到这两个接口来进行iPod Library的播放，大部分开发者都是用这个类中的volme来调整系统音量的（这个属性在SDK 7中也被deprecate掉了）。如果你想用到这个类进行播放的话，这里需要提个醒，给<code>MPMusicPlayerController</code>设置需要播放的音乐时要使用下面两个方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// Call -play to begin playback after setting an item queue source. Setting a query will implicitly use MPMediaGroupingTitle.</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setQueueWithQuery:</span><span class="p">(</span><span class="n">MPMediaQuery</span> <span class="o">*</span><span class="p">)</span><span class="nv">query</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setQueueWithItemCollection:</span><span class="p">(</span><span class="n">MPMediaItemCollection</span> <span class="o">*</span><span class="p">)</span><span class="nv">itemCollection</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>而不是这个属性：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">// Returns the currently playing media item, or nil if none is playing.</span>
</span><span class='line'><span class="c1">// Setting the nowPlayingItem to an item in the current queue will begin playback at that item.</span>
</span><span class='line'><span class="k">@property</span><span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">copy</span><span class="p">)</span> <span class="n">MPMediaItem</span> <span class="o">*</span><span class="n">nowPlayingItem</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>光看名字很容易被<code>nowPlayingItem</code>这个属性迷惑，它的意思其实是说在设置了MediaQuery或者MediaCollection之后再设置这个nowPlayingItem可以让播放器从这个item开始播放，前提是这个item需要在MediaQuery或者MediaCollection的.items集合内。</p>

<hr />

<h1>使用AVAudioPlayer和AVPlayer</h1>

<p>除了使用MediaPlayer中的类还有很多其他方法来进行iPod播放，其中做的比较出色的是<code>AVFoundation</code>中的<code>AVAudioPlayer</code>和<code>AVPlayer</code>。</p>

<p>这两个类的都有通过NSURL生成实例的初始化方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AVAudioPlayer</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithContentsOfURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">url</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//AVPlayer</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">playerWithURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">URL</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">URL</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>其中的NSURL正是来自于<code>MPMediaItem</code>的<code>MPMediaItemPropertyAssetURL</code>属性。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//A URL pointing to the media item,</span>
</span><span class='line'><span class="c1">//from which an AVAsset object (or other URL-based AV Foundation object) can be created, with any options as desired. </span>
</span><span class='line'><span class="c1">//Value is an NSURL object.</span>
</span><span class='line'><span class="n">MP_EXTERN</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">MPMediaItemPropertyAssetURL</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>上面讲到<code>MPMediaItem</code>时已经提到了它是<code>MPMediaEntity</code>子类，可以通过<code>-valueForProperty:</code>方法访问其中的属性。通过传入<code>MPMediaItemPropertyAssetURL</code>就可以得到当前MediaItem对应的URL（ipod-library://xxxxx），生成Player进行播放。大致代码如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">@interface</span> <span class="nc">MyClass</span> : <span class="nc">NSObject</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">AVAudioPlayer</span> <span class="o">*</span><span class="n">_player</span><span class="p">;</span>
</span><span class='line'>  <span class="c1">//AVPlayer *_player;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//设置AudioSession</span>
</span><span class='line'><span class="p">[[</span><span class="n">AVAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setActive:</span><span class="n">YES</span> <span class="nl">error:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'><span class="p">[[</span><span class="n">AVAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setCategory:</span><span class="n">AVAudioSessionCategoryPlayback</span> <span class="nl">error:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//play</span>
</span><span class='line'><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'><span class="n">MPMediaItem</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="p">...;</span>
</span><span class='line'><span class="n">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="n">item</span> <span class="nl">valueForProperty:</span><span class="n">MPMediaItemPropertyAssetURL</span><span class="p">];</span>
</span><span class='line'><span class="n">_player</span> <span class="o">=</span> <span class="p">[[</span><span class="n">AVAudioPlayer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithContentsOfURL:</span><span class="n">url</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span><span class='line'><span class="c1">//_player = [AVPlayer playerWithURL:url];</span>
</span><span class='line'><span class="p">[</span><span class="n">_player</span> <span class="n">play</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>注意：这里我需要更正一下，之前我在<a href="http://msching.github.io/blog/2014/07/08/audio-in-ios-2/">第二篇</a>讲到AudioSession时写了这样一段话<code>在使用AVAudioPlayer/AVPlayer时可以不用关心AudioSession的相关问题，Apple已经把AudioSession的处理过程封装了...</code>。这段话不对，我把AVFoundation和Mediaplayer混淆了，在写的时候也没注意，应该是在使用MPMusicPlayerController播放时不需要关心AudioSession的问题。</strong></p>

<hr />

<h1>读取和导出数据</h1>

<p>前面说到使用<code>MPMediaItem</code>的<code>MPMediaItemPropertyAssetURL</code>属性可以得到一个表示当前MediaItem的NSURL，有了这个NSURL我们使用AVFoundation中的类进行播放。播放只是最基本的需求，有了这个URL我们可以做更多更有趣的事情。</p>

<p>在AVFoundation中还有两个有趣的类：<code>AVAssetReader</code>和<code>AVAssetExportSession</code>。它们可以把iPod Library中的指定歌曲以指定的音频格式导出到内存中或者硬盘中，这个指定的格式包括PCM。这是一个激动人心的特性，有了PCM数据我们就可以做很多很多其他的事情了。</p>

<p>这部分如果要展开的话还会有相当多的内容，国外的先辈们早在2010年就已经发掘了这两个类的用法，详细参见<a href="http://www.subfurther.com/blog/2010/07/19/from-iphone-media-library-to-pcm-samples-in-dozens-of-confounding-potentially-lossy-steps/">这里</a>和<a href="http://www.subfurther.com/blog/2010/12/13/from-ipod-library-to-pcm-samples-in-far-fewer-steps-than-were-previously-necessary/">这里</a>。这两篇讲的比较详细并且附有Sample（其中还涉及了一些Extended Audio File Services的内容），如果里面Sample无法下载可以从点击<a href="http://msching.github.io/files/MediaLibraryExportThrowaway1.zip">MediaLibraryExportThrowaway1.zip</a>和<a href="http://msching.github.io/files/VTM_AViPodReader.zip">VTM_AViPodReader.zip</a>下载。</p>

<p><strong>需要注意的是在使用<code>AVAssetReader</code>的过程中如果访问系统的相机或者照片可能会使<code>AVAssetReader</code>产生<code>AVErrorOperationInterrupted</code>错误，此时需要重新生成Reader后调用<code>-startReading</code>才可以继续读取数据。</strong></p>

<hr />

<h1>小结</h1>

<p>本篇介绍了一些与iPod Library相关的内容，小结一下：</p>

<ul>
<li><p>Apple提供两种方法来访问iPod Library，它们分别是<code>MPMediaPickerController</code>和<code>MPMediaQuery</code>；</p></li>
<li><p><code>MPMediaPickerController</code>和<code>MPMediaQuery</code>最后输出给开发者的对象是<code>MPMediaItem</code>，<code>MPMediaItem</code>的属性需要通过<code>-valueForProperty:</code>方法获取了；</p></li>
<li><p><code>MPMusicPlayerController</code>可以用来播放<code>MPMediaItem</code>，但有很多局限性，使用时需要根据不同的使用场景来决定用哪个类方法生成实例；</p></li>
<li><p><code>AVAudioPlayer</code>和<code>AVPlayer</code>也可以用来播放<code>MPMediaItem</code>，这两个类的功能比较完善，推荐使用，在使用之前别忘记设置AudioSession；</p></li>
<li><p><code>MPMediaItem</code>可以得到对应的URL，这个URL可以用来做很多事情，例如用<code>AVAssetReader</code>和<code>AVAssetExportSession</code>可以导出其中的数据；</p></li>
</ul>


<hr />

<h1>下篇预告</h1>

<p>下一篇会讲一些关于NowPlayingCenter和RemoteControl的内容（就是在锁屏界面和ControlCenter中显示的歌曲信息以及上面的那些播放控制按钮）。</p>

<hr />

<h1>参考资料</h1>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008765">iPod Library Access Programming Guide</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/AboutiPodLibraryAccess/AboutiPodLibraryAccess.html#//apple_ref/doc/uid/TP40008765-CH103-SW9">About iPod Library Access</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/UsingtheMediaItemPicker/UsingtheMediaItemPicker.html#//apple_ref/doc/uid/TP40008765-CH104-SW1">Using the Media Item Picker</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/UsingTheiPodLibrary/UsingTheiPodLibrary.html#//apple_ref/doc/uid/TP40008765-CH101-SW1">Using the iPod Library</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/iPodLibraryAccess_Guide/UsingMediaPlayback/UsingMediaPlayback.html#//apple_ref/doc/uid/TP40008765-CH100-SW1">Using Media Playback</a></p>

<p><a href="http://www.subfurther.com/blog/2010/07/19/from-iphone-media-library-to-pcm-samples-in-dozens-of-confounding-potentially-lossy-steps/">From iPhone Media Library to PCM Samples in Dozens of Confounding, Potentially Lossy Steps</a></p>

<p><a href="http://www.subfurther.com/blog/2010/12/13/from-ipod-library-to-pcm-samples-in-far-fewer-steps-than-were-previously-necessary/">From iPod Library to PCM Samples in Far Fewer Steps Than Were Previously Necessary</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (六)：简单的音频播放器实现]]></title>
    <link href="http://msching.github.io/blog/2014/08/09/audio-in-ios-6/"/>
    <updated>2014-08-09T15:55:22+08:00</updated>
    <id>http://msching.github.io/blog/2014/08/09/audio-in-ios-6</id>
    <content type="html"><![CDATA[<p>在前几篇中我分别讲到了<code>AudioSession</code>、<code>AudioFileStream</code>、<code>AudioFile</code>、<code>AudioQueue</code>，这些类的功能已经涵盖了<a href="http://msching.github.io/blog/2014/07/07/audio-in-ios/">第一篇</a>中所提到的音频播放所需要的步骤：</p>

<ol>
<li>读取MP3文件   <code>NSFileHandle</code></li>
<li>解析采样率、码率、时长等信息，分离MP3中的音频帧  <code>AudioFileStream</code>/<code>AudioFile</code></li>
<li>对分离出来的音频帧解码得到PCM数据  <code>AudioQueue</code></li>
<li><del>对PCM数据进行音效处理（均衡器、混响器等，非必须）</del>  <code>省略</code></li>
<li>把PCM数据解码成音频信号  <code>AudioQueue</code></li>
<li>把音频信号交给硬件播放    <code>AudioQueue</code></li>
<li>重复1-6步直到播放完成</li>
</ol>


<p>下面我们就讲讲述如何用这些部件组成一个简单的<code>本地音乐播放器</code>，这里我会用到<strong>AudioSession</strong>、<strong>AudioFileStream</strong>、<strong>AudioFile</strong>、<strong>AudioQueue</strong>。</p>

<p><strong>注意：在阅读本篇请实现阅读并理解前面1-5篇的内容以及2-5篇最后给出的封装类，本篇中的播放器实现将基于前面几篇中给出的<a href="https://github.com/msching/MCAudioSession">MCAudioSession</a>、<a href="https://github.com/msching/MCAudioFileStream">MCAudioFileStream</a>、<a href="https://github.com/msching/MCAudioFile">MCAudioFile</a>和<a href="https://github.com/msching/MCSimpleAudioPlayer/blob/master/MCSimpleAudioPlayerDemo/MCSimpleAudioPlayer/MCAudioOutputQueue.h">MCAudioOutputQueue</a>进行实现。</strong></p>

<!--more-->


<hr />

<h1>AudioFileStream vs AudioFile</h1>

<p>解释一下为什么我要同时使用<strong>AudioFileStream</strong>和<strong>AudioFile</strong>。</p>

<p>第一，<code>对于网络流播必须有AudioFileStream的支持</code>，这是因为我们在<a href="http://msching.github.io/blog/2014/07/19/audio-in-ios-4/">第四篇</a>中提到过<strong>AudioFile</strong>在Open时会要求使用者提供数据，如果提供的数据不足会直接跳过并且返回错误码，而数据不足的情况在网络流中很常见，故无法使用<strong>AudioFile</strong>单独进行网络流数据的解析；</p>

<p>第二，<code>对于本地音乐播放选用AudioFile更为合适</code>，原因如下：</p>

<ol>
<li><strong>AudioFileStream</strong>的主要是用在流播放中虽然不限于网络流和本地流，但流数据是按顺序提供的所以<strong>AudioFileStream</strong>也是顺序解析的，被解析的音频文件还是需要符合流播放的特性，对于不符合的本地文件<strong>AudioFileStream</strong>会在Parse时返回<code>NotOptimized</code>错误；</li>
<li><strong>AudioFile</strong>的解析过程并不是顺序的，它会在解析时通过回调向使用者索要某个位置的数据，即使数据在文件末尾也不要紧，所以<strong>AudioFile</strong>适用于所有类型的音频文件；</li>
</ol>


<p>基于以上两点我们可以得出这样一个结论：<code>一款完整功能的播放器应当同时使用AudioFileStream和AudioFile</code>，用<strong>AudioFileStream</strong>来应对可以进行流播放的音频数据，以达到边播放边缓冲的最佳体验，用<strong>AudioFile</strong>来处理无法流播放的音频数据，让用户在下载完成之后仍然能够进行播放。</p>

<p>本来这个Demo应该做成基于网络流的音频播放，但由于最近比较忙一直过着公司和床两点一线的生活，来不及写网络流和文件缓存的模块，所以就用本地文件代替了，所以最终在Demo会先尝试用<strong>AudioFileStream</strong>解析数据，如果失败再尝试使用<strong>AudioFile</strong>以达到模拟网络流播放的效果。</p>

<hr />

<h1>准备工作</h1>

<p>第一件事当然是要创建一个新工程，这里我选择了的模板是SingleView，工程名我把它命名为<code>MCSimpleAudioPlayerDemo</code>：</p>

<p><img src="http://msching.github.io/images/iOS-audio/createproject.jpg" alt="" /></p>

<p>创建完工程之后去到Target属性的<code>Capabilities</code>选项卡设置<code>Background Modes</code>，把<code>Audio and Airplay</code>勾选，这样我们的App就可以在进入后台之后继续播放音乐了：</p>

<p><img src="http://msching.github.io/images/iOS-audio/setBackgroundPlayback.jpg" alt="" /></p>

<p>接下来我们需要搭建一个简单的UI，在storyboard上创建两个UIButton和一个UISlider，Button用来做播放器的播放、暂停、停止等功能控制，Slider用来显示播放进度和seek。把这些UI组件和ViewController的属性/方法关联上之后简单的UI也就完成了。</p>

<p><img src="http://msching.github.io/images/iOS-audio/simpleUI.jpg" alt="" /></p>

<hr />

<h1>接口定义</h1>

<p>下面来创建播放器类<code>MCSimpleAudioPlayer</code>，首先是初始化方法（感谢<a href="http://weibo.com/onevcat?topnav=1&amp;wvr=5&amp;topsug=1">@喵神</a>的<a href="https://github.com/onevcat/VVDocumenter-Xcode">VVDocumenter</a>）：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cm">/**</span>
</span><span class='line'><span class="cm"> *  初始化方法</span>
</span><span class='line'><span class="cm"> *</span>
</span><span class='line'><span class="cm"> *  @param filePath 文件绝对路径</span>
</span><span class='line'><span class="cm"> *  @param fileType 文件类型，作为后续创建AudioFileStream和AudioQueue的Hint使用</span>
</span><span class='line'><span class="cm"> *</span>
</span><span class='line'><span class="cm"> *  @return player对象</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">instancetype</span><span class="p">)</span><span class="nf">initWithFilePath:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">filePath</span> <span class="nf">fileType:</span><span class="p">(</span><span class="n">AudioFileTypeID</span><span class="p">)</span><span class="nv">fileType</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>另外播放器作为一个典型的状态机，各种状态也是必不可少的，这里我只简单的定义了四种状态：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">typedef</span> <span class="nf">NS_ENUM</span><span class="p">(</span><span class="n">NSUInteger</span><span class="p">,</span> <span class="n">MCSAPStatus</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">MCSAPStatusStopped</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>    <span class="n">MCSAPStatusPlaying</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
</span><span class='line'>    <span class="n">MCSAPStatusWaiting</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span><span class='line'>    <span class="n">MCSAPStatusPaused</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>再加上一些必不可少的属性和方法组成了<code>MCSimpleAudioPlayer.h</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">@interface</span> <span class="nc">MCSimpleAudioPlayer</span> : <span class="nc">NSObject</span>
</span><span class='line'>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">copy</span><span class="p">,</span><span class="n">readonly</span><span class="p">)</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">filePath</span><span class="p">;</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">assign</span><span class="p">,</span><span class="n">readonly</span><span class="p">)</span> <span class="n">AudioFileTypeID</span> <span class="n">fileType</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">readonly</span><span class="p">)</span> <span class="n">MCSAPStatus</span> <span class="n">status</span><span class="p">;</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">readonly</span><span class="p">)</span> <span class="kt">BOOL</span> <span class="n">isPlayingOrWaiting</span><span class="p">;</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">assign</span><span class="p">,</span><span class="n">readonly</span><span class="p">)</span> <span class="kt">BOOL</span> <span class="n">failed</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">assign</span><span class="p">)</span> <span class="n">NSTimeInterval</span> <span class="n">progress</span><span class="p">;</span>
</span><span class='line'><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span><span class="n">readonly</span><span class="p">)</span> <span class="n">NSTimeInterval</span> <span class="n">duration</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">instancetype</span><span class="p">)</span><span class="nf">initWithFilePath:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">filePath</span> <span class="nf">fileType:</span><span class="p">(</span><span class="n">AudioFileTypeID</span><span class="p">)</span><span class="nv">fileType</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">play</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">pause</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">stop</span><span class="p">;</span>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>初始化</h1>

<p>在init方法中创建一个NSFileHandle的实例以用来读取数据并交给AudioFileStream解析，另外也可以根据生成的实例是否是nil来判断是否能够读取文件，如果返回的是nil就说明文件不存在或者没有权限那么播放也就无从谈起了。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">_fileHandler</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSFileHandle</span> <span class="nl">fileHandleForReadingAtPath:</span><span class="n">_filePath</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>通过NSFileManager获取文件大小</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">_fileSize</span> <span class="o">=</span> <span class="p">[[[</span><span class="n">NSFileManager</span> <span class="n">defaultManager</span><span class="p">]</span> <span class="nl">attributesOfItemAtPath:</span><span class="n">_filePath</span> <span class="nl">error:</span><span class="nb">nil</span><span class="p">]</span> <span class="n">fileSize</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>初始化方法到这里就结束了，作为一个播放器我们自然不能在主线程进行播放，我们需要创建自己的播放线程。</p>

<p>创建一个成员变量<code>_started</code>来表示播放流程是否已经开始，在<code>-play</code>方法中如果<code>_started</code>为NO就创建线程<code>_thread</code>并以<code>-threadMain</code>方法作为main，否则说明线程已经创建并且在播放流程中：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">play</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_started</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_started</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>        <span class="n">_thread</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSThread</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithTarget:</span><span class="n">self</span> <span class="nl">selector:</span><span class="k">@selector</span><span class="p">(</span><span class="n">threadMain</span><span class="p">)</span> <span class="nl">object:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_thread</span> <span class="n">start</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="c1">//如果是Pause状态就resume</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>接下来就可以在<code>-threadMain</code>进行音频播放相关的操作了。</p>

<hr />

<h1>创建AudioSession</h1>

<p>iOS音频播放的第一步，自然是要创建<code>AudioSession</code>，这里引入<a href="http://msching.github.io/blog/2014/07/08/audio-in-ios-2/">第二篇</a>末尾给出的AudioSession封装<a href="https://github.com/msching/MCAudioSession">MCAudioSession</a>，当然各位也可以使用<code>AVAudioSession</code>。</p>

<p>初始化的工作会在调用单例方法时进行，下一步是设置Category。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//初始化并且设置Category</span>
</span><span class='line'><span class="p">[[</span><span class="n">MCAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setCategory:</span><span class="n">kAudioSessionCategory_MediaPlayback</span> <span class="nl">error:</span><span class="nb">NULL</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>成功之后启用AudioSession，还有别忘了监听Interrupt通知。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">if</span> <span class="p">([[</span><span class="n">MCAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setCategory:</span><span class="n">kAudioSessionCategory_MediaPlayback</span> <span class="nl">error:</span><span class="nb">NULL</span><span class="p">])</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//active audiosession</span>
</span><span class='line'>    <span class="p">[[</span><span class="n">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="p">]</span> <span class="nl">addObserver:</span><span class="n">self</span> <span class="nl">selector:</span><span class="k">@selector</span><span class="p">(</span><span class="nl">interruptHandler:</span><span class="p">)</span> <span class="nl">name:</span><span class="n">MCAudioSessionInterruptionNotification</span> <span class="nl">object:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">([[</span><span class="n">MCAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setActive:</span><span class="n">YES</span> <span class="nl">error:</span><span class="nb">NULL</span><span class="p">])</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="c1">//go on</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>读取、解析音频数据</h1>

<p>成功创建并启用AudioSession之后就可以进入播放流程了，播放是一个无限循环的过程，所以我们需要一个while循环，在文件没有被播放完成之前需要反复的读取、解析、播放。那么第一步是需要读取并解析数据。按照之前说的我们会先使用<code>AudioFileStream</code>，引入<a href="http://msching.github.io/blog/2014/07/09/audio-in-ios-3/">第三篇</a>末尾给出的AudioFileStream封装<a href="https://github.com/msching/MCAudioFileStream">MCAudioFileStream</a>。</p>

<p>创建AudioFileStream，<strong>MCAudioFileStream</strong>的init方法会完成这项工作，如果创建成功就设置delegate作为Parse数据的回调。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">_audioFileStream</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MCAudioFileStream</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithFileType:</span><span class="n">_fileType</span> <span class="nl">fileSize:</span><span class="n">_fileSize</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">error</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">_audioFileStream</span><span class="p">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>接下来要读取数据并且解析，用成员变量<code>_offset</code>表示<code>_fileHandler</code>已经读取文件位置，其主要作用是来判断Eof。调用<strong>MCAudioFileStream</strong>的<code>-parseData:error:</code>方法来对数据进行解析。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">_fileHandler</span> <span class="nl">readDataOfLength:</span><span class="mi">1000</span><span class="p">];</span>
</span><span class='line'><span class="n">_offset</span> <span class="o">+=</span> <span class="p">[</span><span class="n">data</span> <span class="n">length</span><span class="p">];</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">_offset</span> <span class="o">&gt;=</span> <span class="n">_fileSize</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">isEof</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="p">[</span><span class="n">_audioFileStream</span> <span class="nl">parseData:</span><span class="n">data</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//解析失败，换用AudioFile</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>解析完文件头之后<strong>MCAudioFileStream</strong>的<code>readyToProducePackets</code>属性会被置为YES，此后所有的Parse方法都回触发<code>-audioFileStream:audioDataParsed:</code>方法并传递<code>MCParsedAudioData</code>的数组来保存解析完成的数据。这样就需要一个buffer来存储这些解析完成的音频数据。</p>

<p>于是创建了<code>MCAudioBuffer</code>类来管理所有解析完成的数据：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">@interface</span> <span class="nc">MCAudioBuffer</span> : <span class="nc">NSObject</span>
</span><span class='line'>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="n">instancetype</span><span class="p">)</span><span class="nf">buffer</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">enqueueData:</span><span class="p">(</span><span class="n">MCParsedAudioData</span> <span class="o">*</span><span class="p">)</span><span class="nv">data</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">enqueueFromDataArray:</span><span class="p">(</span><span class="n">NSArray</span> <span class="o">*</span><span class="p">)</span><span class="nv">dataArray</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">hasData</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">UInt32</span><span class="p">)</span><span class="nf">bufferedSize</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">NSData</span> <span class="o">*</span><span class="p">)</span><span class="nf">dequeueDataWithSize:</span><span class="p">(</span><span class="n">UInt32</span><span class="p">)</span><span class="nv">requestSize</span>
</span><span class='line'>                    <span class="nf">packetCount:</span><span class="p">(</span><span class="n">UInt32</span> <span class="o">*</span><span class="p">)</span><span class="nv">packetCount</span>
</span><span class='line'>                   <span class="nf">descriptions:</span><span class="p">(</span><span class="n">AudioStreamPacketDescription</span> <span class="o">**</span><span class="p">)</span><span class="nv">descriptions</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">clean</span><span class="p">;</span>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>创建一个<strong>MCAudioBuffer</strong>的实例<code>_buffer</code>，解析完成的数据都会通过<code>enqueue</code>方法存储到<strong>_buffer</strong>中，在需要的使用可以通过<code>dequeue</code>取出来使用。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">_buffer</span> <span class="o">=</span> <span class="p">[</span><span class="n">MCAudioBuffer</span> <span class="n">buffer</span><span class="p">];</span> <span class="c1">//初始化方法中创建</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//AudioFileStream解析完成的数据都被存储到了_buffer中</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">audioFileStream:</span><span class="p">(</span><span class="n">MCAudioFileStream</span> <span class="o">*</span><span class="p">)</span><span class="nv">audioFileStream</span> <span class="nf">audioDataParsed:</span><span class="p">(</span><span class="n">NSArray</span> <span class="o">*</span><span class="p">)</span><span class="nv">audioData</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_buffer</span> <span class="nl">enqueueFromDataArray:</span><span class="n">audioData</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果遇到<strong>AudioFileStream</strong>解析失败的话，转而使用<strong>AudioFile</strong>，引入<a href="http://msching.github.io/blog/2014/07/19/audio-in-ios-4/">第四篇</a>末尾给出的AudioFile封装<a href="https://github.com/msching/MCAudioFile">MCAudioFile</a>（之前没有给出，最近补上的）。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">_audioFileStream</span> <span class="nl">parseData:</span><span class="n">data</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//解析失败，换用AudioFile</span>
</span><span class='line'>    <span class="n">_usingAudioFile</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>    <span class="k">continue</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">_usingAudioFile</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_audioFile</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_audioFile</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MCAudioFile</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithFilePath:</span><span class="n">_filePath</span> <span class="nl">fileType:</span><span class="n">_fileType</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">([</span><span class="n">_buffer</span> <span class="n">bufferedSize</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">_bufferSize</span> <span class="o">||</span> <span class="o">!</span><span class="n">_audioQueue</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="c1">//AudioFile解析完成的数据都被存储到了_buffer中</span>
</span><span class='line'>        <span class="n">NSArray</span> <span class="o">*</span><span class="n">parsedData</span> <span class="o">=</span> <span class="p">[</span><span class="n">_audioFile</span> <span class="nl">parseData:</span><span class="o">&amp;</span><span class="n">isEof</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_buffer</span> <span class="nl">enqueueFromDataArray:</span><span class="n">parsedData</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用<strong>AudioFile</strong>时同样需要<strong>NSFileHandle</strong>来读取文件数据，但由于其回获取数据的特性我把FileHandle的相关操作都封装进去了，所以使用<strong>MCAudioFile</strong>解析数据时直接调用Parse方法即可。</p>

<hr />

<h1>播放</h1>

<p>有了解析完成的数据，接下来就该AudioQueue出场了，引入<a href="http://msching.github.io/blog/2014/08/02/audio-in-ios-5/">第五篇</a>末尾提到的AudioQueue的封装<a href="https://github.com/msching/MCSimpleAudioPlayer/blob/master/MCSimpleAudioPlayerDemo/MCSimpleAudioPlayer/MCAudioOutputQueue.h">MCAudioOutputQueue</a>。</p>

<p>首先创建AudioQueue，由于AudioQueue需要实现创建重用buffer所以需要事先确定bufferSize，这里我设置的bufferSize是近似0.1秒的数据量，计算bufferSize需要用到的duration和audioDataByteCount可以从<strong>MCAudioFileStream</strong>或者<strong>MCAudioFile</strong>中获取。有了bufferSize之后，加上数据格式format参数和magicCookie（部分音频格式需要）就可以生成AudioQueue了。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">createAudioQueue</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_audioQueue</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">NSTimeInterval</span> <span class="n">duration</span> <span class="o">=</span> <span class="n">_usingAudioFile</span> <span class="o">?</span> <span class="n">_audioFile</span><span class="p">.</span><span class="n">duration</span> <span class="o">:</span> <span class="n">_audioFileStream</span><span class="p">.</span><span class="n">duration</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt64</span> <span class="n">audioDataByteCount</span> <span class="o">=</span> <span class="n">_usingAudioFile</span> <span class="o">?</span> <span class="n">_audioFile</span><span class="p">.</span><span class="n">audioDataByteCount</span> <span class="o">:</span> <span class="n">_audioFileStream</span><span class="p">.</span><span class="n">audioDataByteCount</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_bufferSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">duration</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_bufferSize</span> <span class="o">=</span> <span class="p">(</span><span class="mf">0.1</span> <span class="o">/</span> <span class="n">duration</span><span class="p">)</span> <span class="o">*</span> <span class="n">audioDataByteCount</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_bufferSize</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">AudioStreamBasicDescription</span> <span class="n">format</span> <span class="o">=</span> <span class="n">_usingAudioFile</span> <span class="o">?</span> <span class="n">_audioFile</span><span class="p">.</span><span class="n">format</span> <span class="o">:</span> <span class="n">_audioFileStream</span><span class="p">.</span><span class="n">format</span><span class="p">;</span>
</span><span class='line'>        <span class="n">NSData</span> <span class="o">*</span><span class="n">magicCookie</span> <span class="o">=</span> <span class="n">_usingAudioFile</span> <span class="o">?</span> <span class="p">[</span><span class="n">_audioFile</span> <span class="n">fetchMagicCookie</span><span class="p">]</span> <span class="o">:</span> <span class="p">[</span><span class="n">_audioFileStream</span> <span class="n">fetchMagicCookie</span><span class="p">];</span>
</span><span class='line'>        <span class="n">_audioQueue</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MCAudioOutputQueue</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithFormat:</span><span class="n">format</span> <span class="nl">bufferSize:</span><span class="n">_bufferSize</span> <span class="nl">macgicCookie:</span><span class="n">magicCookie</span><span class="p">];</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_audioQueue</span><span class="p">.</span><span class="n">available</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">_audioQueue</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'>            <span class="k">return</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>接下来从<strong>_buffer</strong>中读出解析完成的数据，交给AudioQueue播放。如果全部播放完毕了就调用一下<code>-flush</code>让AudioQueue把剩余数据播放完毕。这里需要注意的是<strong>MCAudioOutputQueue</strong>的<code>-playData</code>方法在调用时如果没有可以重用的buffer的话会阻塞当前线程直到<code>AudioQueue</code>回调方法送出可重用的buffer为止。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">UInt32</span> <span class="n">packetCount</span><span class="p">;</span>
</span><span class='line'><span class="n">AudioStreamPacketDescription</span> <span class="o">*</span><span class="n">desces</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span><span class='line'><span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">_buffer</span> <span class="nl">dequeueDataWithSize:</span><span class="n">_bufferSize</span> <span class="nl">packetCount:</span><span class="o">&amp;</span><span class="n">packetCount</span> <span class="nl">descriptions:</span><span class="o">&amp;</span><span class="n">desces</span><span class="p">];</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">packetCount</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioQueue</span> <span class="nl">playData:</span><span class="n">data</span> <span class="nl">packetCount:</span><span class="n">packetCount</span> <span class="nl">packetDescriptions:</span><span class="n">desces</span> <span class="nl">isEof:</span><span class="n">isEof</span><span class="p">];</span>
</span><span class='line'>    <span class="n">free</span><span class="p">(</span><span class="n">desces</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">_buffer</span> <span class="n">hasData</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">isEof</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_audioQueue</span> <span class="n">flush</span><span class="p">];</span>
</span><span class='line'>        <span class="k">break</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>暂停 &amp; 恢复</h1>

<p>暂停方法很简单，调用<strong>MCAudioOutputQueue</strong>的<code>-pause</code>方法就可以了，但要注意的是需要和<code>-playData:</code>同步调用，否则可能引起一些问题（比如触发了pause实际由于并发操作没有真正pause住）。</p>

<p>同步的方法可以采用加锁的方式，也可以通过标志位在<code>threadMain</code>中进行Pause，Demo中我使用了后者。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//pause方法</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">pause</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">isPlayingOrWaiting</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_pauseRequired</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="c1">//threadMain中</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">threadMain</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">...</span>
</span><span class='line'>  
</span><span class='line'>    <span class="c1">//pause</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_pauseRequired</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span> <span class="nl">setStatusInternal:</span><span class="n">MCSAPStatusPaused</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_audioQueue</span> <span class="n">pause</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span> <span class="n">_mutexWait</span><span class="p">];</span>
</span><span class='line'>        <span class="n">_pauseRequired</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>  
</span><span class='line'>    <span class="c1">//play</span>
</span><span class='line'>    <span class="p">...</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在暂停后还要记得阻塞线程。</p>

<p>恢复只要调用<strong>AudioQueue</strong> start方法就可以了，同时记得signal让线程继续跑</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">_resume</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//AudioQueue的start方法被封装到了MCAudioOutputQueue的resume方法中</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioQueue</span> <span class="n">resume</span><span class="p">];</span>
</span><span class='line'>    <span class="p">[</span><span class="n">self</span> <span class="n">_mutexSignal</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>播放进度 &amp; Seek</h1>

<p>对于播放进度我在<a href="http://msching.github.io/blog/2014/08/02/audio-in-ios-5/">第五篇</a>讲<strong>AudioQueue</strong>时已经提到过了，使用<code>AudioQueueGetCurrentTime</code>方法可以获取<code>实际播放的时间</code>如果Seek之后需要根据计算timingOffset，然后根据timeOffset来计算最终的播放进度：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">NSTimeInterval</span><span class="p">)</span><span class="nf">progress</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">_timingOffset</span> <span class="o">+</span> <span class="n">_audioQueue</span><span class="p">.</span><span class="n">playedTime</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>timingOffset的计算在Seek进行，Seek操作和暂停操作一样需要和其他<strong>AudioQueue</strong>的操作同步进行，否则可能造成一些并发问题。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//seek方法</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setProgress:</span><span class="p">(</span><span class="n">NSTimeInterval</span><span class="p">)</span><span class="nv">progress</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">_seekRequired</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_seekTime</span> <span class="o">=</span> <span class="n">progress</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在seek时为了防止播放进度跳动，修改一下获取播放进度的方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="n">NSTimeInterval</span><span class="p">)</span><span class="nf">progress</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_seekRequired</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="n">_seekTime</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">_timingOffset</span> <span class="o">+</span> <span class="n">_audioQueue</span><span class="p">.</span><span class="n">playedTime</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>下面是<code>threadMain</code>中的Seek操作</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">_seekRequired</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">self</span> <span class="nl">setStatusInternal:</span><span class="n">MCSAPStatusWaiting</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">_timingOffset</span> <span class="o">=</span> <span class="n">_seekTime</span> <span class="o">-</span> <span class="n">_audioQueue</span><span class="p">.</span><span class="n">playedTime</span><span class="p">;</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_buffer</span> <span class="n">clean</span><span class="p">];</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_usingAudioFile</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_audioFile</span> <span class="nl">seekToTime:</span><span class="n">_seekTime</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_offset</span> <span class="o">=</span> <span class="p">[</span><span class="n">_audioFileStream</span> <span class="nl">seekToTime:</span><span class="o">&amp;</span><span class="n">_seekTime</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_fileHandler</span> <span class="nl">seekToFileOffset:</span><span class="n">_offset</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="n">_seekRequired</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioQueue</span> <span class="n">reset</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>Seek时需要做如下事情：</p>

<ol>
<li>计算timingOffset</li>
<li>清除之前残余在<code>_buffer</code>中的数据</li>
<li>挪动NSFileHandle的游标</li>
<li>清除<strong>AudioQueue</strong>中已经Enqueue的数据</li>
<li>如果有用到音效器的还需要清除音效器里的残余数据</li>
</ol>


<hr />

<h1>打断</h1>

<p>在接到Interrupt通知时需要处理打断，下面是打断的处理方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">interruptHandler:</span><span class="p">(</span><span class="n">NSNotification</span> <span class="o">*</span><span class="p">)</span><span class="nv">notification</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">UInt32</span> <span class="n">interruptionState</span> <span class="o">=</span> <span class="p">[</span><span class="n">notification</span><span class="p">.</span><span class="n">userInfo</span><span class="p">[</span><span class="n">MCAudioSessionInterruptionStateKey</span><span class="p">]</span> <span class="n">unsignedIntValue</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">interruptionState</span> <span class="o">==</span> <span class="n">kAudioSessionBeginInterruption</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_pausedByInterrupt</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_audioQueue</span> <span class="n">pause</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span> <span class="nl">setStatusInternal:</span><span class="n">MCSAPStatusPaused</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">interruptionState</span> <span class="o">==</span> <span class="n">kAudioSessionEndInterruption</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">AudioSessionInterruptionType</span> <span class="n">interruptionType</span> <span class="o">=</span> <span class="p">[</span><span class="n">notification</span><span class="p">.</span><span class="n">userInfo</span><span class="p">[</span><span class="n">MCAudioSessionInterruptionTypeKey</span><span class="p">]</span> <span class="n">unsignedIntValue</span><span class="p">];</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="n">interruptionType</span> <span class="o">==</span> <span class="n">kAudioSessionInterruptionType_ShouldResume</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">MCSAPStatusPaused</span> <span class="o">&amp;&amp;</span> <span class="n">_pausedByInterrupt</span><span class="p">)</span>
</span><span class='line'>            <span class="p">{</span>
</span><span class='line'>                <span class="k">if</span> <span class="p">([[</span><span class="n">MCAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setActive:</span><span class="n">YES</span> <span class="nl">error:</span><span class="nb">NULL</span><span class="p">])</span>
</span><span class='line'>                <span class="p">{</span>
</span><span class='line'>                    <span class="p">[</span><span class="n">self</span> <span class="n">play</span><span class="p">];</span>
</span><span class='line'>                <span class="p">}</span>
</span><span class='line'>            <span class="p">}</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里需要注意，打断操作我放在了主线程进行而并非放到新开的线程中进行，原因如下：</p>

<ul>
<li><p>一旦打断开始<strong>AudioSession</strong>被抢占后音频立即被打断，此时<strong>AudioQueue</strong>的所有操作会暂停，这就意味着不会有任何数据消耗回调产生；</p></li>
<li><p>我这个Demo的线程模型中在向<strong>AudioQueue</strong> Enqueue了足够多的数据之后会阻塞当前线程等待数据消耗的回调才会signal让线程继续跑；</p></li>
</ul>


<p>于是就得到了这样的结论：一旦打断开始我创建的线程就会被阻塞，所以我需要在主线程来处理暂停和恢复播放。</p>

<hr />

<h1>停止 &amp; 清理</h1>

<p>停止操作也和其他操作一样会放到<code>threadMain</code>中执行</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">stop</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">_stopRequired</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>    <span class="p">[</span><span class="n">self</span> <span class="n">_mutexSignal</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="c1">//treadMain中</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">_stopRequired</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">_stopRequired</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_started</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioQueue</span> <span class="nl">stop:</span><span class="n">YES</span><span class="p">];</span>
</span><span class='line'>    <span class="k">break</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在播放被停止或者出错时会进入到清理流程，这里需要做一大堆操作，清理各种数据，关闭AudioSession，清除各种标记等等。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">cleanup</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//reset file</span>
</span><span class='line'>    <span class="n">_offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_fileHandler</span> <span class="nl">seekToFileOffset:</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//deactive audiosession</span>
</span><span class='line'>    <span class="p">[[</span><span class="n">MCAudioSession</span> <span class="n">sharedInstance</span><span class="p">]</span> <span class="nl">setActive:</span><span class="n">NO</span> <span class="nl">error:</span><span class="nb">NULL</span><span class="p">];</span>
</span><span class='line'>    <span class="p">[[</span><span class="n">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="p">]</span> <span class="nl">removeObserver:</span><span class="n">self</span> <span class="nl">name:</span><span class="n">MCAudioSessionInterruptionNotification</span> <span class="nl">object:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//clean buffer</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_buffer</span> <span class="n">clean</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">_usingAudioFile</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="c1">//close audioFileStream</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioFileStream</span> <span class="n">close</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//close audiofile</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioFile</span> <span class="n">close</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//stop audioQueue</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_audioQueue</span> <span class="nl">stop:</span><span class="n">YES</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//destory mutex &amp; cond</span>
</span><span class='line'>    <span class="p">[</span><span class="n">self</span> <span class="n">_mutexDestory</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">_started</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_timingOffset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_seekTime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_seekRequired</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_pauseRequired</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="n">_stopRequired</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//reset status</span>
</span><span class='line'>    <span class="p">[</span><span class="n">self</span> <span class="nl">setStatusInternal:</span><span class="n">MCSAPStatusStopped</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>连接播放器UI</h1>

<p>播放器代码完成后就需要和UI连起来让播放器跑起来了。</p>

<p>在viewDidLoad时创建一个播放器：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_player</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">NSString</span> <span class="o">*</span><span class="n">path</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSBundle</span> <span class="n">mainBundle</span><span class="p">]</span> <span class="nl">pathForResource:</span><span class="s">@&quot;MP3Sample&quot;</span> <span class="nl">ofType:</span><span class="s">@&quot;mp3&quot;</span><span class="p">];</span>
</span><span class='line'>        <span class="n">_player</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MCSimpleAudioPlayer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithFilePath:</span><span class="n">path</span> <span class="nl">fileType:</span><span class="n">kAudioFileMP3Type</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>        <span class="p">[</span><span class="n">_player</span> <span class="nl">addObserver:</span><span class="n">self</span> <span class="nl">forKeyPath:</span><span class="s">@&quot;status&quot;</span> <span class="nl">options:</span><span class="n">NSKeyValueObservingOptionNew</span> <span class="nl">context:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_player</span> <span class="n">play</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>对播放器的status属性KVO用来操作播放和暂停按钮的状态以及播放进度timer的开启和暂停：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">observeValueForKeyPath:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">keyPath</span> <span class="nf">ofObject:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">object</span> <span class="nf">change:</span><span class="p">(</span><span class="n">NSDictionary</span> <span class="o">*</span><span class="p">)</span><span class="nv">change</span> <span class="nf">context:</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nv">context</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">object</span> <span class="o">==</span> <span class="n">_player</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">([</span><span class="n">keyPath</span> <span class="nl">isEqualToString:</span><span class="s">@&quot;status&quot;</span><span class="p">])</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="p">[</span><span class="n">self</span> <span class="nl">performSelectorOnMainThread:</span><span class="k">@selector</span><span class="p">(</span><span class="n">handleStatusChanged</span><span class="p">)</span> <span class="nl">withObject:</span><span class="nb">nil</span> <span class="nl">waitUntilDone:</span><span class="n">NO</span><span class="p">];</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">handleStatusChanged</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_player</span><span class="p">.</span><span class="n">isPlayingOrWaiting</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">playOrPauseButton</span> <span class="nl">setTitle:</span><span class="s">@&quot;Pause&quot;</span> <span class="nl">forState:</span><span class="n">UIControlStateNormal</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span> <span class="n">startTimer</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">playOrPauseButton</span> <span class="nl">setTitle:</span><span class="s">@&quot;Play&quot;</span> <span class="nl">forState:</span><span class="n">UIControlStateNormal</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span> <span class="n">stopTimer</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">self</span> <span class="nl">progressMove:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>播放进度交给timer来刷新：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">startTimer</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_timer</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">_timer</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSTimer</span> <span class="nl">scheduledTimerWithTimeInterval:</span><span class="mi">1</span> <span class="nl">target:</span><span class="n">self</span> <span class="nl">selector:</span><span class="k">@selector</span><span class="p">(</span><span class="nl">progressMove:</span><span class="p">)</span> <span class="nl">userInfo:</span><span class="nb">nil</span> <span class="nl">repeats:</span><span class="n">YES</span><span class="p">];</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_timer</span> <span class="n">fire</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">stopTimer</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_timer</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_timer</span> <span class="n">invalidate</span><span class="p">];</span>
</span><span class='line'>        <span class="n">_timer</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">progressMove:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//在seek时不要刷新slider的thumb位置</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">self</span><span class="p">.</span><span class="n">progressSlider</span><span class="p">.</span><span class="n">tracking</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="n">_player</span><span class="p">.</span><span class="n">duration</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">self</span><span class="p">.</span><span class="n">progressSlider</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">_player</span><span class="p">.</span><span class="n">progress</span> <span class="o">/</span> <span class="n">_player</span><span class="p">.</span><span class="n">duration</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">else</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">self</span><span class="p">.</span><span class="n">progressSlider</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>监听slider的两个TouchUp时间来进行seek操作：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">IBAction</span><span class="p">)</span><span class="nf">seek:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">_player</span><span class="p">.</span><span class="n">progress</span> <span class="o">=</span> <span class="n">_player</span><span class="p">.</span><span class="n">duration</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">progressSlider</span><span class="p">.</span><span class="n">value</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>添加两个按钮的TouchUpInside事件进行播放控制：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">IBAction</span><span class="p">)</span><span class="nf">playOrPause:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">_player</span><span class="p">.</span><span class="n">isPlayingOrWaiting</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_player</span> <span class="n">pause</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_player</span> <span class="n">play</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">IBAction</span><span class="p">)</span><span class="nf">stop:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">_player</span> <span class="n">stop</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>进阶的内容</h1>

<p>关于简单播放器的构建就讲这么多，以下是一些音频播放相关的进阶内容，由于我自己也没有摸透它们所以暂时就不做详细介绍了以免误人子弟-_-，各位有兴趣可以研究一下，如果有疑问或者有新发现欢迎大家留言或者在<a href="http://weibo.com/msching">微博</a>上和我交流共同提高~</p>

<ol>
<li><code>AudioConverter</code>可以实现音频数据的转换，在播放流程中它可以充当解码器的角色，可以把压缩的音频数据解码成为PCM数据；</li>
<li><code>AudioUnit</code>作为比<code>AudioQueue</code>更底层的音频播放类库，Apple赋予了它更强大的功能，除了一般的播放功能之外它还能使用iPhone自带的多种均衡器对音效进行调节；</li>
<li><code>AUGraph</code>为<code>AudioUnit</code>提供音效处理功能（这个其实我一点也没接触过0_0）</li>
</ol>


<hr />

<h1>示例代码</h1>

<p>上面所讲述的内容对应的工程已经在github上了（<a href="https://github.com/msching/MCSimpleAudioPlayer">MCSimpleAudioPlayer</a>），有任何问题可以给我发issue~</p>

<div class="github-card" data-github="msching/MCSimpleAudioPlayer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<hr />

<h1>下篇预告</h1>

<p>下一篇会介绍一下如何播放iOS系统<code>iPod Library</code>中的歌曲（俗称iPod音乐或者本地音乐）</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (五)：AudioQueue]]></title>
    <link href="http://msching.github.io/blog/2014/08/02/audio-in-ios-5/"/>
    <updated>2014-08-02T14:21:13+08:00</updated>
    <id>http://msching.github.io/blog/2014/08/02/audio-in-ios-5</id>
    <content type="html"><![CDATA[<p>在<a href="http://msching.github.io/blog/2014/07/09/audio-in-ios-3/">第三篇</a>和<a href="http://msching.github.io/blog/2014/07/19/audio-in-ios-4/">第四篇</a>中介绍了如何用<code>AudioStreamFile</code>和<code>AudioFile</code>解析音频数据格式、分离音频帧。下一步终于可以使用分离出来的音频帧进行播放了，本片中将来讲一讲如何使用<code>AudioQueue</code>播放音频数据。</p>

<!--more-->


<hr />

<h1>AudioQueue介绍</h1>

<p><code>AudioQueue</code>是<code>AudioToolBox.framework</code>中的一员，在<a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005343">官方文档</a>中Apple这样描述<code>AudioQueue</code>的：</p>

<p><code>Audio Queue Services provides a straightforward, low overhead way to record and play audio in iOS and Mac OS X. It is the recommended technology to use for adding basic recording or playback features to your iOS or Mac OS X application.</code></p>

<p>在文档中Apple推荐开发者使用<code>AudioQueue</code>来实现app中的播放和录音功能。这里我们会针对播放功能进行介绍。</p>

<p>对于支持的数据格式，Apple这样说：</p>

<pre><code>Audio Queue Services lets you record and play audio in any of the following formats:

* Linear PCM.
* Any compressed format supported natively on the Apple platform you are developing for.
* Any other format for which a user has an installed codec.
</code></pre>

<p>它支持<code>PCM</code>数据、iOS/MacOSX平台支持的压缩格式（MP3、AAC等）、其他用户可以自行提供解码器的音频数据（对于这一条，我的理解就是把音频格式自行解码成PCM数据后再给AudioQueue播放  ）。</p>

<hr />

<h1>AudioQueue的工作模式</h1>

<p>在使用<code>AudioQueue</code>之前首先必须理解其工作模式，它之所以这么命名是因为在其内部有一套缓冲队列（Buffer Queue）的机制。在<code>AudioQueue</code>启动之后需要通过<code>AudioQueueAllocateBuffer</code>生成若干个<code>AudioQueueBufferRef</code>结构，这些Buffer将用来存储即将要播放的音频数据，并且这些Buffer是受生成他们的<code>AudioQueue</code>实例管理的，内存空间也已经被分配（按照Allocate方法的参数），当<code>AudioQueue</code>被Dispose时这些Buffer也会随之被销毁。</p>

<p>当有音频数据需要被播放时首先需要被memcpy到<code>AudioQueueBufferRef</code>的mAudioData中（mAudioData所指向的内存已经被分配，之前<code>AudioQueueAllocateBuffer</code>所做的工作），并给mAudioDataByteSize字段赋值传入的数据大小。完成之后需要调用<code>AudioQueueEnqueueBuffer</code>把存有音频数据的Buffer插入到<code>AudioQueue</code>内置的Buffer队列中。在Buffer队列中有buffer存在的情况下调用<code>AudioQueueStart</code>，此时<code>AudioQueue</code>就回按照Enqueue顺序逐个使用Buffer队列中的buffer进行播放，每当一个Buffer使用完毕之后就会从Buffer队列中被移除并且在使用者指定的RunLoop上触发一个回调来告诉使用者，某个<code>AudioQueueBufferRef</code>对象已经使用完成，你可以继续重用这个对象来存储后面的音频数据。如此循环往复音频数据就会被逐个播放直到结束。</p>

<p><a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW9">官方文档</a>给出了一副图来描述这一过程：</p>

<p>其中的callback按我的理解应该是指一个音频数据装填方法，该方法可以通过之前提到的数据使用后的回调来触发。</p>

<p><img src="http://msching.github.io/images/iOS-audio/audioqueuePlayback.jpg" alt="AudioQueue playback" /></p>

<p>根据Apple提供的<code>AudioQueue</code>工作原理结合自己理解，可以得到其工作流程大致如下：</p>

<ol>
<li>创建<code>AudioQueue</code>，创建一个自己的buffer数组BufferArray;</li>
<li>使用<code>AudioQueueAllocateBuffer</code>创建若干个<code>AudioQueueBufferRef</code>（一般2-3个即可），放入BufferArray；</li>
<li>有数据时从BufferArray取出一个buffer，memcpy数据后用<code>AudioQueueEnqueueBuffer</code>方法把buffer插入<code>AudioQueue</code>中；</li>
<li><code>AudioQueue</code>中存在Buffer后，调用<code>AudioQueueStart</code>播放。（具体等到填入多少buffer后再播放可以自己控制，只要能保证播放不间断即可）；</li>
<li><code>AudioQueue</code>播放音乐后消耗了某个buffer，在另一个线程回调并送出该buffer，把buffer放回BufferArray供下一次使用；</li>
<li>返回步骤3继续循环直到播放结束</li>
</ol>


<p>从以上步骤其实不难看出，<code>AudioQueue</code>播放的过程其实就是一个典型的<a href="http://zh.wikipedia.org/zh/%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E9%97%AE%E9%A2%98">生产者消费者问题</a>。生产者是<code>AudioFileStream</code>或者<code>AudioFile</code>，它们生产处音频数据帧，放入到<code>AudioQueue</code>的buffer队列中，直到buffer填满后需要等待消费者消费；<code>AudioQueue</code>作为消费者，消费了buffer队列中的数据，并且在另一个线程回调通知数据已经被消费生产者可以继续生产。所以在实现<code>AudioQueue</code>播放音频的过程中必然会接触到一些多线程同步、信号量的使用、死锁的避免等等问题。</p>

<p>了解了工作流程之后再回头来看<code>AudioQueue</code>的方法，其中大部分方法都非常好理解，部分需要稍加解释。</p>

<hr />

<h1>创建AudioQueue</h1>

<p>使用下列方法来生成<code>AudioQueue</code>的实例</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueNewOutput</span> <span class="p">(</span><span class="k">const</span> <span class="n">AudioStreamBasicDescription</span> <span class="o">*</span> <span class="n">inFormat</span><span class="p">,</span>
</span><span class='line'>                              <span class="n">AudioQueueOutputCallback</span> <span class="n">inCallbackProc</span><span class="p">,</span>
</span><span class='line'>                              <span class="kt">void</span> <span class="o">*</span> <span class="n">inUserData</span><span class="p">,</span>
</span><span class='line'>                              <span class="n">CFRunLoopRef</span> <span class="n">inCallbackRunLoop</span><span class="p">,</span>
</span><span class='line'>                              <span class="n">CFStringRef</span> <span class="n">inCallbackRunLoopMode</span><span class="p">,</span>
</span><span class='line'>                              <span class="n">UInt32</span> <span class="n">inFlags</span><span class="p">,</span>
</span><span class='line'>                              <span class="n">AudioQueueRef</span> <span class="o">*</span> <span class="n">outAQ</span><span class="p">);</span>
</span><span class='line'>                              
</span><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueNewOutputWithDispatchQueue</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="o">*</span> <span class="n">outAQ</span><span class="p">,</span>
</span><span class='line'>                                              <span class="k">const</span> <span class="n">AudioStreamBasicDescription</span> <span class="o">*</span> <span class="n">inFormat</span><span class="p">,</span>
</span><span class='line'>                                              <span class="n">UInt32</span> <span class="n">inFlags</span><span class="p">,</span>
</span><span class='line'>                                              <span class="n">dispatch_queue_t</span> <span class="n">inCallbackDispatchQueue</span><span class="p">,</span>
</span><span class='line'>                                              <span class="n">AudioQueueOutputCallbackBlock</span> <span class="n">inCallbackBlock</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>先来看第一个方法：</p>

<p>第一个参数表示需要播放的音频数据格式类型，是一个<code>AudioStreamBasicDescription</code>对象，是使用<code>AudioFileStream</code>或者<code>AudioFile</code>解析出来的数据格式信息；</p>

<p>第二个参数<code>AudioQueueOutputCallback</code>是某块Buffer<code>被使用之后</code>的回调；</p>

<p>第三个参数为上下文对象；</p>

<p>第四个参数inCallbackRunLoop为<code>AudioQueueOutputCallback</code>需要在的哪个RunLoop上被回调，如果传入NULL的话就会再<code>AudioQueue</code>的内部RunLoop中被回调，所以一般传NULL就可以了；</p>

<p>第五个参数inCallbackRunLoopMode为RunLoop模式，如果传入NULL就相当于<code>kCFRunLoopCommonModes</code>，也传NULL就可以了；</p>

<p>第六个参数inFlags是保留字段，目前没作用，传0；</p>

<p>第七个参数，返回生成的<code>AudioQueue</code>实例；</p>

<p>返回值用来判断是否成功创建（OSStatus == noErr）。</p>

<p>第二个方法就是把RunLoop替换成了一个dispatch queue，其余参数同相同。</p>

<hr />

<h1>Buffer相关的方法</h1>

<h3>1. 创建Buffer</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueAllocateBuffer</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">UInt32</span> <span class="n">inBufferByteSize</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">AudioQueueBufferRef</span> <span class="o">*</span> <span class="n">outBuffer</span><span class="p">);</span>
</span><span class='line'>                                  
</span><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueAllocateBufferWithPacketDescriptions</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span>
</span><span class='line'>                                                        <span class="n">UInt32</span> <span class="n">inBufferByteSize</span><span class="p">,</span>
</span><span class='line'>                                                        <span class="n">UInt32</span> <span class="n">inNumberPacketDescriptions</span><span class="p">,</span>
</span><span class='line'>                                                        <span class="n">AudioQueueBufferRef</span> <span class="o">*</span> <span class="n">outBuffer</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第一个方法传入<code>AudioQueue</code>实例和Buffer大小，传出的Buffer实例；</p>

<p>第二个方法可以指定生成的Buffer中PacketDescriptions的个数；</p>

<h3>2. 销毁Buffer</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueFreeBuffer</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span><span class="n">AudioQueueBufferRef</span> <span class="n">inBuffer</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>注意这个方法一般只在需要销毁特定某个buffer时才会被用到（因为dispose方法会自动销毁所有buffer），并且这个方法<code>只能在AudioQueue不在处理数据时</code>才能使用。所以这个方法一般不太能用到。</p>

<h3>3. 插入Buffer</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueEnqueueBuffer</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span>
</span><span class='line'>                                 <span class="n">AudioQueueBufferRef</span> <span class="n">inBuffer</span><span class="p">,</span>
</span><span class='line'>                                 <span class="n">UInt32</span> <span class="n">inNumPacketDescs</span><span class="p">,</span>
</span><span class='line'>                                 <span class="k">const</span> <span class="n">AudioStreamPacketDescription</span> <span class="o">*</span> <span class="n">inPacketDescs</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>Enqueue方法一共有两个，上面给出的是第一个方法，第二个方法<code>AudioQueueEnqueueBufferWithParameters</code>可以对Enqueue的buffer进行更多额外的操作，第二个方法我也没有细细研究，一般来说用第一个方法就能满足需求了，这里我也就只针对第一个方法进行说明：</p>

<p>这个Enqueue方法需要传入<code>AudioQueue</code>实例和需要Enqueue的Buffer，对于有inNumPacketDescs和inPacketDescs则需要根据需要选择传入，文档上说这两个参数主要是在播放VBR数据时使用，但之前我们提到过即便是CBR数据AudioFileStream或者AudioFile也会给出PacketDescription所以不能如此一概而论。简单的来说就是有就传PacketDescription没有就给NULL，不必管是不是VBR。</p>

<hr />

<h1>播放控制</h1>

<h3>1.开始播放</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueStart</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span><span class="k">const</span> <span class="n">AudioTimeStamp</span> <span class="o">*</span> <span class="n">inStartTime</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第二个参数可以用来控制播放开始的时间，一般情况下直接开始播放传入NULL即可。</p>

<h3>2.解码数据</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueuePrime</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span>
</span><span class='line'>                          <span class="n">UInt32</span> <span class="n">inNumberOfFramesToPrepare</span><span class="p">,</span>
</span><span class='line'>                          <span class="n">UInt32</span> <span class="o">*</span> <span class="n">outNumberOfFramesPrepared</span><span class="p">);</span>                                    
</span></code></pre></td></tr></table></div></figure>


<p>这个方法并不常用，因为直接调用<code>AudioQueueStart</code>会自动开始解码（如果需要的话）。参数的作用是用来指定需要解码帧数和实际完成解码的帧数；</p>

<h3>3.暂停播放</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueuePause</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>需要注意的是这个方法一旦调用后播放就会立即暂停，这就意味着<code>AudioQueueOutputCallback</code>回调也会暂停，这时需要特别关注线程的调度以防止线程陷入无限等待。</p>

<h3>4.停止播放</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueStop</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span> <span class="n">Boolean</span> <span class="n">inImmediate</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第二个参数如果传入true的话会立即停止播放（同步），如果传入false的话<code>AudioQueue</code>会播放完已经Enqueue的所有buffer后再停止（异步）。使用时注意根据需要传入适合的参数。</p>

<h3>5.Flush</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span>
</span><span class='line'><span class="nf">AudioQueueFlush</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>调用后会播放完Enqueu的所有buffer后重置解码器状态，以防止当前的解码器状态影响到下一段音频的解码（比如切换播放的歌曲时）。如果和<code>AudioQueueStop(AQ,false)</code>一起使用并不会起效，因为Stop方法的false参数也会做同样的事情。</p>

<h3>6.重置</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueReset</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>重置<code>AudioQueue</code>会清除所有已经Enqueue的buffer，并触发<code>AudioQueueOutputCallback</code>,调用<code>AudioQueueStop</code>方法时同样会触发该方法。这个方法的直接调用一般在seek时使用，用来清除残留的buffer（seek时还有一种做法是先<code>AudioQueueStop</code>，等seek完成后重新start）。</p>

<h3>7.获取播放时间</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">OSStatus</span> <span class="nf">AudioQueueGetCurrentTime</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">AudioQueueTimelineRef</span> <span class="n">inTimeline</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">AudioTimeStamp</span> <span class="o">*</span> <span class="n">outTimeStamp</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">Boolean</span> <span class="o">*</span> <span class="n">outTimelineDiscontinuity</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>传入的参数中，第一、第四个参数是和<code>AudioQueueTimeline</code>相关的我们这里并没有用到，传入NULL。调用后的返回<code>AudioTimeStamp</code>，从这个timestap结构可以得出播放时间，计算方法如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AudioTimeStamp</span> <span class="n">time</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//AudioQueueGetCurrentTime方法获取</span>
</span><span class='line'><span class="n">NSTimeInterval</span> <span class="n">playedTime</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">mSampleTime</span> <span class="o">/</span> <span class="n">_format</span><span class="p">.</span><span class="n">mSampleRate</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在使用这个时间获取方法时有两点必须注意：</p>

<p>1、 第一个需要注意的时这个播放时间是指<code>实际播放的时间</code>和一般理解上的播放进度是有区别的。举个例子，开始播放8秒后用户操作slider把播放进度seek到了第20秒之后又播放了3秒钟，此时通常意义上播放时间应该是23秒，即播放进度；而用<code>GetCurrentTime</code>方法中获得的时间为11秒，即实际播放时间。所以每次seek时都必须保存seek的timingOffset：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AudioTimeStamp</span> <span class="n">time</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//AudioQueueGetCurrentTime方法获取</span>
</span><span class='line'><span class="n">NSTimeInterval</span> <span class="n">playedTime</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">mSampleTime</span> <span class="o">/</span> <span class="n">_format</span><span class="p">.</span><span class="n">mSampleRate</span><span class="p">;</span> <span class="c1">//seek时的播放时间</span>
</span><span class='line'>
</span><span class='line'><span class="n">NSTimeInterval</span> <span class="n">seekTime</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//需要seek到哪个时间</span>
</span><span class='line'><span class="n">NSTimeInterval</span> <span class="n">timingOffset</span> <span class="o">=</span> <span class="n">seekTime</span> <span class="o">-</span> <span class="n">playedTime</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>seek后的播放进度需要根据timingOffset和playedTime计算：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">NSTimeInterval</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">timingOffset</span> <span class="o">+</span> <span class="n">playedTime</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>2、 第二个需要注意的是<code>GetCurrentTime</code>方法有时候会失败，所以上次获取的播放时间最好保存起来，如果遇到调用失败，就返回上次保存的结果。</p>

<hr />

<h1>销毁AudioQueue</h1>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AudioQueueDispose</span><span class="p">(</span><span class="n">AudioQueueRef</span> <span class="n">inAQ</span><span class="p">,</span>  <span class="n">Boolean</span> <span class="n">inImmediate</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>销毁的同时会清除其中所有的buffer，第二个参数的意义和用法与<code>AudioQueueStop</code>方法相同。</p>

<p>这个方法使用时需要注意当<code>AudioQueueStart</code>调用之后<code>AudioQueue</code>其实还没有真正开始，期间会有一个短暂的间隙。如果在<code>AudioQueueStart</code>调用后到<code>AudioQueue</code>真正开始运作前的这段时间内调用<code>AudioQueueDispose</code>方法的话会导致程序卡死。这个问题是我在使用<a href="https://github.com/mattgallagher/AudioStreamer">AudioStreamer</a>时发现的，在iOS 6必现（iOS 7我倒是没有测试过，当时发现问题时iOS 7还没发布），起因是由于AudioStreamer会在音频EOF时就进入Cleanup环节，Cleanup环节会flush所有数据然后调用Dispose，那么当音频文件中数据非常少时就有可能出现<code>AudioQueueStart</code>调用之时就已经EOF进入Cleanup，此时就会出现上述问题。</p>

<p>要规避这个问题第一种方法是做好线程的调度，保证Dispose方法调用一定是在每一个播放RunLoop之后（即至少是一个buffer被成功播放之后）。第二种方法是监听<code>kAudioQueueProperty_IsRunning</code>属性，这个属性在<code>AudioQueue</code>真正运作起来之后会变成1，停止后会变成0，所以需要保证Start方法调用后Dispose方法一定要在<code>IsRunning</code>为1时才能被调用。</p>

<hr />

<h1>属性和参数</h1>

<p>和其他的<code>AudioToolBox</code>类一样，<code>AudioToolBox</code>有很多参数和属性可以设置、获取、监听。以下是相关的方法，这里就不再一一赘述：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//参数相关方法</span>
</span><span class='line'><span class="n">AudioQueueGetParameter</span>
</span><span class='line'><span class="n">AudioQueueSetParameter</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//属性相关方法</span>
</span><span class='line'><span class="n">AudioQueueGetPropertySize</span>
</span><span class='line'><span class="n">AudioQueueGetProperty</span>
</span><span class='line'><span class="n">AudioQueueSetProperty</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//监听属性变化相关方法</span>
</span><span class='line'><span class="n">AudioQueueAddPropertyListener</span>
</span><span class='line'><span class="n">AudioQueueRemovePropertyListener</span>
</span></code></pre></td></tr></table></div></figure>


<p>属性和参数的列表：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//属性列表</span>
</span><span class='line'><span class="k">enum</span> <span class="p">{</span> <span class="c1">// typedef UInt32 AudioQueuePropertyID</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_IsRunning</span>               <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqrn</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">kAudioQueueDeviceProperty_SampleRate</span>        <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqsr</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is Float64</span>
</span><span class='line'>    <span class="n">kAudioQueueDeviceProperty_NumberChannels</span>    <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqdc</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_CurrentDevice</span>           <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqcd</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is CFStringRef</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">kAudioQueueProperty_MagicCookie</span>             <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqmc</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is void*</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_MaximumOutputPacketSize</span> <span class="o">=</span> <span class="err">&#39;</span><span class="n">xops</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_StreamDescription</span>       <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqft</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is AudioStreamBasicDescription</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">kAudioQueueProperty_ChannelLayout</span>           <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqcl</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is AudioChannelLayout</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_EnableLevelMetering</span>     <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqme</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_CurrentLevelMeter</span>       <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqmv</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is array of AudioQueueLevelMeterState, 1 per channel</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_CurrentLevelMeterDB</span>     <span class="o">=</span> <span class="err">&#39;</span><span class="n">aqmd</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is array of AudioQueueLevelMeterState, 1 per channel</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">kAudioQueueProperty_DecodeBufferSizeFrames</span>  <span class="o">=</span> <span class="err">&#39;</span><span class="n">dcbf</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_ConverterError</span>          <span class="o">=</span> <span class="err">&#39;</span><span class="n">qcve</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">kAudioQueueProperty_EnableTimePitch</span>         <span class="o">=</span> <span class="err">&#39;</span><span class="n">q_tp</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32, 0/1</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_TimePitchAlgorithm</span>      <span class="o">=</span> <span class="err">&#39;</span><span class="n">qtpa</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32. See values below.</span>
</span><span class='line'>    <span class="n">kAudioQueueProperty_TimePitchBypass</span>         <span class="o">=</span> <span class="err">&#39;</span><span class="n">qtpb</span><span class="err">&#39;</span><span class="p">,</span>       <span class="c1">// value is UInt32, 1=bypassed</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//参数列表</span>
</span><span class='line'><span class="k">enum</span>    <span class="c1">// typedef UInt32 AudioQueueParameterID;</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">kAudioQueueParam_Volume</span>         <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
</span><span class='line'>    <span class="n">kAudioQueueParam_PlayRate</span>       <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span><span class='line'>    <span class="n">kAudioQueueParam_Pitch</span>          <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span><span class='line'>    <span class="n">kAudioQueueParam_VolumeRampTime</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span><span class='line'>    <span class="n">kAudioQueueParam_Pan</span>            <span class="o">=</span> <span class="mi">13</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>其中比较有价值的属性有：</p>

<ul>
<li><code>kAudioQueueProperty_IsRunning</code>监听它可以知道当前<code>AudioQueue</code>是否在运行，这个参数的作用在讲到<code>AudioQueueDispose</code>时已经提到过。</li>
<li><code>kAudioQueueProperty_MagicCookie</code>部分音频格式需要设置magicCookie，这个cookie可以从<code>AudioFileStream</code>和<code>AudioFile</code>中获取。</li>
</ul>


<p>比较有价值的参数有：</p>

<ul>
<li><code>kAudioQueueParam_Volume</code>，它可以用来调节<code>AudioQueue</code>的播放音量，注意这个音量是<code>AudioQueue</code>的内部播放音量和系统音量相互独立设置并且最后叠加生效。</li>
<li><code>kAudioQueueParam_VolumeRampTime</code>参数和<code>Volume</code>参数配合使用可以实现音频播放淡入淡出的效果；</li>
<li><code>kAudioQueueParam_PlayRate</code>参数可以调整播放速率；</li>
</ul>


<hr />

<h1>后记</h1>

<p>至此本片关于<code>AudioQueue</code>的话题接结束了。使用上面提到的方法已经可以满足大部分的播放需求，但<code>AudioQueue</code>的功能远不止如此，<code>AudioQueueTimeline</code>、<code>Offline Rendering</code>、<code>AudioQueueProcessingTap</code>等功能我目前也尚未涉及和研究，未来也许还会有更多新的功能加入，学无止境啊。</p>

<p>另外由于<code>AudioQueue</code>的相关内容无法单独做Demo进行展示，于是我提前把后一篇内容的<a href="https://github.com/msching/MCSimpleAudioPlayer">Demo</a>（一个简单的本地音频播放器）先在这里给出方便大家理解<code>AudioQueue</code>。如果觉得上面提到某一部分的很难以的话理解欢迎在下面留言或者在<a href="http://weibo.com/msching">微博</a>上和我交流，除此之外还可以阅读官方文档（我一直觉得官方文档是学习的最好途径）；</p>

<hr />

<h1>示例代码</h1>

<p><a href="https://github.com/mattgallagher/AudioStreamer">AudioStreamer</a>和<a href="https://github.com/muhku/FreeStreamer">FreeStreamer</a>都用到了AudioQueue。</p>

<div class="github-card" data-github="mattgallagher/AudioStreamer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>




<div class="github-card" data-github="muhku/FreeStreamer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<p>在上面提到的Demo中也有我自己做的封装<a href="https://github.com/msching/MCSimpleAudioPlayer/blob/master/MCSimpleAudioPlayerDemo/MCSimpleAudioPlayer/MCAudioOutputQueue.h">MCAudioOutputQueue</a>。</p>

<div class="github-card" data-github="msching/MCSimpleAudioPlayer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<hr />

<h1>下篇预告</h1>

<p>下一篇将讲述如何利用之前讲到的<code>AudioSession</code>、<code>AudioFileStream</code>和<code>AudioQueue</code>实现一个简单的本地文件播放器。</p>

<hr />

<h1>参考资料</h1>

<p><a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005343">Audio Queue Services Programming Guide</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW9">About Audio Queues</a></p>

<p><a href="http://zh.wikipedia.org/zh/%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E9%97%AE%E9%A2%98">生产者消费者问题</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (四)：AudioFile]]></title>
    <link href="http://msching.github.io/blog/2014/07/19/audio-in-ios-4/"/>
    <updated>2014-07-19T13:38:30+08:00</updated>
    <id>http://msching.github.io/blog/2014/07/19/audio-in-ios-4</id>
    <content type="html"><![CDATA[<p>接着<a href="http://msching.github.io/blog/2014/07/09/audio-in-ios-3/">第三篇</a>的<code>AudioStreamFile</code>这一篇要来聊一下<code>AudioFile</code>。和<code>AudioStreamFile</code>一样<code>AudioFile</code>是<code>AudioToolBox</code> framework中的一员，它也能够完成<a href="http://msching.github.io/blog/2014/07/07/audio-in-ios/">第一篇</a>所述的第2步，读取音频格式信息和进行帧分离，但事实上它的功能远不止如此。</p>

<!--more-->


<hr />

<h1>AudioFile介绍</h1>

<p>按照<a href="https://developer.apple.com/library/mac/documentation/musicaudio/reference/AudioFileConvertRef/Reference/reference.html#//apple_ref/c/func/AudioFileCreateWithURL">官方文档</a>的描述：</p>

<p><code>a C programming interface that enables you to read or write a wide variety of audio data to or from disk or a memory buffer.With Audio File Services you can:</code></p>

<ul>
<li>Create, initialize, open, and close audio files</li>
<li>Read and write audio files</li>
<li>Optimize audio files</li>
<li>Work with user data and global information</li>
</ul>


<p>这个类可以用来创建、初始化音频文件；读写音频数据；对音频文件进行优化；读取和写入音频格式信息等等，功能十分强大，可见它不但可以用来支持音频播放，甚至可以用来生成音频文件。当然，在本篇文章中只会涉及一些和音频播放相关的内容（打开音频文件、读取格式信息、读取音频数据，其实我也只对这些方法有一点了解，其余的功能没用过。。>_&lt;）.</p>

<hr />

<h1>AudioFile的打开“姿势”</h1>

<p><code>AudioFile</code>提供了两个打开文件的方法：</p>

<p>1、 <code>AudioFileOpenURL</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">enum</span> <span class="p">{</span>
</span><span class='line'>  <span class="n">kAudioFileReadPermission</span>      <span class="o">=</span> <span class="mh">0x01</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileWritePermission</span>     <span class="o">=</span> <span class="mh">0x02</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileReadWritePermission</span> <span class="o">=</span> <span class="mh">0x03</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileOpenURL</span> <span class="p">(</span><span class="n">CFURLRef</span> <span class="n">inFileRef</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">SInt8</span> <span class="n">inPermissions</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">AudioFileTypeID</span> <span class="n">inFileTypeHint</span><span class="p">,</span>
</span><span class='line'>                                  <span class="n">AudioFileID</span> <span class="o">*</span> <span class="n">outAudioFile</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>从方法的定义上来看是用来读取本地文件的：</p>

<p>第一个参数，文件路径；</p>

<p>第二个参数，文件的允许使用方式，是读、写还是读写，如果打开文件后进行了允许使用方式以外的操作，就得到<code>kAudioFilePermissionsError</code>错误码（比如Open时声明是<code>kAudioFileReadPermission</code>但却调用了<code>AudioFileWriteBytes</code>）；</p>

<p>第三个参数，和<code>AudioFileStream</code>的open方法中一样是一个帮助<code>AudioFile</code>解析文件的类型提示，如果文件类型确定的话应当传入；</p>

<p>第四个参数，返回AudioFile实例对应的<code>AudioFileID</code>，这个ID需要保存起来作为后续一些方法的参数使用；</p>

<p>返回值用来判断是否成功打开文件（OSSStatus == noErr）。</p>

<hr />

<p>2、 <code>AudioFileOpenWithCallbacks</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileOpenWithCallbacks</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioFile_ReadProc</span> <span class="n">inReadFunc</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioFile_WriteProc</span> <span class="n">inWriteFunc</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioFile_GetSizeProc</span> <span class="n">inGetSizeFunc</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioFile_SetSizeProc</span> <span class="n">inSetSizeFunc</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioFileTypeID</span> <span class="n">inFileTypeHint</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioFileID</span> <span class="o">*</span> <span class="n">outAudioFile</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>看过第一个Open方法后，这个方法乍看上去让人有点迷茫，没有URL的参数如何告诉AudioFile该打开哪个文件？还是先来看一下参数的说明吧：</p>

<p>第一个参数，上下文信息，不再多做解释；</p>

<p>第二个参数，当<code>AudioFile</code>需要读音频数据时进行的回调（调用Open和Read方式后<code>同步</code>回调）；</p>

<p>第三个参数，当<code>AudioFile</code>需要写音频数据时进行的回调（写音频文件功能时使用，暂不讨论）；</p>

<p>第四个参数，当<code>AudioFile</code>需要用到文件的总大小时回调（调用Open和Read方式后<code>同步</code>回调）；</p>

<p>第五个参数，当<code>AudioFile</code>需要设置文件的大小时回调（写音频文件功能时使用，暂不讨论）；</p>

<p>第六、七个参数和返回值同<code>AudioFileOpenURL</code>方法；</p>

<p>这个方法的重点在于<code>AudioFile_ReadProc</code>这个回调。换一个角度理解，这个方法相比于第一个方法自由度更高，AudioFile需要的只是一个数据源，无论是磁盘上的文件、内存里的数据甚至是网络流只要能在<code>AudioFile</code>需要数据时（Open和Read时）通过<code>AudioFile_ReadProc</code>回调为AudioFile提供合适的数据就可以了，也就是说使用方法不仅仅可以读取本地文件也可以如<code>AudioFileStream</code>一样以流的形式读取数据。</p>

<hr />

<p>下面来看一下<code>AudioFile_GetSizeProc</code>和<code>AudioFile_ReadProc</code>这两个读取功能相关的回调</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">typedef</span> <span class="nf">SInt64</span> <span class="p">(</span><span class="o">*</span><span class="n">AudioFile_GetSizeProc</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">typedef</span> <span class="nf">OSStatus</span> <span class="p">(</span><span class="o">*</span><span class="n">AudioFile_ReadProc</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">SInt64</span> <span class="n">inPosition</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">UInt32</span> <span class="n">requestCount</span><span class="p">,</span>
</span><span class='line'>                                       <span class="kt">void</span> <span class="o">*</span> <span class="n">buffer</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">UInt32</span> <span class="o">*</span> <span class="n">actualCount</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>首先是<code>AudioFile_GetSizeProc</code>回调，这个回调很好理解，返回文件总长度即可，总长度的获取途径自然是文件系统或者httpResponse等等。</p>

<p>接下来是<code>AudioFile_ReadProc</code>回调：</p>

<p>第一个参数，上下文对象，不再赘述；</p>

<p>第二个参数，需要读取第几个字节开始的数据；</p>

<p>第三个参数，需要读取的数据长度；</p>

<p>第四个参数，返回参数，是一个数据指针并且其空间已经被分配，我们需要做的是把数据memcpy到buffer中；</p>

<p>第五个参数，实际提供的数据长度，即memcpy到buffer中的数据长度；</p>

<p>返回值，如果没有任何异常产生就返回noErr，如果有异常可以根据异常类型选择需要的error常量返回（一般用不到其他返回值，返回noErr就足够了）；</p>

<p>这里需要解释一下这个回调方法的工作方式。<code>AudioFile</code>需要数据时会调用回调方法，需要数据的时间点有两个：</p>

<ol>
<li><p>Open方法调用时，由于<code>AudioFile</code>的Open方法调用过程中就会对音频格式信息进行解析，只有符合要求的音频格式才能被成功打开否则Open方法就会返回错误码（换句话说，Open方法一旦调用成功就相当于<code>AudioStreamFile</code>在Parse后返回<code>ReadyToProducePackets</code>一样，只要Open成功就可以开始读取音频数据，详见<a href="http://msching.github.io/blog/2014/07/09/audio-in-ios-3/">第三篇</a>），所以在Open方法调用的过程中就需要提供一部分音频数据来进行解析；</p></li>
<li><p>Read相关方法调用时，这个不需要多说很好理解；</p></li>
</ol>


<p>通过回调提供数据时需要注意inPosition和requestCount参数，这两个参数指明了本次回调需要提供的数据范围是从inPosition开始requestCount个字节的数据。这里又可以分为两种情况：</p>

<ol>
<li><p>有充足的数据：那么我们需要把这个范围内的数据拷贝到buffer中，并且给actualCount赋值requestCount，最后返回noError；</p></li>
<li><p>数据不足：没有充足数据的话就只能把手头有的数据拷贝到buffer中，需要注意的是这部分被拷贝的数据必须是从inPosition开始的<code>连续数据</code>，拷贝完成后给actualCount赋值实际拷贝进buffer中的数据长度后返回noErr，这个过程可以用下面的代码来表示：</p></li>
</ol>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">static</span> <span class="n">OSStatus</span> <span class="nf">MyAudioFileReadCallBack</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                        <span class="n">SInt64</span> <span class="n">inPosition</span><span class="p">,</span>
</span><span class='line'>                                        <span class="n">UInt32</span> <span class="n">requestCount</span><span class="p">,</span>
</span><span class='line'>                                        <span class="kt">void</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span>
</span><span class='line'>                                        <span class="n">UInt32</span> <span class="o">*</span><span class="n">actualCount</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">__unsafe_unretained</span> <span class="n">MyContext</span> <span class="o">*</span><span class="n">context</span> <span class="o">=</span> <span class="p">(</span><span class="n">__bridge</span> <span class="n">MyContext</span> <span class="o">*</span><span class="p">)</span><span class="n">inClientData</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="o">*</span><span class="n">actualCount</span> <span class="o">=</span> <span class="p">[</span><span class="n">context</span> <span class="nl">availableDataLengthAtOffset:</span><span class="n">inPosition</span> <span class="nl">maxLength:</span><span class="n">requestCount</span><span class="p">];</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">actualCount</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">context</span> <span class="nl">dataAtOffset:</span><span class="n">inPosition</span> <span class="nl">length:</span><span class="o">*</span><span class="n">actualCount</span><span class="p">];</span>
</span><span class='line'>        <span class="n">memcpy</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="p">[</span><span class="n">data</span> <span class="n">bytes</span><span class="p">],</span> <span class="p">[</span><span class="n">data</span> <span class="n">length</span><span class="p">]);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="n">noErr</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>说到这里又需要分两种情况：</p>

<p>2.1. Open方法调用时的回调数据不足：AudioFile的Open方法会根据文件格式类型分几步进行数据读取以解析确定是否是一个合法的文件格式，其中每一步的inPosition和requestCount都不一样，如果某一步不成功就会直接进行下一步，如果几部下来都失败了，那么Open方法就会失败。简单的说就是在调用Open之前首先需要保证音频文件的格式信息完整，这就意味着<code>AudioFile</code>并不能独立用于音频流的读取，在流播放时首先需要使用<code>AudioStreamFile</code>来得到<code>ReadyToProducePackets</code>标志位来保证信息完整；</p>

<p>2.2. Read方法调用时的回调数据不足：这种情况下inPosition和requestCount的数值与Read方法调用时传入的参数有关，数据不足对于Read方法本身没有影响，只要回调返回noErr，Read就成功，只是实际交给Read方法的调用方的数据会不足，那么就把这个问题的处理交给了Read的调用方；</p>

<hr />

<h1>读取音频格式信息</h1>

<p>成功打开音频文件后就可以读取其中的格式信息了，读取用到的方法如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileGetPropertyInfo</span><span class="p">(</span><span class="n">AudioFileID</span> <span class="n">inAudioFile</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">AudioFilePropertyID</span> <span class="n">inPropertyID</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">UInt32</span> <span class="o">*</span> <span class="n">outDataSize</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">UInt32</span> <span class="o">*</span> <span class="n">isWritable</span><span class="p">);</span>
</span><span class='line'>                                      
</span><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileGetProperty</span><span class="p">(</span><span class="n">AudioFileID</span> <span class="n">inAudioFile</span><span class="p">,</span>
</span><span class='line'>                                     <span class="n">AudioFilePropertyID</span> <span class="n">inPropertyID</span><span class="p">,</span>
</span><span class='line'>                                     <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioDataSize</span><span class="p">,</span>
</span><span class='line'>                                     <span class="kt">void</span> <span class="o">*</span> <span class="n">outPropertyData</span><span class="p">);</span>    
</span></code></pre></td></tr></table></div></figure>


<p><code>AudioFileGetPropertyInfo</code>方法用来获取某个属性对应的数据的大小（outDataSize）以及该属性是否可以被write（isWritable），而<code>AudioFileGetProperty</code>则用来获取属性对应的数据。对于一些大小可变的属性需要先使用<code>AudioFileGetPropertyInfo</code>获取数据大小才能取获取数据（例如formatList），而有些确定类型单个属性则不必先调用<code>AudioFileGetPropertyInfo</code>直接调用<code>AudioFileGetProperty</code>即可（比如BitRate），例子如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AudioFileID</span> <span class="n">fileID</span><span class="p">;</span> <span class="c1">//Open方法返回的AudioFileID</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//获取格式信息</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">formatListSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileGetPropertyInfo</span><span class="p">(</span><span class="n">_fileID</span><span class="p">,</span> <span class="n">kAudioFilePropertyFormatList</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">formatListSize</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">AudioFormatListItem</span> <span class="o">*</span><span class="n">formatList</span> <span class="o">=</span> <span class="p">(</span><span class="n">AudioFormatListItem</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="n">formatListSize</span><span class="p">);</span>
</span><span class='line'>    <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileGetProperty</span><span class="p">(</span><span class="n">fileID</span><span class="p">,</span> <span class="n">kAudioFilePropertyFormatList</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">formatListSize</span><span class="p">,</span> <span class="n">formatList</span><span class="p">);</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">AudioFormatListItem</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">formatListSize</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">AudioFormatListItem</span><span class="p">))</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">AudioStreamBasicDescription</span> <span class="n">pasbd</span> <span class="o">=</span> <span class="n">formatList</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mASBD</span><span class="p">;</span>
</span><span class='line'>            <span class="c1">//选择需要的格式。。                             </span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="n">free</span><span class="p">(</span><span class="n">formatList</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//获取码率</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">bitRate</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">bitRateSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bitRate</span><span class="p">);</span>
</span><span class='line'><span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileGetProperty</span><span class="p">(</span><span class="n">fileID</span><span class="p">,</span> <span class="n">kAudioFilePropertyBitRate</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bitRate</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以获取的属性有下面这些，大家可以参考文档来获取自己需要的信息（注意到这里有EstimatedDuration，可以得到Duration了）：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">enum</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyFileFormat</span>             <span class="o">=</span>    <span class="err">&#39;</span><span class="n">ffmt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyDataFormat</span>             <span class="o">=</span>    <span class="err">&#39;</span><span class="n">dfmt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyIsOptimized</span>            <span class="o">=</span>    <span class="err">&#39;</span><span class="n">optm</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyMagicCookieData</span>        <span class="o">=</span>    <span class="err">&#39;</span><span class="n">mgic</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyAudioDataByteCount</span>     <span class="o">=</span>    <span class="err">&#39;</span><span class="n">bcnt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyAudioDataPacketCount</span>   <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pcnt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyMaximumPacketSize</span>      <span class="o">=</span>    <span class="err">&#39;</span><span class="n">psze</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyDataOffset</span>             <span class="o">=</span>    <span class="err">&#39;</span><span class="n">doff</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyChannelLayout</span>          <span class="o">=</span>    <span class="err">&#39;</span><span class="n">cmap</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyDeferSizeUpdates</span>       <span class="o">=</span>    <span class="err">&#39;</span><span class="n">dszu</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyMarkerList</span>             <span class="o">=</span>    <span class="err">&#39;</span><span class="n">mkls</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyRegionList</span>             <span class="o">=</span>    <span class="err">&#39;</span><span class="n">rgls</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyChunkIDs</span>               <span class="o">=</span>    <span class="err">&#39;</span><span class="n">chid</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyInfoDictionary</span>         <span class="o">=</span>    <span class="err">&#39;</span><span class="n">info</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyPacketTableInfo</span>        <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pnfo</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyFormatList</span>             <span class="o">=</span>    <span class="err">&#39;</span><span class="n">flst</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyPacketSizeUpperBound</span>   <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pkub</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyReserveDuration</span>        <span class="o">=</span>    <span class="err">&#39;</span><span class="n">rsrv</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyEstimatedDuration</span>      <span class="o">=</span>    <span class="err">&#39;</span><span class="n">edur</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyBitRate</span>                <span class="o">=</span>    <span class="err">&#39;</span><span class="n">brat</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyID3Tag</span>                 <span class="o">=</span>    <span class="err">&#39;</span><span class="n">id3t</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertySourceBitDepth</span>         <span class="o">=</span>    <span class="err">&#39;</span><span class="n">sbtd</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyAlbumArtwork</span>           <span class="o">=</span>    <span class="err">&#39;</span><span class="n">aart</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyAudioTrackCount</span>        <span class="o">=</span>    <span class="err">&#39;</span><span class="n">atct</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFilePropertyUseAudioTrack</span>          <span class="o">=</span>    <span class="err">&#39;</span><span class="n">uatk</span><span class="err">&#39;</span>
</span><span class='line'><span class="p">};</span> 
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>读取音频数据</h1>

<p>读取音频数据的方法分为两类：</p>

<p>1、直接读取音频数据：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileReadBytes</span> <span class="p">(</span><span class="n">AudioFileID</span> <span class="n">inAudioFile</span><span class="p">,</span>
</span><span class='line'>                                    <span class="n">Boolean</span> <span class="n">inUseCache</span><span class="p">,</span>
</span><span class='line'>                                    <span class="n">SInt64</span> <span class="n">inStartingByte</span><span class="p">,</span>
</span><span class='line'>                                    <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioNumBytes</span><span class="p">,</span>
</span><span class='line'>                                    <span class="kt">void</span> <span class="o">*</span> <span class="n">outBuffer</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第一个参数，FileID；</p>

<p>第二个参数，是否需要cache，一般来说传false；</p>

<p>第三个参数，从第几个byte开始读取数据</p>

<p>第四个参数，这个参数在调用时作为输入参数表示需要读取读取多少数据，调用完成后作为输出参数表示实际读取了多少数据（即Read回调中的requestCount和actualCount）；</p>

<p>第五个参数，buffer指针，需要事先分配好足够大的内存（ioNumBytes大，即Read回调中的buffer，所以Read回调中不需要再分配内存）；</p>

<p>返回值表示是否读取成功，EOF时会返回<code>kAudioFileEndOfFileError</code>；</p>

<p>使用这个方法得到的数据都是没有进行过帧分离的数据，如果想要用来播放或者解码还必须通过<code>AudioFileStream</code>进行帧分离；</p>

<p>2、按帧（Packet）读取音频数据：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileReadPacketData</span> <span class="p">(</span><span class="n">AudioFileID</span> <span class="n">inAudioFile</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">Boolean</span> <span class="n">inUseCache</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioNumBytes</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">AudioStreamPacketDescription</span> <span class="o">*</span> <span class="n">outPacketDescriptions</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">SInt64</span> <span class="n">inStartingPacket</span><span class="p">,</span>
</span><span class='line'>                                         <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioNumPackets</span><span class="p">,</span>
</span><span class='line'>                                         <span class="kt">void</span> <span class="o">*</span> <span class="n">outBuffer</span><span class="p">);</span>
</span><span class='line'>                                      
</span><span class='line'>
</span><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileReadPackets</span> <span class="p">(</span><span class="n">AudioFileID</span> <span class="n">inAudioFile</span><span class="p">,</span>
</span><span class='line'>                                      <span class="n">Boolean</span> <span class="n">inUseCache</span><span class="p">,</span>
</span><span class='line'>                                      <span class="n">UInt32</span> <span class="o">*</span> <span class="n">outNumBytes</span><span class="p">,</span>
</span><span class='line'>                                      <span class="n">AudioStreamPacketDescription</span> <span class="o">*</span> <span class="n">outPacketDescriptions</span><span class="p">,</span>
</span><span class='line'>                                      <span class="n">SInt64</span> <span class="n">inStartingPacket</span><span class="p">,</span>
</span><span class='line'>                                      <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioNumPackets</span><span class="p">,</span>
</span><span class='line'>                                      <span class="kt">void</span> <span class="o">*</span> <span class="n">outBuffer</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>按帧读取的方法有两个，这两个方法看上去差不多，就连参数也几乎相同，但使用场景和效率上却有所不同，<a href="https://developer.apple.com/library/mac/documentation/musicaudio/reference/AudioFileConvertRef/Reference/reference.html#//apple_ref/c/func/AudioFileCreateWithURL">官方文档</a>中如此描述这两个方法：</p>

<ul>
<li><code>AudioFileReadPacketData</code> is memory efficient when reading variable bit-rate (VBR) audio data;</li>
<li><code>AudioFileReadPacketData</code> is more efficient than <code>AudioFileReadPackets</code> when reading compressed file formats that do not have packet tables, such as MP3 or ADTS. This function is a good choice for reading either CBR (constant bit-rate) or VBR data if you do not need to read a fixed duration of audio.</li>
<li>Use <code>AudioFileReadPackets</code> only when you need to read a fixed duration of audio data, or when you are reading only uncompressed audio.</li>
</ul>


<p>只有当需要读取固定时长音频或者非压缩音频时才会用到<code>AudioFileReadPackets</code>，其余时候使用<code>AudioFileReadPacketData</code>会有更高的效率并且更省内存；</p>

<p>下面来看看这些参数：</p>

<p>第一、二个参数，同<code>AudioFileReadBytes</code>；</p>

<p>第三个参数，对于<code>AudioFileReadPacketData</code>来说ioNumBytes这个参数在输入输出时都要用到，在输入时表示outBuffer的size，输出时表示实际读取了多少size的数据。而对<code>AudioFileReadPackets</code>来说outNumBytes只在输出时使用，表示实际读取了多少size的数据；</p>

<p>第四个参数，帧信息数组指针，在输入前需要分配内存，大小必须足够存在ioNumPackets个帧信息（ioNumPackets * sizeof(AudioStreamPacketDescription)）；</p>

<p>第五个参数，从第几帧开始读取数据；</p>

<p>第六个参数，在输入时表示需要读取多少个帧，在输出时表示实际读取了多少帧；</p>

<p>第七个参数，outBuffer数据指针，在输入前就需要分配好空间，这个参数看上去两个方法一样但其实并非如此。对于<code>AudioFileReadPacketData</code>来说只要分配<code>近似帧大小 * 帧数</code>的内存空间即可，方法本身会针对给定的内存空间大小来决定最后输出多少个帧，如果空间不够会适当减少出的帧数；而对于<code>AudioFileReadPackets</code>来说则需要分配<code>最大帧大小(或帧大小上界) * 帧数</code>的内存空间才行（最大帧大小和帧大小上界的区别等下会说）；这也就是为何第三个参数一个是输入输出双向使用的，而另一个只是输出时使用的原因。就这点来说两个方法中前者在使用的过程中要比后者更省内存；</p>

<p>返回值，同<code>AudioFileReadBytes</code>；</p>

<p>这两个方法读取后的数据为帧分离后的数据，可以直接用来播放或者解码。</p>

<p>下面给出两个方法的使用代码（以MP3为例）：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AudioFileID</span> <span class="n">fileID</span><span class="p">;</span> <span class="c1">//Open方法返回的AudioFileID</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">ioNumPackets</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//要读取多少个packet</span>
</span><span class='line'><span class="n">SInt64</span> <span class="n">inStartingPacket</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//从第几个Packet开始读取</span>
</span><span class='line'>
</span><span class='line'><span class="n">UInt32</span> <span class="n">bitRate</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//AudioFileGetProperty读取kAudioFilePropertyBitRate</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">sampleRate</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//AudioFileGetProperty读取kAudioFilePropertyDataFormat或kAudioFilePropertyFormatList</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">byteCountPerPacket</span> <span class="o">=</span> <span class="mi">144</span> <span class="o">*</span> <span class="n">bitRate</span> <span class="o">/</span> <span class="n">sampleRate</span><span class="p">;</span> <span class="c1">//MP3数据每个Packet的近似大小</span>
</span><span class='line'>
</span><span class='line'><span class="n">UInt32</span> <span class="n">descSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">AudioStreamPacketDescription</span><span class="p">)</span> <span class="o">*</span> <span class="n">ioNumPackets</span><span class="p">;</span>
</span><span class='line'><span class="n">AudioStreamPacketDescription</span> <span class="o">*</span> <span class="n">outPacketDescriptions</span> <span class="o">=</span> <span class="p">(</span><span class="n">AudioStreamPacketDescription</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="n">descSize</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">UInt32</span> <span class="n">ioNumBytes</span> <span class="o">=</span> <span class="n">byteCountPerPacket</span> <span class="o">*</span> <span class="n">ioNumPackets</span><span class="p">;</span>
</span><span class='line'><span class="kt">void</span> <span class="o">*</span> <span class="n">outBuffer</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="n">ioNumBytes</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileReadPacketData</span><span class="p">(</span><span class="n">fileID</span><span class="p">,</span>
</span><span class='line'>                                          <span class="n">false</span><span class="p">,</span>
</span><span class='line'>                                          <span class="o">&amp;</span><span class="n">ioNumBytes</span><span class="p">,</span>
</span><span class='line'>                                          <span class="n">outPacketDescriptions</span><span class="p">,</span>
</span><span class='line'>                                          <span class="n">inStartingPacket</span><span class="p">,</span>
</span><span class='line'>                                          <span class="o">&amp;</span><span class="n">ioNumPackets</span><span class="p">,</span>
</span><span class='line'>                                          <span class="n">outBuffer</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">AudioFileID</span> <span class="n">fileID</span><span class="p">;</span> <span class="c1">//Open方法返回的AudioFileID</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">ioNumPackets</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//要读取多少个packet</span>
</span><span class='line'><span class="n">SInt64</span> <span class="n">inStartingPacket</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//从第几个Packet开始读取</span>
</span><span class='line'>
</span><span class='line'><span class="n">UInt32</span> <span class="n">maxByteCountPerPacket</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//AudioFileGetProperty读取kAudioFilePropertyMaximumPacketSize，最大的packet大小</span>
</span><span class='line'><span class="c1">//也可以用：</span>
</span><span class='line'><span class="c1">//UInt32 byteCountUpperBoundPerPacket = ...; //AudioFileGetProperty读取kAudioFilePropertyPacketSizeUpperBound，当前packet大小上界（未扫描全文件的情况下）</span>
</span><span class='line'>
</span><span class='line'><span class="n">UInt32</span> <span class="n">descSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">AudioStreamPacketDescription</span><span class="p">)</span> <span class="o">*</span> <span class="n">ioNumPackets</span><span class="p">;</span>
</span><span class='line'><span class="n">AudioStreamPacketDescription</span> <span class="o">*</span> <span class="n">outPacketDescriptions</span> <span class="o">=</span> <span class="p">(</span><span class="n">AudioStreamPacketDescription</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="n">descSize</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">UInt32</span> <span class="n">outNumBytes</span> <span class="o">=</span> <span class="mi">0</span><span class="err">；</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">ioNumBytes</span> <span class="o">=</span> <span class="n">maxByteCountPerPacket</span> <span class="o">*</span> <span class="n">ioNumPackets</span><span class="p">;</span>
</span><span class='line'><span class="kt">void</span> <span class="o">*</span> <span class="n">outBuffer</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="n">ioNumBytes</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileReadPackets</span><span class="p">(</span><span class="n">fileID</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">false</span><span class="p">,</span>
</span><span class='line'>                                       <span class="o">&amp;</span><span class="n">outNumBytes</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">outPacketDescriptions</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">inStartingPacket</span><span class="p">,</span>
</span><span class='line'>                                       <span class="o">&amp;</span><span class="n">ioNumPackets</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">outBuffer</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>Seek</h1>

<p>seek的思路和之前讲<code>AudioFileStream</code>时讲到的是一样的，区别在于AudioFile没有方法来帮助修正seek的offset和seek的时间：</p>

<ul>
<li>使用<code>AudioFileReadBytes</code>时需要计算出approximateSeekOffset</li>
<li>使用<code>AudioFileReadPacketData</code>或者<code>AudioFileReadPackets</code>时需要计算出seekToPacket</li>
</ul>


<p>approximateSeekOffset和seekToPacket的计算方法参见<a href="http://msching.github.io/blog/2014/07/09/audio-in-ios-3/">第三篇</a>。</p>

<hr />

<h1>关闭AudioFile</h1>

<p><code>AudioFile</code>使用完毕后需要调用<code>AudioFileClose</code>进行关闭，没啥特别需要注意的。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileClose</span> <span class="p">(</span><span class="n">AudioFileID</span> <span class="n">inAudioFile</span><span class="p">);</span>  
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>小结</h1>

<p>本篇针对<code>AudioFile</code>的音频读取功能做了介绍，小结一下：</p>

<ul>
<li><p><code>AudioFile</code>有两个Open方法，需要针对自身的使用场景选择不同的方法；</p></li>
<li><p><code>AudioFileOpenURL</code>用来读取本地文件</p></li>
<li><p><code>AudioFileOpenWithCallbacks</code>的使用场景比前者要广泛，使用时需要注意<code>AudioFile_ReadProc</code>，这个回调方法在Open方法本身和Read方法被调用时会被<code>同步</code>调用</p></li>
<li><p>必须保证音频文件格式信息可读时才能使用<code>AudioFile</code>的Open方法，AudioFile并不能独立用于音频流的读取，需要配合<code>AudioStreamFile</code>使用才能读取流（需要用<code>AudioStreamFile</code>来判断文件格式信息可读之后再调用Open方法）；</p></li>
<li><p>使用<code>AudioFileGetProperty</code>读取格式信息时需要判断所读取的信息是否需要先调用<code>AudioFileGetPropertyInfo</code>获得数据大小后再进行读取；</p></li>
<li><p>读取音频数据应该根据使用的场景选择不同的音频读取方法，对于不同的读取方法seek时需要计算的变量也不相同；</p></li>
<li><p><code>AudioFile</code>使用完毕后需要调用<code>AudioFileClose</code>进行关闭；</p></li>
</ul>


<hr />

<h1>示例代码</h1>

<p>对于流播放中的AudioFile使用推荐大家阅读豆瓣的开源播放器代码<a href="https://github.com/douban/DOUAudioStreamer">DOUAudioStreamer</a>。</p>

<div class="github-card" data-github="douban/DOUAudioStreamer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<p><del>对于本地文件用AudioFile读取比较简单就不在这里提供demo了，</del></p>

<p><a href="https://github.com/msching/MCAudioFile">简单的AudioFile封装</a>。</p>

<div class="github-card" data-github="msching/MCAudioFile" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<hr />

<h1>下篇预告</h1>

<p>下一篇将讲述如何使用<code>AudioQueue</code>。</p>

<hr />

<h1>参考资料</h1>

<p><a href="https://developer.apple.com/library/mac/documentation/musicaudio/reference/AudioFileConvertRef/Reference/reference.html#//apple_ref/c/func/AudioFileCreateWithURL">Audio File Services Reference</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (三)：AudioFileStream]]></title>
    <link href="http://msching.github.io/blog/2014/07/09/audio-in-ios-3/"/>
    <updated>2014-07-09T11:31:28+08:00</updated>
    <id>http://msching.github.io/blog/2014/07/09/audio-in-ios-3</id>
    <content type="html"><![CDATA[<p>本来说好是要在第三篇中讲<code>AudioFileStream</code>和<code>AudioQueue</code>，但写着写着发现光<code>AudioFileStream</code>就好多内容，最后还是决定分篇介绍，这篇先来说一下<code>AudioFileStream</code>，下一篇计划说一下和<code>AudioFileStream</code>类似的<code>AudioFile</code>，下下篇再来说<code>AudioQueue</code>。</p>

<p>在本篇那种将会提到计算音频时长duration和音频seek的方法，这些方法对于CBR编码形式的音频文件可以做到比较精确而对于VBR编码形式的会存在较大的误差（关于CBR和VBR，请看本系列的<a href="http://msching.github.io/blog/2014/07/07/audio-in-ios/">第一篇</a>），具体讲到duration和seek时会再进行说明。</p>

<!--more-->


<hr />

<h1>AudioFileStream介绍</h1>

<p>在<a href="http://msching.github.io/blog/2014/07/07/audio-in-ios/">第一篇</a>中说到<code>AudioFileStreamer</code>时提到它的作用是用来读取采样率、码率、时长等基本信息以及分离音频帧。那么在<a href="https://developer.apple.com/library/ios/documentation/audiovideo/conceptual/multimediapg/usingaudio/usingaudio.html#//apple_ref/doc/uid/TP40009767-CH2-SW28">官方文档</a>中Apple是这样描述的：</p>

<p><code>To play streamed audio content, such as from a network connection, use Audio File Stream Services in concert with Audio Queue Services. Audio File Stream Services parses audio packets and metadata from common audio file container formats in a network bitstream. You can also use it to parse packets and metadata from on-disk files</code></p>

<p>根据Apple的描述<code>AudioFileStreamer</code>用在流播放中，当然不仅限于网络流，本地文件同样可以用它来读取信息和分离音频帧。<code>AudioFileStreamer</code>的主要数据是文件数据而不是文件路径，所以数据的读取需要使用者自行实现，</p>

<p>支持的文件格式有：</p>

<ul>
<li>MPEG-1 Audio Layer 3, used for .mp3 files</li>
<li>MPEG-2 ADTS, used for the .aac audio data format</li>
<li>AIFC</li>
<li>AIFF</li>
<li>CAF</li>
<li>MPEG-4, used for .m4a, .mp4, and .3gp files</li>
<li>NeXT</li>
<li>WAVE</li>
</ul>


<p>上述格式是iOS、MacOSX所支持的音频格式，这类格式可以被系统提供的API解码，如果想要解码其他的音频格式（如OGG、APE、FLAC）就需要自己实现解码器了。</p>

<hr />

<h1>初始化AudioFileStream</h1>

<p>第一步，自然是要生成一个<code>AudioFileStream</code>的实例：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileStreamOpen</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                     <span class="n">AudioFileStream_PropertyListenerProc</span> <span class="n">inPropertyListenerProc</span><span class="p">,</span>
</span><span class='line'>                                     <span class="n">AudioFileStream_PacketsProc</span> <span class="n">inPacketsProc</span><span class="p">,</span>
</span><span class='line'>                                     <span class="n">AudioFileTypeID</span> <span class="n">inFileTypeHint</span><span class="p">,</span>
</span><span class='line'>                                     <span class="n">AudioFileStreamID</span> <span class="o">*</span> <span class="n">outAudioFileStream</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第一个参数和之前的AudioSession的初始化方法一样是一个上下文对象；</p>

<p>第二个参数<code>AudioFileStream_PropertyListenerProc</code>是歌曲信息解析的回调，每解析出一个歌曲信息都会进行一次回调；</p>

<p>第三个参数<code>AudioFileStream_PacketsProc</code>是分离帧的回调，每解析出一部分帧就会进行一次回调；</p>

<p>第四个参数<code>AudioFileTypeID</code>是文件类型的提示，这个参数来帮助<code>AudioFileStream</code>对文件格式进行解析。这个参数在文件信息不完整（例如信息有缺陷）时尤其有用，它可以给与<code>AudioFileStream</code>一定的提示，帮助其绕过文件中的错误或者缺失从而成功解析文件。所以在确定文件类型的情况下建议各位还是填上这个参数，如果无法确定可以传入0（原理上应该和<a href="http://msching.github.io/blog/2014/05/04/secret-of-avaudioplayer/">这篇博文</a>近似）；</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioFileTypeID枚举</span>
</span><span class='line'><span class="k">enum</span> <span class="p">{</span>
</span><span class='line'>        <span class="n">kAudioFileAIFFType</span>             <span class="o">=</span> <span class="err">&#39;</span><span class="n">AIFF</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileAIFCType</span>             <span class="o">=</span> <span class="err">&#39;</span><span class="n">AIFC</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileWAVEType</span>             <span class="o">=</span> <span class="err">&#39;</span><span class="n">WAVE</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileSoundDesigner2Type</span>   <span class="o">=</span> <span class="err">&#39;</span><span class="n">Sd2f</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileNextType</span>             <span class="o">=</span> <span class="err">&#39;</span><span class="n">NeXT</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileMP3Type</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">MPG3</span><span class="err">&#39;</span><span class="p">,</span>    <span class="c1">// mpeg layer 3</span>
</span><span class='line'>        <span class="n">kAudioFileMP2Type</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">MPG2</span><span class="err">&#39;</span><span class="p">,</span>    <span class="c1">// mpeg layer 2</span>
</span><span class='line'>        <span class="n">kAudioFileMP1Type</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">MPG1</span><span class="err">&#39;</span><span class="p">,</span>    <span class="c1">// mpeg layer 1</span>
</span><span class='line'>        <span class="n">kAudioFileAC3Type</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">ac</span><span class="o">-</span><span class="mi">3</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileAAC_ADTSType</span>         <span class="o">=</span> <span class="err">&#39;</span><span class="n">adts</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileMPEG4Type</span>            <span class="o">=</span> <span class="err">&#39;</span><span class="n">mp4f</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileM4AType</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">m4af</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileM4BType</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">m4bf</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFileCAFType</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">caff</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFile3GPType</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="mi">3</span><span class="n">gpp</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>        <span class="n">kAudioFile3GP2Type</span>             <span class="o">=</span> <span class="err">&#39;</span><span class="mi">3</span><span class="n">gp2</span><span class="err">&#39;</span><span class="p">,</span>        
</span><span class='line'>        <span class="n">kAudioFileAMRType</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">amrf</span><span class="err">&#39;</span>        
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>第五个参数是返回的AudioFileStream实例对应的<code>AudioFileStreamID</code>，这个ID需要保存起来作为后续一些方法的参数使用；</p>

<p>返回值用来判断是否成功初始化（OSStatus == noErr）。</p>

<hr />

<h1>解析数据</h1>

<p>在初始化完成之后，只要拿到文件数据就可以进行解析了。解析时调用方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileStreamParseBytes</span><span class="p">(</span><span class="n">AudioFileStreamID</span> <span class="n">inAudioFileStream</span><span class="p">,</span>
</span><span class='line'>                                          <span class="n">UInt32</span> <span class="n">inDataByteSize</span><span class="p">,</span>
</span><span class='line'>                                          <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">inData</span><span class="p">,</span>
</span><span class='line'>                                          <span class="n">UInt32</span> <span class="n">inFlags</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第一个参数<code>AudioFileStreamID</code>，即初始化时返回的ID；</p>

<p>第二个参数inDataByteSize，本次解析的数据长度；</p>

<p>第三个参数inData，本次解析的数据；</p>

<p>第四个参数是说本次的解析和上一次解析是否是连续的关系，如果是连续的传入0，否则传入<code>kAudioFileStreamParseFlag_Discontinuity</code>。</p>

<p>这里需要插入解释一下何谓“连续”。在第一篇中我们提到过形如MP3的数据都以帧的形式存在的，解析时也需要以帧为单位解析。但在解码之前我们不可能知道每个帧的边界在第几个字节，所以就会出现这样的情况：我们传给AudioFileStreamParseBytes的数据在解析完成之后会有一部分数据余下来，这部分数据是接下去那一帧的前半部分，如果再次有数据输入需要继续解析时就必须要用到前一次解析余下来的数据才能保证帧数据完整，所以在正常播放的情况下传入0即可。目前知道的需要传入<code>kAudioFileStreamParseFlag_Discontinuity</code>的情况有两个，一个是在<strong>seek完毕之后</strong>显然seek后的数据和之前的数据完全无关；另一个是开源播放器<a href="https://github.com/mattgallagher/AudioStreamer">AudioStreamer</a>的作者@Matt Gallagher曾在自己的<a href="http://www.cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html">blog</a>中提到过的：</p>

<p><code>the Audio File Stream Services hit me with a nasty bug: AudioFileStreamParseBytes will crash when trying to parse a streaming MP3.</code></p>

<p><code>In this case, if we pass the kAudioFileStreamParseFlag_Discontinuity flag to AudioFileStreamParseBytes on every invocation between receiving kAudioFileStreamProperty_ReadyToProducePackets and the first successful call to MyPacketsProc, then AudioFileStreamParseBytes will be extra cautious in its approach and won't crash.</code></p>

<p>Matt发布这篇blog是在2008年，这个Bug年代相当久远了，而且原因未知，究竟是否修复也不得而知，而且由于环境不同（比如测试用的mp3文件和所处的iOS系统）无法重现这个问题，所以我个人觉得还是按照Matt的work around在回调得到<code>kAudioFileStreamProperty_ReadyToProducePackets</code>之后，在正常解析第一帧之前都传入<code>kAudioFileStreamParseFlag_Discontinuity</code>比较好。</p>

<p>回到之前的内容，<code>AudioFileStreamParseBytes</code>方法的返回值表示当前的数据是否被正常解析，如果OSStatus的值不是noErr则表示解析不成功，其中错误码包括：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">enum</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_UnsupportedFileType</span>        <span class="o">=</span> <span class="err">&#39;</span><span class="n">typ</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_UnsupportedDataFormat</span>      <span class="o">=</span> <span class="err">&#39;</span><span class="n">fmt</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_UnsupportedProperty</span>        <span class="o">=</span> <span class="err">&#39;</span><span class="n">pty</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_BadPropertySize</span>            <span class="o">=</span> <span class="err">&#39;</span><span class="o">!</span><span class="n">siz</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_NotOptimized</span>               <span class="o">=</span> <span class="err">&#39;</span><span class="n">optm</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_InvalidPacketOffset</span>        <span class="o">=</span> <span class="err">&#39;</span><span class="n">pck</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_InvalidFile</span>                <span class="o">=</span> <span class="err">&#39;</span><span class="n">dta</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_ValueUnknown</span>               <span class="o">=</span> <span class="err">&#39;</span><span class="n">unk</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_DataUnavailable</span>            <span class="o">=</span> <span class="err">&#39;</span><span class="n">more</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_IllegalOperation</span>           <span class="o">=</span> <span class="err">&#39;</span><span class="n">nope</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_UnspecifiedError</span>           <span class="o">=</span> <span class="err">&#39;</span><span class="n">wht</span><span class="o">?</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamError_DiscontinuityCantRecover</span>   <span class="o">=</span> <span class="err">&#39;</span><span class="n">dsc</span><span class="o">!</span><span class="err">&#39;</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>大多数都可以从字面上理解，需要提一下的是<code>kAudioFileStreamError_NotOptimized</code>，文档上是这么说的：</p>

<p><code>It is not possible to produce output packets because the file's packet table or other defining info is either not present or is after the audio data.</code></p>

<p>它的含义是说这个音频文件的文件头不存在或者说文件头可能在文件的末尾，当前无法正常Parse，换句话说就是这个文件需要全部下载完才能播放，无法流播。</p>

<p><strong>注意<code>AudioFileStreamParseBytes</code>方法每一次调用都应该注意返回值，一旦出现错误就可以不必继续Parse了。</strong></p>

<hr />

<h1>解析文件格式信息</h1>

<p>在调用<code>AudioFileStreamParseBytes</code>方法进行解析时会首先读取格式信息，并同步的进入<code>AudioFileStream_PropertyListenerProc</code>回调方法</p>

<p><img src="http://msching.github.io/images/iOS-audio/audiofilestreamParse-1.jpg" alt="" /></p>

<p>来看一下这个回调方法的定义</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">AudioFileStream_PropertyListenerProc</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                                     <span class="n">AudioFileStreamID</span> <span class="n">inAudioFileStream</span><span class="p">,</span>
</span><span class='line'>                                                     <span class="n">AudioFileStreamPropertyID</span> <span class="n">inPropertyID</span><span class="p">,</span>
</span><span class='line'>                                                     <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioFlags</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>回调的第一个参数是Open方法中的上下文对象；</p>

<p>第二个参数inAudioFileStream是和Open方法中第四个返回参数<code>AudioFileStreamID</code>一样，表示当前FileStream的ID；</p>

<p>第三个参数是此次回调解析的信息ID。表示当前PropertyID对应的信息已经解析完成信息（例如数据格式、音频数据的偏移量等等），使用者可以通过<code>AudioFileStreamGetProperty</code>接口获取PropertyID对应的值或者数据结构；</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileStreamGetProperty</span><span class="p">(</span><span class="n">AudioFileStreamID</span> <span class="n">inAudioFileStream</span><span class="p">,</span>
</span><span class='line'>                                           <span class="n">AudioFileStreamPropertyID</span> <span class="n">inPropertyID</span><span class="p">,</span>
</span><span class='line'>                                           <span class="n">UInt32</span> <span class="o">*</span> <span class="n">ioPropertyDataSize</span><span class="p">,</span>
</span><span class='line'>                                           <span class="kt">void</span> <span class="o">*</span> <span class="n">outPropertyData</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第四个参数ioFlags是一个返回参数，表示这个property是否需要被缓存，如果需要赋值<code>kAudioFileStreamPropertyFlag_PropertyIsCached</code>否则不赋值（这个参数我也不知道应该在啥场景下使用。。一直都没去理他）；</p>

<p>这个回调会进来多次，但并不是每一次都需要进行处理，可以根据需求处理需要的PropertyID进行处理（PropertyID列表如下）。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioFileStreamProperty枚举</span>
</span><span class='line'><span class="k">enum</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_ReadyToProducePackets</span>           <span class="o">=</span>    <span class="err">&#39;</span><span class="n">redy</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_FileFormat</span>                      <span class="o">=</span>    <span class="err">&#39;</span><span class="n">ffmt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_DataFormat</span>                      <span class="o">=</span>    <span class="err">&#39;</span><span class="n">dfmt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_FormatList</span>                      <span class="o">=</span>    <span class="err">&#39;</span><span class="n">flst</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_MagicCookieData</span>                 <span class="o">=</span>    <span class="err">&#39;</span><span class="n">mgic</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_AudioDataByteCount</span>              <span class="o">=</span>    <span class="err">&#39;</span><span class="n">bcnt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_AudioDataPacketCount</span>            <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pcnt</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_MaximumPacketSize</span>               <span class="o">=</span>    <span class="err">&#39;</span><span class="n">psze</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_DataOffset</span>                      <span class="o">=</span>    <span class="err">&#39;</span><span class="n">doff</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_ChannelLayout</span>                   <span class="o">=</span>    <span class="err">&#39;</span><span class="n">cmap</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_PacketToFrame</span>                   <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pkfr</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_FrameToPacket</span>                   <span class="o">=</span>    <span class="err">&#39;</span><span class="n">frpk</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_PacketToByte</span>                    <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pkby</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_ByteToPacket</span>                    <span class="o">=</span>    <span class="err">&#39;</span><span class="n">bypk</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_PacketTableInfo</span>                 <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pnfo</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_PacketSizeUpperBound</span>            <span class="o">=</span>    <span class="err">&#39;</span><span class="n">pkub</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_AverageBytesPerPacket</span>           <span class="o">=</span>    <span class="err">&#39;</span><span class="n">abpp</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_BitRate</span>                         <span class="o">=</span>    <span class="err">&#39;</span><span class="n">brat</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="n">kAudioFileStreamProperty_InfoDictionary</span>                  <span class="o">=</span>    <span class="err">&#39;</span><span class="n">info</span><span class="err">&#39;</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里列几个我认为比较重要的PropertyID：</p>

<p>1、<code>kAudioFileStreamProperty_BitRate</code>：</p>

<p>表示音频数据的码率，获取这个Property是为了计算音频的总时长Duration（因为AudioFileStream没有这样的接口。。）。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">UInt32</span> <span class="n">bitRate</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">bitRateSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bitRate</span><span class="p">);</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamGetProperty</span><span class="p">(</span><span class="n">inAudioFileStream</span><span class="p">,</span> <span class="n">kAudioFileStreamProperty_BitRate</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bitRateSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bitRate</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>2014.8.2 补充：</strong>
发现在流播放的情况下，有时数据流量比较小时会出现<code>ReadyToProducePackets</code>还是没有获取到bitRate的情况，这时就需要分离一些拼音帧然后计算平均bitRate，计算公式如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">UInt32</span> <span class="n">averageBitRate</span> <span class="o">=</span> <span class="n">totalPackectByteCount</span> <span class="o">/</span> <span class="n">totalPacketCout</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>2、<code>kAudioFileStreamProperty_DataOffset</code>：</p>

<p>表示音频数据在整个音频文件中的offset（因为大多数音频文件都会有一个文件头之后才使真正的音频数据），这个值在seek时会发挥比较大的作用，音频的seek并不是直接seek文件位置而seek时间（比如seek到2分10秒的位置），seek时会根据时间计算出音频数据的字节offset然后需要再加上音频数据的offset才能得到在文件中的真正offset。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">SInt64</span> <span class="n">dataOffset</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">offsetSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dataOffset</span><span class="p">);</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamGetProperty</span><span class="p">(</span><span class="n">inAudioFileStream</span><span class="p">,</span> <span class="n">kAudioFileStreamProperty_DataOffset</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">offsetSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dataOffset</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>3、<code>kAudioFileStreamProperty_DataFormat</code></p>

<p>表示音频文件结构信息，是一个AudioStreamBasicDescription的结构</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">struct</span> <span class="n">AudioStreamBasicDescription</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">Float64</span> <span class="n">mSampleRate</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mFormatID</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mFormatFlags</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mBytesPerPacket</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mFramesPerPacket</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mBytesPerFrame</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mChannelsPerFrame</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mBitsPerChannel</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mReserved</span><span class="p">;</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="n">AudioStreamBasicDescription</span> <span class="n">asbd</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">asbdSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">asbd</span><span class="p">);</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamGetProperty</span><span class="p">(</span><span class="n">inAudioFileStream</span><span class="p">,</span> <span class="n">kAudioFileStreamProperty_DataFormat</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">asbdSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">asbd</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>  
</span></code></pre></td></tr></table></div></figure>


<p>4、<code>kAudioFileStreamProperty_FormatList</code></p>

<p>作用和<code>kAudioFileStreamProperty_DataFormat</code>是一样的，区别在于用这个PropertyID获取到是一个AudioStreamBasicDescription的数组，这个参数是用来支持AAC SBR这样的包含多个文件类型的音频格式。由于到底有多少个format我们并不知晓，所以需要先获取一下总数据大小：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//获取数据大小</span>
</span><span class='line'><span class="n">Boolean</span> <span class="n">outWriteable</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">formatListSize</span><span class="p">;</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamGetPropertyInfo</span><span class="p">(</span><span class="n">inAudioFileStream</span><span class="p">,</span> <span class="n">kAudioFileStreamProperty_FormatList</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">formatListSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">outWriteable</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//获取formatlist</span>
</span><span class='line'><span class="n">AudioFormatListItem</span> <span class="o">*</span><span class="n">formatList</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">formatListSize</span><span class="p">);</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamGetProperty</span><span class="p">(</span><span class="n">inAudioFileStream</span><span class="p">,</span> <span class="n">kAudioFileStreamProperty_FormatList</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">formatListSize</span><span class="p">,</span> <span class="n">formatList</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//选择需要的格式</span>
</span><span class='line'><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">AudioFormatListItem</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">formatListSize</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">AudioStreamBasicDescription</span> <span class="n">pasbd</span> <span class="o">=</span> <span class="n">formatList</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mASBD</span><span class="p">;</span>
</span><span class='line'>    <span class="c1">//选择需要的格式。。                             </span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="n">free</span><span class="p">(</span><span class="n">formatList</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>5、<code>kAudioFileStreamProperty_AudioDataByteCount</code></p>

<p>顾名思义，音频文件中音频数据的总量。这个Property的作用一是用来计算音频的总时长，二是可以在seek时用来计算时间对应的字节offset。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">UInt64</span> <span class="n">audioDataByteCount</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">byteCountSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">audioDataByteCount</span><span class="p">);</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamGetProperty</span><span class="p">(</span><span class="n">inAudioFileStream</span><span class="p">,</span> <span class="n">kAudioFileStreamProperty_AudioDataByteCount</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">byteCountSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">audioDataByteCount</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//错误处理</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>2014.8.2 补充：</strong>
发现在流播放的情况下，有时数据流量比较小时会出现<code>ReadyToProducePackets</code>还是没有获取到audioDataByteCount的情况，这时就需要近似计算audioDataByteCount。一般来说音频文件的总大小一定是可以得到的（利用文件系统或者Http请求中的contentLength），那么计算方法如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">UInt32</span> <span class="n">dataOffset</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//kAudioFileStreamProperty_DataOffset</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">fileLength</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//音频文件大小</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">audioDataByteCount</span> <span class="o">=</span> <span class="n">fileLength</span> <span class="o">-</span> <span class="n">dataOffset</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>5、<code>kAudioFileStreamProperty_ReadyToProducePackets</code></p>

<p>这个PropertyID可以不必获取对应的值，一旦回调中这个PropertyID出现就代表解析完成，接下来可以对音频数据进行帧分离了。</p>

<hr />

<h1>计算时长Duration</h1>

<p>获取时长的最佳方法是从ID3信息中去读取，那样是最准确的。如果ID3信息中没有存，那就依赖于文件头中的信息去计算了。</p>

<p>计算duration的公式如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="kt">double</span> <span class="n">duration</span> <span class="o">=</span> <span class="p">(</span><span class="n">audioDataByteCount</span> <span class="o">*</span> <span class="mi">8</span><span class="p">)</span> <span class="o">/</span> <span class="n">bitRate</span>
</span></code></pre></td></tr></table></div></figure>


<p>音频数据的字节总量audioDataByteCount可以通过<code>kAudioFileStreamProperty_AudioDataByteCount</code>获取，码率bitRate可以通过<code>kAudioFileStreamProperty_BitRate</code>获取也可以通过Parse一部分数据后计算平均码率来得到。</p>

<p>对于CBR数据来说用这样的计算方法的duration会比较准确，对于VBR数据就不好说了。所以对于VBR数据来说，最好是能够从ID3信息中获取到duration，获取不到再想办法通过计算平均码率的途径来计算duration。</p>

<hr />

<h1>分离音频帧</h1>

<p>读取格式信息完成之后继续调用<code>AudioFileStreamParseBytes</code>方法可以对帧进行分离，并同步的进入<code>AudioFileStream_PacketsProc</code>回调方法。</p>

<p><img src="http://msching.github.io/images/iOS-audio/audiofilestreamParse-2.jpg" alt="" /></p>

<p>回调的定义：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">AudioFileStream_PacketsProc</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">UInt32</span> <span class="n">numberOfBytes</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">UInt32</span> <span class="n">numberOfPackets</span><span class="p">,</span>
</span><span class='line'>                                            <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">inInputData</span><span class="p">,</span>
</span><span class='line'>                                            <span class="n">AudioStreamPacketDescription</span> <span class="o">*</span> <span class="n">inPacketDescriptions</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>第一个参数，一如既往的上下文对象；</p>

<p>第二个参数，本次处理的数据大小；</p>

<p>第三个参数，本次总共处理了多少帧（即代码里的Packet）；</p>

<p>第四个参数，本次处理的所有数据；</p>

<p>第五个参数，<code>AudioStreamPacketDescription</code>数组，存储了每一帧数据是从第几个字节开始的，这一帧总共多少字节。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioStreamPacketDescription结构</span>
</span><span class='line'><span class="c1">//这里的mVariableFramesInPacket是指实际的数据帧只有VBR的数据才能用到（像MP3这样的压缩数据一个帧里会有好几个数据帧）</span>
</span><span class='line'><span class="k">struct</span>  <span class="n">AudioStreamPacketDescription</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">SInt64</span>  <span class="n">mStartOffset</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mVariableFramesInPacket</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span>  <span class="n">mDataByteSize</span><span class="p">;</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>下面是我按照自己的理解实现的回调方法片段：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">static</span> <span class="kt">void</span> <span class="nf">MyAudioFileStreamPacketsCallBack</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                             <span class="n">UInt32</span> <span class="n">numberOfBytes</span><span class="p">,</span>
</span><span class='line'>                                             <span class="n">UInt32</span> <span class="n">numberOfPackets</span><span class="p">,</span>
</span><span class='line'>                                             <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">inInputData</span><span class="p">,</span>
</span><span class='line'>                                             <span class="n">AudioStreamPacketDescription</span>  <span class="o">*</span><span class="n">inPacketDescriptions</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="c1">//处理discontinuous..</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">numberOfBytes</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">numberOfPackets</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="kt">BOOL</span> <span class="n">deletePackDesc</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">packetDescriptioins</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="c1">//如果packetDescriptioins不存在，就按照CBR处理，平均每一帧的数据后生成packetDescriptioins</span>
</span><span class='line'>        <span class="n">deletePackDesc</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>        <span class="n">UInt32</span> <span class="n">packetSize</span> <span class="o">=</span> <span class="n">numberOfBytes</span> <span class="o">/</span> <span class="n">numberOfPackets</span><span class="p">;</span>
</span><span class='line'>        <span class="n">packetDescriptioins</span> <span class="o">=</span> <span class="p">(</span><span class="n">AudioStreamPacketDescription</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">AudioStreamPacketDescription</span><span class="p">)</span> <span class="o">*</span> <span class="n">numberOfPackets</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">numberOfPackets</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">UInt32</span> <span class="n">packetOffset</span> <span class="o">=</span> <span class="n">packetSize</span> <span class="o">*</span> <span class="n">i</span><span class="p">;</span>
</span><span class='line'>            <span class="n">descriptions</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mStartOffset</span> <span class="o">=</span> <span class="n">packetOffset</span><span class="p">;</span>
</span><span class='line'>            <span class="n">descriptions</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mVariableFramesInPacket</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>            <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">==</span> <span class="n">numberOfPackets</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span><span class='line'>            <span class="p">{</span>
</span><span class='line'>                <span class="n">packetDescriptioins</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mDataByteSize</span> <span class="o">=</span> <span class="n">numberOfBytes</span> <span class="o">-</span> <span class="n">packetOffset</span><span class="p">;</span>
</span><span class='line'>            <span class="p">}</span>
</span><span class='line'>            <span class="k">else</span>
</span><span class='line'>            <span class="p">{</span>
</span><span class='line'>                <span class="n">packetDescriptioins</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mDataByteSize</span> <span class="o">=</span> <span class="n">packetSize</span><span class="p">;</span>
</span><span class='line'>            <span class="p">}</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">numberOfPackets</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">SInt64</span> <span class="n">packetOffset</span> <span class="o">=</span> <span class="n">packetDescriptioins</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mStartOffset</span><span class="p">;</span>
</span><span class='line'>        <span class="n">UInt32</span> <span class="n">packetSize</span>   <span class="o">=</span> <span class="n">packetDescriptioins</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">mDataByteSize</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>        <span class="c1">//把解析出来的帧数据放进自己的buffer中</span>
</span><span class='line'>        <span class="p">...</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">deletePackDesc</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">free</span><span class="p">(</span><span class="n">packetDescriptioins</span><span class="p">);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>inPacketDescriptions这个字段为空时需要按CBR的数据处理。但其实在解析CBR数据时inPacketDescriptions一般也会有返回，因为即使是CBR数据帧的大小也不是恒定不变的，例如CBR的MP3会在每一帧的数据后放1 byte的填充位，这个填充位也并非时时刻刻存在，所以帧的大小会有1 byte的浮动。（比如采样率44.1KHZ，码率160kbps的CBR MP3文件每一帧的大小在522字节和523字节浮动。所以不能因为有inPacketDescriptions没有返回NULL而判定音频数据就是VBR编码的）。</p>

<hr />

<h1>Seek</h1>

<p>就音频的角度来seek功能描述为“我要拖到xx分xx秒”，而实际操作时我们需要操作的是文件，所以我们需要知道的是“我要拖到xx分xx秒”这个操作对应到文件上是要从第几个字节开始读取音频数据。</p>

<p>对于原始的PCM数据来说每一个PCM帧都是固定长度的，对应的播放时长也是固定的，但一旦转换成压缩后的音频数据就会因为编码形式的不同而不同了。对于CBR而言每个帧中所包含的PCM数据帧是恒定的，所以每一帧对应的播放时长也是恒定的；而VBR则不同，为了保证数据最优并且文件大小最小，VBR的每一帧中所包含的PCM数据帧是不固定的，这就导致在流播放的情况下VBR的数据想要做seek并不容易。这里我们也只讨论CBR下的seek。</p>

<p>CBR数据的seek一般是这样实现的（参考并修改自<a href="http://www.cocoawithlove.com/2010/03/streaming-mp3aac-audio-again.html">matt的blog</a>）：</p>

<p>1、近似地计算应该seek到哪个字节</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="kt">double</span> <span class="n">seekToTime</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//需要seek到哪个时间，秒为单位</span>
</span><span class='line'><span class="n">UInt64</span> <span class="n">audioDataByteCount</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//通过kAudioFileStreamProperty_AudioDataByteCount获取的值</span>
</span><span class='line'><span class="n">SInt64</span> <span class="n">dataOffset</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//通过kAudioFileStreamProperty_DataOffset获取的值</span>
</span><span class='line'><span class="kt">double</span> <span class="n">durtion</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">//通过公式(AudioDataByteCount * 8) / BitRate计算得到的时长</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//近似seekOffset = 数据偏移 + seekToTime对应的近似字节数</span>
</span><span class='line'><span class="n">SInt64</span> <span class="n">approximateSeekOffset</span> <span class="o">=</span> <span class="n">dataOffset</span> <span class="o">+</span> <span class="p">(</span><span class="n">seekToTime</span> <span class="o">/</span> <span class="n">duration</span><span class="p">)</span> <span class="o">*</span> <span class="n">audioDataByteCount</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>2、计算seekToTime对应的是第几个帧（Packet）</p>

<p>我们可以利用之前Parse得到的音频格式信息来计算PacketDuration。<em>audioItem.fileFormat.mFramesPerPacket / </em>audioItem.fileFormat.mSampleRate;</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//首先需要计算每个packet对应的时长</span>
</span><span class='line'><span class="n">AudioStreamBasicDescription</span> <span class="n">asbd</span> <span class="o">=</span> <span class="p">...;</span> <span class="c1">////通过kAudioFileStreamProperty_DataFormat或者kAudioFileStreamProperty_FormatList获取的值</span>
</span><span class='line'><span class="kt">double</span> <span class="n">packetDuration</span> <span class="o">=</span> <span class="n">asbd</span><span class="p">.</span><span class="n">mFramesPerPacket</span> <span class="o">/</span> <span class="n">asbd</span><span class="p">.</span><span class="n">mSampleRate</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//然后计算packet位置</span>
</span><span class='line'><span class="n">SInt64</span> <span class="n">seekToPacket</span> <span class="o">=</span> <span class="n">floor</span><span class="p">(</span><span class="n">seekToTime</span> <span class="o">/</span> <span class="n">packetDuration</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>3、使用<code>AudioFileStreamSeek</code>计算精确的字节偏移和时间</p>

<p><code>AudioFileStreamSeek</code>可以用来寻找某一个帧（Packet）对应的字节偏移（byte offset）：</p>

<ul>
<li>如果ioFlags里有kAudioFileStreamSeekFlag_OffsetIsEstimated说明给出的outDataByteOffset是估算的，并不准确，那么还是应该用第1步计算出来的approximateSeekOffset来做seek；</li>
<li>如果ioFlags里没有kAudioFileStreamSeekFlag_OffsetIsEstimated说明给出了准确的outDataByteOffset，就是输入的seekToPacket对应的字节偏移量，我们可以根据outDataByteOffset来计算出精确的seekOffset和seekToTime；</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">SInt64</span> <span class="n">seekByteOffset</span><span class="p">;</span>
</span><span class='line'><span class="n">UInt32</span> <span class="n">ioFlags</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'><span class="n">SInt64</span> <span class="n">outDataByteOffset</span><span class="p">;</span>
</span><span class='line'><span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioFileStreamSeek</span><span class="p">(</span><span class="n">audioFileStreamID</span><span class="p">,</span> <span class="n">seekToPacket</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">outDataByteOffset</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ioFlags</span><span class="p">);</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">noErr</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="p">(</span><span class="n">ioFlags</span> <span class="o">&amp;</span> <span class="n">kAudioFileStreamSeekFlag_OffsetIsEstimated</span><span class="p">))</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="c1">//如果AudioFileStreamSeek方法找到了准确的帧字节偏移，需要修正一下时间</span>
</span><span class='line'>  <span class="n">seekToTime</span> <span class="o">-=</span> <span class="p">((</span><span class="n">approximateSeekOffset</span> <span class="o">-</span> <span class="n">dataOffset</span><span class="p">)</span> <span class="o">-</span> <span class="n">outDataByteOffset</span><span class="p">)</span> <span class="o">*</span> <span class="mf">8.0</span> <span class="o">/</span> <span class="n">bitRate</span><span class="p">;</span>
</span><span class='line'>  <span class="n">seekByteOffset</span> <span class="o">=</span> <span class="n">outDataByteOffset</span> <span class="o">+</span> <span class="n">dataOffset</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="k">else</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">seekByteOffset</span> <span class="o">=</span> <span class="n">approximateSeekOffset</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>4、按照seekByteOffset读取对应的数据继续使用<code>AudioFileStreamParseByte</code>进行解析</p>

<p>如果是网络流可以通过设置range头来获取字节，本地文件的话直接seek就好了。调用<code>AudioFileStreamParseByte</code>时注意刚seek完第一次Parse数据需要加参数<code>kAudioFileStreamParseFlag_Discontinuity</code>。</p>

<hr />

<h1>关闭AudioFileStream</h1>

<p><code>AudioFileStream</code>使用完毕后需要调用<code>AudioFileStreamClose</code>进行关闭，没啥特别需要注意的。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioFileStreamClose</span><span class="p">(</span><span class="n">AudioFileStreamID</span> <span class="n">inAudioFileStream</span><span class="p">);</span> 
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>小结</h1>

<p>本篇关于<code>AudioFileStream</code>做了详细介绍，小结一下：</p>

<ul>
<li><p>使用<code>AudioFileStream</code>首先需要调用<code>AudioFileStreamOpen</code>，需要注意的是尽量提供inFileTypeHint参数帮助<code>AudioFileStream</code>解析数据，调用完成后记录<code>AudioFileStreamID</code>；</p></li>
<li><p>当有数据时调用<code>AudioFileStreamParseBytes</code>进行解析，每一次解析都需要注意返回值，返回值一旦出现noErr以外的值就代表Parse出错，其中<code>kAudioFileStreamError_NotOptimized</code>代表该文件缺少头信息或者其头信息在文件尾部不适合流播放；</p></li>
<li><p>使用<code>AudioFileStreamParseBytes</code>需要注意第四个参数在需要合适的时候传入<code>kAudioFileStreamParseFlag_Discontinuity</code>；</p></li>
<li><p>调用<code>AudioFileStreamParseBytes</code>后会首先同步进入<code>AudioFileStream_PropertyListenerProc</code>回调来解析文件格式信息，如果回调得到<code>kAudioFileStreamProperty_ReadyToProducePackets</code>表示解析格式信息完成；</p></li>
<li><p>解析格式信息完成后继续调用<code>AudioFileStreamParseBytes</code>会进入<code>MyAudioFileStreamPacketsCallBack</code>回调来分离音频帧，在回调中应该将分离出来的帧信息保存到自己的buffer中</p></li>
<li><p>seek时需要先近似的计算seekTime对应的seekByteOffset，然后利用<code>AudioFileStreamSeek</code>计算精确的offset，如果能得到精确的offset就修正一下seektime，如果无法得到精确的offset就用之前的近似结果</p></li>
<li><p><code>AudioFileStream</code>使用完毕后需要调用<code>AudioFileStreamClose</code>进行关闭；</p></li>
</ul>


<hr />

<h1>示例代码</h1>

<p><a href="https://github.com/mattgallagher/AudioStreamer">AudioStreamer</a>和<a href="https://github.com/muhku/FreeStreamer">FreeStreamer</a>这两个优秀的开源播放器都用到<code>AudioFileStream</code>大家可以借鉴。</p>

<div class="github-card" data-github="mattgallagher/AudioStreamer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>




<div class="github-card" data-github="muhku/FreeStreamer" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<p>我自己也写了一个<a href="https://github.com/msching/MCAudioFileStream">简单的AudioFileStream封装</a>。</p>

<div class="github-card" data-github="msching/MCAudioFileStream" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<hr />

<h1>下篇预告</h1>

<p>下一篇将讲述如何使用<code>AudioFile</code>。</p>

<hr />

<h1>参考资料</h1>

<p><a href="https://developer.apple.com/library/ios/documentation/audiovideo/conceptual/multimediapg/usingaudio/usingaudio.html#//apple_ref/doc/uid/TP40009767-CH2-SW28">Using Audio</a></p>

<p><a href="http://www.cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html">Streaming and playing an MP3 stream</a></p>

<p><a href="http://www.cocoawithlove.com/2010/03/streaming-mp3aac-audio-again.html">Streaming MP3/AAC audio again</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (二)：AudioSession]]></title>
    <link href="http://msching.github.io/blog/2014/07/08/audio-in-ios-2/"/>
    <updated>2014-07-08T13:58:27+08:00</updated>
    <id>http://msching.github.io/blog/2014/07/08/audio-in-ios-2</id>
    <content type="html"><![CDATA[<p>本篇为《iOS音频播放》系列的第二篇。</p>

<p>在实施<a href="http://msching.github.io/blog/2014/07/07/audio-in-ios/">前一篇</a>中所述的7个步骤之前还必须面对一个麻烦的问题，AudioSession。</p>

<p>本篇主要介绍关于AudioSession使用、期间需要注意的地方以及可能面临的坑。</p>

<!--more-->


<hr />

<h1>AudioSession简介</h1>

<p>AudioSession这个玩意的主要功能包括以下几点（图片来自<a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007875">官方文档</a>）：</p>

<ol>
<li>确定你的app如何使用音频（是播放？还是录音？）</li>
<li>为你的app选择合适的输入输出设备（比如输入用的麦克风，输出是耳机、手机功放或者airplay）</li>
<li>协调你的app的音频播放和系统以及其他app行为（例如有电话时需要打断，电话结束时需要恢复，按下静音按钮时是否歌曲也要静音等）</li>
</ol>


<p><img src="http://msching.github.io/images/iOS-audio/audiosession.jpg" alt="AudioSession" /></p>

<p>AudioSession相关的类有两个：</p>

<ol>
<li><code>AudioToolBox</code>中的<code>AudioSession</code></li>
<li><code>AVFoundation</code>中的<code>AVAudioSession</code></li>
</ol>


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

<ul>
<li>如果最低版本支持iOS 5，可以使用<code>AudioSession</code>，也可以使用<code>AVAudioSession</code>；</li>
<li>如果最低版本支持iOS 6及以上，请使用<code>AVAudioSession</code></li>
</ul>


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

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

<p><strong>注意：在使用<code>MPMusicPlayerController</code>时不必关心AudioSession的问题。</strong></p>

<hr />

<h1>初始化AudioSession</h1>

<p>使用<code>AudioSession</code>类首先需要调用初始化方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioSessionInitialize</span><span class="p">(</span><span class="n">CFRunLoopRef</span> <span class="n">inRunLoop</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">CFStringRef</span> <span class="n">inRunLoopMode</span><span class="p">,</span>
</span><span class='line'>                                       <span class="n">AudioSessionInterruptionListener</span> <span class="n">inInterruptionListener</span><span class="p">,</span>
</span><span class='line'>                                       <span class="kt">void</span> <span class="o">*</span><span class="n">inClientData</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


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

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">AudioSessionInterruptionListener</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">inInterruptionState</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>这才刚开始，坑就来了。这里会有两个问题：</p>

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

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

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

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

<hr />

<h1>监听RouteChange事件</h1>

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

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioSessionAddPropertyListener</span><span class="p">(</span><span class="n">AudioSessionPropertyID</span> <span class="n">inID</span><span class="p">,</span>
</span><span class='line'>                                                <span class="n">AudioSessionPropertyListener</span> <span class="n">inProc</span><span class="p">,</span>
</span><span class='line'>                                                <span class="kt">void</span> <span class="o">*</span><span class="n">inClientData</span><span class="p">);</span>
</span><span class='line'>                                              
</span><span class='line'><span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">AudioSessionPropertyListener</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span> <span class="n">inClientData</span><span class="p">,</span>
</span><span class='line'>                                             <span class="n">AudioSessionPropertyID</span> <span class="n">inID</span><span class="p">,</span>
</span><span class='line'>                                             <span class="n">UInt32</span> <span class="n">inDataSize</span><span class="p">,</span>
</span><span class='line'>                                             <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">inData</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>调用上述方法，AudioSessionPropertyID参数传<code>kAudioSessionProperty_AudioRouteChange</code>，AudioSessionPropertyListener参数传对应的回调方法。inClientData参数同AudioSessionInitialize方法。</p>

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

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioSession的AudioRouteChangeReason枚举</span>
</span><span class='line'><span class="k">enum</span> <span class="p">{</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_Unknown</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_NewDeviceAvailable</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_OldDeviceUnavailable</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_CategoryChange</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_Override</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_WakeFromSleep</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_NoSuitableRouteForCategory</span> <span class="o">=</span> <span class="mi">7</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionRouteChangeReason_RouteConfigurationChange</span> <span class="o">=</span> <span class="mi">8</span>
</span><span class='line'>  <span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AVAudioSession的AudioRouteChangeReason枚举</span>
</span><span class='line'><span class="k">typedef</span> <span class="nf">NS_ENUM</span><span class="p">(</span><span class="n">NSUInteger</span><span class="p">,</span> <span class="n">AVAudioSessionRouteChangeReason</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonUnknown</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonNewDeviceAvailable</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonOldDeviceUnavailable</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonCategoryChange</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonOverride</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonWakeFromSleep</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory</span> <span class="o">=</span> <span class="mi">7</span><span class="p">,</span>
</span><span class='line'>  <span class="n">AVAudioSessionRouteChangeReasonRouteConfigurationChange</span> <span class="n">NS_ENUM_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">7</span><span class="n">_0</span><span class="p">)</span> <span class="o">=</span> <span class="mi">8</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


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

<hr />

<p>这里附带两个方法的实现，都是基于<code>AudioSession</code>类的（使用<code>AVAudioSession</code>的同学帮不到你们啦）。</p>

<p>1、判断是否插了耳机：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">usingHeadset</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'><span class="cp">#if TARGET_IPHONE_SIMULATOR</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'><span class="cp">#endif</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">CFStringRef</span> <span class="n">route</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span> <span class="n">propertySize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">CFStringRef</span><span class="p">);</span>
</span><span class='line'>    <span class="n">AudioSessionGetProperty</span><span class="p">(</span><span class="n">kAudioSessionProperty_AudioRoute</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">propertySize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">route</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="kt">BOOL</span> <span class="n">hasHeadset</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="k">if</span><span class="p">((</span><span class="n">route</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">CFStringGetLength</span><span class="p">(</span><span class="n">route</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">))</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="c1">// Silent Mode</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="cm">/* Known values of route:</span>
</span><span class='line'><span class="cm">         * &quot;Headset&quot;</span>
</span><span class='line'><span class="cm">         * &quot;Headphone&quot;</span>
</span><span class='line'><span class="cm">         * &quot;Speaker&quot;</span>
</span><span class='line'><span class="cm">         * &quot;SpeakerAndMicrophone&quot;</span>
</span><span class='line'><span class="cm">         * &quot;HeadphonesAndMicrophone&quot;</span>
</span><span class='line'><span class="cm">         * &quot;HeadsetInOut&quot;</span>
</span><span class='line'><span class="cm">         * &quot;ReceiverAndMicrophone&quot;</span>
</span><span class='line'><span class="cm">         * &quot;Lineout&quot;</span>
</span><span class='line'><span class="cm">         */</span>
</span><span class='line'>        <span class="n">NSString</span><span class="o">*</span> <span class="n">routeStr</span> <span class="o">=</span> <span class="p">(</span><span class="n">__bridge</span> <span class="n">NSString</span><span class="o">*</span><span class="p">)</span><span class="n">route</span><span class="p">;</span>
</span><span class='line'>        <span class="n">NSRange</span> <span class="n">headphoneRange</span> <span class="o">=</span> <span class="p">[</span><span class="n">routeStr</span> <span class="n">rangeOfString</span> <span class="o">:</span> <span class="s">@&quot;Headphone&quot;</span><span class="p">];</span>
</span><span class='line'>        <span class="n">NSRange</span> <span class="n">headsetRange</span> <span class="o">=</span> <span class="p">[</span><span class="n">routeStr</span> <span class="n">rangeOfString</span> <span class="o">:</span> <span class="s">@&quot;Headset&quot;</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="n">headphoneRange</span><span class="p">.</span><span class="n">location</span> <span class="o">!=</span> <span class="n">NSNotFound</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">hasHeadset</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">headsetRange</span><span class="p">.</span><span class="n">location</span> <span class="o">!=</span> <span class="n">NSNotFound</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">hasHeadset</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">route</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">CFRelease</span><span class="p">(</span><span class="n">route</span><span class="p">);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="n">hasHeadset</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>2、判断是否开了Airplay(来自<a href="http://stackoverflow.com/questions/13044894/get-name-of-airplay-device-using-avplayer">StackOverflow</a>)：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">isAirplayActived</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">CFDictionaryRef</span> <span class="n">currentRouteDescriptionDictionary</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span> <span class="n">dataSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">currentRouteDescriptionDictionary</span><span class="p">);</span>
</span><span class='line'>    <span class="n">AudioSessionGetProperty</span><span class="p">(</span><span class="n">kAudioSessionProperty_AudioRouteDescription</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dataSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">currentRouteDescriptionDictionary</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="kt">BOOL</span> <span class="n">airplayActived</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">currentRouteDescriptionDictionary</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">CFArrayRef</span> <span class="n">outputs</span> <span class="o">=</span> <span class="n">CFDictionaryGetValue</span><span class="p">(</span><span class="n">currentRouteDescriptionDictionary</span><span class="p">,</span> <span class="n">kAudioSession_AudioRouteKey_Outputs</span><span class="p">);</span>
</span><span class='line'>        <span class="k">if</span><span class="p">(</span><span class="n">outputs</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">CFArrayGetCount</span><span class="p">(</span><span class="n">outputs</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">CFDictionaryRef</span> <span class="n">currentOutput</span> <span class="o">=</span> <span class="n">CFArrayGetValueAtIndex</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span><span class='line'>            <span class="c1">//Get the output type (will show airplay / hdmi etc</span>
</span><span class='line'>            <span class="n">CFStringRef</span> <span class="n">outputType</span> <span class="o">=</span> <span class="n">CFDictionaryGetValue</span><span class="p">(</span><span class="n">currentOutput</span><span class="p">,</span> <span class="n">kAudioSession_AudioRouteKey_Type</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>            <span class="n">airplayActived</span> <span class="o">=</span> <span class="p">(</span><span class="n">CFStringCompare</span><span class="p">(</span><span class="n">outputType</span><span class="p">,</span> <span class="n">kAudioSessionOutputRoute_AirPlay</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="n">kCFCompareEqualTo</span><span class="p">);</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="n">CFRelease</span><span class="p">(</span><span class="n">currentRouteDescriptionDictionary</span><span class="p">);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">return</span> <span class="n">airplayActived</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>设置类别</h1>

<p>下一步要设置AudioSession的Category，使用<code>AudioSession</code>时调用下面的接口</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioSessionSetProperty</span><span class="p">(</span><span class="n">AudioSessionPropertyID</span> <span class="n">inID</span><span class="p">,</span>
</span><span class='line'>                                        <span class="n">UInt32</span> <span class="n">inDataSize</span><span class="p">,</span>
</span><span class='line'>                                        <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">inData</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果我需要的功能是播放，执行如下代码</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="n">UInt32</span> <span class="n">sessionCategory</span> <span class="o">=</span> <span class="n">kAudioSessionCategory_MediaPlayback</span><span class="p">;</span>
</span><span class='line'><span class="n">AudioSessionSetProperty</span> <span class="p">(</span><span class="n">kAudioSessionProperty_AudioCategory</span><span class="p">,</span>
</span><span class='line'>                         <span class="k">sizeof</span><span class="p">(</span><span class="n">sessionCategory</span><span class="p">),</span>
</span><span class='line'>                         <span class="o">&amp;</span><span class="n">sessionCategory</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用<code>AVAudioSession</code>时调用下面的接口</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cm">/* set session category */</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">setCategory:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">category</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span><span class="p">;</span>
</span><span class='line'><span class="cm">/* set session category with options */</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">setCategory:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">category</span> <span class="nf">withOptions:</span> <span class="p">(</span><span class="n">AVAudioSessionCategoryOptions</span><span class="p">)</span><span class="nv">options</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span> <span class="n">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">6</span><span class="n">_0</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>至于Category的类型在<a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionBasics/AudioSessionBasics.html#//apple_ref/doc/uid/TP40007875-CH3-SW1">官方文档</a>中都有介绍，我这里也只罗列一下具体就不赘述了，各位在使用时可以依照自己需要的功能设置Category。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioSession的AudioSessionCategory枚举</span>
</span><span class='line'><span class="k">enum</span> <span class="p">{</span>
</span><span class='line'>      <span class="n">kAudioSessionCategory_AmbientSound</span>               <span class="o">=</span> <span class="err">&#39;</span><span class="n">ambi</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionCategory_SoloAmbientSound</span>           <span class="o">=</span> <span class="err">&#39;</span><span class="n">solo</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionCategory_MediaPlayback</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">medi</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionCategory_RecordAudio</span>                <span class="o">=</span> <span class="err">&#39;</span><span class="n">reca</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionCategory_PlayAndRecord</span>              <span class="o">=</span> <span class="err">&#39;</span><span class="n">plar</span><span class="err">&#39;</span><span class="p">,</span>
</span><span class='line'>      <span class="n">kAudioSessionCategory_AudioProcessing</span>            <span class="o">=</span> <span class="err">&#39;</span><span class="n">proc</span><span class="err">&#39;</span>
</span><span class='line'>  <span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioSession的AudioSessionCategory字符串</span>
</span><span class='line'><span class="cm">/*  Use this category for background sounds such as rain, car engine noise, etc.  </span>
</span><span class='line'><span class="cm"> Mixes with other music. */</span>
</span><span class='line'><span class="n">AVF_EXPORT</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">AVAudioSessionCategoryAmbient</span><span class="p">;</span>
</span><span class='line'>  
</span><span class='line'><span class="cm">/*  Use this category for background sounds.  Other music will stop playing. */</span>
</span><span class='line'><span class="n">AVF_EXPORT</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">AVAudioSessionCategorySoloAmbient</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="cm">/* Use this category for music tracks.*/</span>
</span><span class='line'><span class="n">AVF_EXPORT</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">AVAudioSessionCategoryPlayback</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="cm">/*  Use this category when recording audio. */</span>
</span><span class='line'><span class="n">AVF_EXPORT</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">AVAudioSessionCategoryRecord</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="cm">/*  Use this category when recording and playing back audio. */</span>
</span><span class='line'><span class="n">AVF_EXPORT</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">AVAudioSessionCategoryPlayAndRecord</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="cm">/*  Use this category when using a hardware codec or signal processor while</span>
</span><span class='line'><span class="cm"> not playing or recording audio. */</span>
</span><span class='line'><span class="n">AVF_EXPORT</span> <span class="n">NSString</span> <span class="o">*</span><span class="k">const</span> <span class="n">AVAudioSessionCategoryAudioProcessing</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>启用</h1>

<p>有了Category就可以启动AudioSession了，启动方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="c1">//AudioSession的启动方法</span>
</span><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioSessionSetActive</span><span class="p">(</span><span class="n">Boolean</span> <span class="n">active</span><span class="p">);</span>
</span><span class='line'><span class="k">extern</span> <span class="n">OSStatus</span> <span class="nf">AudioSessionSetActiveWithFlags</span><span class="p">(</span><span class="n">Boolean</span> <span class="n">active</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">inFlags</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//AVAudioSession的启动方法</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">setActive:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">active</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">setActive:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">active</span> <span class="nf">withFlags:</span><span class="p">(</span><span class="n">NSInteger</span><span class="p">)</span><span class="nv">flags</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span> <span class="n">NS_DEPRECATED_IOS</span><span class="p">(</span><span class="mi">4</span><span class="n">_0</span><span class="p">,</span> <span class="mi">6</span><span class="n">_0</span><span class="p">);</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">setActive:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">active</span> <span class="nf">withOptions:</span><span class="p">(</span><span class="n">AVAudioSessionSetActiveOptions</span><span class="p">)</span><span class="nv">options</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span> <span class="n">NS_AVAILABLE_IOS</span><span class="p">(</span><span class="mi">6</span><span class="n">_0</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


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

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

<p>大概流程是这样的：</p>

<ol>
<li>一个音乐软件A正在播放；</li>
<li>用户打开你的软件播放对话语音，AudioSession active；</li>
<li>音乐软件A音乐被打断并收到InterruptBegin事件；</li>
<li>对话语音播放结束，AudioSession deactive并且传入NotifyOthersOnDeactivation参数；</li>
<li>音乐软件A收到InterruptEnd事件，查看Resume参数，如果是ShouldResume控制音频继续播放，如果是ShouldNotResume就维持打断状态；</li>
</ol>


<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/ConfiguringanAudioSession/ConfiguringanAudioSession.html#//apple_ref/doc/uid/TP40007875-CH2-SW1">官方文档</a>中有一张很形象的图来阐述这个现象：</p>

<p><img src="http://msching.github.io/images/iOS-audio/audiosession-active.jpg" alt="" /></p>

<p>然而现在某些语音通讯软件和某些音乐软件却无视了<code>NotifyOthersOnDeactivation</code>和<code>ShouldResume</code>的正确用法，导致我们经常接到这样的用户反馈：</p>

<pre><code>你们的app在使用xx语音软件听了一段话后就不会继续播放了，但xx音乐软件可以继续播放啊。
</code></pre>

<p>好吧，上面只是吐槽一下。请无视我吧。</p>

<p><strong>2014.7.14补充，7.19更新：</strong></p>

<p>发现即使之前已经调用过<code>AudioSessionInitialize</code>方法，在某些情况下被打断之后可能出现AudioSession失效的情况，需要再次调用<code>AudioSessionInitialize</code>方法来重新生成AudioSession。否则调用<code>AudioSessionSetActive</code>会返回560557673（其他AudioSession方法也雷同，所有方法调用前必须首先初始化AudioSession），转换成string后为&#8221;!ini&#8221;即<code>kAudioSessionNotInitialized</code>，这个情况在iOS 5.1.x上比较容易发生，iOS 6.x 和 7.x也偶有发生（<del>具体的原因还不知晓</del>好像和打断时直接调用<code>AudioOutputUnitStop</code>有关，又是个坑啊）。</p>

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

<p>附上OSStatus转成string的方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="cp">#import &lt;Endian.h&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="n">NSString</span> <span class="o">*</span> <span class="nf">OSStatusToString</span><span class="p">(</span><span class="n">OSStatus</span> <span class="n">status</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt32</span><span class="p">);</span>
</span><span class='line'>    <span class="kt">long</span> <span class="n">addr</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="o">&amp;</span><span class="n">status</span><span class="p">;</span>
</span><span class='line'>    <span class="kt">char</span> <span class="n">cstring</span><span class="p">[</span><span class="mi">5</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="n">status</span> <span class="o">&gt;&gt;</span> <span class="mi">24</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">len</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">len</span><span class="p">;</span>
</span><span class='line'>    <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="n">status</span> <span class="o">&gt;&gt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">len</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">len</span><span class="p">;</span>
</span><span class='line'>    <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="n">status</span> <span class="o">&gt;&gt;</span>  <span class="mi">8</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">len</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">len</span><span class="p">;</span>
</span><span class='line'>    <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="n">status</span> <span class="o">&gt;&gt;</span>  <span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">len</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">len</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">addr</span> <span class="o">+=</span> <span class="p">(</span><span class="mi">4</span> <span class="o">-</span> <span class="n">len</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">status</span> <span class="o">=</span> <span class="n">EndianU32_NtoB</span><span class="p">(</span><span class="n">status</span><span class="p">);</span>        <span class="c1">// strings are big endian</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">strncpy</span><span class="p">(</span><span class="n">cstring</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">addr</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span><span class='line'>    <span class="n">cstring</span><span class="p">[</span><span class="n">len</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="p">[</span><span class="n">NSString</span> <span class="nl">stringWithCString:</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">cstring</span> <span class="nl">encoding:</span><span class="n">NSMacOSRomanStringEncoding</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>打断处理</h1>

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

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

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">static</span> <span class="kt">void</span> <span class="nf">MyAudioSessionInterruptionListener</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">inClientData</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">inInterruptionState</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">AudioSessionInterruptionType</span> <span class="n">interruptionType</span> <span class="o">=</span> <span class="n">kAudioSessionInterruptionType_ShouldNotResume</span><span class="p">;</span>
</span><span class='line'>    <span class="n">UInt32</span> <span class="n">interruptionTypeSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">interruptionType</span><span class="p">);</span>
</span><span class='line'>    <span class="n">AudioSessionGetProperty</span><span class="p">(</span><span class="n">kAudioSessionProperty_InterruptionType</span><span class="p">,</span>
</span><span class='line'>                            <span class="o">&amp;</span><span class="n">interruptionTypeSize</span><span class="p">,</span>
</span><span class='line'>                            <span class="o">&amp;</span><span class="n">interruptionType</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">NSDictionary</span> <span class="o">*</span><span class="n">userInfo</span> <span class="o">=</span> <span class="err">@</span><span class="p">{</span><span class="nl">MyAudioInterruptionStateKey:</span><span class="err">@</span><span class="p">(</span><span class="n">inInterruptionState</span><span class="p">),</span>
</span><span class='line'>                               <span class="nl">MyAudioInterruptionTypeKey:</span><span class="err">@</span><span class="p">(</span><span class="n">interruptionType</span><span class="p">)};</span>
</span><span class='line'>
</span><span class='line'>    <span class="p">[[</span><span class="n">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="p">]</span> <span class="nl">postNotificationName:</span><span class="n">MyAudioInterruptionNotification</span> <span class="nl">object:</span><span class="nb">nil</span> <span class="nl">userInfo:</span><span class="n">userInfo</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>收到通知后的处理方法如下（注意ShouldResume参数）：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
</pre></td><td class='code'><pre><code class='objc'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">interruptionNotificationReceived:</span><span class="p">(</span><span class="n">NSNotification</span> <span class="o">*</span><span class="p">)</span><span class="nv">notification</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">UInt32</span> <span class="n">interruptionState</span> <span class="o">=</span> <span class="p">[</span><span class="n">notification</span><span class="p">.</span><span class="n">userInfo</span><span class="p">[</span><span class="n">MyAudioInterruptionStateKey</span><span class="p">]</span> <span class="n">unsignedIntValue</span><span class="p">];</span>
</span><span class='line'>    <span class="n">AudioSessionInterruptionType</span> <span class="n">interruptionType</span> <span class="o">=</span> <span class="p">[</span><span class="n">notification</span><span class="p">.</span><span class="n">userInfo</span><span class="p">[</span><span class="n">MyAudioInterruptionTypeKey</span><span class="p">]</span> <span class="n">unsignedIntValue</span><span class="p">];</span>
</span><span class='line'>    <span class="p">[</span><span class="n">self</span> <span class="nl">handleAudioSessionInterruptionWithState:</span><span class="n">interruptionState</span> <span class="nl">type:</span><span class="n">interruptionType</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">handleAudioSessionInterruptionWithState:</span><span class="p">(</span><span class="n">UInt32</span><span class="p">)</span><span class="nv">interruptionState</span> <span class="nf">type:</span><span class="p">(</span><span class="n">AudioSessionInterruptionType</span><span class="p">)</span><span class="nv">interruptionType</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">interruptionState</span> <span class="o">==</span> <span class="n">kAudioSessionBeginInterruption</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="c1">//控制UI，暂停播放</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">interruptionState</span> <span class="o">==</span> <span class="n">kAudioSessionEndInterruption</span><span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="n">interruptionType</span> <span class="o">==</span> <span class="n">kAudioSessionInterruptionType_ShouldResume</span><span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">OSStatus</span> <span class="n">status</span> <span class="o">=</span> <span class="n">AudioSessionSetActive</span><span class="p">(</span><span class="n">true</span><span class="p">);</span>
</span><span class='line'>            <span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">noErr</span><span class="p">)</span>
</span><span class='line'>            <span class="p">{</span>
</span><span class='line'>                <span class="c1">//控制UI，继续播放</span>
</span><span class='line'>            <span class="p">}</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>小结</h1>

<p>关于AudioSession的话题到此结束（码字果然很累。。）。小结一下：</p>

<ul>
<li>如果最低版本支持iOS 5，可以使用<code>AudioSession</code>也可以考虑使用<code>AVAudioSession</code>，需要有一个类统一管理AudioSession的所有回调，在接到回调后发送对应的自定义通知；</li>
<li>如果最低版本支持iOS 6及以上，请使用<code>AVAudioSession</code>，不用统一管理，接AVAudioSession的通知即可；</li>
<li>根据app的应用场景合理选择<code>Category</code>；</li>
<li>在deactive时需要注意app的应用场景来合理的选择是否使用<code>NotifyOthersOnDeactivation</code>参数；</li>
<li>在处理InterruptEnd事件时需要注意<code>ShouldResume</code>的值。</li>
</ul>


<hr />

<h1>示例代码</h1>

<p><a href="https://github.com/msching/MCAudioSession">这里</a>有我自己写的<code>AudioSession</code>的封装，如果各位需要支持iOS 5的话可以使用一下。</p>

<div class="github-card" data-github="msching/MCAudioSession" data-width="400" data-height="" data-theme="default"></div>


<script src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>


<hr />

<h1>下篇预告</h1>

<p><del>下一篇将讲述如何使用<code>AudioFileStreamer</code>分离音频帧，以及如何使用<code>AudioQueue</code>进行播放。</del></p>

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

<hr />

<h1>参考资料</h1>

<p><a href="https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007875">AudioSession</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS音频播放 (一)：概述]]></title>
    <link href="http://msching.github.io/blog/2014/07/07/audio-in-ios/"/>
    <updated>2014-07-07T14:40:42+08:00</updated>
    <id>http://msching.github.io/blog/2014/07/07/audio-in-ios</id>
    <content type="html"><![CDATA[<p>从事音乐相关的app开发也已经有一段时日了，在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究。写这个系列的博客目的一方面希望能够抛砖引玉，另一方面也是希望能帮助国内其他的iOS开发者和爱好者少走弯路（我自己就遇到了不少的坑=。=）。</p>

<p>本篇为《iOS音频播放》系列的第一篇，主要将对iOS下实现音频播放的方法进行概述。</p>

<!--more-->


<hr />

<h1>基础</h1>

<p>先来简单了解一下一些基础的音频知识。</p>

<p>目前我们在计算机上进行音频播放都需要依赖于音频文件，音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程，人耳所能听到的声音，最低的频率是从20Hz起一直到最高频率20KHZ，因此音频文件格式的最大带宽是20KHZ。根据<a href="http://zh.wikipedia.org/wiki/%E5%A5%88%E5%A5%8E%E6%96%AF%E7%89%B9%E9%A2%91%E7%8E%87">奈奎斯特</a>的理论，只有采样频率高于声音信号最高频率的两倍时，才能把数字信号表示的声音还原成为原来的声音，所以音频文件的采样率一般在40~50KHZ，比如最常见的CD音质采样率44.1KHZ。</p>

<p>对声音进行采样、量化过程被称为<a href="http://zh.wikipedia.org/wiki/%E8%84%88%E8%A1%9D%E7%B7%A8%E8%99%9F%E8%AA%BF%E8%AE%8A">脉冲编码调制</a>（Pulse Code Modulation），简称<code>PCM</code>。PCM数据是最原始的音频数据完全无损，所以PCM数据虽然音质优秀但体积庞大，为了解决这个问题先后诞生了一系列的音频格式，这些音频格式运用不同的方法对音频数据进行压缩，其中有无损压缩（ALAC、APE、FLAC）和有损压缩（MP3、AAC、OGG、WMA）两种。</p>

<p>目前最为常用的音频格式是MP3，MP3是一种有损压缩的音频格式，设计这种格式的目的就是为了大幅度的减小音频的数据量，它舍弃PCM音频数据中人类听觉不敏感的部分，从下面的比较图我们可以明显的看到MP3数据相比PCM数据明显矮了一截（图片引自<a href="http://bbs.imp3.net/thread-243641-1-1.html">imp3论坛</a>）。</p>

<p><img src="http://msching.github.io/images/iOS-audio/pcm.jpg" alt="上图为pcm数据" />
<img src="http://msching.github.io/images/iOS-audio/mp3.jpg" alt="上图为mp3数据" /></p>

<p>MP3格式中的码率（BitRate）代表了MP3数据的压缩质量，现在常用的码率有128kbit/s、160kbit/s、320kbit/s等等，这个值越高声音质量也就越高。MP3编码方式常用的有两种<a href="http://zh.wikipedia.org/wiki/%E5%9B%BA%E5%AE%9A%E7%A0%81%E7%8E%87">固定码率</a>(Constant bitrate，CBR)和<a href="http://zh.wikipedia.org/wiki/%E5%8F%AF%E5%8F%98%E7%A0%81%E7%8E%87">可变码率</a>(Variable bitrate，VBR)。</p>

<p>MP3格式中的数据通常由两部分组成，一部分为<a href="http://zh.wikipedia.org/zh/ID3">ID3</a>用来存储歌名、演唱者、专辑、音轨数等信息，另一部分为音频数据。音频数据部分以帧(frame)为单位存储，每个音频都有自己的帧头，如图所示就是一个MP3文件帧结构图（图片同样来自互联网）。MP3中的每一个帧都有自己的帧头，其中存储了采样率等解码必须的信息，所以每一个帧都可以独立于文件存在和播放，这个特性加上高压缩比使得MP3文件成为了音频流播放的主流格式。帧头之后存储着音频数据，这些音频数据是若干个PCM数据帧经过压缩算法压缩得到的，对CBR的MP3数据来说每个帧中包含的PCM数据帧是固定的，而VBR是可变的。</p>

<p><img src="http://msching.github.io/images/iOS-audio/mp3frame.jpg" alt="" /></p>

<hr />

<h1>iOS音频播放概述</h1>

<p>了解了基础概念之后我们就可以列出一个经典的音频播放流程（以MP3为例）：</p>

<ol>
<li>读取MP3文件</li>
<li>解析采样率、码率、时长等信息，分离MP3中的音频帧</li>
<li>对分离出来的音频帧解码得到PCM数据</li>
<li>对PCM数据进行音效处理（均衡器、混响器等，非必须）</li>
<li>把PCM数据解码成音频信号</li>
<li>把音频信号交给硬件播放</li>
<li>重复1-6步直到播放完成</li>
</ol>


<p>在iOS系统中apple对上述的流程进行了封装并提供了不同层次的接口（图片引自<a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html#//apple_ref/doc/uid/TP40003577-CH10-SW1">官方文档</a>）。</p>

<p><img src="http://msching.github.io/images/iOS-audio/api-architectural-layers.png" alt="CoreAudio的接口层次" /></p>

<p>下面对其中的中高层接口进行功能说明：</p>

<ul>
<li>Audio File Services：读写音频数据，可以完成播放流程中的第2步；</li>
<li>Audio File Stream Services：对音频进行解码，可以完成播放流程中的第2步；</li>
<li>Audio Converter services：音频数据转换，可以完成播放流程中的第3步；</li>
<li>Audio Processing Graph Services：音效处理模块，可以完成播放流程中的第4步；</li>
<li>Audio Unit Services：播放音频数据：可以完成播放流程中的第5步、第6步；</li>
<li>Extended Audio File Services：Audio File Services和Audio Converter services的结合体；</li>
<li>AVAudioPlayer/AVPlayer(AVFoundation)：高级接口，可以完成整个音频播放的过程（包括本地文件和网络流播放，第4步除外）；</li>
<li>Audio Queue Services：高级接口，可以进行录音和播放，可以完成播放流程中的第3、5、6步；</li>
<li>OpenAL：用于游戏音频播放，暂不讨论</li>
</ul>


<p>可以看到apple提供的接口类型非常丰富，可以满足各种类别类需求：</p>

<ul>
<li><p>如果你只是想实现音频的播放，没有其他需求AVFoundation会很好的满足你的需求。它的接口使用简单、不用关心其中的细节；</p></li>
<li><p>如果你的app需要对音频进行流播放并且同时存储，那么AudioFileStreamer加AudioQueue能够帮到你，你可以先把音频数据下载到本地，一边下载一边用NSFileHandler等接口读取本地音频文件并交给AudioFileStreamer或者AudioFile解析分离音频帧，分离出来的音频帧可以送给AudioQueue进行解码和播放。如果是本地文件直接读取文件解析即可。（这两个都是比较直接的做法，这类需求也可以用AVFoundation+本地server的方式实现，AVAudioPlayer会把请求发送给本地server，由本地server转发出去，获取数据后在本地server中存储并转送给AVAudioPlayer。另一个比较trick的做法是先把音频下载到文件中，在下载到一定量的数据后把文件路径给AVAudioPlayer播放，当然这种做法在音频seek后就回有问题了。）；</p></li>
<li><p>如果你正在开发一个专业的音乐播放软件，需要对音频施加音效（均衡器、混响器），那么除了数据的读取和解析以外还需要用到AudioConverter来把音频数据转换成PCM数据，再由AudioUnit+AUGraph来进行音效处理和播放（但目前多数带音效的app都是自己开发音效模块来坐PCM数据的处理，这部分功能自行开发在自定义性和扩展性上会比较强一些。PCM数据通过音效器处理完成后就可以使用AudioUnit播放了，当然AudioQueue也支持直接使对PCM数据进行播放。）。下图描述的就是使用AudioFile + AudioConverter + AudioUnit进行音频播放的流程（图片引自<a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/CoreAudioOverview/ARoadmaptoCommonTasks/ARoadmaptoCommonTasks.html#//apple_ref/doc/uid/TP40003577-CH6-SW1">官方文档</a>）。</p></li>
</ul>


<p><img src="http://msching.github.io/images/iOS-audio/audioUnitPlay.jpg" alt="" /></p>

<hr />

<h1>下篇预告</h1>

<p>下一篇将讲述iOS音频播放中必须面对的难（da）题（keng），AudioSession。</p>

<hr />

<h1>参考资料</h1>

<p><a href="http://zh.wikipedia.org/zh/%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F">音频文件格式</a></p>

<p><a href="http://zh.wikipedia.org/wiki/%E8%84%88%E8%A1%9D%E7%B7%A8%E8%99%9F%E8%AA%BF%E8%AE%8A">脉冲编码调制</a></p>

<p><a href="http://zh.wikipedia.org/zh/%E9%87%87%E6%A0%B7%E7%8E%87">采样率</a></p>

<p><a href="http://zh.wikipedia.org/wiki/%E5%A5%88%E5%A5%8E%E6%96%AF%E7%89%B9%E9%A2%91%E7%8E%87">奈奎斯特频率</a></p>

<p><a href="http://zh.wikipedia.org/wiki/MP3">MP3</a></p>

<p><a href="http://zh.wikipedia.org/zh/ID3">ID3</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html#//apple_ref/doc/uid/TP40003577-CH10-SW1">Core Audio Essential</a></p>

<p><a href="https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/CoreAudioOverview/ARoadmaptoCommonTasks/ARoadmaptoCommonTasks.html#//apple_ref/doc/uid/TP40003577-CH6-SW1">Common Tasks in OS X</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[奇怪的Graphics消耗（iOS 7.1.x）]]></title>
    <link href="http://msching.github.io/blog/2014/07/04/strange-high-graphics-cost-in-ios-7-dot-1-x/"/>
    <updated>2014-07-04T14:25:31+08:00</updated>
    <id>http://msching.github.io/blog/2014/07/04/strange-high-graphics-cost-in-ios-7-dot-1-x</id>
    <content type="html"><![CDATA[<p>近期有部分用户反馈我们的app在使用时有发热现象，在排查原因的过程中发现了一个奇怪的问题。在某个页面推出时<code>Instruments</code>的<code>Core Animation</code>会有帧数显示，数值在59~60，而此时并没有任何需要消耗Graphics的代码在跑，所有UI都是静止的。这个现象只会在iOS 7.1.x上出现，其他系统包括最新发布的iOS8 beta上均没有出现类似问题。</p>

<p>造成这个现象的页面比较特殊，其展现形式看上去虽然是一个UIViewController把一个UINavigationController用ModalView的形式push出来了，但实际上是把UINavigationController的.view直接add在了UIViewController的.view上，并用一个UIView动画展示页面。</p>

<p>为了弄清楚到底为什么会在UI静止的情况下产生Graphics消耗，我新建了一个空工程用相同的方法实现了一个推出页面的动画，然后profile却没有发现有问题。再次回头看app发现这个推出页面的controller是个UITabBarController，于是为测试工程加上tabBarController后再次profile，问题重现了，Core Animation在页面出现之后显示了帧数，页面隐藏之后帧数显示就消失了，从而可以推断是UITabBarController上的某个UI元素导致了这个问题。</p>

<p>接下来的步骤就是遍历UITabBarController的view上所有的subview并逐个隐藏来进行测试，幸运的是第一个就蒙对了，在我隐藏了UITabBarController的tabbar以后奇怪的帧数就不再出现了。这个现象很快让我想到了iOS 7以后苹果加入的模糊效果，这个效果在UINavigationBar、UITabBar、UIToolBar等UI控件上都有使用，下面把UITabBarController去掉，在view上直接add一个UIToolBar，发现问题同样会出现。至此基本确定是由于这个模糊效果造成的，至于为什么只会在7.1.x上出现这个问题。。可能只有苹果才知道了=。=。</p>

<p>测试程序代码点<a href="https://github.com/msching/TestGPU">这里</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iOS8beta1下WebCore可能会打断音频播放]]></title>
    <link href="http://msching.github.io/blog/2014/06/26/audio-interrput-by-webcore-in-ios-8-beta-1/"/>
    <updated>2014-06-26T16:09:56+08:00</updated>
    <id>http://msching.github.io/blog/2014/06/26/audio-interrput-by-webcore-in-ios-8-beta-1</id>
    <content type="html"><![CDATA[<h1>问题</h1>

<p>前不久在QA发现一个问题，在iOS8 beta1上使用我们的app播放歌曲时进入某些内嵌的web页面（UIWebview实现）时歌曲会暂停播放，但是界面仍然显示为正在播放状态。把真机连上Xcode6调试后发现在进入部分网页时会再console上打印如下log：</p>

<pre><code>AVAudioSession.mm:623: -[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.
</code></pre>

<p>bt后堆栈如下：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>frame #1: 0x299632fe libAVFAudio.dylib`-[AVAudioSession setActive:error:] + 26
</span><span class='line'>frame #2: 0x3551b92e WebCore`WebCore::AudioSession::setActive(bool) + 62
</span><span class='line'>frame #3: 0x35af2674 WebCore`WebCore::MediaSessionManager::updateSessionState() + 100
</span><span class='line'>frame #4: 0x35af03b6 WebCore`WebCore::MediaSessionManager::addSession(WebCore::MediaSession&) + 74
</span><span class='line'>frame #5: 0x35af0002 WebCore`WebCore::MediaSession::MediaSession(WebCore::MediaSessionClient&) + 38
</span><span class='line'>frame #6: 0x35735a20 WebCore`WebCore::HTMLMediaSession::create(WebCore::MediaSessionClient&) + 20
</span><span class='line'>frame #7: 0x35724c68 WebCore`WebCore::HTMLMediaElement::HTMLMediaElement(WebCore::QualifiedName const&, WebCore::Document&, bool) + 976
</span><span class='line'>frame #8: 0x3570ad24 WebCore`WebCore::HTMLAudioElement::create(WebCore::QualifiedName const&, WebCore::Document&, bool) + 36
</span><span class='line'>frame #9: 0x35718184 WebCore`WebCore::audioConstructor(WebCore::QualifiedName const&, WebCore::Document&, WebCore::HTMLFormElement*, bool) + 56
</span><span class='line'>frame #10: 0x3571803a WebCore`WebCore::HTMLElementFactory::createElement(WebCore::QualifiedName const&, WebCore::Document&, WebCore::HTMLFormElement*, bool) + 230
</span><span class='line'>frame #11: 0x3533a26c WebCore`WebCore::HTMLDocument::createElement(WTF::AtomicString const&, int&) + 88
</span><span class='line'>frame #12: 0x3533a1ae WebCore`WebCore::jsDocumentPrototypeFunctionCreateElement(JSC::ExecState*) + 242
</span><span class='line'>frame #13: 0x2c1cc4d4 JavaScriptCore`llint_entry + 21380</span></code></pre></td></tr></table></div></figure>


<p>发现是WebCore调用了<code>AVAudioSession</code>的setActive方法，并且把active置为了NO。这个过程其实类似于音乐在播放时被其他事件打断（例如电话、siri）一样，audio会被打断，同时会发送<code>kAudioSessionBeginInterruption</code>事件通知app音频播放已经被打断，需要修正播放器和UI状态；打断结束后回发送<code>kAudioSessionEndInterruption</code>事件通知app恢复播放状态。区别在于WebCore的打断并没有任何通知，所以就导致界面上的播放状态为播放中而实际音乐却被打断。</p>

<h1>适配</h1>

<p>接下来就要对这个问题进行适配了：</p>

<ol>
<li>首先，联系了前段组的同事对出现问题的页面进行检查，之后被告知是某个页面的js中调用了一些播放相关的代码导致了这个问题，这些js是之前版本中使用的，现在已经被废弃但没有及时的删除。在删除这些js后，问题自然就消失了。</li>
<li>客户端本身也应该做一些适配来防止下次再有页面出现类似问题，目前我能想到的办法是做一个<code>AVAudioSession</code>的category，method swizzle方法<code>setActive:withOptions:error:</code>在设置active值时发送通知来修改UI的状态。</li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[初始化AudioUnit的正确姿势]]></title>
    <link href="http://msching.github.io/blog/2014/06/25/init-audiounit-when-app-is-in-background/"/>
    <updated>2014-06-25T13:56:27+08:00</updated>
    <id>http://msching.github.io/blog/2014/06/25/init-audiounit-when-app-is-in-background</id>
    <content type="html"><![CDATA[<p>在使用AudioUnit的过程中发现当app在后台时调用<code>extern OSStatus AudioUnitInitialize(AudioUnit inUnit)</code>方法返回<code>561015905</code>错误码，解析成string后是<code>!pla</code>，google错误码后毫无收获，于是只能workaround。面对这个问题我的workaround是当出现初始化失败的情况下会在程序进入前台时再尝试调用<code>AudioUnitInitialize</code>方法来初始化AudioUnit。至此问题已经在一定程度上得到了解决，只要用户进入前台就可以正确初始化AudioUnit并且播放音乐。</p>

<p>今天在应对某个用户反馈时发现该用户在使用remoteControl过程中无法启动播放的情况正是因为后台init AudioUnit会失败导致程序无法如预期工作。于是灵光一闪，觉得在初始化AudioUnit之前先调用<code>AudioSessionInitialize</code>并setActive是否就可以解决问题。尝试之后发现果然可以&hellip;（之前都在AudioUnitInitialize成功后才去init audiosession）。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[合并生成模拟器和真机通用的framework]]></title>
    <link href="http://msching.github.io/blog/2014/05/05/custom-framework-merging/"/>
    <updated>2014-05-05T17:58:00+08:00</updated>
    <id>http://msching.github.io/blog/2014/05/05/custom-framework-merging</id>
    <content type="html"><![CDATA[<p>在使用<a href="https://github.com/kstenerud/iOS-Universal-Framework">iOS-Universal-Framework</a>制作framework的过程中经常会遇到编译出来的framework只能被真机使用或者只能被模拟器使用的情况。</p>

<p>造成这个问题的原因是由于在编译时选择的目标设备不同的情况下编译出来framework体系结构不同，选择真机进行编辑时会编译产生<code>armv7</code>、<code>armv7s</code>、<code>arm64</code>下的库文件，而选择模拟器会产生<code>i386</code>、<code>x86_64</code>下的库文件。
具体查看的方法可以执行下列命令：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ lipo -info /Debug-iphoneos/Someframework.framwork/Someframework
</span><span class='line'># Architectures in the fat file: Someframework are: armv7 armv7s arm64 
</span><span class='line'>
</span><span class='line'>$ lipo -info /Debug-iphonesimulator/Someframework.framwork/Someframework
</span><span class='line'># Architectures in the fat file: Someframework are: i386 x86_64 
</span></code></pre></td></tr></table></div></figure>


<p>要同时对模拟器和真机进行支持，只要对两个编译出来的framework进行合并就可以了。</p>

<p>执行如下命令就可以进行合并</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ lipo –create /Debug-iphoneos/Someframework.framwork/Someframework Debug-iphonesimulator/Someframework.framwork/Someframework –output Someframework</span></code></pre></td></tr></table></div></figure>


<p>完成后再查看framework的版本</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ lipo -info Someframework
</span><span class='line'># Architectures in the fat file: Someframework are: armv7 armv7s arm64 i386 x86_64</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[AVAudioPlayer的1937337955错误研究]]></title>
    <link href="http://msching.github.io/blog/2014/05/04/secret-of-avaudioplayer/"/>
    <updated>2014-05-04T19:46:53+08:00</updated>
    <id>http://msching.github.io/blog/2014/05/04/secret-of-avaudioplayer</id>
    <content type="html"><![CDATA[<h1>问题</h1>

<p>前两天公司有一位同事在使用AVAudioPlayer的过程中遇到了这样一个问题：</p>

<p>他需要播放一段网络上的音频，实现策略是把音频下载到本地，然后使用AVAudioPlayer进行播放。代码大致是这样的：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="n">NSString</span> <span class="o">*</span><span class="n">path</span> <span class="o">=</span> <span class="p">...</span><span class="o">/</span><span class="n">xxx</span><span class="p">.</span><span class="n">mp3</span><span class="p">;</span> <span class="c1">//mp3 file path</span>
</span><span class='line'><span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSData</span> <span class="nl">dataWithContentsOfURL:</span><span class="p">[</span><span class="n">NSURL</span> <span class="nl">fileURLWithPath:</span><span class="n">path</span><span class="p">]];</span>
</span><span class='line'><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'><span class="n">AVAudioPlayer</span> <span class="o">*</span><span class="n">player</span> <span class="o">=</span> <span class="p">[[</span><span class="n">AVAudioPlayer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithData:</span><span class="n">data</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>但他在init AVAudioPlayer时遇到了下面的错误。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="n">Error</span> <span class="n">Domain</span><span class="o">=</span><span class="n">NSOSStatusErrorDomain</span> <span class="n">Code</span><span class="o">=</span><span class="mi">1937337955</span> <span class="s">&quot;The operation couldn’t be completed. (OSStatus error 1937337955.)&quot;</span>
</span></code></pre></td></tr></table></div></figure>




<!--more-->


<hr />

<h1>普遍的解决方法</h1>

<p>在google搜索之后发现1937337955这个code并不少见，在<a href="http://stackoverflow.com/questions/4901709/iphone-avaudioplayer-unsupported-file-type">StackOverflow</a>上有人提问问到这个问题，国内的一些博客中也有提到（例如<a href="http://zhu340381425.blog.163.com/blog/static/75952514201192021013852">@我的桌子</a>和<a href="http://blog.sina.com.cn/s/blog_7cb9b3b80101d8ap.html">@SkyLine</a>）。</p>

<p>其中提到的解决方法都一样，就是使用AVAudioPlayer的另一个init方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithContentsOfURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">url</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>于是尝试修改了代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="n">NSString</span> <span class="o">*</span><span class="n">path</span> <span class="o">=</span> <span class="p">...</span><span class="o">/</span><span class="n">xxx</span><span class="p">.</span><span class="n">mp3</span><span class="p">;</span> <span class="c1">//mp3 file path</span>
</span><span class='line'><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'><span class="n">AVAudioPlayer</span> <span class="o">*</span><span class="n">player</span> <span class="o">=</span> <span class="p">[[</span><span class="n">AVAudioPlayer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithContentsOfURL:</span><span class="p">[</span><span class="n">NSURL</span> <span class="nl">fileURLWithPath:</span><span class="n">path</span><span class="p">]</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>果然没有出现错误，player成功创建并且能够播放。</p>

<hr />

<h1>深究</h1>

<p>不能播放的问题到这里已经fix了，但这个问题本身还没有完结，为什么使用<code>-initWithContentsOfURL:error:</code>方法就可以播呢？这个时候也许有的人会认为这是一个apple的bug，认为<code>-initWithContentsOfURL:error:</code>方法比<code>-initWithData::error:</code>具有更好的适应性。</p>

<pre><code>Oh that's very interesting. Perhaps that should be submitted to Apple as a bug? 
In the end I opted for the saved file anyways because it fit better with what we were trying to do. 
Thanks for the Tip! –  mtmurdock Mar 19 '11 at 0:26
</code></pre>

<p>但凡事遇到错误，都应该先从自身开始找原因。经过搜索发现，1937337955这个Errorcode其实就是<code>kAudioFileUnsupportedFileTypeError</code>，一般出现在文件格式不符合规范的情况下。假设apple并没有写出bug的话，那么上述问题中的这个mp3一定在文件格式上有缺陷，最终导致了<code>-initWithData::error:</code>方法返回错误，而<code>-initWithContentsOfURL:error:</code>采用某种方式规避了这个格式缺陷。</p>

<p>回过头去查看<code>AVAudioPlayer.h</code>头文件可以看到SDK7中多了两个init方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="cm">/* The file type hint is a constant defined in AVMediaFormat.h whose value is a UTI for a file format. e.g. AVFileTypeAIFF. */</span>
</span><span class='line'><span class="cm">/* Sometimes the type of a file cannot be determined from the data, or it is actually corrupt.</span>
</span><span class='line'><span class="cm">The file type hint tells the parser what kind of data to look for so that files which are not self identifying or possibly even corrupt can be successfully parsed. */</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithContentsOfURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">url</span> <span class="nf">fileTypeHint:</span><span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span><span class="nv">utiString</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span> <span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="mi">10</span><span class="n">_9</span><span class="p">,</span> <span class="mi">7</span><span class="n">_0</span><span class="p">);</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithData:</span><span class="p">(</span><span class="n">NSData</span> <span class="o">*</span><span class="p">)</span><span class="nv">data</span> <span class="nf">fileTypeHint:</span><span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span><span class="nv">utiString</span> <span class="nf">error:</span><span class="p">(</span><span class="n">NSError</span> <span class="o">**</span><span class="p">)</span><span class="nv">outError</span> <span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="mi">10</span><span class="n">_9</span><span class="p">,</span> <span class="mi">7</span><span class="n">_0</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>多出来的这个hint参数和<code>AudioToolbox</code>framework中<code>AudioFileStream</code>、<code>AudioFile</code>两个类的Open方法中所使用的hint参数作用一样，可以辅助系统判定当前的文件格式。</p>

<p>接下来尝试在iOS7系统下使用新的init方法生成AVAudioPlayer：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="n">NSString</span> <span class="o">*</span><span class="n">path</span> <span class="o">=</span> <span class="p">...</span><span class="o">/</span><span class="n">xxx</span><span class="p">.</span><span class="n">mp3</span><span class="p">;</span> <span class="c1">//mp3 file path</span>
</span><span class='line'><span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSData</span> <span class="nl">dataWithContentsOfURL:</span><span class="p">[</span><span class="n">NSURL</span> <span class="nl">fileURLWithPath:</span><span class="n">path</span><span class="p">]];</span>
</span><span class='line'><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'><span class="n">AVAudioPlayer</span> <span class="o">*</span><span class="n">player</span> <span class="o">=</span> <span class="p">[[</span><span class="n">AVAudioPlayer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithData:</span><span class="n">data</span> <span class="nl">fileTypeHint:</span><span class="n">AVFileTypeMPEGLayer3</span> <span class="nl">error:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>结果没有错误产生，没有出现错误，player成功创建并且能够播放。</p>

<p>进而进行另两个实验：</p>

<ol>
<li>把保存文件时的.mp3后缀去掉后使用<code>-initWithContentsOfURL:error:</code>生成对象，结果发现产生了错误。</li>
<li>把保存文件时的.mp3后缀改为.wav后使用<code>-initWithContentsOfURL:error:</code>生成对象，结果发现产生了错误。</li>
</ol>


<p>于是确定<code>-initWithContentsOfURL:error:</code>方法是利用后缀名作为hintType对文件的解码进行辅助，而<code>-initWithData::error:</code>方法由于没有任何hint并且文件本身格式又有缺陷导致错误的产生。</p>

<hr />

<h1>更完整的解决方法</h1>

<p>基于以上分析可以得出一个更为完整的解决方法，可以有效的规避这一类错误：</p>

<ol>
<li>对于iOS7以上的系统（含iOS7）,在确定文件格式的情况下可以使用<code>initWithData:fileTypeHint:error:</code>和<code>initWithContentsOfURL:fileTypeHint:error:</code>生成实例，或者把data保存为对应后缀名的文件后使用<code>-initWithContentsOfURL:error:</code>后再生成实例；</li>
<li>对于iOS7以下的系统，在确定文件格式的情况下，最为安全的方法是把data保存为对应后缀名的文件后使用<code>-initWithContentsOfURL:error:</code>生成实例；</li>
</ol>


<p>如果上述方法帮不了你，那么就只能去检查文件格式有没有问题或者采用其他的实现方式进行尝试了（比如<code>AVPlayer</code>和<code>AudioToolBox</code>）。不管怎么说，客户端所能做的只是尽量减少错误发生的频率，最终解决这类问题还是需要音频文件的提供者确保音频文件的格式符合标准没有错误和缺陷。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ASIHTTPRequest iOS7下内存泄漏问题解决记录]]></title>
    <link href="http://msching.github.io/blog/2014/05/04/leak-of-asihttprequest-on-ios7/"/>
    <updated>2014-05-04T14:43:40+08:00</updated>
    <id>http://msching.github.io/blog/2014/05/04/leak-of-asihttprequest-on-ios7</id>
    <content type="html"><![CDATA[<h3>这是2013年下半年解决iOS7下ASIHTTPRequest内存泄露时所做的记录，现在搬运过来了。</h3>

<h3>现在这个修复方法已经被merge到ASIHTTPRequest的主分支上，经过测试可以通过apple的审核，大家可以直接从主分支fork并使用了。</h3>

<h1>发现问题</h1>

<p>iOS7发布后，我们对产品进行了iOS7的适配。适配完成之后的某天，我使用Leaks对产品的新版本进行内存泄漏检测时发现ASIHTTPRequest存在内存泄漏问题，当时使用的设备是iTouch5，系统为iOS7.0.2。</p>

<p><strong>Leaks检测结果</strong></p>

<p><img src="http://ww3.sinaimg.cn/large/7d97a68cgw1eb8uxr5ui3j20sl068acm.jpg" alt="Leaks" /></p>

<p>(ps:使用的是ASIHTTPRequest iPhoneSample的检测图，结果是一样的)</p>

<p>发现之初，我以为是某处ASIHTTPRequest使用不当导致的泄漏，于是把leaks中的堆栈全部都检查了一边，但没有发现任何产品工程中的代码（其中一处泄漏的堆栈如图）。</p>

<p><strong>Leaks中StackTrace结果</strong></p>

<p><img src="http://ww2.sinaimg.cn/large/7d97a68cgw1eb8uxtkz79j209s0gnmz2.jpg" alt="StackTrace" /></p>

<p>由于在iOS7发布之前的所有版本中并未看到类似的内存泄漏，所以我就开始怀疑是ASIHTTPRequest在iOS7才产生的。于是我在iOS5和iOS6的设备上进行了Leak Profile，结果没有发现任何泄漏。</p>

<p>对于这样的结果我仍然不是很确信，因为项目的需要我们对ASIHTTPRequest进行了一定的定制，修改了其中一部分代码。为了确定问题确实是出在ASIHTTPRequest上，我去github上翻出了ASIHTTPRequest的repo，pull了最新的代码，用Leaks在iOS7系统上进行了profile。在profile过程中我对iPhone Sample中的每个Tab以此进行了测试，结果在<strong>Synchronous</strong>和<strong>Queue</strong>上并没有发现内存泄漏，在<strong>Upload</strong>上发现了和之前一样泄漏。随后在iOS5和iOS6上也进行了一样的测试，结果依然是没有任何泄漏。</p>

<p>自此确定了这是ASIHTTPRequest在iOS7下特有的内存泄漏，并且只会出现在有POST body的情况下。</p>

<!--more-->


<hr />

<h1>寻找解答</h1>

<p>发现问题之后，我仔细查看了Leaks Profile的结果，发现泄漏集中在<code>ASIInputStream</code>上，应该是在使用的过程中<code>release</code>方法在某种情况下没有被调用到。于是我重写了ASIIputStream的release方法并在其中断点，分别在iOS7和iOS6进行调试后发现iOS7比iOS6少了一次Release的调用，堆栈如图所示。</p>

<p><strong>缺少的Release的断点Stack Trace结果</strong></p>

<p><img src="http://ww3.sinaimg.cn/large/7d97a68cgw1eb8uxssluaj20dd0bsab3.jpg" alt="Release" /></p>

<p>从堆栈来看似乎是 <code>CoreReadStreamFromCFReadStream</code> 这个类的析构函数在iOS7下没有被调用到。当时就感觉没救了，这是私有类，想要强行触发析构似乎是不可能的，能做的就是尽量减少其中的泄漏。于是我们做了如下修改：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">close</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="n">stream</span> <span class="n">close</span><span class="p">];</span>
</span><span class='line'>    <span class="p">[</span><span class="n">stream</span> <span class="n">release</span><span class="p">];</span>
</span><span class='line'>    <span class="n">stream</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>修改了<code>ASIInputStream</code>的<code>close</code>方法，在close完成后把其中的<code>NSInputStream</code>对象release掉，以减少内存泄漏，同时保证ASIHTTPRequest不被重复使用（因为其中的NSInputStream已经被release了无法再使用）。</p>

<p>这样一来泄漏有了一定的减少，如图。</p>

<p><strong>修改后的Leaks检测结果</strong></p>

<p><img src="http://ww2.sinaimg.cn/large/7d97a68cgw1eb8uxs8d4kj20su023q3r.jpg" alt="Leakss" /></p>

<p>但这样并不能真正解决问题，泄漏依然存在，但我一时也想不到很好的办法，于是只能给repo发issue期望能够得到原作者的回复（虽然我知道希望不大，这哥们很久没管这事了- -）。
这是我发的issue：<a href="https://github.com/pokeb/asi-http-request/issues/378">https://github.com/pokeb/asi-http-request/issues/378</a></p>

<hr />

<h1>解决问题</h1>

<p>发issue大约一周后的某一天收到github的邮件，说有人回复我的issue了，进去一看有一个好心人这样解答道：</p>

<pre><code>@mjohnson12

The leak is because ASIInputStream is being cast to a CFReadStreamRef but ASIInputStream does not derive from NSInputStream it just wraps it.

My Solution is to get rid of ASIInputStream and create a NSInputStream instead in the ASIHTTPRequest startRequest: method.

It breaks using the metrics that ASIInputStream records but I wasn't using them.

I'm using a fairly old version of ASIHTTPRequest v.1.6.2 so your milage may vary.
</code></pre>

<p>于是我按照他的做法，把ASIHTTPRequest里的<code>ASIInputStream</code>全部替换成了<code>NSInputStream</code>，再用Leaks Profile的时候泄漏果真消失了。也正如这位仁兄所说的，<code>ASIInputStream</code>只是把自己伪装成一个<code>NSInputStream</code>，并且实现了一些<code>NSInputStream</code>的接口，实际都是由其中包含的<code>NSInputStream</code>实例完成的。<code>ASIInputStream</code>这个类的功能主要是用来做流量限制，如果不需要这个功能的话，直接把<code>ASIInputStream</code>替换成<code>NSInputStream</code>即可解决问题。</p>

<p>那么如果我把<code>ASIInputStream</code>继承自<code>NSInputStream</code>的话是不是就能既保留流量限制功能又解决泄漏问题了呢？于是我开始尝试继承<code>NSInputStream</code>，其中碰到了一些困难。NSInputStream的init方法都是写在一个Category里的，无法被继承- -！。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="k">@interface</span> <span class="nc">NSInputStream</span> <span class="nl">(NSInputStreamExtensions)</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithData:</span><span class="p">(</span><span class="n">NSData</span> <span class="o">*</span><span class="p">)</span><span class="nv">data</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithFileAtPath:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">path</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">url</span> <span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="mi">10</span><span class="n">_6</span><span class="p">,</span> <span class="mi">4</span><span class="n">_0</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">inputStreamWithData:</span><span class="p">(</span><span class="n">NSData</span> <span class="o">*</span><span class="p">)</span><span class="nv">data</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">inputStreamWithFileAtPath:</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">path</span><span class="p">;</span>
</span><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">inputStreamWithURL:</span><span class="p">(</span><span class="n">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="nv">url</span> <span class="n">NS_AVAILABLE</span><span class="p">(</span><span class="mi">10</span><span class="n">_6</span><span class="p">,</span> <span class="mi">4</span><span class="n">_0</span><span class="p">);</span>
</span><span class='line'><span class="k">@end</span>
</span></code></pre></td></tr></table></div></figure>


<p>经过一番google我在git上发现了一个repo：<a href="https://github.com/bjhomer/HSCountingInputStream">https://github.com/bjhomer/HSCountingInputStream</a></p>

<p>这个repo中实现了对于<code>NSInputStream</code>的继承。仔细阅读完成后发现其实所谓的继承也只不过时在类的@interface中声明了一下继承自<code>NSInputStream</code>而已，实际的工作还是由类中的一个<code>NSInputStream</code>实例完成的，但相比于<code>ASIInputStream</code>这个repo里的实现多了几个方法：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="c1">//私有的CFRunLoopRef schedule、unschedule方法</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">_scheduleInCFRunLoop:</span><span class="p">(</span><span class="n">CFRunLoopRef</span><span class="p">)</span><span class="nv">aRunLoop</span> <span class="nf">forMode:</span><span class="p">(</span><span class="n">CFStringRef</span><span class="p">)</span><span class="nv">aMode</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">_setCFClientFlags:</span><span class="p">(</span><span class="n">CFOptionFlags</span><span class="p">)</span><span class="nv">inFlags</span>
</span><span class='line'>                 <span class="nf">callback:</span><span class="p">(</span><span class="n">CFReadStreamClientCallBack</span><span class="p">)</span><span class="nv">inCallback</span>
</span><span class='line'>                  <span class="nf">context:</span><span class="p">(</span><span class="n">CFStreamClientContext</span> <span class="o">*</span><span class="p">)</span><span class="nv">inContext</span><span class="p">;</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">_unscheduleFromCFRunLoop:</span><span class="p">(</span><span class="n">CFRunLoopRef</span><span class="p">)</span><span class="nv">aRunLoop</span> <span class="nf">forMode:</span><span class="p">(</span><span class="n">CFStringRef</span><span class="p">)</span><span class="nv">aMode</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//NSInputStream的代理方法</span>
</span><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">stream:</span><span class="p">(</span><span class="n">NSStream</span> <span class="o">*</span><span class="p">)</span><span class="nv">aStream</span> <span class="nf">handleEvent:</span><span class="p">(</span><span class="n">NSStreamEvent</span><span class="p">)</span><span class="nv">eventCode</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>这些方法的作用大家可以参考链接：<a href="http://blog.octiplex.com/2011/06/how-to-implement-a-corefoundation-toll-free-bridged-nsinputstream-subclass/">http://blog.octiplex.com/2011/06/how-to-implement-a-corefoundation-toll-free-bridged-nsinputstream-subclass/</a>。</p>

<p>简单的说就是<code>NSInputStream</code>需要同时支持<code>NSRunLoop</code>和<code>CFRunLoopRef</code>的schedule和unschedule方法，那几个私有方法就是负责<code>CFRunLoopRef</code>的schedule，NSInputStream的代理方法则是负责事件的传递。这使我联想到了之前提到的没有被调用的<code>release</code>方法，iOS6下它的堆栈中正好有<code>CFRunLoopRef</code>的一些方法。莫非这就是<code>ASIInputStream</code>内存泄漏的原因所在？</p>

<p>我当时的猜测是，iOS7下的内存泄漏是<code>ASIInputStream</code>伪装的不够像而导致的，之所以这么认为因为虽然有泄漏但<code>ASIInputStream</code>的功能依然存在，HTTP请求并未因此失效，这说明<code>ASIInputStream</code>还是被bridge成了<code>CFReadStream</code>，但只是因为少了和<code>NSInputStream</code>一样的unschedule方法导致其没有被正常unschedule。如果我把<code>ASIInputStream</code>的unschedule方法补上是否就可以解决问题？</p>

<p>我把上述的几个方法在<code>ASIInputStream</code>中实现了以后再进行Leaks Profile，内存泄漏果然如预期的那样消失了！在<code>ASIInputStream</code>的<code>release</code>方法中打断点后发现也能够正常调用了。问题到这里算是解决了。接下来要解决这些私有方法调用可能会碰到的审核不通过问题，正好上面那篇文章里提供了思路，用runtime把三个私有方法重定向到自定义的方法上（方法名只是把&#8221;_&ldquo;去掉了）。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='objective-c'><span class='line'><span class="k">+</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span> <span class="nf">resolveInstanceMethod:</span><span class="p">(</span><span class="kt">SEL</span><span class="p">)</span> <span class="n">selector</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="n">NSString</span> <span class="o">*</span> <span class="n">name</span> <span class="o">=</span> <span class="n">NSStringFromSelector</span><span class="p">(</span><span class="n">selector</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span> <span class="p">[</span><span class="n">name</span> <span class="nl">hasPrefix:</span><span class="s">@&quot;_&quot;</span><span class="p">]</span> <span class="p">)</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="n">name</span> <span class="o">=</span> <span class="p">[</span><span class="n">name</span> <span class="nl">substringFromIndex:</span><span class="mi">1</span><span class="p">];</span>
</span><span class='line'>        <span class="kt">SEL</span> <span class="n">aSelector</span> <span class="o">=</span> <span class="n">NSSelectorFromString</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
</span><span class='line'>        <span class="n">Method</span> <span class="n">method</span> <span class="o">=</span> <span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">aSelector</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span> <span class="n">method</span> <span class="p">)</span>
</span><span class='line'>        <span class="p">{</span>
</span><span class='line'>            <span class="n">class_addMethod</span><span class="p">(</span><span class="n">self</span><span class="p">,</span>
</span><span class='line'>                            <span class="n">selector</span><span class="p">,</span>
</span><span class='line'>                            <span class="n">method_getImplementation</span><span class="p">(</span><span class="n">method</span><span class="p">),</span>
</span><span class='line'>                            <span class="n">method_getTypeEncoding</span><span class="p">(</span><span class="n">method</span><span class="p">));</span>
</span><span class='line'>            <span class="k">return</span> <span class="n">YES</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">return</span> <span class="p">[</span><span class="n">super</span> <span class="nl">resolveInstanceMethod:</span><span class="n">selector</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>至此大功告成，问题顺利解决。但这个问题之所以会出现的真正缘由我还是没有弄清楚，Apple在iOS7下究竟做了什么，哪位大神如果知道的话还请告知。。感激不尽 ~_~。</p>

<p>附上修改过后的<code>ASIInputStream</code><a href="https://github.com/OpenFibers/asi-http-request/commit/499a3be1f92d7023e2d2092197dbb71c77cdd330">代码</a></p>

<hr />

<h1>后记</h1>

<p>问题没解决的时候其他同事建议我更换成AFNetworking等等其他开源库，因为这些库更新快文档全，ASIHTTPRequest接口复杂、代码繁多而且如今已年久失修无人维护了。确实如此，但由于一些项目上的原因我们无法更换，况且ASIHTTRequest在效率上略好，并且拥有其他基于NSURLConnection的库不具备一些功能。在解决问题的过程中也让我对NSInputStream的工作机制有了更深的理解，可谓一石二鸟。由于鄙人能力有限，其中一些地方可能说的有错误或者有纰漏的话还请大神们指正:)</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[基于Github和Octopress搭建属于自己的博客]]></title>
    <link href="http://msching.github.io/blog/2014/04/11/starting/"/>
    <updated>2014-04-11T14:10:07+08:00</updated>
    <id>http://msching.github.io/blog/2014/04/11/starting</id>
    <content type="html"><![CDATA[<h1>前言</h1>

<p>从小我就相信“好记性不如烂笔头”这句谚语，所以搭Blog想法在我的脑海中已经酝酿了很久。刚工作那会就想利用Blog来记录工作中所积累的知识，对此我也进行了一些尝试，但最终因为国内的一些博客站点糟糕的排版、设计、代码高亮等等各种原因而放弃了。之后的一段时间由于工作忙碌、其他各种事情以及犯懒的缘故一直没有把这件事情落实下来。最近在项目进行的过程中发现自己对之前碰到过的一些技术问题的记忆逐渐变得模糊起来，于是才把搭建Blog这件事情重新提上了日程。经过一番Google之后我发现现在的程序猿们都偏向于<a href="http://octopress.org/">Octopress</a>这个开源的框架加上<a href="https://pages.github.com/">Github Pages</a>服务来搭建Blog，看上非常的高大上，排版、代码高亮都做得非常棒，以<a href="http://zh.wikipedia.org/zh/Markdown">Markdown</a>写Blog的方式也非常符合我的日常工作习惯，于是决定立马付诸行动。</p>

<!--more-->


<hr />

<h1>安装Octopress</h1>

<p><a href="http://octopress.org/docs/setup/">这里</a>是Octopress的官方指南，各位可以按照其中的步骤进行安装，下面的文字只是记录了我个人的安装过程，可以为大家提供一些参考。</p>

<p>由于Octopress的使用需要Ruby，于是搭环境就这条路上的第一只拦路虎。Ruby版本繁多并且版本之间向下兼容做的不好，所以基于Ruby所做的框架大多要求特定版本的Ruby才能正常运行。</p>

<p>Octopress要求的是Ruby1.9.3，MacOSX自带的Ruby版本是2.x的，所以需要利用一些工具来安装低版本的Ruby。Octopress的官方指南推荐使用的是RVM和rbenv，可以根据需要选择使用需要的工具。</p>

<h3>安装rbenv</h3>

<p>在第一次安装octopress（OSX 10.10时代）的过程中我首先使用了RVM，但碰到了一些莫名的问题无法解决，最后还是使用了rbenv。</p>

<p>这里我使用<a href="http://brew.sh/">Homebrew</a>来安装rbenv，如果你没有Homebrew，打开终端，使用以下命令安装吧。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"</span></code></pre></td></tr></table></div></figure>


<p>有了Homebrew就可以安装rbenv了</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ brew update
</span><span class='line'>$ brew install rbenv
</span><span class='line'>$ brew install ruby-build</span></code></pre></td></tr></table></div></figure>


<h3>用rbenv安装Ruby</h3>

<p>使用rbenv安装1.9.3版本的ruby，一开始我安装的是1.9.3-p0的版本，但出现了一些错误，在搜素如何解决的过程中发现<a href="http://www.cnblogs.com/peterpan507/p/3538057.html">@Peter潘</a>在blog中写到可以尝试用1.9.3-p125，经过尝试成功的安装上了Ruby1.9.3-p125</p>

<p>安装完成后可以用ruby &mdash;version进行验证</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ rbenv install 1.9.3-p125
</span><span class='line'>$ rbenv local 1.9.3-p125
</span><span class='line'>$ rbenv rehash
</span><span class='line'>$ ruby --version #ruby 1.9.3p125 (2012-02-16 revision 34643)</span></code></pre></td></tr></table></div></figure>


<h3>安装RVM</h3>

<p>在第二次安装octopress（OSX 10.11时代）的过程中依照之前的经验使用rbenv，但却怎么也安不上需要的ruby版本，最后换回了RVM。。</p>

<p>安装octopress官方提供的安装方法</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ curl -L https://get.rvm.io | bash -s stable --ruby</span></code></pre></td></tr></table></div></figure>


<h3>用RVM安装Ruby</h3>

<p>使用rvm安装1.9.3版本的ruby，我依照之前的经验安装1.9.3-p125的版本，但在编译时出现了一些错误，继而尝试了评论中<a href="">@lance_lan</a>提到得1.9.3-p551版本才安装成功。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ rvm install ruby-1.9.3-p551 --with-gcc=clang
</span><span class='line'>$ rvm use 1.9.3-p551
</span><span class='line'>$ rvm rubygems latest
</span><span class='line'>$ ruby --version #ruby 1.9.3p551 (2014-11-13 revision 48407)</span></code></pre></td></tr></table></div></figure>


<h3>安装Octopress</h3>

<p>安装Ruby完成后就按照官方指南安装Octpress</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#clone octopress
</span><span class='line'>$ git clone git://github.com/imathis/octopress.git octopress
</span><span class='line'>$ cd octopress
</span><span class='line'>
</span><span class='line'>#安装依赖
</span><span class='line'>$ gem install bundler
</span><span class='line'>$ rbenv rehash  # 如果你刚才用了rbenv
</span><span class='line'>$ bundle install
</span><span class='line'>
</span><span class='line'>#安装octopress默认主题
</span><span class='line'>$ rake install</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>部署</h1>

<p>接下来需要把Blog部署到github上去，第一步要做的是去<a href="https://github.com/new">github</a>创建一个<code>username.github.io</code>的repo，比如我的就叫<code>msching.github.io</code>。</p>

<p>然后运行以下命令，并依照提示完成github和Octopress的关联</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ rake setup_github_pages</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>创建博客</h1>

<h3>生成博客</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ rake generate
</span><span class='line'>$ rake deploy</span></code></pre></td></tr></table></div></figure>


<p>把生成后的代码上传到github</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ git add .
</span><span class='line'>$ git commit -m 'create blog'
</span><span class='line'>$ git push origin source</span></code></pre></td></tr></table></div></figure>


<p>完成后等待一段时间后就能访问<code>http://username.github.io</code>看到自己的博客了</p>

<h3>修改配置</h3>

<p>配置文件路径为<code>~/octopress/_config.yml</code></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>url:                # For rewriting urls for RSS, etc
</span><span class='line'>title:              # Used in the header and title tags
</span><span class='line'>subtitle:           # A description used in the header
</span><span class='line'>author:             # Your name, for RSS, Copyright, Metadata
</span><span class='line'>simple_search:      # Search engine for simple site search
</span><span class='line'>description:        # A default meta description for your site
</span><span class='line'>date_format:        # Format dates using Ruby's date strftime syntax
</span><span class='line'>subscribe_rss:      # Url for your blog's feed, defauts to /atom.xml
</span><span class='line'>subscribe_email:    # Url to subscribe by email (service required)
</span><span class='line'>category_feeds:     # Enable per category RSS feeds (defaults to false in 2.1)
</span><span class='line'>email:              # Email address for the RSS feed if you want it.</span></code></pre></td></tr></table></div></figure>


<p>编辑完成后</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ rake generate
</span><span class='line'>
</span><span class='line'>$ git add .
</span><span class='line'>$ git commit -m "settings" 
</span><span class='line'>$ git push origin source
</span><span class='line'>
</span><span class='line'>$ rake deploy</span></code></pre></td></tr></table></div></figure>


<h3>安装第三方主题</h3>

<p>Octopress有许多第三方主题可以选择，首先在<a href="http://opthemes.com/">这里</a>上寻找喜欢的主题，点击进入对应主题的git，一般在readme上都会有安装流程</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#这里以安装allenhsu定制的greyshade主题为例，原作者是shashankmehta
</span><span class='line'>$ git clone git@github.com:allenhsu/greyshade.git .themes/greyshade
</span><span class='line'>
</span><span class='line'>#Substitue 'color' with your highlight color
</span><span class='line'>$ echo "\$greyshade: color;" &gt;&gt; sass/custom/_colors.scss 
</span><span class='line'>
</span><span class='line'>$ rake "install[greyshade]"
</span><span class='line'>$ rake generate
</span><span class='line'>
</span><span class='line'>$ git add .
</span><span class='line'>$ git commit -m "theme" 
</span><span class='line'>$ git push origin source
</span><span class='line'>
</span><span class='line'>$ rake deploy</span></code></pre></td></tr></table></div></figure>


<h3>定制第三方主题</h3>

<p>使用第三方主题也并非是一个“拎包入住”的过程，其中必然会有一些需要定制的地方。定制的过程中会涉及一些web相关的知识，但对于各位来说应该都并非难事。</p>

<p>例如刚安装完greyshade之后我们会发现左边navigation上的<strong>About me</strong>是指向作者的个人主页，我们需要把这个文字连接定向到自己的个人主页上。</p>

<p>这个aboutme对应的html为<code>/source/_includes/custom/navigation.html</code></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>&lt;li&gt;&lt;a href="http://msching.github.io/"&gt;Blog&lt;/a&gt;&lt;/li&gt;
</span><span class='line'>&lt;li&gt;&lt;a href="http://about.me/shashankmehta"&gt;About&lt;/a&gt;&lt;/li&gt;
</span><span class='line'>&lt;li&gt;&lt;a href="http://msching.github.io/blog/archives"&gt;Archives&lt;/a&gt;&lt;/li&gt;</span></code></pre></td></tr></table></div></figure>


<p>把其中的<code>http://about.me/shashankmehta</code>替换成需要的url，替换完之后</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ rake generate
</span><span class='line'>
</span><span class='line'>$ git add .
</span><span class='line'>$ git commit -m "theme" 
</span><span class='line'>$ git push origin source
</span><span class='line'>
</span><span class='line'>$ rake deploy</span></code></pre></td></tr></table></div></figure>


<h3>支持中文标签</h3>

<p>目前版本的Octopress会在<code>/source/blog/categories</code>下创建一个<code>index.markdown</code>来作为分类的首页，但这个首页在标签有中文时会出现无法跳转的情况，原因是因为在出现中文标签时Octopress会把文件的路径中的中文转换成拼音，而在Category跳转时是直接写了中文路径，结果自然是404。解决方法是自己实现一个分类首页并处理中文。</p>

<p>首先按照<a href="https://kaworu.ch/blog/2013/09/23/categories-page-with-octopress/">这里</a>的方法实现<code>index.html</code></p>

<p>将<code>plugins/category_list_tag.rb</code>中的</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>category_url = File.join(category_dir, category.gsub(/_|\P{Word}/, '-').gsub(/-{2,}/, '-').downcase)</span></code></pre></td></tr></table></div></figure>


<p>替换成</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>category_url = File.join(category_dir, category.to_url.downcase)</span></code></pre></td></tr></table></div></figure>


<p>这样你的博客就可以支持中文标签的跳转了。</p>

<hr />

<h1>写博客</h1>

<p>经过上面几部后，博客已经成功搭建，现在就可以开始写博文了。</p>

<h3>创建博文</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#如果用的是终端
</span><span class='line'>$ rake new_post['title']
</span><span class='line'>
</span><span class='line'>#如果用的是ZSH
</span><span class='line'>$ rake "new_post[title]"
</span><span class='line'>#或者
</span><span class='line'>$ rake new_post\['title'\]</span></code></pre></td></tr></table></div></figure>


<p>生成的文件在<code>~/source/_posts</code>目录下</p>

<h3>编辑博文</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#...markdown写博文
</span><span class='line'>
</span><span class='line'>$ rake preview #localhost:4000
</span><span class='line'>
</span><span class='line'>$ rake generate
</span><span class='line'>
</span><span class='line'>$ git add .
</span><span class='line'>$ git commit -m "comment" 
</span><span class='line'>$ git push origin source
</span><span class='line'>
</span><span class='line'>$ rake deploy</span></code></pre></td></tr></table></div></figure>


<hr />

<h1>参考资料</h1>

<ul>
<li><a href="http://octopress.org/">http://octopress.org/</a></li>
<li><a href="http://blog.devtang.com/blog/2012/02/10/setup-blog-based-on-github/">http://blog.devtang.com/blog/2012/02/10/setup-blog-based-on-github/</a></li>
<li><a href="http://www.cnblogs.com/peterpan507/p/3538057.html">http://www.cnblogs.com/peterpan507/p/3538057.html</a></li>
<li><a href="http://biaobiaoqi.me/blog/2013/03/21/building-octopress-in-github-mac/">http://biaobiaoqi.me/blog/2013/03/21/building-octopress-in-github-mac/</a></li>
<li><a href="http://biaobiaoqi.me/blog/2013/07/10/decorate-octopress/">http://biaobiaoqi.me/blog/2013/07/10/decorate-octopress/</a></li>
<li><a href="http://yanping.me/cn/blog/2012/01/07/theming-and-customization/">http://yanping.me/cn/blog/2012/01/07/theming-and-customization/</a></li>
</ul>

]]></content>
  </entry>
  
</feed>
