<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>hardman的blog</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://hardman.github.io/"/>
  <updated>2018-07-19T07:13:09.421Z</updated>
  <id>https://hardman.github.io/</id>
  
  <author>
    <name>hardman</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（十一）sps&amp;amp;pps和AudioSpecificConfig介绍（完结） </title>
    <link href="https://hardman.github.io/2017/11/23/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E5%8D%81%E4%B8%80%EF%BC%89sps-amp-pps%E5%92%8CAudioSpecificConfig%E4%BB%8B%E7%BB%8D%EF%BC%88%E5%AE%8C%E7%BB%93%EF%BC%89/"/>
    <id>https://hardman.github.io/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/</id>
    <published>2017-11-23T04:22:25.000Z</published>
    <updated>2018-07-19T07:13:09.421Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><h2 id="简述sps-pps-AudioSpecificConfig"><a href="#简述sps-pps-AudioSpecificConfig" class="headerlink" title="简述sps/pps/AudioSpecificConfig"></a>简述sps/pps/AudioSpecificConfig</h2><p>前文中已经多次提到过sps&amp;pps/AudioSpecificConfig。</p><p>sps&amp;pps是h264中的概念，它包含了一些编码信息，如profile，图像尺寸等信息。在flv中，包含sps&amp;pps的部分被称为 AVC Sequence header（即AVCDecoderConfigurationRecord，参考ISO-14496-15 AVC file format）。</p><p>AudioSpecificConfig是aac中的概念，它包含了音频信息，如采样率，声道数等信息。在flv中包含AudioSpecificConfig的部分被称为 AAC Sequence header（即AudioSpecificConfig，参考ISO-14496-3 Audio）。</p><p>这两种数据格式可参考标准文档或者网络上的博文，这里只介绍一下在硬编码/软编码的情况下，如何获取并处理这些数据。</p><p>可以看出，这两个概念其实就是编码的一个配置文件，保存的是后续音视频数据的一些公共属性。</p><h2 id="sps-amp-pps"><a href="#sps-amp-pps" class="headerlink" title="sps&amp;pps"></a>sps&amp;pps</h2><p>h264编码后，能够直接获取sps&amp;pps数据。</p><p>软编码获取sps&amp;pps数据的代码在aw_x264.c中<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">//软编码x264获取sps&amp;pps数据</span><br><span class="line">static void aw_encode_x264_header(aw_x264_context *aw_ctx)&#123;</span><br><span class="line">    //主要就是libx264中的此方法</span><br><span class="line">    x264_encoder_headers(aw_ctx-&gt;x264_handler, &amp;aw_ctx-&gt;nal, &amp;aw_ctx-&gt;nal_count);</span><br><span class="line">    </span><br><span class="line">    //将获取到的sps&amp;pps数据取出来，保存到aw_ctx-&gt;sps_pps_data中</span><br><span class="line">    //保存sps pps data</span><br><span class="line">    uint8_t *sps_bytes = NULL;</span><br><span class="line">    int8_t sps_len = 0;</span><br><span class="line">    uint8_t *pps_bytes = NULL;</span><br><span class="line">    int8_t pps_len = 0;</span><br><span class="line">    int i = 0;</span><br><span class="line">    for (; i &lt; aw_ctx-&gt;nal_count; i++) &#123;</span><br><span class="line">        if (aw_ctx-&gt;nal[i].i_type == NAL_SPS) &#123;</span><br><span class="line">            sps_bytes = (uint8_t *)aw_ctx-&gt;nal[i].p_payload + 4;</span><br><span class="line">            sps_len = aw_ctx-&gt;nal[i].i_payload - 4;</span><br><span class="line">        &#125;else if(aw_ctx-&gt;nal[i].i_type == NAL_PPS)&#123;</span><br><span class="line">            pps_bytes = (uint8_t *)aw_ctx-&gt;nal[i].p_payload + 4;</span><br><span class="line">            pps_len = aw_ctx-&gt;nal[i].i_payload - 4;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    aw_data *avc_decoder_record = aw_create_sps_pps_data(sps_bytes, sps_len, pps_bytes, pps_len);</span><br><span class="line">    memcpy_aw_data(&amp;aw_ctx-&gt;sps_pps_data, avc_decoder_record-&gt;data, avc_decoder_record-&gt;size);</span><br><span class="line">    free_aw_data(&amp;avc_decoder_record);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>硬编码的sps&amp;pps数据能够通过关键帧获取。代码在AWHWH264Encoder.m中</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">//硬编码h264获取sps&amp;pps数据</span><br><span class="line">static void vtCompressionSessionCallback (void * CM_NULLABLE outputCallbackRefCon,</span><br><span class="line">                                          void * CM_NULLABLE sourceFrameRefCon,</span><br><span class="line">                                          OSStatus status,</span><br><span class="line">                                          VTEncodeInfoFlags infoFlags,</span><br><span class="line">                                          CM_NULLABLE CMSampleBufferRef sampleBuffer )&#123;</span><br><span class="line">    ... ...</span><br><span class="line">    ... ...</span><br><span class="line">    //是否是关键帧，关键帧和非关键帧要区分清楚。推流时也要注明。</span><br><span class="line">    BOOL isKeyFrame = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);</span><br><span class="line">    </span><br><span class="line">    //首先获取sps 和pps</span><br><span class="line">    //sps pss 也是h264的一部分，可以认为它们是特别的h264视频帧，保存了h264视频的一些必要信息。</span><br><span class="line">    //没有这部分数据h264视频很难解析出来。</span><br><span class="line">    //数据处理时，sps pps 数据可以作为一个普通h264帧，放在h264视频流的最前面。</span><br><span class="line">    BOOL needSpsPps = NO;</span><br><span class="line">    //这里判断一下只取一次sps&amp;pps即可</span><br><span class="line">    if (!encoder.spsPpsData) &#123;</span><br><span class="line">        if (isKeyFrame) &#123;</span><br><span class="line">            //获取avcC，这就是我们想要的sps和pps数据。</span><br><span class="line">            //如果保存到文件中，需要将此数据前加上 [0 0 0 1] 4个字节，写入到h264文件的最前面。</span><br><span class="line">            //如果推流，将此数据放入flv数据区即可。</span><br><span class="line">            CMFormatDescriptionRef sampleBufFormat = CMSampleBufferGetFormatDescription(sampleBuffer);</span><br><span class="line">            NSDictionary *dict = (__bridge NSDictionary *)CMFormatDescriptionGetExtensions(sampleBufFormat);</span><br><span class="line">            encoder.spsPpsData = dict[@&quot;SampleDescriptionExtensionAtoms&quot;][@&quot;avcC&quot;];</span><br><span class="line">        &#125;</span><br><span class="line">        needSpsPps = YES;</span><br><span class="line">    &#125;</span><br><span class="line">    ... ... </span><br><span class="line">    ... ...</span><br></pre></td></tr></table></figure><p>成功获取sps&amp;pps数据后，可通过aw_sw_x264_encoder.c中的方法aw_encoder_create_sps_pps_tag创建对应的video tag，之后可直接像普通video tag一样发送。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">//创建sps_pps_tag</span><br><span class="line">extern aw_flv_video_tag *aw_encoder_create_sps_pps_tag(aw_data *sps_pps_data)&#123;</span><br><span class="line">    //创建普通video tag</span><br><span class="line">    aw_flv_video_tag *sps_pps_tag = aw_sw_encoder_create_flv_video_tag();</span><br><span class="line">    //关键帧</span><br><span class="line">    sps_pps_tag-&gt;frame_type = aw_flv_v_frame_type_key;</span><br><span class="line">    //package类型，固定的写0即可</span><br><span class="line">    sps_pps_tag-&gt;h264_package_type = aw_flv_v_h264_packet_type_seq_header;</span><br><span class="line">    //cts写0</span><br><span class="line">    sps_pps_tag-&gt;h264_composition_time = 0;</span><br><span class="line">    //sps&amp;pps数据，数据上同真实video tag的h264数据放同一个位置</span><br><span class="line">    sps_pps_tag-&gt;config_record_data = copy_aw_data(sps_pps_data);</span><br><span class="line">    //pts写0</span><br><span class="line">    sps_pps_tag-&gt;common_tag.timestamp = 0;</span><br><span class="line">    //数据总长度</span><br><span class="line">    sps_pps_tag-&gt;common_tag.data_size = sps_pps_data-&gt;size + 11 + sps_pps_tag-&gt;common_tag.header_size;</span><br><span class="line">    //返回</span><br><span class="line">    return sps_pps_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="AudioSpecificConfig"><a href="#AudioSpecificConfig" class="headerlink" title="AudioSpecificConfig"></a>AudioSpecificConfig</h2><p>aac软编码库faac初始化之后，能够直接获取AudioSpecificConfig数据，在aw_faac.c中。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">static void aw_open_faac_enc_handler(aw_faac_context *faac_ctx)&#123;</span><br><span class="line">    //开启faac</span><br><span class="line">    faac_ctx-&gt;faac_handler = faacEncOpen(faac_ctx-&gt;config.sample_rate, faac_ctx-&gt;config.channel_count, &amp;faac_ctx-&gt;max_input_sample_count, &amp;faac_ctx-&gt;max_output_byte_count);</span><br><span class="line">    </span><br><span class="line">    ... ...</span><br><span class="line">    ... ...</span><br><span class="line">    </span><br><span class="line">    //配置好faac</span><br><span class="line">    faacEncSetConfiguration(faac_ctx-&gt;faac_handler, faac_config);</span><br><span class="line">    </span><br><span class="line">    //主要通过此方法获取AudioSpecificConfig，audio_specific_data就是想要的数据</span><br><span class="line">    uint8_t *audio_specific_data = NULL;</span><br><span class="line">    unsigned long audio_specific_data_len = 0;</span><br><span class="line">    faacEncGetDecoderSpecificInfo(faac_ctx-&gt;faac_handler, &amp;audio_specific_data, &amp;audio_specific_data_len);</span><br><span class="line">    </span><br><span class="line">    ... ...</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，AudioSpecificConfig数据结构很简单，可以自己简单构造一份。可参考AWHWAACEncoder.m中的createAudioSpecificConfigFlvTag函数。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">-(aw_flv_audio_tag *)createAudioSpecificConfigFlvTag&#123;</span><br><span class="line">    //AudioSpecificConfig中包含3种元素：profile，sampleRate，channelCount</span><br><span class="line">    //结构是：profile(5bit)-sampleRate(4bit)-channelCount(4bit)-空(3bit)</span><br><span class="line">    uint8_t profile = kMPEG4Object_AAC_LC;</span><br><span class="line">    uint8_t sampleRate = 4;</span><br><span class="line">    uint8_t chanCfg = 1;</span><br><span class="line">    uint8_t config1 = (profile &lt;&lt; 3) | ((sampleRate &amp; 0xe) &gt;&gt; 1);</span><br><span class="line">    uint8_t config2 = ((sampleRate &amp; 0x1) &lt;&lt; 7) | (chanCfg &lt;&lt; 3);</span><br><span class="line">    </span><br><span class="line">    //写入config_data中</span><br><span class="line">    aw_data *config_data = NULL;</span><br><span class="line">    data_writer.write_uint8(&amp;config_data, config1);</span><br><span class="line">    data_writer.write_uint8(&amp;config_data, config2);</span><br><span class="line">    </span><br><span class="line">    ... ...</span><br><span class="line">    ... ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>拿到AudioSpecificConfig数据后，可通过aw_sw_faac_encoder.c中的aw_encoder_create_audio_specific_config_tag来创建对应的flv audio tag，之后可像正常audio tag一样发送。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">extern aw_flv_audio_tag *aw_encoder_create_audio_specific_config_tag(aw_data *audio_specific_config_data, aw_faac_config *faac_config)&#123;</span><br><span class="line">    //创建普通的audio tag</span><br><span class="line">    aw_flv_audio_tag *audio_tag = aw_sw_encoder_create_flv_audio_tag(faac_config);</span><br><span class="line">    </span><br><span class="line">    //AudioSpecificConfig数据，同正常的audio tag在相同位置</span><br><span class="line">    audio_tag-&gt;config_record_data = copy_aw_data(audio_specific_config_data);</span><br><span class="line">    //时间戳0</span><br><span class="line">    audio_tag-&gt;common_tag.timestamp = 0;</span><br><span class="line">    //整个tag长度</span><br><span class="line">    audio_tag-&gt;common_tag.data_size = audio_specific_config_data-&gt;size + 11 + audio_tag-&gt;common_tag.header_size;</span><br><span class="line">    </span><br><span class="line">    return audio_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>rtmp连接成功后，一定要先发送sps&amp;pps和AudioSpecificConfig这两个数据对应的tag，否则视频是播放不出来的。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（十）librtmp使用介绍 </title>
    <link href="https://hardman.github.io/2017/11/23/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E5%8D%81%EF%BC%89librtmp%E4%BD%BF%E7%94%A8%E4%BB%8B%E7%BB%8D/"/>
    <id>https://hardman.github.io/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/</id>
    <published>2017-11-23T04:21:27.000Z</published>
    <updated>2018-07-19T07:12:49.761Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>rtmp(一般大写，小写会被认为英文不好或不专业，iOS开发者对这一点更为敏感)协议是Adobe公司为Flash视频的实时传输开发的一个开放协议。</p><p>本文不探究rtmp协议的原理，只是从代码角度来看，客户端如何使用librtmp完成推流功能。</p><h2 id="librtmp"><a href="#librtmp" class="headerlink" title="librtmp"></a>librtmp</h2><p>项目内使用的librtmp是使用rtmpdump编译的。如果遇到代码上的疑问可以通过阅读rtmpdump的源码寻找答案。</p><h2 id="代码解析"><a href="#代码解析" class="headerlink" title="代码解析"></a>代码解析</h2><h2 id="外部接口"><a href="#外部接口" class="headerlink" title="外部接口"></a>外部接口</h2><p>rtmp相关代码在aw_rtmp.c和aw_rtmp.h中。<br>对外接口包含一个context和3个函数：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">//aw_rtmp_context是一个context，用于存储一些外部传入及内部共享的变量。</span><br><span class="line">//写成context统一管理，否则就要写很多全局变量了。</span><br><span class="line">typedef struct aw_rtmp_context&#123;</span><br><span class="line">    //rtmp url</span><br><span class="line">    char rtmp_url[256];</span><br><span class="line">    //librtmp 中的结构体，作为RTMP连接上下文</span><br><span class="line">    RTMP *rtmp;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    //外部状态检测</span><br><span class="line">    //状态变化回调，注意，不要在状态回调中做释放aw_rtmp_context的操作。</span><br><span class="line">    //如果非要释放，请延迟一帧。</span><br><span class="line">    aw_rtmp_state_changed_cb state_changed_cb;</span><br><span class="line">    //当前状态</span><br><span class="line">    aw_rtmp_state rtmp_state;</span><br><span class="line">&#125; aw_rtmp_context;</span><br><span class="line"></span><br><span class="line">//打开rtmp</span><br><span class="line">extern int aw_rtmp_open(aw_rtmp_context *ctx);</span><br><span class="line"></span><br><span class="line">//写入数据</span><br><span class="line">extern int aw_rtmp_write(aw_rtmp_context *ctx, const char *buf, int size);</span><br><span class="line"></span><br><span class="line">//关闭rtmp</span><br><span class="line">extern int aw_rtmp_close(aw_rtmp_context *ctx);</span><br></pre></td></tr></table></figure></p><p>3个主要函数分别是：打开，写入数据，关闭。<br>除此之外，对于外部调用者来说，最重要的是要监听rtmp连接的各种状态来调整上层逻辑。<br>而状态回调就在 aw_rtmp_context中。</p><p>项目中，初始化 &amp; 关闭rtmp的代码在 aw_streamer.c 中<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">//初始化rtmp连接</span><br><span class="line">static int8_t aw_steamer_open_rtmp_context()&#123;</span><br><span class="line">    //创建context 传入rtmpurl及状态回调</span><br><span class="line">    if (!s_rtmp_ctx) &#123;</span><br><span class="line">        s_rtmp_ctx = alloc_aw_rtmp_context(s_rtmp_url, aw_streamer_rtmp_state_changed_callback);</span><br><span class="line">    &#125;</span><br><span class="line">    //open</span><br><span class="line">    return aw_rtmp_open(s_rtmp_ctx);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//关闭rtmp连接</span><br><span class="line">static void aw_streamer_close_rtmp_context()&#123;</span><br><span class="line">    if (s_rtmp_ctx) &#123;</span><br><span class="line">        aw_rtmp_close(s_rtmp_ctx);</span><br><span class="line">    &#125;</span><br><span class="line">    aw_log(&quot;[d] closed rtmp context&quot;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>发送数据的代码在aw_streamer.c中：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">static void aw_streamer_send_flv_tag_to_rtmp(aw_flv_common_tag *common_tag)&#123;</span><br><span class="line">    ... ...</span><br><span class="line"></span><br><span class="line">    aw_rtmp_write(s_rtmp_ctx, (const char *)s_output_buf-&gt;data, s_output_buf-&gt;size);</span><br><span class="line"></span><br><span class="line">    ... ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="打开rtmp"><a href="#打开rtmp" class="headerlink" title="打开rtmp"></a>打开rtmp</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">//打开rtmp，都是固定套路。</span><br><span class="line">int aw_rtmp_open(aw_rtmp_context *ctx)&#123;</span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line">    //初始化</span><br><span class="line">    ctx-&gt;rtmp = RTMP_Alloc();</span><br><span class="line">    RTMP_Init(ctx-&gt;rtmp);</span><br><span class="line">    //连接超时</span><br><span class="line">    ctx-&gt;rtmp-&gt;Link.timeout = 1;</span><br><span class="line">    //设置url</span><br><span class="line">    if (!RTMP_SetupURL(ctx-&gt;rtmp, ctx-&gt;rtmp_url)) &#123;</span><br><span class="line">        AWLog(&quot;[error ] aw rtmp setup url = %s\n&quot;, ctx-&gt;rtmp_url);</span><br><span class="line">        recode = -2;</span><br><span class="line">        goto FAILED;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //可写</span><br><span class="line">    RTMP_EnableWrite(ctx-&gt;rtmp);</span><br><span class="line">    </span><br><span class="line">    //buffer长度</span><br><span class="line">    RTMP_SetBufferMS(ctx-&gt;rtmp, 0);</span><br><span class="line">    </span><br><span class="line">    //开始连接</span><br><span class="line">    if (!RTMP_Connect(ctx-&gt;rtmp, NULL)) &#123;</span><br><span class="line">        recode = -3;</span><br><span class="line">        goto FAILED;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //连接</span><br><span class="line">    if (!RTMP_ConnectStream(ctx-&gt;rtmp, 0)) &#123;</span><br><span class="line">        recode = -4;</span><br><span class="line">        goto FAILED;</span><br><span class="line">    &#125;</span><br><span class="line">    return 1;</span><br><span class="line">FAILED:</span><br><span class="line">    //若中间环节出错，断开连接</span><br><span class="line">    aw_rtmp_close(ctx);</span><br><span class="line">    return !recode;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="rtmp写入（发送）数据"><a href="#rtmp写入（发送）数据" class="headerlink" title="rtmp写入（发送）数据"></a>rtmp写入（发送）数据</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">int aw_rtmp_write(aw_rtmp_context *ctx, const char *buf, int size)&#123;</span><br><span class="line">    ... ...</span><br><span class="line">    //RTMP_Write内部有时会排出SIGPIPE信号，在这里处理一下</span><br><span class="line">    signal(SIGPIPE, SIG_IGN);</span><br><span class="line">    int write_ret = RTMP_Write(ctx-&gt;rtmp, buf, size);</span><br><span class="line">    ... ...</span><br><span class="line">    return write_ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="rtmp关闭"><a href="#rtmp关闭" class="headerlink" title="rtmp关闭"></a>rtmp关闭</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">int aw_rtmp_close(aw_rtmp_context *ctx)&#123;</span><br><span class="line">    ... ...</span><br><span class="line">    //主要这两句</span><br><span class="line">    RTMP_Close(ctx-&gt;rtmp);</span><br><span class="line">    RTMP_Free(ctx-&gt;rtmp);</span><br><span class="line">    ... ...</span><br><span class="line">    return 1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>librtmp库使用方法介绍完毕。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步 </title>
    <link href="https://hardman.github.io/2017/11/23/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E4%B9%9D%EF%BC%89flv-%E7%BC%96%E7%A0%81%E4%B8%8E%E9%9F%B3%E8%A7%86%E9%A2%91%E6%97%B6%E9%97%B4%E6%88%B3%E5%90%8C%E6%AD%A5/"/>
    <id>https://hardman.github.io/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/</id>
    <published>2017-11-23T04:20:18.000Z</published>
    <updated>2018-07-19T07:12:12.151Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>前文介绍了如何获取音视频的aac/h264数据，那么如何将数据写入rtmp流中呢？<br>rtmp最初是Adobe Flash用于音视频播放的一个实时传输协议。而flv正是Adobe推出的一个视频格式，因此rtmp协议支持flv视频流。<br>这里可以我们把获取的aac/h264的数据，直接转成flv格式的视频帧，然后按照时间戳依次发送给服务端即可。</p><h2 id="flv格式简介"><a href="#flv格式简介" class="headerlink" title="flv格式简介"></a>flv格式简介</h2><p>flv总体来说是一个简单的视频格式，它包含2部分：header 和 body。</p><p>header是固定格式的数据，表示本文件是一个flv文件。<br>header的长度是9个字节。</p><p>header后面紧跟着body数据。body是由一个一个称为的tag数据组成。<br>tag其实就是一个固定格式的数据块，构造方式同header类似，只是叫法不同而已。</p><p>tag分为3种。script tag，video tag，audio tag。<br>script tag是flv的第一个tag，用于放一些视频信息的，比如duration，width，height等。script tag对于flv格式的视频文件比较重要，对于rtmp来说，可以不写入script tag。<br>video tag是视频数据的封装，也就是我们获取的h264数据基础之上，增加一些flv特定的数据。<br>audio tag同video tag类似，是acc数据的封装。</p><h2 id="代码解析"><a href="#代码解析" class="headerlink" title="代码解析"></a>代码解析</h2><p>flv相关代码在 aw_encode_flv.h和aw_encode_flv.c中。<br>此模块提供了flv编码(aac+h264)功能。</p><p>这个模块的暴露给外部的api为2部分：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">//一部分是创建flv的方法</span><br><span class="line">//写入header</span><br><span class="line">extern void aw_write_flv_header(aw_data **flv_data);</span><br><span class="line">//写入flv tag</span><br><span class="line">extern void aw_write_flv_tag(aw_data **flv_data, aw_flv_common_tag *common_tag);</span><br><span class="line"></span><br><span class="line">//第二部分是所有tag的构造</span><br><span class="line">//script tag</span><br><span class="line">extern aw_flv_script_tag *alloc_aw_flv_script_tag();</span><br><span class="line">extern void free_aw_flv_script_tag(aw_flv_script_tag **);</span><br><span class="line"></span><br><span class="line">//audio tag</span><br><span class="line">extern aw_flv_audio_tag *alloc_aw_flv_audio_tag();</span><br><span class="line">extern void free_aw_flv_audio_tag(aw_flv_audio_tag **);</span><br><span class="line"></span><br><span class="line">//video tag</span><br><span class="line">extern aw_flv_video_tag *alloc_aw_flv_video_tag();</span><br><span class="line">extern void free_aw_flv_video_tag(aw_flv_video_tag **);</span><br></pre></td></tr></table></figure></p><p>外部使用时，可根据具体数据先创建不同的tag，填充好各个数据，然后使用aw_write_flv_tag方法将tag写入aw_data中。<br>可用上述方法可以构造出完整的flv文件。</p><h2 id="aw-data"><a href="#aw-data" class="headerlink" title="aw_data"></a>aw_data</h2><p>aw_data是为了方便文件数据的读取/写入和管理而创建的工具模块。<br>此模块已处理了大端小端差异，能够让文件读写更加方便快捷。<br>相关代码在aw_data.h / aw_data.c中。</p><h2 id="flv-header"><a href="#flv-header" class="headerlink" title="flv header"></a>flv header</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">extern void aw_write_flv_header(aw_data **flv_data)&#123;</span><br><span class="line">    uint8_t</span><br><span class="line">    f = &apos;F&apos;, l = &apos;L&apos;, v = &apos;V&apos;,//FLV</span><br><span class="line">    version = 1,//固定值</span><br><span class="line">    av_flag = 5;//5表示av，5表示只有a，1表示只有v</span><br><span class="line">    uint32_t flv_header_len = 9;//header固定长度为9</span><br><span class="line">    data_writer.write_uint8(flv_data, f);</span><br><span class="line">    data_writer.write_uint8(flv_data, l);</span><br><span class="line">    data_writer.write_uint8(flv_data, v);</span><br><span class="line">    data_writer.write_uint8(flv_data, version);</span><br><span class="line">    data_writer.write_uint8(flv_data, av_flag);</span><br><span class="line">    data_writer.write_uint32(flv_data, flv_header_len);</span><br><span class="line">    </span><br><span class="line">    //first previous tag size 根据flv协议，每个tag后要写入当前tag的size，称为previous tag size，header后面需要写入4字节空数据。</span><br><span class="line">    data_writer.write_uint32(flv_data, 0);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="flv-body"><a href="#flv-body" class="headerlink" title="flv body"></a>flv body</h2><blockquote><p>注意<br>如果是要构造flv文件，写入header之后就可以写入script tag了。<br>如果是使用rtmp协议，则无需构造header，也无需script tag。可直接写入 video tag和audio tag。<br>若使用rtmp协议必须在首帧写入AVCDecoderConfigurationRecord (包含sps pps数据)和 AudioSpecificConfig，否则服务端无法正常解析音视频数据。</p></blockquote><p>flv的body是由一个接一个的tag构成的。<br>一个flv tag分为3部分：tag header + tag body + tag data size。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">extern void aw_write_flv_tag(aw_data **flv_data, aw_flv_common_tag *common_tag)&#123;</span><br><span class="line">    //写入header</span><br><span class="line">    aw_write_tag_header(flv_data, common_tag);</span><br><span class="line">    //写入body</span><br><span class="line">    aw_write_tag_body(flv_data, common_tag);</span><br><span class="line">    //写入data size</span><br><span class="line">    aw_write_tag_data_size(flv_data, common_tag);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="tag-header"><a href="#tag-header" class="headerlink" title="tag header"></a>tag header</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">static void aw_write_tag_header(aw_data **flv_data, aw_flv_common_tag *common_tag)&#123;</span><br><span class="line">    //header 长度为固定11个字节</span><br><span class="line">    //写入tag type，video：9 audio：8 script：18</span><br><span class="line">    data_writer.write_uint8(flv_data, common_tag-&gt;tag_type);</span><br><span class="line">    //写入body的size(data_size为整个tag的长度)</span><br><span class="line">    data_writer.write_uint24(flv_data, common_tag-&gt;data_size - 11);</span><br><span class="line">    //写入时间戳</span><br><span class="line">    data_writer.write_uint24(flv_data, common_tag-&gt;timestamp);</span><br><span class="line">    data_writer.write_uint8(flv_data, common_tag-&gt;timestamp_extend);</span><br><span class="line">    //写入stream id为0</span><br><span class="line">    data_writer.write_uint24(flv_data, common_tag-&gt;stream_id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="script-tag-body"><a href="#script-tag-body" class="headerlink" title="script tag body"></a>script tag body</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">static void aw_write_script_tag_body(aw_data **flv_data, aw_flv_script_tag *script_tag)&#123;</span><br><span class="line">    //script tag写入规则为：类型-内容-类型-内容...类型-内容</span><br><span class="line">    //类型是1个字节整数，可取12种值：</span><br><span class="line">    //    0 = Number type</span><br><span class="line">    //    1 = Boolean type</span><br><span class="line">    //    2 = String type</span><br><span class="line">    //    3 = Object type</span><br><span class="line">    //    4 = MovieClip type</span><br><span class="line">    //    5 = Null type</span><br><span class="line">    //    6 = Undefined type</span><br><span class="line">    //    7 = Reference type</span><br><span class="line">    //    8 = ECMA array type</span><br><span class="line">    //    10 = Strict array type</span><br><span class="line">    //    11 = Date type</span><br><span class="line">    //    12 = Long string type</span><br><span class="line">    // 比如：如果类型是字符串，那么先写入1个字节表类型的2。另，写入真正的字符串前，需要写入2个字节的字符串长度。</span><br><span class="line">    // data_writer.write_string能够在写入字符串前，先写入字符串长度，此函数第三个参数表示用多少字节来存储字符串长度。</span><br><span class="line">    // script tag 的结构基本上是固定的，首先写入一个字符串: onMetaData，然后写入一个数组。</span><br><span class="line">    // 写入数组需要先写入数组编号1字节：8，然后写入数组长度4字节：11。</span><br><span class="line">    // 数组同OC的Dictionary类似，可写入一个字符串+一个value。</span><br><span class="line">    // 所以每个数组元素可先写入一个字符串，然后写入一个Number Type，再写入具体的数值。</span><br><span class="line">    // 结束时需写入3个字节的0x000009表示数组结束。</span><br><span class="line">    // 下面代码中的duration/width/filesize均遵循此规则。</span><br><span class="line"></span><br><span class="line">    //2表示类型，字符串</span><br><span class="line">    data_writer.write_uint8(flv_data, 2);</span><br><span class="line">    data_writer.write_string(flv_data, &quot;onMetaData&quot;, 2);</span><br><span class="line">    </span><br><span class="line">    //数组类型：8</span><br><span class="line">    data_writer.write_uint8(flv_data, 8);</span><br><span class="line">    //数组长度：11</span><br><span class="line">    data_writer.write_uint32(flv_data, 11);</span><br><span class="line">    </span><br><span class="line">    //写入duration 0表示double，1表示uint8</span><br><span class="line">    data_writer.write_string(flv_data, &quot;duration&quot;, 2);</span><br><span class="line">    data_writer.write_uint8(flv_data, 0);</span><br><span class="line">    data_writer.write_double(flv_data, script_tag-&gt;duration);</span><br><span class="line">    //写入width</span><br><span class="line">    data_writer.write_string(flv_data, &quot;width&quot;, 2);</span><br><span class="line">    data_writer.write_uint8(flv_data, 0);</span><br><span class="line">    data_writer.write_double(flv_data, script_tag-&gt;width);</span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line">    //写入file_size</span><br><span class="line">    data_writer.write_string(flv_data, &quot;filesize&quot;, 2);</span><br><span class="line">    data_writer.write_uint8(flv_data, 0);</span><br><span class="line">    data_writer.write_double(flv_data, script_tag-&gt;file_size);</span><br><span class="line">    </span><br><span class="line">    //3字节的0x9表示数组结束</span><br><span class="line">    data_writer.write_uint24(flv_data, 9);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="video-tag-body"><a href="#video-tag-body" class="headerlink" title="video tag body"></a>video tag body</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">static void aw_write_video_tag_body(aw_data **flv_data, aw_flv_video_tag *video_tag)&#123;</span><br><span class="line">    // video tag body 结构是这样的：</span><br><span class="line">    // frame_type(4bit) + codec_id(4bit) + h264_package_type(8bit) + h264_composition_time(24bit) + video_tag_data(many bits)</span><br><span class="line">    // frame_type 表示是否关键帧，关键帧为1，非关键帧为2（当然还有更多取值，请参考[flv协议](https://wuyuans.com/img/2012/08/video_file_format_spec_v10.rar)</span><br><span class="line">    // codec_id 表示视频协议：h264是7 h263是2。</span><br><span class="line">    // h264_package_type表示视频帧数据的类型，2种取值：sequence header（也就是前面说的 sps pps 数据，rtmp要求首帧发送此数据，也称为AVCDecoderConfigurationRecord），另一种为nalu，正常的h264视频帧。</span><br><span class="line">    // h264_compsition_time：cts是pts与dts的差值，flv中的timestamp表示的应该是pts。如果h264数据中不包含B帧，那么此数据可传0。</span><br><span class="line">    // video_tag_data 即纯264数据。</span><br><span class="line"></span><br><span class="line">    uint8_t video_header = 0;</span><br><span class="line">    video_header |= video_tag-&gt;frame_type &lt;&lt; 4 &amp; 0xf0;</span><br><span class="line">    video_header |= video_tag-&gt;codec_id;</span><br><span class="line">    data_writer.write_uint8(flv_data, video_header);</span><br><span class="line">    </span><br><span class="line">    if (video_tag-&gt;codec_id == aw_flv_v_codec_id_H264) &#123;</span><br><span class="line">        data_writer.write_uint8(flv_data, video_tag-&gt;h264_package_type);</span><br><span class="line">        data_writer.write_uint24(flv_data, video_tag-&gt;h264_composition_time);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    switch (video_tag-&gt;h264_package_type) &#123;</span><br><span class="line">        case aw_flv_v_h264_packet_type_seq_header: &#123;</span><br><span class="line">            data_writer.write_bytes(flv_data, video_tag-&gt;config_record_data-&gt;data, video_tag-&gt;config_record_data-&gt;size);</span><br><span class="line">            break;</span><br><span class="line">        &#125;</span><br><span class="line">        case aw_flv_v_h264_packet_type_nalu: &#123;</span><br><span class="line">            data_writer.write_bytes(flv_data, video_tag-&gt;frame_data-&gt;data, video_tag-&gt;frame_data-&gt;size);</span><br><span class="line">            break;</span><br><span class="line">        &#125;</span><br><span class="line">        case aw_flv_v_h264_packet_type_end_of_seq: &#123;</span><br><span class="line">            //nothing</span><br><span class="line">            break;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="audio-tag-body"><a href="#audio-tag-body" class="headerlink" title="audio tag body"></a>audio tag body</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">static void aw_write_audio_tag_body(aw_data **flv_data, aw_flv_audio_tag *audio_tag)&#123;</span><br><span class="line">    // audio tag body的结构是这样的：</span><br><span class="line">    // sound_format(4bit) + sound_rate(sample_rate)(2bit) + sound_size(sample_size)(1bit) + sound_type(1bit) + aac_packet_type(8bit) + aac_data(many bits)</span><br><span class="line">    // sound_format 表示声音格式，2表示mp3，10表示aac，一般是aac</span><br><span class="line">    // sound_rate 采样率，表示1秒钟采集多少个样本，可选4个值，0表示5.5kHZ，1表示11kHZ，2表示22kHZ，3表示44kHZ，一般是3。</span><br><span class="line">    // sound_size 采样尺寸，单个样本的size。2个选择，0表示8bit，1表示16bit。</span><br><span class="line">    // 直观上看，采样率和采样尺寸应该和质量有一定关系。采样率高，采样尺寸大效果应该会好，但是生成的数据量也大。</span><br><span class="line">    // sound_type 表示声音类型，0表示单声道，1表示立体声。(立体声有2条声道)。</span><br><span class="line">    // aac_packet_type表示aac数据类型，有2种选择：0表示sequence header，即 必须首帧发送的数据(AudioSpecificConfig)，1表示正常的aac数据。</span><br><span class="line"></span><br><span class="line">    uint8_t audio_header = 0;</span><br><span class="line">    audio_header |= audio_tag-&gt;sound_format &lt;&lt; 4 &amp; 0xf0;</span><br><span class="line">    audio_header |= audio_tag-&gt;sound_rate &lt;&lt; 2 &amp; 0xc;</span><br><span class="line">    audio_header |= audio_tag-&gt;sound_size &lt;&lt; 1 &amp; 0x2;</span><br><span class="line">    audio_header |= audio_tag-&gt;sound_type &amp; 0x1;</span><br><span class="line">    data_writer.write_uint8(flv_data, audio_header);</span><br><span class="line">    </span><br><span class="line">    if (audio_tag-&gt;sound_format == aw_flv_a_codec_id_AAC) &#123;</span><br><span class="line">        data_writer.write_uint8(flv_data, audio_tag-&gt;aac_packet_type);</span><br><span class="line">    &#125;</span><br><span class="line">    switch (audio_tag-&gt;aac_packet_type) &#123;</span><br><span class="line">        case aw_flv_a_aac_package_type_aac_sequence_header: &#123;</span><br><span class="line">            data_writer.write_bytes(flv_data, audio_tag-&gt;config_record_data-&gt;data, audio_tag-&gt;config_record_data-&gt;size);</span><br><span class="line">            break;</span><br><span class="line">        &#125;</span><br><span class="line">        case aw_flv_a_aac_package_type_aac_raw: &#123;</span><br><span class="line">            data_writer.write_bytes(flv_data, audio_tag-&gt;frame_data-&gt;data, audio_tag-&gt;frame_data-&gt;size);</span><br><span class="line">            break;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="tag-data-size"><a href="#tag-data-size" class="headerlink" title="tag data size"></a>tag data size</h2><p>根据flv协议，每个flv tag结束时，需要写入此tag的全部长度：header+body的长度，header长度固定为11字节，而body的长度可通过上面构造body时写入的数据进行计算。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">static void aw_write_tag_data_size(aw_data **flv_data, aw_flv_common_tag *common_tag)&#123;</span><br><span class="line">    data_writer.write_uint32(flv_data, common_tag-&gt;data_size);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>上面的data_size由外部使用此模块的函数，在创建tag时计算出来的。<br>可以看aw_sw_faac_encoder.c中的aw_encoder_create_audio_tag方法：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">extern aw_flv_audio_tag *aw_encoder_create_audio_tag(int8_t *aac_data, long len, uint32_t timeStamp, aw_faac_config *faac_cfg)&#123;</span><br><span class="line">    aw_flv_audio_tag *audio_tag = aw_sw_encoder_create_flv_audio_tag(faac_cfg);</span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line">    //此处计算的data_size长度为 11(tag header size) + body header size(即下面的header_size，表示body中除去aac data的部分) + aac data size</span><br><span class="line">    audio_tag-&gt;common_tag.data_size = audio_tag-&gt;frame_data-&gt;size + 11 + audio_tag-&gt;common_tag.header_size;</span><br><span class="line">    return audio_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>这是本项目的处理方式。当然data size也可以在写入header和body时，同步计算出来。</p><h2 id="flv时间戳"><a href="#flv时间戳" class="headerlink" title="flv时间戳"></a>flv时间戳</h2><p>flv的tag中有2个字段表示时间戳，一个是 timestamp(pts)，一个是Composition Time(cts)。<br>pts表示展示时间戳，表示这一帧什么时候展示。<br>说cts之前有必要介绍一下dts，dts表示解码时间戳。<br>我们知道h264中有3种视频帧，I帧，P帧，B帧。<br>I和P帧不必说。<br>因为B帧的存在，可能会令后面的视频帧先于前面的视频帧解析，这样就需要在视频帧信息中保存dts。<br>flv中的cts可以做这件事情，cts = pts - dts。</p><p>另一个问题是，rtmp中的flv时间戳有一个规则就是，音频+视频帧须按照pts递增顺序发送。<br>因为音频和视频有各自的帧率，每个音视频帧可计算出各自的时间戳。<br>由于音频和视频在不同的线程中编码，编码后的音视频会合并到相同的线程中发送。<br>因为编码速度等各种原因，编码后的数据合并到相同线程时，可能并不是按照时间戳升序排列的。</p><p>为了保证排序，有2种办法解决此问题:</p><ol><li>将数据缓存起来，每次发送前都保证发送的是最早的数据帧。</li><li>以音频（或视频）为主，一旦遇到视频（或音频）帧时间戳小于已经发送的时间戳，则调整视频（或音频）帧时间戳。</li></ol><h2 id="推流时保存发送的flv文件"><a href="#推流时保存发送的flv文件" class="headerlink" title="推流时保存发送的flv文件"></a>推流时保存发送的flv文件</h2><p>根据本文介绍，我们可以把发送到rtmp服务器的数据保存到本地flv文件。<br>可以修改aw_streamer.c文件。</p><ol><li>当调用aw_streamer_open_rtmp_context时创建aw_data，并写入flv header和flv script tag。</li><li>调用aw_streamer_send_video_data和aw_streamer_send_audio_data时，将video tag和audio tag写入aw_data中。</li><li>当调用aw_streamer_close_rtmp_context时，将aw_data写入到本地文件，保存成flv格式，然后释放aw_data。</li></ol><p>至此，flv编码介绍完毕。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（八）h264/aac 软编码 </title>
    <link href="https://hardman.github.io/2017/11/23/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E5%85%AB%EF%BC%89h264-aac-%E8%BD%AF%E7%BC%96%E7%A0%81/"/>
    <id>https://hardman.github.io/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/</id>
    <published>2017-11-23T04:19:04.000Z</published>
    <updated>2018-07-19T07:09:07.719Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>软编码包含3部分内容：</p><ol><li>将pcm/yuv数据编码成aac/h264格式</li><li>将aac/h264数据封装成flv格式</li><li>另外无论软编码还是硬编码，最后获得的flv格式数据，需要通过rtmp协议发送至服务器。</li></ol><p>本篇将介绍第1部分内容。另外两部分内容将在后续文章中介绍。</p><p>根据上文介绍，软编码实现，对应音频／视频编码分别为：AWSWFaacEncoder 和 AWSWX264Encoder。</p><p>这两个类只是用OC封装的一个壳，实际上使用的是 libfaac 和 libx264 进行处理。</p><h2 id="音频软编码"><a href="#音频软编码" class="headerlink" title="音频软编码"></a>音频软编码</h2><p>aw_faac.h和aw_faac.c这两个文件是对libfaac这个库使用方法的简单封装。<br>这两个文件预期功能是，封装出一个函数，将pcm数据，转成aac数据。</p><p>faac的使用步骤：</p><ol><li>使用 faacEncOpen 开启编码环境 配置编码属性。</li><li>使用 faacEncEncode 函数编码。</li><li>使用完毕后，调用 faacEncClose 关闭编码环境。</li></ol><p>根据这个步骤，来看aw_faac.c文件。</p><h2 id="faac封装第一步：开启编码环境"><a href="#faac封装第一步：开启编码环境" class="headerlink" title="faac封装第一步：开启编码环境"></a>faac封装第一步：开启编码环境</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    aw_faac_context 是自己创建的结构体，用于辅助aac编码，存储了faac库的必需的数据，及一些过程变量。</span><br><span class="line">    它的创建及关闭请看demo中的代码，很简单，这里不需要解释。</span><br><span class="line">*/</span><br><span class="line">static void aw_open_faac_enc_handler(aw_faac_context *faac_ctx)&#123;</span><br><span class="line">    // 开启faac</span><br><span class="line">    // 参数依次为：</span><br><span class="line">    // 输入 采样率(44100) 声道数(2)</span><br><span class="line">    // 得到 最大输入样本数(1024) 最大输出字节数(2048)</span><br><span class="line">    faac_ctx-&gt;faac_handler = faacEncOpen(faac_ctx-&gt;config.sample_rate, faac_ctx-&gt;config.channel_count, &amp;faac_ctx-&gt;max_input_sample_count, &amp;faac_ctx-&gt;max_output_byte_count);</span><br><span class="line">    </span><br><span class="line">    //根据最大输入样本数得到最大输入字节数</span><br><span class="line">    faac_ctx-&gt;max_input_byte_count = faac_ctx-&gt;max_input_sample_count * faac_ctx-&gt;config.sample_size / 8;</span><br><span class="line">    </span><br><span class="line">    if(!faac_ctx-&gt;faac_handler)&#123;</span><br><span class="line">        aw_log(&quot;[E] aac handler open failed&quot;);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //创建buffer</span><br><span class="line">    faac_ctx-&gt;aac_buffer = aw_alloc(faac_ctx-&gt;max_output_byte_count);</span><br><span class="line">    </span><br><span class="line">    //获取配置</span><br><span class="line">    faacEncConfigurationPtr faac_config = faacEncGetCurrentConfiguration(faac_ctx-&gt;faac_handler);</span><br><span class="line">    if (faac_ctx-&gt;config.sample_size == 16) &#123;</span><br><span class="line">        faac_config-&gt;inputFormat = FAAC_INPUT_16BIT;</span><br><span class="line">    &#125;else if (faac_ctx-&gt;config.sample_size == 24) &#123;</span><br><span class="line">        faac_config-&gt;inputFormat = FAAC_INPUT_24BIT;</span><br><span class="line">    &#125;else if (faac_ctx-&gt;config.sample_size == 32) &#123;</span><br><span class="line">        faac_config-&gt;inputFormat = FAAC_INPUT_32BIT;</span><br><span class="line">    &#125;else&#123;</span><br><span class="line">        faac_config-&gt;inputFormat = FAAC_INPUT_FLOAT;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //配置</span><br><span class="line">    faac_config-&gt;aacObjectType = LOW;//aac对象类型: LOW Main LTP</span><br><span class="line">    faac_config-&gt;mpegVersion = MPEG4;//mpeg版本: MPEG2 MPEG4</span><br><span class="line">    faac_config-&gt;useTns = 1;//抗噪</span><br><span class="line">    faac_config-&gt;allowMidside = 0;// 是否使用mid/side编码</span><br><span class="line">    if(faac_ctx-&gt;config.bitrate)&#123;</span><br><span class="line">        //每秒钟每个通道的bitrate</span><br><span class="line">        faac_config-&gt;bitRate = faac_ctx-&gt;config.bitrate / faac_ctx-&gt;config.channel_count;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    faacEncSetConfiguration(faac_ctx-&gt;faac_handler, faac_config);</span><br><span class="line">    </span><br><span class="line">    //获取audio specific config，本系列文章中第六篇里面介绍了这个数据，它存储了aac格式的一些关键数据，</span><br><span class="line">    //在rtmp协议中，必须将此数据在所有音频帧之前发送</span><br><span class="line">    uint8_t *audio_specific_data = NULL;</span><br><span class="line">    unsigned long audio_specific_data_len = 0;</span><br><span class="line">    faacEncGetDecoderSpecificInfo(faac_ctx-&gt;faac_handler, &amp;audio_specific_data, &amp;audio_specific_data_len);</span><br><span class="line">    </span><br><span class="line">    //将获取的audio specific config data 存储到faac_ctx中</span><br><span class="line">    if (audio_specific_data_len &gt; 0) &#123;</span><br><span class="line">        faac_ctx-&gt;audio_specific_config_data = alloc_aw_data(0);</span><br><span class="line">        memcpy_aw_data(&amp;faac_ctx-&gt;audio_specific_config_data, audio_specific_data, (uint32_t)audio_specific_data_len);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br><span class="line">//函数内具体参数配置，请参考：</span><br><span class="line">//http://wenku.baidu.com/link?url=0E9GnSo7hZ-3WmB_eXz8EfnG8NqJJJtvjrVNW7hW-VEYWW-gYBMVM-CnFSicDE-veDl2tzfL-nu2FQ8msGcCOALuT8VW1l_NjQL9Gvw5V6_</span><br></pre></td></tr></table></figure><h2 id="faac封装第二步：开始编码"><a href="#faac封装第二步：开始编码" class="headerlink" title="faac封装第二步：开始编码"></a>faac封装第二步：开始编码</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    pcm_data 为 pcm格式的音频数据</span><br><span class="line">    len 表示数据字节数</span><br><span class="line">*/</span><br><span class="line">extern void aw_encode_pcm_frame_2_aac(aw_faac_context *ctx, int8_t *pcm_data, long len)&#123;</span><br><span class="line">    //判断输入参数</span><br><span class="line">    if (!pcm_data || len &lt;= 0) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_encode_pcm_frame_2_aac params error&quot;);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    //清空encoded_aac_data，每次编码数据最终会存储到此字段中，所以首先清空。</span><br><span class="line">    reset_aw_data(&amp;ctx-&gt;encoded_aac_data);</span><br><span class="line">    </span><br><span class="line">    /*</span><br><span class="line">        下列代码根据第一步&quot;开启编码环境&quot;函数中计算的最大输入子节数</span><br><span class="line">        将pcm_data分割成合适的大小，使用faacEncEncode函数将pcm数据编码成aac数据。</span><br><span class="line"></span><br><span class="line">        下列代码执行完成后，编码出的aac数据将会存储到encoded_aac_data字段中。</span><br><span class="line">    */</span><br><span class="line">    long max_input_count = ctx-&gt;max_input_byte_count;</span><br><span class="line">    long curr_read_count = 0;</span><br><span class="line">    </span><br><span class="line">    do&#123;</span><br><span class="line">        long remain_count = len - curr_read_count;</span><br><span class="line">        if (remain_count &lt;= 0) &#123;</span><br><span class="line">            break;</span><br><span class="line">        &#125;</span><br><span class="line">        long read_count = 0;</span><br><span class="line">        if (remain_count &gt; max_input_count) &#123;</span><br><span class="line">            read_count = max_input_count;</span><br><span class="line">        &#125;else&#123;</span><br><span class="line">            read_count = remain_count;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        long input_samples = read_count * 8 / ctx-&gt;config.sample_size;</span><br><span class="line">        int write_count = faacEncEncode(ctx-&gt;faac_handler, (int32_t * )(pcm_data + curr_read_count), (uint32_t)input_samples, (uint8_t *)ctx-&gt;aac_buffer, (uint32_t)ctx-&gt;max_output_byte_count);</span><br><span class="line">        </span><br><span class="line">        if (write_count &gt; 0) &#123;</span><br><span class="line">            data_writer.write_bytes(&amp;ctx-&gt;encoded_aac_data, (const uint8_t *)ctx-&gt;aac_buffer, write_count);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        curr_read_count += read_count;</span><br><span class="line">    &#125; while (curr_read_count + max_input_count &lt; len);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="faac封装第三步：关闭编码器："><a href="#faac封装第三步：关闭编码器：" class="headerlink" title="faac封装第三步：关闭编码器："></a>faac封装第三步：关闭编码器：</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">extern void free_aw_faac_context(aw_faac_context **context_p)&#123;</span><br><span class="line">    ...</span><br><span class="line">    //关闭faac编码器</span><br><span class="line">    faacEncClose(context-&gt;faac_handler);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码仅仅作为faac编码器的封装，能够实现打开编码器。</p><p>真正实现编码过程的文件是：aw_sw_faac_encoder.h/aw_sw_faac_encoder.c文件</p><p>此文件的功能是：将传入的pcm数据通过aw_faac.c提供的功能，将数据转成aac数据格式，然后将aac数据格式转成flv格式，如何转成flv格式，会在后续文章介绍。</p><p>来看一下 aw_sw_faac_encoder.c文件的实现。<br>此文件逻辑也很清晰，它实现的功能有：</p><ol><li>开启编码器，创建一些过程变量。</li><li>将audio specific config data 转成flv帧数据。</li><li>将接收到的pcm数据，转成aac数据，然后将aac数据转成flv音频数据。</li><li>关闭编码器。</li></ol><p>可以看出，这种类似功能性代码，一般都是三部曲：打开－使用－关闭。</p><p>下面来看代码。</p><h2 id="音频软编码器第一步：开启编码器"><a href="#音频软编码器第一步：开启编码器" class="headerlink" title="音频软编码器第一步：开启编码器"></a>音频软编码器第一步：开启编码器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    faac_config：需要由上层传入相关配置属性</span><br><span class="line">*/</span><br><span class="line">extern void aw_sw_encoder_open_faac_encoder(aw_faac_config *faac_config)&#123;</span><br><span class="line">    //是否已经开启了，避免重复开启</span><br><span class="line">    if (aw_sw_faac_encoder_is_valid()) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_open_faac_encoder when encoder is already inited&quot;);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //创建配置</span><br><span class="line">    int32_t faac_cfg_len = sizeof(aw_faac_config);</span><br><span class="line">    if (!s_faac_config) &#123;</span><br><span class="line">        s_faac_config = aw_alloc(faac_cfg_len);</span><br><span class="line">    &#125;</span><br><span class="line">    memcpy(s_faac_config, faac_config, faac_cfg_len);</span><br><span class="line">    </span><br><span class="line">    //开启faac软编码</span><br><span class="line">    s_faac_ctx = alloc_aw_faac_context(*faac_config);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="音频软编码第二步：将audio-specific-config-data-转成flv帧数据。"><a href="#音频软编码第二步：将audio-specific-config-data-转成flv帧数据。" class="headerlink" title="音频软编码第二步：将audio specific config data 转成flv帧数据。"></a>音频软编码第二步：将audio specific config data 转成flv帧数据。</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">extern aw_flv_audio_tag *aw_sw_encoder_create_faac_specific_config_tag()&#123;</span><br><span class="line">    //是否已打开编码器</span><br><span class="line">    if(!aw_sw_faac_encoder_is_valid())&#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_create_faac_specific_config_tag when audio encoder is not inited&quot;);</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //创建 audio specfic config record</span><br><span class="line">    aw_flv_audio_tag *aac_tag = aw_sw_encoder_create_flv_audio_tag(&amp;s_faac_ctx-&gt;config);</span><br><span class="line">    //根据flv协议：audio specific data对应的 aac_packet_type 固定为 aw_flv_a_aac_package_type_aac_sequence_header 值为0</span><br><span class="line">    //普通的音频帧，此处值为1.</span><br><span class="line">    aac_tag-&gt;aac_packet_type = aw_flv_a_aac_package_type_aac_sequence_header;</span><br><span class="line">    </span><br><span class="line">    aac_tag-&gt;config_record_data = copy_aw_data(s_faac_ctx-&gt;audio_specific_config_data);</span><br><span class="line">    aac_tag-&gt;common_tag.timestamp = 0;</span><br><span class="line">    aac_tag-&gt;common_tag.data_size = s_faac_ctx-&gt;audio_specific_config_data-&gt;size + 11 + aac_tag-&gt;common_tag.header_size;</span><br><span class="line">    </span><br><span class="line">    return aac_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="音频软编码器第三步：将接收到的pcm数据转成aac数据，然后将aac数据转成flv音频数据"><a href="#音频软编码器第三步：将接收到的pcm数据转成aac数据，然后将aac数据转成flv音频数据" class="headerlink" title="音频软编码器第三步：将接收到的pcm数据转成aac数据，然后将aac数据转成flv音频数据"></a>音频软编码器第三步：将接收到的pcm数据转成aac数据，然后将aac数据转成flv音频数据</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    pcm_data: 传入的pcm数据</span><br><span class="line">    len: pcm数据长度</span><br><span class="line">    timestamp：flv时间戳，rtmp协议要求发送的flv音视频帧的时间戳需为均匀增加，不允许 后发送的数据时间戳 比 先发送的数据的时间戳 还要小。</span><br><span class="line">    aw_flv_audio_tag: 返回类型，生成的flv音频数据（flv中，每帧数据称为一个tag）。</span><br><span class="line">*/</span><br><span class="line">extern aw_flv_audio_tag *aw_sw_encoder_encode_faac_data(int8_t *pcm_data, long len, uint32_t timestamp)&#123;</span><br><span class="line">    if (!aw_sw_faac_encoder_is_valid()) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_encode_faac_data when encoder is not inited&quot;);</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //将pcm数据编码成aac数据</span><br><span class="line">    aw_encode_pcm_frame_2_aac(s_faac_ctx, pcm_data, len);</span><br><span class="line">    </span><br><span class="line">    // 使用faac编码的数据会带有7个字节的adts头。rtmp不接受此值，在此去掉前7个字节。</span><br><span class="line">    int adts_header_size = 7;</span><br><span class="line">    </span><br><span class="line">    //除去ADTS头的7字节</span><br><span class="line">    if (s_faac_ctx-&gt;encoded_aac_data-&gt;size &lt;= adts_header_size) &#123;</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //将aac数据封装成flv音频帧。flv帧仅仅是将aac数据增加一些固定信息。并没有对aac数据进行编码操作。</span><br><span class="line">    aw_flv_audio_tag *audio_tag = aw_encoder_create_audio_tag((int8_t *)s_faac_ctx-&gt;encoded_aac_data-&gt;data + adts_header_size, s_faac_ctx-&gt;encoded_aac_data-&gt;size - adts_header_size, timestamp, &amp;s_faac_ctx-&gt;config);</span><br><span class="line">    </span><br><span class="line">    audio_count++;</span><br><span class="line">    </span><br><span class="line">    //返回结果</span><br><span class="line">    return audio_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="音频软编码器第四步：关闭编码器"><a href="#音频软编码器第四步：关闭编码器" class="headerlink" title="音频软编码器第四步：关闭编码器"></a>音频软编码器第四步：关闭编码器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">extern void aw_sw_encoder_close_faac_encoder()&#123;</span><br><span class="line">    //避免重复关闭</span><br><span class="line">    if (!aw_sw_faac_encoder_is_valid()) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_close_faac_encoder when encoder is not inited&quot;);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //是否aw_faac_context，也就关闭了faac编码环境。</span><br><span class="line">    free_aw_faac_context(&amp;s_faac_ctx);</span><br><span class="line">    </span><br><span class="line">    //释放配置数据</span><br><span class="line">    if (s_faac_config) &#123;</span><br><span class="line">        aw_free(s_faac_config);</span><br><span class="line">        s_faac_config = NULL;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到此为止，音频软编码器就介绍完了。已经成功实现了将pcm数据转成flv音频帧。</p><p>下面介绍视频软编码。<br>套路同音频编码一致，对应的视频软编码是对x264这个库的封装。<br>文件在aw_x264.h/aw_x264.c中。</p><p>它实现的功能如下：</p><ol><li>初始化x264参数，打开编码环境</li><li>进行编码</li><li>关闭编码环境。</li></ol><h2 id="x264封装第一步：初始化x264参数，打开编码环境"><a href="#x264封装第一步：初始化x264参数，打开编码环境" class="headerlink" title="x264封装第一步：初始化x264参数，打开编码环境"></a>x264封装第一步：初始化x264参数，打开编码环境</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    config 表示配置数据</span><br><span class="line">    aw_x264_context 是自定义结构体，用于存储x264编码重要属性及过程变量。</span><br><span class="line">*/</span><br><span class="line">extern aw_x264_context *alloc_aw_x264_context(aw_x264_config config)&#123;</span><br><span class="line">    aw_x264_context *ctx = aw_alloc(sizeof(aw_x264_context));</span><br><span class="line">    memset(ctx, 0, sizeof(aw_x264_context));</span><br><span class="line">    </span><br><span class="line">    //数据数据默认为 I420</span><br><span class="line">    if (!config.input_data_format) &#123;</span><br><span class="line">        config.input_data_format = X264_CSP_I420;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //创建handler</span><br><span class="line">    memcpy(&amp;ctx-&gt;config, &amp;config, sizeof(aw_x264_config));</span><br><span class="line">    x264_param_t *x264_param = NULL;</span><br><span class="line">    //x264参数，具体请参考：http://blog.csdn.net/table/article/details/8085115</span><br><span class="line">    aw_create_x264_param(ctx, &amp;x264_param);</span><br><span class="line">    //开启编码器</span><br><span class="line">    aw_open_x264_handler(ctx, x264_param);</span><br><span class="line">    aw_free(x264_param);</span><br><span class="line">    </span><br><span class="line">    //创建pic_in，x264内部用于存储输入图像数据的一段空间。</span><br><span class="line">    x264_picture_t *pic_in = aw_alloc(sizeof(x264_picture_t));</span><br><span class="line">    x264_picture_init(pic_in);</span><br><span class="line">    </span><br><span class="line">    //[注意有坑]</span><br><span class="line">    //aw_stride是一个宏，用于将视频宽度转成16的倍数。如果不是16的倍数，有时候会编码失败（颜色缺失等）。</span><br><span class="line">    int alloc_width = aw_stride(config.width);</span><br><span class="line">    </span><br><span class="line">    x264_picture_alloc(pic_in, config.input_data_format, alloc_width, config.height);</span><br><span class="line"></span><br><span class="line">    pic_in-&gt;img.i_csp = config.input_data_format;</span><br><span class="line">    </span><br><span class="line">    //i_stride 表示换行步长，跟plane数及格式有关，x264内部用来判定读取多少数据需要换行。</span><br><span class="line">    //关于yuv数据格式在第二章里面介绍过，这里再次回顾一下。</span><br><span class="line">    if (config.input_data_format == X264_CSP_NV12) &#123;</span><br><span class="line">        //nv12数据包含2个plane，第一个plane存储了y数据大小为 width * height，</span><br><span class="line">        //第二个plane存储uv数据，u和v隔位存储，数据大小为：width * (height / 2)</span><br><span class="line">        pic_in-&gt;img.i_stride[0] = alloc_width;</span><br><span class="line">        pic_in-&gt;img.i_stride[1] = alloc_width;</span><br><span class="line">        pic_in-&gt;img.i_plane = 2;</span><br><span class="line">    &#125;else if(config.input_data_format == X264_CSP_BGR || config.input_data_format == X264_CSP_RGB)&#123;</span><br><span class="line">        //rgb数据包含一个plane，数据长度为 width * 3 * height。</span><br><span class="line">        pic_in-&gt;img.i_stride[0] = alloc_width * 3;</span><br><span class="line">        pic_in-&gt;img.i_plane = 1;</span><br><span class="line">    &#125;else if(config.input_data_format == X264_CSP_BGRA)&#123;</span><br><span class="line">        //bgra同rgb类似</span><br><span class="line">        pic_in-&gt;img.i_stride[0] = alloc_width * 4;</span><br><span class="line">        pic_in-&gt;img.i_plane = 1;</span><br><span class="line">    &#125;else&#123;//YUV420</span><br><span class="line">        //yuv420即I420格式。</span><br><span class="line">        //包含3个plane，第一个plane存储y数据大小为width * height</span><br><span class="line">        //第二个存储u数据，数据大小为 width * height / 4</span><br><span class="line">        //第三个存储v数据，数据大小为 width * height / 4</span><br><span class="line">        pic_in-&gt;img.i_stride[0] = alloc_width;</span><br><span class="line">        pic_in-&gt;img.i_stride[1] = alloc_width / 2;</span><br><span class="line">        pic_in-&gt;img.i_stride[2] = alloc_width / 2;</span><br><span class="line">        pic_in-&gt;img.i_plane = 3;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //其他数据初始化，pic_in 用于存储输入数据(yuv/rgb等数据)，pic_out用于存储输出数据(h264数据)</span><br><span class="line">    ctx-&gt;pic_in = pic_in;</span><br><span class="line">    </span><br><span class="line">    ctx-&gt;pic_out = aw_alloc(sizeof(x264_picture_t));</span><br><span class="line">    x264_picture_init(ctx-&gt;pic_out);</span><br><span class="line">    </span><br><span class="line">    //编码后数据变量</span><br><span class="line">    ctx-&gt;encoded_h264_data = alloc_aw_data(0);</span><br><span class="line">    ctx-&gt;sps_pps_data = alloc_aw_data(0);</span><br><span class="line">    </span><br><span class="line">    //获取sps pps</span><br><span class="line">    // sps pps 数据是rtmp协议要求的必需在所有flv视频帧之前发送的一帧数据，存储了h264视频的一些关键属性。</span><br><span class="line">    // 具体获取方法请看demo，很简单，这里就不解释了。</span><br><span class="line">    aw_encode_x264_header(ctx);</span><br><span class="line">    </span><br><span class="line">    return ctx;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="x264封装第二步：开始编码"><a href="#x264封装第二步：开始编码" class="headerlink" title="x264封装第二步：开始编码"></a>x264封装第二步：开始编码</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">//编码一帧数据</span><br><span class="line">extern void aw_encode_yuv_frame_2_x264(aw_x264_context *aw_ctx, int8_t *yuv_frame, int len)&#123;</span><br><span class="line">    if (len &gt; 0 &amp;&amp; yuv_frame) &#123;</span><br><span class="line">        //将视频数据填充到pic_in中，pic_in上面已经介绍过，x264需要这样处理。</span><br><span class="line">        int actual_width = aw_stride(aw_ctx-&gt;config.width);</span><br><span class="line">        //数据保存到pic_in中</span><br><span class="line">        if (aw_ctx-&gt;config.input_data_format == X264_CSP_NV12) &#123;</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[0] = (uint8_t *)yuv_frame;</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[1] = (uint8_t *)yuv_frame + actual_width * aw_ctx-&gt;config.height;</span><br><span class="line">        &#125;else if(aw_ctx-&gt;config.input_data_format == X264_CSP_BGR || aw_ctx-&gt;config.input_data_format == X264_CSP_RGB)&#123;</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[0] = (uint8_t *)yuv_frame;</span><br><span class="line">        &#125;else if(aw_ctx-&gt;config.input_data_format == X264_CSP_BGRA)&#123;</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[0] = (uint8_t *)yuv_frame;</span><br><span class="line">        &#125;else&#123;//YUV420</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[0] = (uint8_t *)yuv_frame;</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[1] = (uint8_t *)yuv_frame + actual_width * aw_ctx-&gt;config.height;</span><br><span class="line">            aw_ctx-&gt;pic_in-&gt;img.plane[2] = (uint8_t *)yuv_frame + actual_width * aw_ctx-&gt;config.height * 5 / 4;</span><br><span class="line">        &#125;</span><br><span class="line">        //x264编码，编码后的数据存储在aw_ctx-&gt;nal中</span><br><span class="line">        x264_encoder_encode(aw_ctx-&gt;x264_handler, &amp;aw_ctx-&gt;nal, &amp;aw_ctx-&gt;nal_count, aw_ctx-&gt;pic_in, aw_ctx-&gt;pic_out);</span><br><span class="line">        aw_ctx-&gt;pic_in-&gt;i_pts++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //将编码后的数据转存到encoded_h264_data中，这里面存储的就是编码好的h264视频帧了。</span><br><span class="line">    reset_aw_data(&amp;aw_ctx-&gt;encoded_h264_data);</span><br><span class="line">    if (ctx-&gt;nal_count &gt; 0) &#123;</span><br><span class="line">        int i = 0;</span><br><span class="line">        for (; i &lt; ctx-&gt;nal_count; i++) &#123;</span><br><span class="line">            data_writer.write_bytes(&amp;ctx-&gt;encoded_h264_data, ctx-&gt;nal[i].p_payload, ctx-&gt;nal[i].i_payload);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="x264封装第三步：关闭编码环境。"><a href="#x264封装第三步：关闭编码环境。" class="headerlink" title="x264封装第三步：关闭编码环境。"></a>x264封装第三步：关闭编码环境。</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    很简单，分别释放pic_in，pic_out，x264_handler即可</span><br><span class="line">*/</span><br><span class="line">extern void free_aw_x264_context(aw_x264_context **ctx_p)&#123;</span><br><span class="line">    aw_x264_context *ctx = *ctx_p;</span><br><span class="line">    if (ctx) &#123;</span><br><span class="line">        //释放pic_in</span><br><span class="line">        if (ctx-&gt;pic_in) &#123;</span><br><span class="line">            x264_picture_clean(ctx-&gt;pic_in);</span><br><span class="line">            aw_free(ctx-&gt;pic_in);</span><br><span class="line">            ctx-&gt;pic_in = NULL;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        //释放pic_out</span><br><span class="line">        if (ctx-&gt;pic_out) &#123;</span><br><span class="line">            aw_free(ctx-&gt;pic_out);</span><br><span class="line">            ctx-&gt;pic_out = NULL;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line">        </span><br><span class="line">        //关闭handler</span><br><span class="line">        if (ctx-&gt;x264_handler) &#123;</span><br><span class="line">            x264_encoder_close(ctx-&gt;x264_handler);</span><br><span class="line">            ctx-&gt;x264_handler = NULL;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码只是对x264编码流程进行简单封装。<br>真正实现完整转码逻辑的是在 aw_sw_x264_encoder.h/aw_sw_x264_encoder.c 中。</p><p>它实现了如下功能：</p><ol><li>将收到的yuv数据编码成 h264格式。</li><li>生成包含sps/pps数据的flv视频帧。</li><li>将h264格式的数据转成flv视频数据。</li><li>关闭编码器。</li></ol><h2 id="视频软编码器第一步：收到yuv数据，并编码成h264格式。"><a href="#视频软编码器第一步：收到yuv数据，并编码成h264格式。" class="headerlink" title="视频软编码器第一步：收到yuv数据，并编码成h264格式。"></a>视频软编码器第一步：收到yuv数据，并编码成h264格式。</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">//打开编码器，就是在aw_x264基础上，封了一层。</span><br><span class="line">extern void aw_sw_encoder_open_x264_encoder(aw_x264_config *x264_config)&#123;</span><br><span class="line">    if (aw_sw_x264_encoder_is_valid()) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_open_video_encoder when video encoder is not inited&quot;);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    int32_t x264_cfg_len = sizeof(aw_x264_config);</span><br><span class="line">    if (!s_x264_config) &#123;</span><br><span class="line">        s_x264_config = aw_alloc(x264_cfg_len);</span><br><span class="line">    &#125;</span><br><span class="line">    memcpy(s_x264_config, x264_config, x264_cfg_len);</span><br><span class="line">    </span><br><span class="line">    s_x264_ctx = alloc_aw_x264_context(*x264_config);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="视频软编码器第二步：生成包含sps-pps数据的flv视频帧"><a href="#视频软编码器第二步：生成包含sps-pps数据的flv视频帧" class="headerlink" title="视频软编码器第二步：生成包含sps/pps数据的flv视频帧"></a>视频软编码器第二步：生成包含sps/pps数据的flv视频帧</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">//根据flv/h264/aac协议创建video/audio首帧tag，flv 格式相关代码在 aw_encode_flv.h/aw_encode_flv.c 中</span><br><span class="line">extern aw_flv_video_tag *aw_sw_encoder_create_x264_sps_pps_tag()&#123;</span><br><span class="line">    if(!aw_sw_x264_encoder_is_valid())&#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_create_video_sps_pps_tag when video encoder is not inited&quot;);</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //创建 sps pps</span><br><span class="line">    // 创建flv视频tag</span><br><span class="line">    aw_flv_video_tag *sps_pps_tag = aw_sw_encoder_create_flv_video_tag();</span><br><span class="line">    // 关键帧</span><br><span class="line">    sps_pps_tag-&gt;frame_type = aw_flv_v_frame_type_key;</span><br><span class="line">    // package type 为header，固定</span><br><span class="line">    sps_pps_tag-&gt;h264_package_type = aw_flv_v_h264_packet_type_seq_header;</span><br><span class="line">    // cts，项目内所有视频帧的cts 都为0</span><br><span class="line">    sps_pps_tag-&gt;h264_composition_time = 0;</span><br><span class="line">    // 将aw_x264中生成的sps/pps数据copy到tag中</span><br><span class="line">    sps_pps_tag-&gt;config_record_data = copy_aw_data(s_x264_ctx-&gt;sps_pps_data);</span><br><span class="line">    // 时间戳为0</span><br><span class="line">    sps_pps_tag-&gt;common_tag.timestamp = 0;</span><br><span class="line">    // flv tag长度为：header size + data header(11字节) + 数据长度（后续介绍）</span><br><span class="line">    sps_pps_tag-&gt;common_tag.data_size = s_x264_ctx-&gt;sps_pps_data-&gt;size + 11 + sps_pps_tag-&gt;common_tag.header_size;</span><br><span class="line">    return sps_pps_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="视频软编码器第三步：将h264格式的数据转成flv视频数据。"><a href="#视频软编码器第三步：将h264格式的数据转成flv视频数据。" class="headerlink" title="视频软编码器第三步：将h264格式的数据转成flv视频数据。"></a>视频软编码器第三步：将h264格式的数据转成flv视频数据。</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">//将采集到的video yuv数据，编码为flv video tag</span><br><span class="line">extern aw_flv_video_tag * aw_sw_encoder_encode_x264_data(int8_t *yuv_data, long len, uint32_t timeStamp)&#123;</span><br><span class="line">    //是否已开启编码</span><br><span class="line">    if (!aw_sw_x264_encoder_is_valid()) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_encode_video_data when video encoder is not inited&quot;);</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //执行编码</span><br><span class="line">    aw_encode_yuv_frame_2_x264(s_x264_ctx, yuv_data, (int32_t)len);</span><br><span class="line">    </span><br><span class="line">    //编码后是否能取到数据</span><br><span class="line">    if (s_x264_ctx-&gt;encoded_h264_data-&gt;size &lt;= 0) &#123;</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //将h264数据转成flv tag</span><br><span class="line">    x264_picture_t *pic_out = s_x264_ctx-&gt;pic_out;</span><br><span class="line">    </span><br><span class="line">    aw_flv_video_tag *video_tag = aw_encoder_create_video_tag((int8_t *)s_x264_ctx-&gt;encoded_h264_data-&gt;data, s_x264_ctx-&gt;encoded_h264_data-&gt;size, timeStamp, (uint32_t)((pic_out-&gt;i_pts - pic_out-&gt;i_dts) * 1000.0 / s_x264_ctx-&gt;config.fps), pic_out-&gt;b_keyframe);</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    return video_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="视频软编码器第四步：关闭编码器"><a href="#视频软编码器第四步：关闭编码器" class="headerlink" title="视频软编码器第四步：关闭编码器"></a>视频软编码器第四步：关闭编码器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">//关闭编码器</span><br><span class="line">extern void aw_sw_encoder_close_x264_encoder()&#123;</span><br><span class="line">    //避免重复关闭</span><br><span class="line">    if (!aw_sw_x264_encoder_is_valid()) &#123;</span><br><span class="line">        aw_log(&quot;[E] aw_sw_encoder_close_video_encoder s_faac_ctx is NULL&quot;);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //释放配置</span><br><span class="line">    if (s_x264_config) &#123;</span><br><span class="line">        aw_free(s_x264_config);</span><br><span class="line">        s_x264_config = NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //释放context</span><br><span class="line">    free_aw_x264_context(&amp;s_x264_ctx);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，软编码代码介绍完毕。<br>可以通过 AWSWFaacEncoder/AWSWX264Encoder 类调用上面的软编码器，给上层提供一致的接口。</p><p>总结，软编码器涉及的内容：</p><ol><li>第三方编码器：libfaac/libx264</li><li>第三方编码器封装：aw_faac.h/aw_faac.c，aw_x264.h/aw_x264.c</li><li>编码器(将原始数据转成最终数据)封装：aw_sw_faac_encoder.h/aw_sw_faac_encoder.c，aw_sw_x264_encoder.h/aw_sw_x264_encoder.c</li><li>顶层抽象：AWSWFaacEncoder/AWSWX264Encoder</li></ol><p>编码过程中需要注意的地方：</p><ol><li>注意 audio specific config 及 sps/pps数据的获取，不获取这两种数据，服务器没办法识别音视频帧的。</li><li>faac编码后注意去除adts头部。</li><li>x264编码器如果输入分辨率的宽度不是16的倍数，需要将其扩展成16的倍数，否则编码可能会出问题(颜色丢失，uv混乱)。</li></ol><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里 </title>
    <link href="https://hardman.github.io/2017/01/25/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E7%95%AA%E5%A4%96%EF%BC%89%E8%BF%90%E8%A1%8C%E4%B8%8D%E8%B5%B7AWLive%E7%9A%84demo%E7%9A%84%E5%90%8C%E5%AD%A6%E8%AF%B7%E7%9C%8B%E8%BF%99%E9%87%8C/"/>
    <id>https://hardman.github.io/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/</id>
    <published>2017-01-25T02:49:01.000Z</published>
    <updated>2018-07-19T07:13:13.681Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>我收到了很多反馈和评论，问我为什么AWLive运行不起来。<br>大概是说报这样一个错误：<br><img src="https://github.com/hardman/OutLinkImages/raw/master/AWLive/番外/1.png" alt=""><br>我实在没想到会有人问这个问题。<br>没想到的原因有2个：</p><ol><li>这个问题不是一个技术问题，而是工具使用问题。我没想到会有人存在疑问。<br>如果是培训机构出来的同学，应该第一天就会学这个。<br>如果自学的同学，那学习能力肯定没得说，不可能代码写的好，工具却不会用。<br>所以这部分刚入门没多久的初学者，或者是没有注意到工具使用的一些同学，被我忽略了，很抱歉，特写此文档说明。</li><li>我认为这个问题可能简单地搜索一下就能解决。所以有人问这个问题的时候，我没在意，也没办法回复。后续有好几个问这个，我才意识到可能是我的错误，应该当时制作工程时就排除此问题。</li></ol><h2 id="解决步骤"><a href="#解决步骤" class="headerlink" title="解决步骤"></a>解决步骤</h2><p><strong>注意:画圈的部分为点击／操作位置。请操作之前将手机连接到电脑</strong></p><ul><li>打开页面：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a> 然后下载工程</li></ul><p><img src="https://github.com/hardman/OutLinkImages/raw/master/AWLive/番外/down.png" alt=""></p><ul><li>解压</li></ul><p><img src="https://github.com/hardman/OutLinkImages/raw/master/AWLive/番外/unzip.png" alt=""></p><ul><li>打开工程，点击Documentation</li></ul><p><img src="https://github.com/hardman/OutLinkImages/raw/master/AWLive/番外/2.png" alt=""></p><ul><li>点击AWLive，右侧选择真机<br><img src="https://github.com/hardman/OutLinkImages/raw/master/AWLive/番外/3.png" alt=""></li></ul><ul><li>选择正确的 code sign(开发者账号对应的证书名和profile，如果不清楚可自行查询)<br><img src="https://github.com/hardman/OutLinkImages/raw/master/AWLive/番外/4.png" alt=""></li></ul><ul><li>点击运行。99%的情况下，可以正确运行。如遇到另外的1%，请留言。</li></ul><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码 </title>
    <link href="https://hardman.github.io/2016/12/07/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E4%B8%83%EF%BC%89h264-aac-%E7%A1%AC%E7%BC%96%E7%A0%81/"/>
    <id>https://hardman.github.io/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/</id>
    <published>2016-12-07T14:02:59.000Z</published>
    <updated>2018-07-19T07:08:35.653Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>前面已经介绍了如何从硬件设备获取到音视频数据（pcm，NV12）。</p><p>但是我们需要的视频格式是 aac和 h264。</p><p>现在就介绍一下如何将pcm编码aac，将NV12数据编码为h264。</p><p>编码分为软编码和硬编码。</p><p>硬编码是系统提供的，由系统专门嵌入的硬件设备处理音视频编码，主要计算操作在对应的硬件中。硬编码的特点是，速度快，cpu占用少，但是不够灵活，只能使用一些特定的功能。</p><p>软编码是指，通过代码计算进行数据编码，主要计算操作在cpu中。软编码的特点是，灵活，多样，功能丰富可扩展，但是cpu占用较多。</p><p>在代码中，编码器是通过AWEncoderManager获取的。</p><p>AWENcoderManager是一个工厂，通过audioEncoderType和videoEncoderType指定编码器类型。</p><p>编码器分为两类，音频编码器（AWAudioEncoder），视频编码器（AWVideoEncoder）。</p><p>音视频编码器又分别分为硬编码（在HW目录中）和软编码（在SW目录中）。</p><p>所以编码部分主要有4个文件：硬编码H264（AWHWH264Encoder），硬编码AAC（AWHWAACEncoder），软编码AAC（AWSWFaacEncoder），软编码H264（AWSWX264Encoder）</p><h2 id="硬编码H264"><a href="#硬编码H264" class="headerlink" title="硬编码H264"></a>硬编码H264</h2><p>第一步，开启硬编码器<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">-(void)open&#123;</span><br><span class="line">    //创建 video encode session</span><br><span class="line">    // 创建 video encode session</span><br><span class="line">    // 传入视频宽高，编码类型：kCMVideoCodecType_H264</span><br><span class="line">    // 编码回调：vtCompressionSessionCallback，这个回调函数为编码结果回调，编码成功后，会将数据传入此回调中。</span><br><span class="line">    // (__bridge void * _Nullable)(self)：这个参数会被原封不动地传入vtCompressionSessionCallback中，此参数为编码回调同外界通信的唯一参数。</span><br><span class="line">    // &amp;_vEnSession，c语言可以给传入参数赋值。在函数内部会分配内存并初始化_vEnSession。</span><br><span class="line">    OSStatus status = VTCompressionSessionCreate(NULL, (int32_t)(self.videoConfig.pushStreamWidth), (int32_t)self.videoConfig.pushStreamHeight, kCMVideoCodecType_H264, NULL, NULL, NULL, vtCompressionSessionCallback, (__bridge void * _Nullable)(self), &amp;_vEnSession);</span><br><span class="line">    if (status == noErr) &#123;</span><br><span class="line">        // 设置参数</span><br><span class="line">        // ProfileLevel，h264的协议等级，不同的清晰度使用不同的ProfileLevel。</span><br><span class="line">        VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);</span><br><span class="line">        // 设置码率</span><br><span class="line">        VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(self.videoConfig.bitrate));</span><br><span class="line">        // 设置实时编码</span><br><span class="line">        VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);</span><br><span class="line">        // 关闭重排Frame，因为有了B帧（双向预测帧，根据前后的图像计算出本帧）后，编码顺序可能跟显示顺序不同。此参数可以关闭B帧。</span><br><span class="line">        VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);</span><br><span class="line">        // 关键帧最大间隔，关键帧也就是I帧。此处表示关键帧最大间隔为2s。</span><br><span class="line">        VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(self.videoConfig.fps * 2));</span><br><span class="line">        // 关于B帧 P帧 和I帧，请参考：http://blog.csdn.net/abcjennifer/article/details/6577934</span><br><span class="line">        </span><br><span class="line">        //参数设置完毕，准备开始，至此初始化完成，随时来数据，随时编码</span><br><span class="line">        status = VTCompressionSessionPrepareToEncodeFrames(_vEnSession);</span><br><span class="line">        if (status != noErr) &#123;</span><br><span class="line">            [self onErrorWithCode:AWEncoderErrorCodeVTSessionPrepareFailed des:@&quot;硬编码vtsession prepare失败&quot;];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;else&#123;</span><br><span class="line">        [self onErrorWithCode:AWEncoderErrorCodeVTSessionCreateFailed des:@&quot;硬编码vtsession创建失败&quot;];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>第二步，向编码器丢数据：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">//这里的参数yuvData就是从相机获取的NV12数据。</span><br><span class="line">-(aw_flv_video_tag *)encodeYUVDataToFlvTag:(NSData *)yuvData&#123;</span><br><span class="line">    if (!_vEnSession) &#123;</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    //yuv 变成 转CVPixelBufferRef</span><br><span class="line">    OSStatus status = noErr;</span><br><span class="line">    </span><br><span class="line">    //视频宽度</span><br><span class="line">    size_t pixelWidth = self.videoConfig.pushStreamWidth;</span><br><span class="line">    //视频高度</span><br><span class="line">    size_t pixelHeight = self.videoConfig.pushStreamHeight;</span><br><span class="line"></span><br><span class="line">    //现在要把NV12数据放入 CVPixelBufferRef中，因为 硬编码主要调用VTCompressionSessionEncodeFrame函数，此函数不接受yuv数据，但是接受CVPixelBufferRef类型。</span><br><span class="line">    CVPixelBufferRef pixelBuf = NULL;</span><br><span class="line">    //初始化pixelBuf，数据类型是kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange，此类型数据格式同NV12格式相同。</span><br><span class="line">    CVPixelBufferCreate(NULL, pixelWidth, pixelHeight, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &amp;pixelBuf);</span><br><span class="line">    </span><br><span class="line">    // Lock address，锁定数据，应该是多线程防止重入操作。</span><br><span class="line">    if(CVPixelBufferLockBaseAddress(pixelBuf, 0) != kCVReturnSuccess)&#123;</span><br><span class="line">        [self onErrorWithCode:AWEncoderErrorCodeLockSampleBaseAddressFailed des:@&quot;encode video lock base address failed&quot;];</span><br><span class="line">        return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //将yuv数据填充到CVPixelBufferRef中</span><br><span class="line">    size_t y_size = pixelWidth * pixelHeight;</span><br><span class="line">    size_t uv_size = y_size / 4;</span><br><span class="line">    uint8_t *yuv_frame = (uint8_t *)yuvData.bytes;</span><br><span class="line">    </span><br><span class="line">    //处理y frame</span><br><span class="line">    uint8_t *y_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuf, 0);</span><br><span class="line">    memcpy(y_frame, yuv_frame, y_size);</span><br><span class="line">    </span><br><span class="line">    uint8_t *uv_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuf, 1);</span><br><span class="line">    memcpy(uv_frame, yuv_frame + y_size, uv_size * 2);</span><br><span class="line">    </span><br><span class="line">    //硬编码 CmSampleBufRef</span><br><span class="line">    </span><br><span class="line">    //时间戳</span><br><span class="line">    uint32_t ptsMs = self.manager.timestamp + 1; //self.vFrameCount++ * 1000.f / self.videoConfig.fps;</span><br><span class="line">    </span><br><span class="line">    CMTime pts = CMTimeMake(ptsMs, 1000);</span><br><span class="line">    </span><br><span class="line">    //硬编码主要其实就这一句。将携带NV12数据的PixelBuf送到硬编码器中，进行编码。</span><br><span class="line">    status = VTCompressionSessionEncodeFrame(_vEnSession, pixelBuf, pts, kCMTimeInvalid, NULL, pixelBuf, NULL);</span><br><span class="line"></span><br><span class="line">    ... ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>第三步，通过硬编码回调获取h264数据</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">static void vtCompressionSessionCallback (void * CM_NULLABLE outputCallbackRefCon,</span><br><span class="line">                                          void * CM_NULLABLE sourceFrameRefCon,</span><br><span class="line">                                          OSStatus status,</span><br><span class="line">                                          VTEncodeInfoFlags infoFlags,</span><br><span class="line">                                          CM_NULLABLE CMSampleBufferRef sampleBuffer )&#123;</span><br><span class="line">    //通过outputCallbackRefCon获取AWHWH264Encoder的对象指针，将编码好的h264数据传出去。</span><br><span class="line">    AWHWH264Encoder *encoder = (__bridge AWHWH264Encoder *)(outputCallbackRefCon);</span><br><span class="line"></span><br><span class="line">    //判断是否编码成功</span><br><span class="line">    if (status != noErr) &#123;</span><br><span class="line">        dispatch_semaphore_signal(encoder.vSemaphore);</span><br><span class="line">        [encoder onErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFailed des:@&quot;encode video frame error 1&quot;];</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //是否数据是完整的</span><br><span class="line">    if (!CMSampleBufferDataIsReady(sampleBuffer)) &#123;</span><br><span class="line">        dispatch_semaphore_signal(encoder.vSemaphore);</span><br><span class="line">        [encoder onErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFailed des:@&quot;encode video frame error 2&quot;];</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //是否是关键帧，关键帧和非关键帧要区分清楚。推流时也要注明。 </span><br><span class="line">    BOOL isKeyFrame = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);</span><br><span class="line">    </span><br><span class="line">    //首先获取sps 和pps</span><br><span class="line">    //sps pss 也是h264的一部分，可以认为它们是特别的h264视频帧，保存了h264视频的一些必要信息。</span><br><span class="line">    //没有这部分数据h264视频很难解析出来。</span><br><span class="line">    //数据处理时，sps pps 数据可以作为一个普通h264帧，放在h264视频流的最前面。</span><br><span class="line">    BOOL needSpsPps = NO;</span><br><span class="line">    if (!encoder.spsPpsData) &#123;</span><br><span class="line">        if (isKeyFrame) &#123;</span><br><span class="line">            //获取avcC，这就是我们想要的sps和pps数据。</span><br><span class="line">            //如果保存到文件中，需要将此数据前加上 [0 0 0 1] 4个字节，写入到h264文件的最前面。</span><br><span class="line">            //如果推流，将此数据放入flv数据区即可。</span><br><span class="line">            CMFormatDescriptionRef sampleBufFormat = CMSampleBufferGetFormatDescription(sampleBuffer);</span><br><span class="line">            NSDictionary *dict = (__bridge NSDictionary *)CMFormatDescriptionGetExtensions(sampleBufFormat);</span><br><span class="line">            encoder.spsPpsData = dict[@&quot;SampleDescriptionExtensionAtoms&quot;][@&quot;avcC&quot;];</span><br><span class="line">        &#125;</span><br><span class="line">        needSpsPps = YES;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //获取真正的视频帧数据</span><br><span class="line">    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);</span><br><span class="line">    size_t blockDataLen;</span><br><span class="line">    uint8_t *blockData;</span><br><span class="line">    status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &amp;blockDataLen, (char **)&amp;blockData);</span><br><span class="line">    if (status == noErr) &#123;</span><br><span class="line">        size_t currReadPos = 0;</span><br><span class="line">        //一般情况下都是只有1帧，在最开始编码的时候有2帧，取最后一帧</span><br><span class="line">        while (currReadPos &lt; blockDataLen - 4) &#123;</span><br><span class="line">            uint32_t naluLen = 0;</span><br><span class="line">            memcpy(&amp;naluLen, blockData + currReadPos, 4);</span><br><span class="line">            naluLen = CFSwapInt32BigToHost(naluLen);</span><br><span class="line">            </span><br><span class="line">            //naluData 即为一帧h264数据。</span><br><span class="line">            //如果保存到文件中，需要将此数据前加上 [0 0 0 1] 4个字节，按顺序写入到h264文件中。</span><br><span class="line">            //如果推流，需要将此数据前加上4个字节表示数据长度的数字，此数据需转为大端字节序。</span><br><span class="line">            //关于大端和小端模式，请参考此网址：http://blog.csdn.net/hackbuteer1/article/details/7722667</span><br><span class="line">            encoder.naluData = [NSData dataWithBytes:blockData + currReadPos + 4 length:naluLen];</span><br><span class="line">            </span><br><span class="line">            currReadPos += 4 + naluLen;</span><br><span class="line">            </span><br><span class="line">            encoder.isKeyFrame = isKeyFrame;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;else&#123;</span><br><span class="line">        [encoder onErrorWithCode:AWEncoderErrorCodeEncodeGetH264DataFailed des:@&quot;got h264 data failed&quot;];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    ... ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第四步，其实，此时硬编码已结束，这一步跟编码无关，将取得的h264数据，送到推流器中。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">-(aw_flv_video_tag *)encodeYUVDataToFlvTag:(NSData *)yuvData&#123;</span><br><span class="line">    </span><br><span class="line">    ... ...</span><br><span class="line">    </span><br><span class="line">    if (status == noErr) &#123;</span><br><span class="line">        dispatch_semaphore_wait(self.vSemaphore, DISPATCH_TIME_FOREVER);</span><br><span class="line">        if (_naluData) &#123;</span><br><span class="line">            //此处 硬编码成功，_naluData内的数据即为h264视频帧。</span><br><span class="line">            //我们是推流，所以获取帧长度，转成大端字节序，放到数据的最前面</span><br><span class="line">            uint32_t naluLen = (uint32_t)_naluData.length;</span><br><span class="line">            //小端转大端。计算机内一般都是小端，而网络和文件中一般都是大端。大端转小端和小端转大端算法一样，就是字节序反转就行了。</span><br><span class="line">            uint8_t naluLenArr[4] = &#123;naluLen &gt;&gt; 24 &amp; 0xff, naluLen &gt;&gt; 16 &amp; 0xff, naluLen &gt;&gt; 8 &amp; 0xff, naluLen &amp; 0xff&#125;;</span><br><span class="line">            //将数据拼在一起</span><br><span class="line">            NSMutableData *mutableData = [NSMutableData dataWithBytes:naluLenArr length:4];</span><br><span class="line">            [mutableData appendData:_naluData];</span><br><span class="line"></span><br><span class="line">            //将h264数据合成flv tag，合成flvtag之后就可以直接发送到服务端了。后续会介绍</span><br><span class="line">            aw_flv_video_tag *video_tag = aw_encoder_create_video_tag((int8_t *)mutableData.bytes, mutableData.length, ptsMs, 0, self.isKeyFrame);</span><br><span class="line">            </span><br><span class="line">            //到此，编码工作完成，清除状态。</span><br><span class="line">            _naluData = nil;</span><br><span class="line">            _isKeyFrame = NO;</span><br><span class="line">            </span><br><span class="line">            CVPixelBufferUnlockBaseAddress(pixelBuf, 0);</span><br><span class="line">            </span><br><span class="line">            CFRelease(pixelBuf);</span><br><span class="line">            </span><br><span class="line">            return video_tag;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;else&#123;</span><br><span class="line">        [self onErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFailed des:@&quot;encode video frame error&quot;];</span><br><span class="line">    &#125;</span><br><span class="line">    CVPixelBufferUnlockBaseAddress(pixelBuf, 0);</span><br><span class="line">    </span><br><span class="line">    CFRelease(pixelBuf);</span><br><span class="line">    </span><br><span class="line">    return NULL;</span><br></pre></td></tr></table></figure></p><p>第五步，关闭编码器<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">//永远不忘记关闭释放资源。</span><br><span class="line">-(void)close&#123;</span><br><span class="line">    dispatch_semaphore_signal(self.vSemaphore);</span><br><span class="line">    </span><br><span class="line">    VTCompressionSessionInvalidate(_vEnSession);</span><br><span class="line">    _vEnSession = nil;</span><br><span class="line">    </span><br><span class="line">    self.naluData = nil;</span><br><span class="line">    self.isKeyFrame = NO;</span><br><span class="line">    self.spsPpsData = nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="硬编码AAC"><a href="#硬编码AAC" class="headerlink" title="硬编码AAC"></a>硬编码AAC</h2><p>硬编码AAC逻辑同H264差不多。</p><h2 id="第一步，打开编码器"><a href="#第一步，打开编码器" class="headerlink" title="第一步，打开编码器"></a>第一步，打开编码器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">-(void)open&#123;</span><br><span class="line">    //创建audio encode converter也就是AAC编码器</span><br><span class="line">    //初始化一系列参数</span><br><span class="line">    AudioStreamBasicDescription inputAudioDes = &#123;</span><br><span class="line">        .mFormatID = kAudioFormatLinearPCM,</span><br><span class="line">        .mSampleRate = self.audioConfig.sampleRate,</span><br><span class="line">        .mBitsPerChannel = (uint32_t)self.audioConfig.sampleSize,</span><br><span class="line">        .mFramesPerPacket = 1,//每个包1帧</span><br><span class="line">        .mBytesPerFrame = 2,//每帧2字节</span><br><span class="line">        .mBytesPerPacket = 2,//每个包1帧也是2字节</span><br><span class="line">        .mChannelsPerFrame = (uint32_t)self.audioConfig.channelCount,//声道数，推流一般使用单声道</span><br><span class="line">        //下面这个flags的设置参照此文：http://www.mamicode.com/info-detail-986202.html</span><br><span class="line">        .mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsNonInterleaved,</span><br><span class="line">        .mReserved = 0</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    //设置输出格式，声道数</span><br><span class="line">    AudioStreamBasicDescription outputAudioDes = &#123;</span><br><span class="line">        .mChannelsPerFrame = (uint32_t)self.audioConfig.channelCount,</span><br><span class="line">        .mFormatID = kAudioFormatMPEG4AAC,</span><br><span class="line">        0</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    //初始化_aConverter</span><br><span class="line">    uint32_t outDesSize = sizeof(outputAudioDes);</span><br><span class="line">    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &amp;outDesSize, &amp;outputAudioDes);</span><br><span class="line">    OSStatus status = AudioConverterNew(&amp;inputAudioDes, &amp;outputAudioDes, &amp;_aConverter);</span><br><span class="line">    if (status != noErr) &#123;</span><br><span class="line">        [self onErrorWithCode:AWEncoderErrorCodeCreateAudioConverterFailed des:@&quot;硬编码AAC创建失败&quot;];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //设置码率</span><br><span class="line">    uint32_t aBitrate = (uint32_t)self.audioConfig.bitrate;</span><br><span class="line">    uint32_t aBitrateSize = sizeof(aBitrate);</span><br><span class="line">    status = AudioConverterSetProperty(_aConverter, kAudioConverterEncodeBitRate, aBitrateSize, &amp;aBitrate);</span><br><span class="line">    </span><br><span class="line">    //查询最大输出</span><br><span class="line">    uint32_t aMaxOutput = 0;</span><br><span class="line">    uint32_t aMaxOutputSize = sizeof(aMaxOutput);</span><br><span class="line">    AudioConverterGetProperty(_aConverter, kAudioConverterPropertyMaximumOutputPacketSize, &amp;aMaxOutputSize, &amp;aMaxOutput);</span><br><span class="line">    self.aMaxOutputFrameSize = aMaxOutput;</span><br><span class="line">    if (aMaxOutput == 0) &#123;</span><br><span class="line">        [self onErrorWithCode:AWEncoderErrorCodeAudioConverterGetMaxFrameSizeFailed des:@&quot;AAC 获取最大frame size失败&quot;];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第二步，获取audio specific config，这是一个特别的flv tag，存储了使用的aac的一些关键数据，作为解析音频帧的基础。<br>在rtmp中，必须将此帧在所有音频帧之前发送。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">-(aw_flv_audio_tag *)createAudioSpecificConfigFlvTag&#123;</span><br><span class="line">    //profile，表示使用的协议</span><br><span class="line">    uint8_t profile = kMPEG4Object_AAC_LC;</span><br><span class="line">    //采样率</span><br><span class="line">    uint8_t sampleRate = 4;</span><br><span class="line">    //channel信息</span><br><span class="line">    uint8_t chanCfg = 1;</span><br><span class="line">    //将上面3个信息拼在一起，成为2字节</span><br><span class="line">    uint8_t config1 = (profile &lt;&lt; 3) | ((sampleRate &amp; 0xe) &gt;&gt; 1);</span><br><span class="line">    uint8_t config2 = ((sampleRate &amp; 0x1) &lt;&lt; 7) | (chanCfg &lt;&lt; 3);</span><br><span class="line">    </span><br><span class="line">    //将数据转成aw_data</span><br><span class="line">    aw_data *config_data = NULL;</span><br><span class="line">    data_writer.write_uint8(&amp;config_data, config1);</span><br><span class="line">    data_writer.write_uint8(&amp;config_data, config2);</span><br><span class="line">    </span><br><span class="line">    //转成flv tag</span><br><span class="line">    aw_flv_audio_tag *audio_specific_config_tag = aw_encoder_create_audio_specific_config_tag(config_data, &amp;_faacConfig);</span><br><span class="line">    </span><br><span class="line">    free_aw_data(&amp;config_data);</span><br><span class="line">    </span><br><span class="line">    //返回给调用方，准备发送</span><br><span class="line">    return audio_specific_config_tag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>第三步：当从麦克风获取到音频数据时，将数据交给AAC编码器编码。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">-(aw_flv_audio_tag *)encodePCMDataToFlvTag:(NSData *)pcmData&#123;</span><br><span class="line">    self.curFramePcmData = pcmData;</span><br><span class="line">    </span><br><span class="line">    //构造输出结构体，编码器需要</span><br><span class="line">    AudioBufferList outAudioBufferList = &#123;0&#125;;</span><br><span class="line">    outAudioBufferList.mNumberBuffers = 1;</span><br><span class="line">    outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.audioConfig.channelCount;</span><br><span class="line">    outAudioBufferList.mBuffers[0].mDataByteSize = self.aMaxOutputFrameSize;</span><br><span class="line">    outAudioBufferList.mBuffers[0].mData = malloc(self.aMaxOutputFrameSize);</span><br><span class="line">    </span><br><span class="line">    uint32_t outputDataPacketSize = 1;</span><br><span class="line">    </span><br><span class="line">    //执行编码，此处需要传一个回调函数aacEncodeInputDataProc，以同步的方式，在回调中填充pcm数据。</span><br><span class="line">    OSStatus status = AudioConverterFillComplexBuffer(_aConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &amp;outputDataPacketSize, &amp;outAudioBufferList, NULL);</span><br><span class="line">    if (status == noErr) &#123;</span><br><span class="line">        //编码成功，获取数据</span><br><span class="line">        NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];</span><br><span class="line">        //时间戳(ms) = 1000 * 每秒采样数 / 采样率;</span><br><span class="line">        self.manager.timestamp += 1024 * 1000 / self.audioConfig.sampleRate;</span><br><span class="line">        //获取到aac数据，转成flv audio tag，发送给服务端。</span><br><span class="line">        return aw_encoder_create_audio_tag((int8_t *)rawAAC.bytes, rawAAC.length, (uint32_t)self.manager.timestamp, &amp;_faacConfig);</span><br><span class="line">    &#125;else&#123;</span><br><span class="line">        //编码错误</span><br><span class="line">        [self onErrorWithCode:AWEncoderErrorCodeAudioEncoderFailed des:@&quot;aac 编码错误&quot;];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    return NULL;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//回调函数，系统指定格式</span><br><span class="line">static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)&#123;</span><br><span class="line">    AWHWAACEncoder *hwAacEncoder = (__bridge AWHWAACEncoder *)inUserData;</span><br><span class="line">    //将pcm数据交给编码器</span><br><span class="line">    if (hwAacEncoder.curFramePcmData) &#123;</span><br><span class="line">        ioData-&gt;mBuffers[0].mData = (void *)hwAacEncoder.curFramePcmData.bytes;</span><br><span class="line">        ioData-&gt;mBuffers[0].mDataByteSize = (uint32_t)hwAacEncoder.curFramePcmData.length;</span><br><span class="line">        ioData-&gt;mNumberBuffers = 1;</span><br><span class="line">        ioData-&gt;mBuffers[0].mNumberChannels = (uint32_t)hwAacEncoder.audioConfig.channelCount;</span><br><span class="line">        </span><br><span class="line">        return noErr;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    return -1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>第四步：关闭编码器释放资源<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">-(void)close&#123;</span><br><span class="line">    AudioConverterDispose(_aConverter);</span><br><span class="line">    _aConverter = nil;</span><br><span class="line">    self.curFramePcmData = nil;</span><br><span class="line">    self.aMaxOutputFrameSize = 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍 </title>
    <link href="https://hardman.github.io/2016/11/24/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E5%85%AD%EF%BC%89h264%E3%80%81aac%E3%80%81flv%E4%BB%8B%E7%BB%8D/"/>
    <id>https://hardman.github.io/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/</id>
    <published>2016-11-24T14:43:38.000Z</published>
    <updated>2018-07-19T07:09:12.941Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>前面介绍了如何捕获音视频原始数据，介绍了yuv和pcm。</p><p>下面来介绍一下我们的想要转换的目标音视频格式：h264，aac，flv。</p><h2 id="什么是h264？"><a href="#什么是h264？" class="headerlink" title="什么是h264？"></a>什么是h264？</h2><p>这里就不贴名词解释了。</p><p>说明一下，为什么需要这种格式。</p><p>其实除了h264格式之外，视频格式有很多种，出现这些格式原因无非有3种。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">1. 压缩尺寸。</span><br><span class="line"></span><br><span class="line">我们来计算一下：</span><br><span class="line"></span><br><span class="line">yuv420格式，宽度为480，高度为320的视频，每一帧 需要 480*320*3/2 = 230400 字节。</span><br><span class="line"></span><br><span class="line">假设视频每秒20帧，那么30秒的视频 是 230400 * 20 * 30 = 138240000 字节 大约138M。</span><br><span class="line"></span><br><span class="line">你可能觉得不是很大，但是这个分辨率同样清晰度的h264视频可能只需要1-2M左右（当然也跟码率有关）。</span><br><span class="line"></span><br><span class="line">2. 为了与当前产品业务匹配，或者版权专利等原因，创造一种独特的格式。</span><br><span class="line"></span><br><span class="line">比如QuickTime，WMV等。</span><br><span class="line"></span><br><span class="line">3. 对当前技术的改进。</span><br><span class="line"></span><br><span class="line">h264正是对h263的改进。</span><br></pre></td></tr></table></figure><p>h264 怎么压缩视频数据呢？</p><p>最重要的一点是将视频帧分为关键帧和非关键帧。</p><p>关键帧的数据是完整的。包含了所有的颜色数据。这样的视频帧称为I帧。</p><p>非关键帧数据不完整，但是它能够根据前面或者后面的帧数据，甚至自己的部分帧数据，将自身数据补充完整。这种视频帧被称为 B/P 帧。</p><p>总体来说，h264跟yuv相比，最大的不同就是它是压缩的（通常此过程称为编码，不只是简单的压缩）。</p><h2 id="什么是aac"><a href="#什么是aac" class="headerlink" title="什么是aac"></a>什么是aac</h2><p>aac同h264性质一样，它也是pcm的压缩（编码）格式。</p><p>mp3大家都听说过，以前听歌保存到电脑里的歌曲90%都是mp3格式。</p><p>aac 相对 mp3来说，是更先进的压缩格式。</p><h2 id="什么是flv"><a href="#什么是flv" class="headerlink" title="什么是flv"></a>什么是flv</h2><p>h264是视频编码格式。</p><p>aac是音频编码格式。</p><p>除了这两种格式之外，还有一种将视频和音频合成(muxer 这个词会在相关代码中经常出现)在一起的格式。</p><p>比如：mp4，avi，rmvb，flv。</p><p>flv 是一种简单的视频合成格式。</p><p>它支持指定的音视频格式，如：h263，h264，VP6 及 AAC，MP3，Nellymoser等。</p><p>简单说来，flv的组成如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flv header + script tag + video tag + audio tag + ... + video tag + audio tag</span><br></pre></td></tr></table></figure></p><p>flv由 flv header 和无数的tag组成的。</p><p>flv header 内容是固定的。</p><p>一个tag就像是一个数组中的元素。是一个单独的储存了信息的数据块。</p><p>script tag 内存储了视频相关信息，如：宽高，码率，fps，文件大小，音频信息等等。</p><p>video tag 中 存储的是完整的视频压缩格式的一帧数据，如h264数据。</p><p>audio tag 中 存储的是完整的音频压缩格式的一帧数据，如 aac数据。</p><p>这样把所有数据拼接在一起，写入文件。这个文件就是flv格式。可以使用播放器播放了。</p><p>而flv刚好支持 h264 和 aac。</p><p>为什么介绍flv呢？</p><p>因为rtmp协议所传输的视频流，就要求是flv格式。</p><p>所以，程序从相机和麦克风捕获到音视频数据后，分别转成 aac和h264格式的音视频帧。</p><p>然后将aac和h264音视频帧合成flv格式的视频后发送到rtmp服务器。客户端就可以播放我们推的流了。</p><p>注：上述内容不一定够精确，以容易理解为上。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</title>
    <link href="https://hardman.github.io/2016/11/16/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E4%BA%94%EF%BC%89yuv%E3%80%81pcm%E6%95%B0%E6%8D%AE%E7%9A%84%E4%BB%8B%E7%BB%8D%E5%92%8C%E8%8E%B7%E5%8F%96/"/>
    <id>https://hardman.github.io/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/</id>
    <published>2016-11-16T15:17:40.000Z</published>
    <updated>2018-07-19T07:08:59.401Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>前面介绍了如何通过相机实时获取音视频数据。</p><p>我们接下来就需要了解获取到的数据到底是什么样的。</p><p><a href="http://blog.csdn.net/hard_man/article/details/53157423" target="_blank" rel="noopener">使用系统提供的接口获取到的音视频数据</a>都保存在CMSampleBufferRef中。</p><p><a href="http://blog.csdn.net/hard_man/article/details/53180497" target="_blank" rel="noopener">使用GPUImamge获取</a>到的音频数据为CMSampleBufferRef，获取到的视频格式为BGRA格式的二进制数据。</p><h2 id="CMSampleBufferRef介绍"><a href="#CMSampleBufferRef介绍" class="headerlink" title="CMSampleBufferRef介绍"></a>CMSampleBufferRef介绍</h2><p>这个结构在iOS中表示一帧音频/视频数据。</p><p>它里面包含了这一帧数据的内容和格式。</p><p>我们可以把它的内容取出来，提取出/转换成 我们想要的数据。</p><p>代表视频的CMSampleBufferRef中保存的数据是yuv420格式的视频帧(因为我们在视频输出设置中将输出格式设为：kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)。</p><p>代表音频的CMSampleBufferRef中保存的数据是PCM格式的音频帧。</p><h2 id="yuv是什么？NV12又是什么？"><a href="#yuv是什么？NV12又是什么？" class="headerlink" title="yuv是什么？NV12又是什么？"></a>yuv是什么？NV12又是什么？</h2><p>视频是由一帧一帧的数据连接而成，而一帧视频数据其实就是一张图片。</p><p>yuv是一种图片储存格式，跟RGB格式类似。</p><p>RGB格式的图片很好理解，计算机中的大多数图片，都是以RGB格式存储的。</p><p>yuv中，y表示亮度，单独只有y数据就可以形成一张图片，只不过这张图片是灰色的。u和v表示色差(u和v也被称为：Cb－蓝色差，Cr－红色差)，</p><p>为什么要yuv？</p><p>有一定历史原因，最早的电视信号，为了兼容黑白电视，采用的就是yuv格式。</p><p>一张yuv的图像，去掉uv，只保留y，这张图片就是黑白的。</p><p>而且yuv可以通过抛弃色差来进行带宽优化。</p><p>比如yuv420格式图像相比RGB来说，要节省一半的字节大小，抛弃相邻的色差对于人眼来说，差别不大。</p><p>一张yuv格式的图像，占用字节数为 (width <em> height + (width </em> height) / 4 + (width <em> height) / 4) = (width </em> height) <em> 3 / 2<br>一张RGB格式的图像，占用字节数为（width </em> height） * 3</p><p>在传输上，yuv格式的视频也更灵活(yuv3种数据可分别传输)。</p><p>很多视频编码器最初是不支持rgb格式的。但是所有的视频编码器都支持yuv格式。</p><p>综合来讲，我们选择使用yuv格式，所以我们编码之前，首先将视频数据转成yuv格式。</p><p>我们这里使用的就是yuv420格式的视频。</p><p>yuv420也包含不同的数据排列格式：I420，NV12，NV21.</p><p>其格式分别如下，<br>I420格式：y,u,v 3个部分分别存储：Y0,Y1…Yn,U0,U1…Un/2,V0,V1…Vn/2<br>NV12格式：y和uv 2个部分分别存储：Y0,Y1…Yn,U0,V0,U1,V1…Un/2,Vn/2<br>NV21格式：同NV12，只是U和V的顺序相反。</p><p>综合来说，除了存储顺序不同之外，上述格式对于显示来说没有任何区别。</p><p>使用哪种视频的格式，取决于初始化相机时设置的视频输出格式。<br>设置为kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange时，表示输出的视频格式为NV12；<br>设置为kCVPixelFormatType_420YpCbCr8Planar时，表示使用I420。</p><p>GPUImage设置相机输出数据时，使用的就是NV12.</p><p>为了一致，我们这里也选择NV12格式输出视频。</p><h2 id="PCM是什么？"><a href="#PCM是什么？" class="headerlink" title="PCM是什么？"></a>PCM是什么？</h2><p>脉冲编码调制，其实是将不规则的模拟信号转换成数字信号，这样就可以通过物理介质存储起来。</p><p>而声音也是一种特定频率（20-20000HZ）的模拟信号，也可以通过这种技术转换成数字信号，从而保存下来。</p><p>PCM格式，就是录制声音时，保存的最原始的声音数据格式。</p><p>相信你应该听说过wav格式的音频，它其实就是给PCM数据流加上一段header数据，就成为了wav格式。</p><p>而wav格式有时候之所以被称为无损格式，就是因为他保存的是原始pcm数据（也跟采样率和比特率有关）。</p><p>像我们耳熟能详的那些音频格式，mp3，aac等等，都是有损压缩，为了节约占用空间，在很少损失音效的基础上，进行最大程度的压缩。</p><p>所有的音频编码器，都支持pcm编码，而且录制的声音，默认也是PCM格式，所以我们下一步就是要获取录制的PCM数据。</p><h2 id="从CMSampleBufferRef中提取yuv数据"><a href="#从CMSampleBufferRef中提取yuv数据" class="headerlink" title="从CMSampleBufferRef中提取yuv数据"></a>从CMSampleBufferRef中提取yuv数据</h2><p>在前面的文章(<a href="http://blog.csdn.net/hard_man/article/details/53157423" target="_blank" rel="noopener">使用系统接口捕获视频</a>)中，初始化输出设备时，我们将输出的数据设置为kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange。<br>因此在CMSampleBufferRef中保存的是yuv420(NV12)格式数据。<br>通过下面的方法将CMSampleBufferRef转为yuv420(NV12)。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">// AWVideoEncoder.m文件</span><br><span class="line">-(NSData *) convertVideoSmapleBufferToYuvData:(CMSampleBufferRef) videoSample&#123;</span><br><span class="line">    // 获取yuv数据</span><br><span class="line">    // 通过CMSampleBufferGetImageBuffer方法，获得CVImageBufferRef。</span><br><span class="line">    // 这里面就包含了yuv420(NV12)数据的指针</span><br><span class="line">    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(videoSample);</span><br><span class="line">    </span><br><span class="line">    //表示开始操作数据</span><br><span class="line">    CVPixelBufferLockBaseAddress(pixelBuffer, 0);</span><br><span class="line">    </span><br><span class="line">    //图像宽度（像素）</span><br><span class="line">    size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);</span><br><span class="line">    //图像高度（像素）</span><br><span class="line">    size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);</span><br><span class="line">    //yuv中的y所占字节数</span><br><span class="line">    size_t y_size = pixelWidth * pixelHeight;</span><br><span class="line">    //yuv中的uv所占的字节数</span><br><span class="line">    size_t uv_size = y_size / 2;</span><br><span class="line">    </span><br><span class="line">    uint8_t *yuv_frame = aw_alloc(uv_size + y_size);</span><br><span class="line">    </span><br><span class="line">    //获取CVImageBufferRef中的y数据</span><br><span class="line">    uint8_t *y_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);</span><br><span class="line">    memcpy(yuv_frame, y_frame, y_size);</span><br><span class="line">    </span><br><span class="line">    //获取CMVImageBufferRef中的uv数据</span><br><span class="line">    uint8_t *uv_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);</span><br><span class="line">    memcpy(yuv_frame + y_size, uv_frame, uv_size);</span><br><span class="line">    </span><br><span class="line">    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);</span><br><span class="line">    </span><br><span class="line">    //返回数据</span><br><span class="line">    return [NSData dataWithBytesNoCopy:yuv_frame length:y_size + uv_size];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="将GPUImage获取到的BGRA格式的图片转成yuv-NV12-格式"><a href="#将GPUImage获取到的BGRA格式的图片转成yuv-NV12-格式" class="headerlink" title="将GPUImage获取到的BGRA格式的图片转成yuv(NV12)格式"></a>将GPUImage获取到的BGRA格式的图片转成yuv(NV12)格式</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">//AWGPUImageAVCapture.m文件</span><br><span class="line">-(void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex&#123;</span><br><span class="line">    [super newFrameReadyAtTime:frameTime atIndex:textureIndex];</span><br><span class="line">    if(!self.capture || !self.capture.isCapturing)&#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    //将bgra转为yuv</span><br><span class="line">    //图像宽度</span><br><span class="line">    int width = imageSize.width;</span><br><span class="line">    //图像高度</span><br><span class="line">    int height = imageSize.height;</span><br><span class="line">    //宽*高</span><br><span class="line">    int w_x_h = width * height;</span><br><span class="line">    //yuv数据长度 = (宽 * 高) * 3 / 2</span><br><span class="line">    int yuv_len = w_x_h * 3 / 2;</span><br><span class="line">    </span><br><span class="line">    //yuv数据</span><br><span class="line">    uint8_t *yuv_bytes = malloc(yuv_len);</span><br><span class="line">    </span><br><span class="line">    //ARGBToNV12这个函数是libyuv这个第三方库提供的一个将bgra图片转为yuv420格式的一个函数。</span><br><span class="line">    //libyuv是google提供的高性能的图片转码操作。支持大量关于图片的各种高效操作，是视频推流不可缺少的重要组件，你值得拥有。</span><br><span class="line">    [self lockFramebufferForReading];</span><br><span class="line">    ARGBToNV12(self.rawBytesForImage, width * 4, yuv_bytes, width, yuv_bytes + w_x_h, width, width, height);</span><br><span class="line">    [self unlockFramebufferAfterReading];</span><br><span class="line">    </span><br><span class="line">    NSData *yuvData = [NSData dataWithBytesNoCopy:yuv_bytes length:yuv_len];</span><br><span class="line">    </span><br><span class="line">    [self.capture sendVideoYuvData:yuvData];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="从CMSampleBufferRef中提取PCM数据"><a href="#从CMSampleBufferRef中提取PCM数据" class="headerlink" title="从CMSampleBufferRef中提取PCM数据"></a>从CMSampleBufferRef中提取PCM数据</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">// AWAudioEncoder.m 文件</span><br><span class="line">-(NSData *) convertAudioSmapleBufferToPcmData:(CMSampleBufferRef) audioSample&#123;</span><br><span class="line">    //获取pcm数据大小</span><br><span class="line">    NSInteger audioDataSize = CMSampleBufferGetTotalSampleSize(audioSample);</span><br><span class="line">    </span><br><span class="line">    //分配空间</span><br><span class="line">    int8_t *audio_data = aw_alloc((int32_t)audioDataSize);</span><br><span class="line">    </span><br><span class="line">    //获取CMBlockBufferRef</span><br><span class="line">    //这个结构里面就保存了 PCM数据</span><br><span class="line">    CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(audioSample);</span><br><span class="line">    //直接将数据copy至我们自己分配的内存中</span><br><span class="line">    CMBlockBufferCopyDataBytes(dataBuffer, 0, audioDataSize, audio_data);</span><br><span class="line">    </span><br><span class="line">    //返回数据</span><br><span class="line">    return [NSData dataWithBytesNoCopy:audio_data length:audioDataSize];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此我们已经将捕获的视频数据转为了yuv420格式，将音频数据转为了pcm格式。</p><p>接下来就可以对这些数据进行各种编码了。编码完成后，就可以将数据发送给服务器了。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</title>
    <link href="https://hardman.github.io/2016/11/16/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E5%9B%9B%EF%BC%89%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8GPUImage%EF%BC%8C%E5%A6%82%E4%BD%95%E7%BE%8E%E9%A2%9C/"/>
    <id>https://hardman.github.io/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/</id>
    <published>2016-11-15T16:03:54.000Z</published>
    <updated>2018-07-19T07:09:28.845Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p><a href="http://blog.csdn.net/hard_man/article/details/53157423" target="_blank" rel="noopener">上一篇文章</a>介绍了如何使用系统方法捕获视频数据，但是更多的时候，为了使用美颜滤镜，我们会选择GPUImage来获取视频数据。</p><p><a href="https://github.com/BradLarson/GPUImage" target="_blank" rel="noopener">GPUImage</a>是一个可以为录制视频添加实时滤镜的一个著名第三方库。</p><p>该框架大概原理是，使用OpenGL着色器对视频图像进行颜色处理，然后存到frameBuffer，之后可以对此数据再次处理。重复上述过程，即可达到多重滤镜效果。</p><p>具体实现不细说，这里简要介绍一下GPUImage的使用，如何美颜，如何获取音视频数据。</p><h2 id="使用GPUImage"><a href="#使用GPUImage" class="headerlink" title="使用GPUImage"></a>使用GPUImage</h2><p>GPUImage的主要代码在 AWGPUImageAVCapture 这个类中。</p><p>初始化AWAVCaptureManager对象时将captureType设为AWAVCaptureTypeGPUImage，就会自动调用AWGPUImageAVCapture类来捕获视频数据。</p><p>代码在 onInit 方法中：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">-(void)onInit&#123;</span><br><span class="line">    //摄像头初始化</span><br><span class="line">    // AWGPUImageVideoCamera 继承自 GPUImageVideoCamera。继承是为了获取音频数据，原代码中，默认情况下音频数据发送给了 audioEncodingTarget。</span><br><span class="line">    // 这个东西一看类型是GPUImageMovieWriter，应该是文件写入功能。果断覆盖掉processAudioSampleBuffer方法，拿到音频数据后自己处理。</span><br><span class="line">    // 音频就这样可以了，GPUImage主要工作还是在视频处理这里。</span><br><span class="line">    // 设置预览分辨率 self.captureSessionPreset是根据AWVideoConfig的设置，获取的分辨率。设置前置、后置摄像头。</span><br><span class="line">    _videoCamera = [[AWGPUImageVideoCamera alloc] initWithSessionPreset:self.captureSessionPreset cameraPosition:AVCaptureDevicePositionFront];</span><br><span class="line"></span><br><span class="line">    //开启捕获声音</span><br><span class="line">    [_videoCamera addAudioInputsAndOutputs];</span><br><span class="line"></span><br><span class="line">    //设置输出图像方向，可用于横屏推流。</span><br><span class="line">    _videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;</span><br><span class="line"></span><br><span class="line">    //镜像策略，这里这样设置是最自然的。跟系统相机默认一样。</span><br><span class="line">    _videoCamera.horizontallyMirrorRearFacingCamera = NO;</span><br><span class="line">    _videoCamera.horizontallyMirrorFrontFacingCamera = YES;</span><br><span class="line">    </span><br><span class="line">    //设置预览view</span><br><span class="line">    _gpuImageView = [[GPUImageView alloc] initWithFrame:self.preview.bounds];</span><br><span class="line">    [self.preview addSubview:_gpuImageView];</span><br><span class="line">    </span><br><span class="line">    //初始化美颜滤镜</span><br><span class="line">    _beautifyFilter = [[GPUImageBeautifyFilter alloc] init];</span><br><span class="line"></span><br><span class="line">    //相机获取视频数据输出至美颜滤镜</span><br><span class="line">    [_videoCamera addTarget:_beautifyFilter];</span><br><span class="line">    </span><br><span class="line">    //美颜后输出至预览</span><br><span class="line">    [_beautifyFilter addTarget:_gpuImageView];</span><br><span class="line">    </span><br><span class="line">    // 到这里我们已经能够打开相机并预览了。</span><br><span class="line">    // 因为要推流，除了预览之外，我们还要截取到视频数据。这就需要使用GPUImage中的GPUImageRawDataOutput，它能将美颜后的数据输出，便于我们处理后发送出去。</span><br><span class="line">    // AWGPUImageAVCaptureDataHandler继承自GPUImageRawDataOutput，从 newFrameReadyAtTime 方法中就可以获取到美颜后输出的数据。</span><br><span class="line">    // 输出的图片格式为BGRA。</span><br><span class="line">    _dataHandler = [[AWGPUImageAVCaptureDataHandler alloc] initWithImageSize:CGSizeMake(self.videoConfig.width, self.videoConfig.height) resultsInBGRAFormat:YES capture:self];</span><br><span class="line">    [_beautifyFilter addTarget:_dataHandler];</span><br><span class="line"></span><br><span class="line">    // 令AWGPUImageAVCaptureDataHandler实现AWGPUImageVideoCameraDelegate协议，并且让camera的awAudioDelegate指向_dataHandler对象。</span><br><span class="line">    // 将音频数据转到_dataHandler中处理。然后音视频数据就可以都在_dataHandler中处理了。</span><br><span class="line">    _videoCamera.awAudioDelegate = _dataHandler;</span><br><span class="line">    </span><br><span class="line">    //开始捕获视频</span><br><span class="line">    [self.videoCamera startCameraCapture];</span><br><span class="line">    </span><br><span class="line">    //修改帧率</span><br><span class="line">    [self updateFps:self.videoConfig.fps];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>美颜滤镜使用的是：<a href="https://github.com/Guikunzhi/BeautifyFaceDemo" target="_blank" rel="noopener">https://github.com/Guikunzhi/BeautifyFaceDemo</a><br>感谢Guikunzhi的分享。想了解美颜详细算法的同学，可以自行学习。</p><h2 id="AWGPUImageAVCaptureDataHandler中音视频处理方法："><a href="#AWGPUImageAVCaptureDataHandler中音视频处理方法：" class="headerlink" title="AWGPUImageAVCaptureDataHandler中音视频处理方法："></a>AWGPUImageAVCaptureDataHandler中音视频处理方法：</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">// 获取到音频数据，通过sendAudioSampleBuffer发送出去</span><br><span class="line">-(void)processAudioSample:(CMSampleBufferRef)sampleBuffer&#123;</span><br><span class="line">    if(!self.capture || !self.capture.isCapturing)&#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    [self.capture sendAudioSampleBuffer:sampleBuffer];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 获取到视频数据，转换格式后，使用sendVideoYuvData 发送出去。</span><br><span class="line">-(void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex&#123;</span><br><span class="line">    [super newFrameReadyAtTime:frameTime atIndex:textureIndex];</span><br><span class="line">    if(!self.capture || !self.capture.isCapturing)&#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // GPUImage获取到的数据是BGRA格式。</span><br><span class="line">    // 而各种编码器最适合编码的格式还是yuv。</span><br><span class="line">    // 所以在此将BGRA格式的视频数据转成yuv格式。(后面会介绍yuv和pcm格式)</span><br><span class="line">    // 将bgra转为yuv</span><br><span class="line">    int width = imageSize.width;</span><br><span class="line">    int height = imageSize.height;</span><br><span class="line">    int w_x_h = width * height;</span><br><span class="line">    // 1帧yuv数据长度为 宽x高 * 3 / 2</span><br><span class="line">    int yuv_len = w_x_h * 3 / 2;</span><br><span class="line">    </span><br><span class="line">    uint8_t *yuv_bytes = malloc(yuv_len);</span><br><span class="line">    </span><br><span class="line">    //使用libyuv库，做格式转换。libyuv中的格式都是大端(高位存高位，低位存低位)，而iOS设备是小端(高位存低位，低位存高位)，小端为BGRA，则大端为ARGB，所以这里使用ARGBToNV12。</span><br><span class="line">    //self.rawBytesForImage就是美颜后的图片数据，格式是BGRA。</span><br><span class="line">    //关于大端小端，请自行baidu。</span><br><span class="line">    [self lockFramebufferForReading];</span><br><span class="line">    ARGBToNV12(self.rawBytesForImage, width * 4, yuv_bytes, width, yuv_bytes + w_x_h, width, width, height);</span><br><span class="line">    [self unlockFramebufferAfterReading];</span><br><span class="line">    </span><br><span class="line">    NSData *yuvData = [NSData dataWithBytesNoCopy:yuv_bytes length:yuv_len];</span><br><span class="line">    </span><br><span class="line">    //将获取到的yuv420数据发送出去</span><br><span class="line">    [self.capture sendVideoYuvData:yuvData];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，已经成功使用GPUImage获取视频，美颜，格式转换，准备发送数据。还是很简单的。</p><p>我们现在能够使用2种方法来获取音频数据，接下来会介绍音视频编码相关内容。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据</title>
    <link href="https://hardman.github.io/2016/11/14/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E4%B8%89%EF%BC%89%E4%BD%BF%E7%94%A8%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3%E6%8D%95%E8%8E%B7%E9%9F%B3%E8%A7%86%E9%A2%91%E6%95%B0%E6%8D%AE/"/>
    <id>https://hardman.github.io/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/</id>
    <published>2016-11-14T03:34:25.000Z</published>
    <updated>2018-07-19T07:08:42.795Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><p>通过系统相机录制视频获取音视频数据，是推流的第一步。<br>源码中提供2种获取音视频数据的方法：一是使用系统自带接口；二是使用GPUImage。</p><p>本篇首先介绍第一种。</p><p>网络上关于获取视频数据的代码有不少，但是为了方便代码阅读，这里简要介绍一下。</p><p><strong><em><font color="red">[注意]请仔细阅读代码注释</font></em></strong></p><h2 id="相关代码入口"><a href="#相关代码入口" class="headerlink" title="相关代码入口"></a>相关代码入口</h2><p>整套推流代码的入口：AWAVCaptureManager，它是根据参数创建上述2种获取数据方法的一个工厂类。</p><p>可以通过设置 captureType 来决定使用哪种数据获取方式。</p><p>AWAVCaptureManager部分代码如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">typedef enum : NSUInteger &#123;</span><br><span class="line">    AWAVCaptureTypeNone,</span><br><span class="line">    AWAVCaptureTypeSystem,</span><br><span class="line">    AWAVCaptureTypeGPUImage,</span><br><span class="line">&#125; AWAVCaptureType;</span><br><span class="line"></span><br><span class="line">@interface AWAVCaptureManager : NSObject</span><br><span class="line">//视频捕获类型</span><br><span class="line">@property (nonatomic, unsafe_unretained) AWAVCaptureType captureType;</span><br><span class="line">@property (nonatomic, weak) AWAVCapture *avCapture;</span><br><span class="line"></span><br><span class="line">//省略其他代码</span><br><span class="line">......</span><br><span class="line">@end</span><br></pre></td></tr></table></figure></p><p>设置了captureType之后，直接可以通过avCapture获取到正确的捕获视频数据的对象了。</p><p>AWAVCapture 是一个虚基类（c++中的说法，不会直接产生对象，只用来继承的类，java中叫做抽象类）。<br>它的两个子类分别是 AWSystemAVCapture 和 AWGPUImageAVCapture。</p><p>这里使用了多态。</p><p>如果 captureType设置的是 AWAVCaptureTypeSystem，avCapture获取到的真实对象就是 AWSystemAVCapture类型；<br>如果 captureType设置的是 AWAVCaptureTypeGPUImage，avCapture获取到的真实对象就是 AWGPUImageAVCapture类型。</p><p>AWSystemAVCapture类的功能只有一个：调用系统相机，获取音视频数据。</p><h2 id="相机数据获取的方法"><a href="#相机数据获取的方法" class="headerlink" title="相机数据获取的方法"></a>相机数据获取的方法</h2><p>分为3步骤：</p><ol><li>初始化输入输出设备。</li><li>创建AVCaptureSession，用来管理视频与数据的捕获。</li><li>创建预览UI。<br>还包括一些其他功能：</li><li>切换摄像头</li><li>更改fps</li></ol><p>在代码中对应的是 AWSystemAVCapture中的 onInit方法。只要初始化就会调用。</p><p>【注意】<strong><em>请仔细阅读下文代码中的注释</em></strong></p><h2 id="初始化输入设备"><a href="#初始化输入设备" class="headerlink" title="初始化输入设备"></a>初始化输入设备</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">-(void) createCaptureDevice&#123;</span><br><span class="line">    // 初始化前后摄像头</span><br><span class="line">    // 执行这几句代码后，系统会弹框提示：应用想要访问您的相机。请点击同意</span><br><span class="line">    // 另外iOS10 需要在info.plist中添加字段NSCameraUsageDescription。否则会闪退，具体请自行baidu。</span><br><span class="line">    NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];</span><br><span class="line">    self.frontCamera = [AVCaptureDeviceInput deviceInputWithDevice:videoDevices.firstObject error:nil];</span><br><span class="line">    self.backCamera =[AVCaptureDeviceInput deviceInputWithDevice:videoDevices.lastObject error:nil];</span><br><span class="line">    </span><br><span class="line">    // 初始化麦克风</span><br><span class="line">    // 执行这几句代码后，系统会弹框提示：应用想要访问您的麦克风。请点击同意</span><br><span class="line">    // 另外iOS10 需要在info.plist中添加字段NSMicrophoneUsageDescription。否则会闪退，具体请自行baidu。</span><br><span class="line">    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];</span><br><span class="line">    self.audioInputDevice = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];</span><br><span class="line">    </span><br><span class="line">    //省略其他代码</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="初始化输出设备"><a href="#初始化输出设备" class="headerlink" title="初始化输出设备"></a>初始化输出设备</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">-(void) createOutput&#123;   </span><br><span class="line">//创建数据获取线程</span><br><span class="line">    dispatch_queue_t captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);</span><br><span class="line">    </span><br><span class="line">    //视频数据输出</span><br><span class="line">    self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];</span><br><span class="line">    //设置代理，需要当前类实现protocol：AVCaptureVideoDataOutputSampleBufferDelegate</span><br><span class="line">    [self.videoDataOutput setSampleBufferDelegate:self queue:captureQueue];</span><br><span class="line">    //抛弃过期帧，保证实时性</span><br><span class="line">    [self.videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];</span><br><span class="line">    //设置输出格式为 yuv420</span><br><span class="line">    [self.videoDataOutput setVideoSettings:@&#123;</span><br><span class="line">                                             (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)</span><br><span class="line">                                             &#125;];</span><br><span class="line"></span><br><span class="line">    //音频数据输出</span><br><span class="line">    self.audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];</span><br><span class="line">    //设置代理，需要当前类实现protocol：AVCaptureAudioDataOutputSampleBufferDelegate</span><br><span class="line">    [self.audioDataOutput setSampleBufferDelegate:self queue:captureQueue];</span><br><span class="line"></span><br><span class="line">    // AVCaptureVideoDataOutputSampleBufferDelegate 和 AVCaptureAudioDataOutputSampleBufferDelegate 回调方法名相同都是：</span><br><span class="line">    // captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection</span><br><span class="line">    // 最终视频和音频数据都可以在此方法中获取。</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="创建-captureSession"><a href="#创建-captureSession" class="headerlink" title="创建 captureSession"></a>创建 captureSession</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">// AVCaptureSession 创建逻辑很简单，它像是一个中介者，从音视频输入设备获取数据，处理后，传递给输出设备(数据代理/预览layer)。</span><br><span class="line">-(void) createCaptureSession&#123;</span><br><span class="line">//初始化</span><br><span class="line">    self.captureSession = [AVCaptureSession new];</span><br><span class="line">    </span><br><span class="line">    //修改配置</span><br><span class="line">    [self.captureSession beginConfiguration];</span><br><span class="line">    </span><br><span class="line">    //加入视频输入设备</span><br><span class="line">    if ([self.captureSession canAddInput:self.videoInputDevice]) &#123;</span><br><span class="line">        [self.captureSession addInput:self.videoInputDevice];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //加入音频输入设备</span><br><span class="line">    if ([self.captureSession canAddInput:self.audioInputDevice]) &#123;</span><br><span class="line">        [self.captureSession addInput:self.audioInputDevice];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //加入视频输出</span><br><span class="line">    if([self.captureSession canAddOutput:self.videoDataOutput])&#123;</span><br><span class="line">        [self.captureSession addOutput:self.videoDataOutput];</span><br><span class="line">        [self setVideoOutConfig];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //加入音频输出</span><br><span class="line">    if([self.captureSession canAddOutput:self.audioDataOutput])&#123;</span><br><span class="line">        [self.captureSession addOutput:self.audioDataOutput];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //设置预览分辨率</span><br><span class="line">    //这个分辨率有一个值得注意的点：</span><br><span class="line">    //iphone4录制视频时 前置摄像头只能支持 480*640 后置摄像头不支持 540*960 但是支持 720*1280</span><br><span class="line">    //诸如此类的限制，所以需要写一些对分辨率进行管理的代码。</span><br><span class="line">    //目前的处理是，对于不支持的分辨率会抛出一个异常</span><br><span class="line">    //但是这样做是不够、不完整的，最好的方案是，根据设备，提供不同的分辨率。</span><br><span class="line">    //如果必须要用一个不支持的分辨率，那么需要根据需求对数据和预览进行裁剪，缩放。</span><br><span class="line">    if (![self.captureSession canSetSessionPreset:self.captureSessionPreset]) &#123;</span><br><span class="line">        @throw [NSException exceptionWithName:@&quot;Not supported captureSessionPreset&quot; reason:[NSString stringWithFormat:@&quot;captureSessionPreset is [%@]&quot;, self.captureSessionPreset] userInfo:nil];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    self.captureSession.sessionPreset = self.captureSessionPreset;</span><br><span class="line">    </span><br><span class="line">    //提交配置变更</span><br><span class="line">    [self.captureSession commitConfiguration];</span><br><span class="line">    </span><br><span class="line">    //开始运行，此时，CaptureSession将从输入设备获取数据，处理后，传递给输出设备。</span><br><span class="line">    [self.captureSession startRunning];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="创建预览UI"><a href="#创建预览UI" class="headerlink" title="创建预览UI"></a>创建预览UI</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// 其实只有一句代码：CALayer layer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];</span><br><span class="line">// 它其实是 AVCaptureSession的一个输出方式而已。</span><br><span class="line">// CaptureSession会将从input设备得到的数据，处理后，显示到此layer上。</span><br><span class="line">// 我们可以将此layer变换后加入到任意UIView中。</span><br><span class="line">-(void) createPreviewLayer&#123;</span><br><span class="line">    self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];</span><br><span class="line">    self.previewLayer.frame = self.preview.bounds;</span><br><span class="line">    [self.preview.layer addSublayer:self.previewLayer];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="切换摄像头"><a href="#切换摄像头" class="headerlink" title="切换摄像头"></a>切换摄像头</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">-(void)setVideoInputDevice:(AVCaptureDeviceInput *)videoInputDevice&#123;</span><br><span class="line">    if ([videoInputDevice isEqual:_videoInputDevice]) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    //captureSession 修改配置</span><br><span class="line">    [self.captureSession beginConfiguration];</span><br><span class="line">    //移除当前输入设备</span><br><span class="line">    if (_videoInputDevice) &#123;</span><br><span class="line">        [self.captureSession removeInput:_videoInputDevice];</span><br><span class="line">    &#125;</span><br><span class="line">    //增加新的输入设备</span><br><span class="line">    if (videoInputDevice) &#123;</span><br><span class="line">        [self.captureSession addInput:videoInputDevice];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    //提交配置，至此前后摄像头切换完毕</span><br><span class="line">    [self.captureSession commitConfiguration];</span><br><span class="line">    </span><br><span class="line">    _videoInputDevice = videoInputDevice;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="设置fps"><a href="#设置fps" class="headerlink" title="设置fps"></a>设置fps</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">-(void) updateFps:(NSInteger) fps&#123;</span><br><span class="line">//获取当前capture设备</span><br><span class="line">    NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];</span><br><span class="line">    </span><br><span class="line">    //遍历所有设备（前后摄像头）</span><br><span class="line">    for (AVCaptureDevice *vDevice in videoDevices) &#123;</span><br><span class="line">    //获取当前支持的最大fps</span><br><span class="line">        float maxRate = [(AVFrameRateRange *)[vDevice.activeFormat.videoSupportedFrameRateRanges objectAtIndex:0] maxFrameRate];</span><br><span class="line">        //如果想要设置的fps小于或等于做大fps，就进行修改</span><br><span class="line">        if (maxRate &gt;= fps) &#123;</span><br><span class="line">        //实际修改fps的代码</span><br><span class="line">            if ([vDevice lockForConfiguration:NULL]) &#123;</span><br><span class="line">                vDevice.activeVideoMinFrameDuration = CMTimeMake(10, (int)(fps * 10));</span><br><span class="line">                vDevice.activeVideoMaxFrameDuration = vDevice.activeVideoMinFrameDuration;</span><br><span class="line">                [vDevice unlockForConfiguration];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="获取音视频数据"><a href="#获取音视频数据" class="headerlink" title="获取音视频数据"></a>获取音视频数据</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection&#123;</span><br><span class="line">    if (self.isCapturing) &#123;</span><br><span class="line">        if ([self.videoDataOutput isEqual:captureOutput]) &#123;</span><br><span class="line">        //捕获到视频数据，通过sendVideoSampleBuffer发送出去，后续文章会解释接下来的详细流程。</span><br><span class="line">            [self sendVideoSampleBuffer:sampleBuffer];</span><br><span class="line">        &#125;else if([self.audioDataOutput isEqual:captureOutput])&#123;</span><br><span class="line">        //捕获到音频数据，通过sendVideoSampleBuffer发送出去</span><br><span class="line">            [self sendAudioSampleBuffer:sampleBuffer];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，我们达到了所有目标：能够录制视频，预览，获取音视频数据，切换前后摄像头，修改捕获视频的fps。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（二）代码架构概述</title>
    <link href="https://hardman.github.io/2016/11/10/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E4%BA%8C%EF%BC%89%E4%BB%A3%E7%A0%81%E6%9E%B6%E6%9E%84%E6%A6%82%E8%BF%B0/"/>
    <id>https://hardman.github.io/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/</id>
    <published>2016-11-10T15:33:21.000Z</published>
    <updated>2018-07-19T07:08:54.336Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><h2 id="推流流程"><a href="#推流流程" class="headerlink" title="推流流程"></a>推流流程</h2><p>使用rtmp协议（其他协议也类似）推流的大体流程如下：</p><ol><li>通过系统相机捕获视频及声音，该美颜的美颜，该滤镜的滤镜。</li><li>捕获的视频帧为yuv格式，音频帧为pcm格式。</li><li>将捕获的音视频数据，传入一个串行队列(编码队列)，在队列中进行编码操作。</li><li>将yuv格式视频数据，转成h264格式视频帧；将pcm格式音频数据，转成aac格式音频帧。</li><li>将转好的h264及aac格式数据，转成flv视频帧。放入编码缓冲区，待发送。继续获取视频帧并编码。</li><li>建立rtmp连接到服务器，成功后，创建另一个串行队列（发送队列）。</li><li>rtmp协议，需要在首帧发送 sps/pps和AudioSpecificConfig这2种特别的帧数据。</li><li>发送了首帧之后，发送队列不停从编码队列中获取flv视频帧，发送至rtmp服务端。</li><li>结束直播，关闭推流，释放资源。</li></ol><p>我的代码严格按照上述流程编写。这些逻辑也适用于市面上出现的几乎所有的推流代码。</p><p>我把上述流程及源代码画了2个图。里面有详细的流程及使用的技术。</p><h2 id="推流流程图"><a href="#推流流程图" class="headerlink" title="推流流程图"></a>推流流程图</h2><p><img src="https://raw.githubusercontent.com/hardman/OutLinkImages/master/AWLive/AWLive_flow.png" alt=""></p><h2 id="代码结构类图"><a href="#代码结构类图" class="headerlink" title="代码结构类图"></a>代码结构类图</h2><p><img src="https://raw.githubusercontent.com/hardman/OutLinkImages/master/AWLive/AWLive_UML.png" alt=""></p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>1小时学会：最简单的iOS直播推流（一）介绍</title>
    <link href="https://hardman.github.io/2016/11/07/1%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9A%EF%BC%9A%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84iOS%E7%9B%B4%E6%92%AD%E6%8E%A8%E6%B5%81%EF%BC%88%E4%B8%80%EF%BC%89%E4%BB%8B%E7%BB%8D/"/>
    <id>https://hardman.github.io/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/</id>
    <published>2016-11-06T16:13:19.000Z</published>
    <updated>2018-07-19T07:08:24.471Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！</p></blockquote><blockquote><p>源代码：<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">https://github.com/hardman/AWLive</a></p></blockquote><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>最近在做iOS直播，研究了相关直播技术，主要包含两方面：推流，播放。</p><p>因为之前使用cocos2dx做过一个视频游戏（<a href="https://itunes.apple.com/cn/app/lian-ai-gong-yu-shi-pin-yang/id1149280715?mt=8" target="_blank" rel="noopener">恋爱公寓</a>），用ffmpeg+sdl+cocos2dx实现过视频播放器。</p><p>游戏中的视频是hevc(h265)+aac合成mp4文件，使用aes加密。视频播放的时候，需要使用ffmpeg中的crypt模块进行aes解密后播放视频，解析出来的yuv图片数据直接送给OpenGL显示。</p><p>所以这次主要研究推流技术。并将<a href="https://github.com/hardman/AWLive" target="_blank" rel="noopener">代码开源</a>。</p><p>其实直播技术中不论播放还是推流，更多的应该算是技术整合，就是将前人做好的协议和实现，整合成我们自己想要的功能。</p><p>而这次做这个项目也并不是做了什么技术创新，github里面已经有着很多直播源代码，可能比我写的更好更完整。而我的代码，特点就是简单直接，直奔主题。</p><p>我会在<a href="http://blog.csdn.net/hard_man" target="_blank" rel="noopener">我的博客</a>里做一些简单的解析，目的是希望让更多的人了解直播技术，能够了解直播内部的一些简单的原理，不再知其然不知其所以然。</p><h2 id="功能范围"><a href="#功能范围" class="headerlink" title="功能范围"></a>功能范围</h2><ul><li>视频捕获：系统方法捕获，GPUImage捕获，CMSampleRef解析</li><li>美颜滤镜：GPUImage，</li><li>视频变换：libyuv</li><li>软编码：faac，x264</li><li>硬编码：VideoToolbox(aac/h264)</li><li>libaw：C语言函数库</li><li>flv协议及编码</li><li>推流协议：librtmp，rtmp重连，rtmp各种状态回调</li></ul><h2 id="代码使用及注意"><a href="#代码使用及注意" class="headerlink" title="代码使用及注意"></a>代码使用及注意</h2><p>代码使用方法见Demo。后续会根据上述功能的每一点对源代码进行解析。</p><p>如果有什么疑问或者问题，请评论指出。希望能够给愿意了解直播技术的人抛出一块好砖。</p><p>注1：项目中所有相关的文件名，类名，全局变量，全局方法都会加AW/aw作为前缀。</p><p>注2：项目中关键代码都使用c语言编写，理论上可以很容易地移植到android中。</p><h2 id="文章列表"><a href="#文章列表" class="headerlink" title="文章列表"></a>文章列表</h2><ol><li><a href="/2016/11/07/1小时学会：最简单的iOS直播推流（一）介绍/">1小时学会：最简单的iOS直播推流（一）项目介绍</a></li><li><a href="/2016/11/10/1小时学会：最简单的iOS直播推流（二）代码架构概述/">1小时学会：最简单的iOS直播推流（二）代码架构概述</a></li><li><a href="/2016/11/14/1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频数据/">1小时学会：最简单的iOS直播推流（三）使用系统接口捕获音视频</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜/">1小时学会：最简单的iOS直播推流（四）如何使用GPUImage，如何美颜</a></li><li><a href="/2016/11/16/1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取/">1小时学会：最简单的iOS直播推流（五）yuv、pcm数据的介绍和获取</a></li><li><a href="/2016/11/24/1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍/">1小时学会：最简单的iOS直播推流（六）h264、aac、flv介绍</a></li><li><a href="/2016/12/07/1小时学会：最简单的iOS直播推流（七）h264-aac-硬编码/">1小时学会：最简单的iOS直播推流（七）h264/aac 硬编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（八）h264-aac-软编码/">1小时学会：最简单的iOS直播推流（八）h264/aac 软编码</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（九）flv-编码与音视频时间戳同步/">1小时学会：最简单的iOS直播推流（九）flv 编码与音视频时间戳同步</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十）librtmp使用介绍/">1小时学会：最简单的iOS直播推流（十）librtmp使用介绍</a></li><li><a href="/2017/11/23/1小时学会：最简单的iOS直播推流（十一）sps-amp-pps和AudioSpecificConfig介绍（完结）/">1小时学会：最简单的iOS直播推流（十一）sps&amp;pps和AudioSpecificConfig介绍（完结）</a></li><li><a href="/2017/01/25/1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里/">1小时学会：最简单的iOS直播推流（番外）运行不起AWLive的demo的同学请看这里</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最简单的iOS 推流代码，视频捕获，软编码(faac，x264)，硬编码（aac，h264），美颜，flv编码，rtmp协议，陆续更新代码解析，你想学的知识这里都有，愿意懂直播技术的同学快来看！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockq
      
    
    </summary>
    
      <category term="iOS推流" scheme="https://hardman.github.io/categories/iOS%E6%8E%A8%E6%B5%81/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="iOS推流" scheme="https://hardman.github.io/tags/iOS%E6%8E%A8%E6%B5%81/"/>
    
      <category term="源码" scheme="https://hardman.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
      <category term="教程" scheme="https://hardman.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    
      <category term="github" scheme="https://hardman.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>Adobe源码泄漏？3行代码搞定，Flash动画无缝导入Android/iOS/cocos2dx（二）</title>
    <link href="https://hardman.github.io/2016/04/22/Adobe%E6%BA%90%E7%A0%81%E6%B3%84%E6%BC%8F%EF%BC%9F3%E8%A1%8C%E4%BB%A3%E7%A0%81%E6%90%9E%E5%AE%9A%EF%BC%8CFlash%E5%8A%A8%E7%94%BB%E6%97%A0%E7%BC%9D%E5%AF%BC%E5%85%A5Android-iOS-cocos2dx%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
    <id>https://hardman.github.io/2016/04/22/Adobe源码泄漏？3行代码搞定，Flash动画无缝导入Android-iOS-cocos2dx（二）/</id>
    <published>2016-04-22T11:40:15.000Z</published>
    <updated>2018-07-19T06:24:37.009Z</updated>
    
    <content type="html"><![CDATA[<p><strong>[注] iOS代码已重构，效率提升90%，200层动画不卡。[2016.10.27]</strong></p><p>上一篇 <a href="http://blog.csdn.net/hard_man/article/details/51222423" target="_blank" rel="noopener">点此阅读</a> 简要介绍了FlashToAnimation的功能，也就是将flash动画无缝导入到Android/iOS及cocos2dx中运行, 这一篇介绍这个库的使用方法。<a href="https://github.com/hardman/FlashAnimationToMobile" target="_blank" rel="noopener">点此查看源码。</a></p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>首先确保系统中安装了flash，并且flash版本应该在cs3或者以上。<br>然后把”源码根目录/tools/flashScript”目录内的所有文件和文件夹copy到如下目录：</p><ul><li>Mac：~/Library/Application Support/Adobe/[Flash CS+版本号]/[en_US或者zh_CN]/Configuration/Commands</li><li>Windows：C:\Users[用户名]\AppData\Local\Adobe[Flash CS+版本号][en_US或者zh_CN]\Configuration\Commands</li></ul><p>在文件管理器（或Finder）目录中看起来是这样的：</p><pre><code>--Commands    -- 1.根据png创建元件.jsfl    -- 2.修改fla中元素的名字.jsfl    -- 3.导出动画数据.jsfl    -- libs/        --json2.jsfl    -- ....其他文件</code></pre><p>如图：<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/1.png?raw=true" width="600"></p><p>这时候打开flash，点击菜单栏中的 Commands（中文的话应该是命令），在下拉菜单中就能看到我们加入的脚本啦。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/2.png?raw=true" width="600"></p><p>到此为止准备工作就绪。</p><h2 id="美术人员制作flash动画的步骤"><a href="#美术人员制作flash动画的步骤" class="headerlink" title="美术人员制作flash动画的步骤"></a>美术人员制作flash动画的步骤</h2><p>下面步骤看起来很长，其实内容很简单，都是大家各自平时使用的经验，在这里写这么多是为了让小白用户不出错而已。<br>美术人员使用步骤：</p><ul><li><p>新建一个as3.0的Flash Document。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/3.png?raw=true" width="600"></p></li><li><p>保存文档，请务必保存文档，否则脚本不生效，并按照如下规则命名：<br>fla的命名应该以 “.” 分为3部分：<br>   测试.test.fla<br>第一部分：中文，对本文件的中文描述。（不重要，可以随意取。）<br>第二部分：英文，表示本文件的英文标识符。（重要，在代码中会使用到这个关键字。）<br>第三部分：后缀，默认即可不用管。（使用.fla即可。）<br>其中第一部分中文可忽略。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/4.png?raw=true" width="600"></p></li><li><p>在新建的Flash文件窗口右侧的Library栏中，点击右键，新建一个文件夹名为“pics”(<strong>注意，名字不能错，后面有类似的要求也要遵守</strong>)。</p></li><li><p>把制作flash的图片（png格式）拖入pics文件夹中。[<strong>!!!注意，所有的png图片必须带后缀.png否则会出错！</strong>]<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/5.png?raw=true" width="600"></p></li><li><p>点击commands中的脚本“1.根据png创建元件”。结果如图：<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/6.png?raw=true" width="600"></p></li><li><p>如果是cocos2dx中使用，为了避免Sprite Frame Cache重名，或者想要为图片生成跟本动画相关的独一无二的前缀，可以点击commands中的脚本“2.修改fla中元素的名字”。结果如下：<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/7.png?raw=true" width="600"></p></li><li><p>iOS可能也有此问题。因为直接拖入xcode中的文件一般选择“create groups”，这个只是逻辑文件夹，如果其他文件夹内存在同名文件则会冲突。所以最好每次制作动画，添加png图片的时候，都执行一次脚本“2.修改fla中元素的名字“。</p></li><li><p>新建一个Movie clip（影片剪辑），取一个合适的名字。然后拖入anims文件夹中<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/8.png?raw=true" width="600"><br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/9.png?raw=true" width="600"></p></li><li><p>双击该Movie clip，进入编辑模式，此时就可以使用eles文件夹中的Movie clip，制作动画了。制作动画的具体细节要求，见下面的要求。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/10.png?raw=true" width="600"></p></li><li><p>制作完成后，保存，美术人员的工作就完成了。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/11.png?raw=true" width="600"></p></li></ul><h2 id="美术人员制作flash动画完整要求"><a href="#美术人员制作flash动画完整要求" class="headerlink" title="美术人员制作flash动画完整要求"></a>美术人员制作flash动画完整要求</h2><ol><li>下面涉及名字的地方可以使用 英文字母，数字和下划线，不要用中文。</li><li>先制作动画所需要的图片，png/jpg格式的，所有的动画元素需要全部使用图片，不可以使用矢量图和文字等等。</li><li>图片命名尽量简单，以减少程序处理的数据量。</li><li>建立fla时，使用Action Script 3。</li><li>在库中建立3个文件夹，名字为：pics（图片），anims（动画的动作，比如idle, move等），eles（图片对应的元件）。对应的资源请在不同的文件夹中建立。</li><li>每张图片（pics）都需要生成一个元件（eles），不要把多张图片放在一个元件中。所以元件的数量应该同图片的数量是相同的。</li><li>所有的元件请使用 “影片剪辑”(movie clip), 不要使用 “按钮” 和 “图片”。</li><li>把制作好的png图片（只用png，不要用jpg或其它格式图片）导入到flash中，并拖进pics文件夹下面。</li><li>依次生成png图片对应的元件（影片剪辑），把图片拖到元件中。使图片居中。元件名字应该同图片的名字完全相同。这一步可以使用脚本（“1.根据png创建元件“）代替这个操作。</li><li>建立新的元件，还是使用”影片剪辑”(movie clip)，然后拖进anims文件夹中。这就是需要制作的动作了。</li><li>这时候，就可以使用eles(不要使用pics中的图片)中的元件在时间轴中制作动作了。</li><li>制作动作，帧的普通操作(关键帧关键帧之间的传统补间，只能使用传统补间)都可以使用，但是对关键帧的处理只支持以下几种：移动，缩放，旋转，倾斜，颜色叠加，透明度的变化 这5种变换。</li><li>不要使用除13条中描述的其他任何对关键帧的操作，比如滤镜，显示混合等。</li><li>不要使用缓动，不要使用补间动画时元件旋转等高端操作。如果某一帧某个元件不可见，可以通过设置它的透明度为0，或者插入空白关键帧来实现。</li><li>不要使用嵌套动画：就是说关键帧上最好只用eles中的元素来做，不要做好了一段动画，把这段动画作为关键帧使用。。</li><li><font color="red">使用eles中的原件制作动画时，始终保持锚点的位置在原件的中央，否则会出现位置不对的问题。默认锚点是在中央的，不要手动去调整它。</font></li><li>最后，保存成fla就可以了。美术人员最终输出就是一个.fla文件。</li></ol><h2 id="程序人员使用美术制作好的动画"><a href="#程序人员使用美术制作好的动画" class="headerlink" title="程序人员使用美术制作好的动画"></a>程序人员使用美术制作好的动画</h2><p>程序拿到美术人员制作好的fla文件后，首先要进行一番检查，看看是否合格。<br>所以需要确保程序员熟悉flash的页面和菜单，并了解一些简单的flash软件操作。</p><ul><li>打开.fla文件。简单检查一下文件完成度。<ul><li>是否3个文件夹都在(anims，pics，eles)。</li><li>是否动画文件都在anims文件夹内。</li><li>是否pics与eles内文件数量相同，并且一一对应，相对应的2个组件名字也要完全一致。</li><li>是否pics和eles内的组件名字都有.png后缀。</li></ul></li><li><p>如果需要给关键帧添加事件，需要选中该关键帧（首先在timeline中选中关键帧，然后在主页面中选中该帧代表的图片，过程中最好隐藏timeline中的其它层），然后点击右侧与library同级的标签页properties。在第一行标有 &lt; Instance Name &gt; 的输入框，输入你的事件名，程序能够在播放到这一帧的时候，触发这个事件<font color="red">（在代码中，事件对应的字段为”mark”）</font>。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/12.png?raw=true" width="600"></p></li><li><p>事件添加完成后，选择菜单：Commands（命令）- “3.导出动画数据”。窗口底部同Timeline（时间轴）同级的Output（输出）栏中会显示脚本执行过程。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/13.png?raw=true" width="600"></p></li><li><p>成功后，打开.fla文件所在的目录，即可看到”.flajson文件”和.fla同名”图片文件夹”（里面是图片）。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/images/14.png?raw=true" width="600"></p></li><li><p>如果需要使用二进制动画描述文件，则需要把”.flajson文件”转为”.flabin文件”，这两个后缀也不能改。<br>转换需要使用脚本”源码根目录/tools/JsonToBin.py”文件。这是一个python脚本。如果系统内没有python，则需要安装一个。<br>然后打开命令行（mac中使用终端，Windows中可使用cmd）执行如下命令，执行后的.flabin就是转换成二进制后的文件。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python 源码根目录/tools/JsonToBin.py [.flajson文件全路径] [.flabin文件全路径]</span><br></pre></td></tr></table></figure></li><li><p>这时候可以把”.flajson文件”（或者 “.flabin文件”，二者使用其一即可，代码库内部处理，无需额外写代码判断）和”图片文件夹”放入程序指定目录就可以使用了。</p><ul><li>cocos2dx可以放在资源目录中任意位置。代码初始化时需要指定目录。</li><li>Android需要将这2个文件放入 Assets文件夹的子文件夹flashAnims中。</li><li>iOS拖入xcode中，选择“copy if need”和“create groups”，点击确定。</li></ul></li></ul><h2 id="程序员如何在代码中调用动画"><a href="#程序员如何在代码中调用动画" class="headerlink" title="程序员如何在代码中调用动画"></a>程序员如何在代码中调用动画</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//cocos2dx版本使用方法</span><br><span class="line">//包含头文件</span><br><span class="line">#include &quot;AnimNode.h&quot;</span><br><span class="line">using namespace windy;</span><br><span class="line"></span><br><span class="line">... ...</span><br><span class="line"></span><br><span class="line">//使用代码</span><br><span class="line">AnimNode *animNode = AnimNode::create();</span><br><span class="line">animNode-&gt;load(&quot;xxxx/flashFileName.flajson&quot;);</span><br><span class="line">animNode-&gt;play(&quot;animationName&quot;, WINDY_ANIMNODE_LOOP_FOREVER);//这里的animationName就是flash中anims文件夹内的动画名称</span><br><span class="line">superNode-&gt;addChild(animNode);</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&lt;!--Android版本使用方法--&gt;</span><br><span class="line">&lt;!--Android还需要在manifest文件中添加权限，与demo中相同添加即可。不要忘记res/values目录中的flashview_attr.xml文件。 --&gt;</span><br><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</span><br><span class="line">&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;</span><br><span class="line">    xmlns:tools=&quot;http://schemas.android.com/tools&quot;</span><br><span class="line">    xmlns:FlashView=&quot;http://schemas.android.com/apk/res-auto&quot; &lt;!--!!!!!!注意这个要加--&gt;</span><br><span class="line">    android:layout_width=&quot;match_parent&quot;</span><br><span class="line">    android:layout_height=&quot;match_parent&quot;</span><br><span class="line">    tools:context=&quot;com.xcyo.yoyo.flashsupport.MainActivity&quot;&gt;</span><br><span class="line"></span><br><span class="line">    &lt;com.flashanimation.view.FlashView</span><br><span class="line">        android:layout_width=&quot;match_parent&quot;</span><br><span class="line">        android:layout_height=&quot;match_parent&quot;</span><br><span class="line">        FlashView:flashDir=&quot;flashAnims&quot;</span><br><span class="line">        FlashView:flashFileName=&quot;callTextAnim&quot;</span><br><span class="line">        FlashView:defaultAnim=&quot;arriving1&quot; &lt;!--这里的defaultAnim就是flash中anims文件夹内的动画名称--&gt;</span><br><span class="line">        FlashView:designDPI=&quot;326&quot;</span><br><span class="line">        FlashView:loopTimes=&quot;0&quot;</span><br><span class="line">        android:id=&quot;@+id/flashview&quot;</span><br><span class="line">        /&gt;</span><br><span class="line"></span><br><span class="line">&lt;/RelativeLayout&gt;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">//iOS版本使用方法</span><br><span class="line">#import &quot;FlashView.h&quot;</span><br><span class="line"></span><br><span class="line">... ...</span><br><span class="line"></span><br><span class="line">FlashView *flashView = [[FlashView alloc] initWithFlashName:@&quot;flashFileName&quot;];</span><br><span class="line">flashView.frame = self.view.frame;// CGRectMake(100, 100, 200, 500);</span><br><span class="line">flashView.backgroundColor = [UIColor clearColor];</span><br><span class="line">[superView addSubview:flashView];</span><br><span class="line">[flashView play:@&quot;animationName&quot; loopTimes:FOREVER];//这里的animationName就是flash中anims文件夹内的动画名称</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;strong&gt;[注] iOS代码已重构，效率提升90%，200层动画不卡。[2016.10.27]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;上一篇 &lt;a href=&quot;http://blog.csdn.net/hard_man/article/details/51222423&quot; t
      
    
    </summary>
    
      <category term="iOS" scheme="https://hardman.github.io/categories/iOS/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="动画" scheme="https://hardman.github.io/tags/%E5%8A%A8%E7%94%BB/"/>
    
      <category term="flash" scheme="https://hardman.github.io/tags/flash/"/>
    
      <category term="android" scheme="https://hardman.github.io/tags/android/"/>
    
      <category term="cocos2dx" scheme="https://hardman.github.io/tags/cocos2dx/"/>
    
  </entry>
  
  <entry>
    <title>Adobe源码泄漏？3行代码搞定，Flash动画无缝导入Android/iOS/cocos2dx（一）</title>
    <link href="https://hardman.github.io/2016/04/22/Adobe%E6%BA%90%E7%A0%81%E6%B3%84%E6%BC%8F%EF%BC%9F3%E8%A1%8C%E4%BB%A3%E7%A0%81%E6%90%9E%E5%AE%9A%EF%BC%8CFlash%E5%8A%A8%E7%94%BB%E6%97%A0%E7%BC%9D%E5%AF%BC%E5%85%A5Android-iOS-cocos2dx%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>https://hardman.github.io/2016/04/22/Adobe源码泄漏？3行代码搞定，Flash动画无缝导入Android-iOS-cocos2dx（一）/</id>
    <published>2016-04-22T10:36:44.000Z</published>
    <updated>2018-07-19T06:24:05.986Z</updated>
    
    <content type="html"><![CDATA[<p><strong>[注] iOS代码已重构，效率提升90%，200层动画不卡。[2016.10.27]</strong></p><h2 id="项目介绍"><a href="#项目介绍" class="headerlink" title=" 项目介绍"></a> 项目介绍</h2><p>项目名称：<a href="https://github.com/hardman/FlashAnimationToMobile" target="_blank" rel="noopener">FlashAnimationToMobile 源码</a>。     <a href="http://blog.csdn.net/hard_man/article/details/51222696" target="_blank" rel="noopener">使用方法点这里</a>。</p><p>这是一个把flash中的关键帧动画(不是序列帧)导出，然后在iOS／Android原生应用中解析并播放的一个插件。除了原生App，它也能够支持Cocos2dx（3.x）。</p><p>对于Flash软件，则支持Flash CS3及以上版本及最新的Animate CC。</p><p>这个库能够满足游戏，App开发中90%的2D动画需求。<br>它可以用来做游戏中的人物动画：走动，攻击，跳跃，闪避等，以及UI特效，升级，转场等。<br>也可以用于App动画：秀场礼物，用户升级，活动礼包，等等。</p><p>这个库目前已在3个线上项目(2个游戏，一个App: android+iOS)中使用了。<br>它最大的特点就是：原生，关键帧动画。</p><p>实际效果如下：</p><ul><li><p>flash:<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/flash.gif?raw=true" width="600"></p></li><li><p>iOS:<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/iOS.gif?raw=true" width="300/"></p></li><li><p>android:<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/android.gif?raw=true" width="300/"></p></li><li><p>cocos2dx:<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/cocos2dx.gif?raw=true" width="600/"></p><p>项目来由</p></li></ul><hr><p>最开始有把flash关键帧动画导出的想法是当初做cocos2dx开发游戏的时候。<br>当时开发的一个游戏项目，模仿《刀塔传奇》的动画样式和战斗模式。<br>了解的朋友应该知道，《刀塔传奇》里面有很多英雄，每个英雄都有很多个动作。<br>一般情况下，这种复杂动画应该避免使用序列帧动画(对内存要求高)，而应该用更高效的关键帧动画。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/dt1.gif?raw=true" width="200/"><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/dt2.gif?raw=true" width="200/"><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/dt3.gif?raw=true" width="200/"></p><p>而当初立项的时候，项目组的美术人员对flash比较熟悉，希望用flash来做各个英雄的动画。更能节约时间。<br>而cocos2dx当时还不能直接导入flash动画。<br>于是，作为程序的我，就需要查阅各种资料，想解决方案。于是就有了这个项目。</p><p>我们的游戏当初45个英雄，每个英雄9个动作，全部使用flash制作，并用这个库来播放动画。同屏20个英雄，无卡顿完美运行。</p><p>看下我们游戏制作的flash原图：<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/自己的dt.gif?raw=true" width="400/"></p><p>过了一年，我的主要工作从游戏转移到app。现在是在做秀场项目。<br>后来大家觉得送礼物的特效不够炫，希望手机端送礼物时也能够有网页版的那种效果。<br><img src="https://github.com/hardman/OutLinkImages/blob/master/FlashAnimationToMobile/gifs/礼物特效.gif?raw=true" width="600/"></p><p>于是我就产生了把这个cocos2dx的动画库，移植到iOS和android中的想法。就是今天介绍的这个项目了。</p><p>用这个动画库来播放美术人员做出来的flash特效作为秀场礼物动画。可令礼物丰富多彩，不再单调。</p><h2 id="代码实现及功能范围"><a href="#代码实现及功能范围" class="headerlink" title="代码实现及功能范围"></a>代码实现及功能范围</h2><p>代码分为两部分：</p><pre><code>1. flash／python脚本2. 各平台（iOS/Android/cocos2dx）解析库。</code></pre><ul><li>其中flash脚本的部分参考的是这几篇文章：<ul><li><a href="http://wenku.baidu.com/view/fed5a20ce87101f69e31959e.html" target="_blank" rel="noopener">应用Flash JavaScript API解析fla文件</a></li><li><a href="http://www.g168.net/txt/flash/ExtendingFlash/index.htm" target="_blank" rel="noopener">jsfl参考文档1</a><br>  <a href="http://tool.admin5.com/shouce/flash/ExtendingFlash/" target="_blank" rel="noopener">jsfl参考文档2</a></li></ul></li><li>python脚本是为了把json数据(.flajson文件)转换成二进制数据(.flabin文件)，进一步缩小文件体积，同时带有一定的加密效果。<ul><li>其中cocos2dx的项目只支持二进制格式，Android和iOS版本支持json和二进制格式。</li><li>二进制格式的文件大小要比json格式小10倍左右，加载速度也比json快。</li></ul></li></ul><p>当初在cocos2dx中做程序实现的时候，我是完全把flash的运行机制在cocos2dx中复制了一遍。<br>其中包含了，元件，层和关键帧的概念。<br>到后面移植App的时候，我的思路发生了变化，我觉得把层的概念淡化，然后在任何一帧，把不同层的图片同时绘制。这种思路可能更简单一些。<br>所以App的代码实现逻辑，同cocos2dx版本的代码有一定的区别。</p><p>项目中的代码是最简单的能够使用的版本，功能和限制如下：</p><pre><code>- 只能用图片，不能用矢量图- 只能使用如下属性：位置，缩放，旋转，切变，颜色叠加，透明度变化。- cocos2dx版本计算content size的部分没有实现。- app版本我为不同分辨率手机做了适配，但是没有编写计算其size的方法，因为我没有用到。- 上述两点如果有这种需求，则需要自行添加这部分代码</code></pre><p>虽说简单，但是这个库已经能够满足90%的相关需求了。</p><p>如果感兴趣，可以通过阅读资料，为其增加矢量图，滤镜，遮罩等功能。这些都是可以实现的。</p><p>我在代码关键部分都加了注释。感兴趣的可以围观一下。帮忙指出错误。</p><p><a href="https://github.com/hardman/FlashAnimationToMobile" target="_blank" rel="noopener">FlashAnimationToMobile 点此进入</a>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;strong&gt;[注] iOS代码已重构，效率提升90%，200层动画不卡。[2016.10.27]&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;项目介绍&quot;&gt;&lt;a href=&quot;#项目介绍&quot; class=&quot;headerlink&quot; title=&quot; 项目介绍&quot;&gt;&lt;/a&gt; 项目介绍&lt;
      
    
    </summary>
    
      <category term="iOS" scheme="https://hardman.github.io/categories/iOS/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="动画" scheme="https://hardman.github.io/tags/%E5%8A%A8%E7%94%BB/"/>
    
      <category term="flash" scheme="https://hardman.github.io/tags/flash/"/>
    
      <category term="android" scheme="https://hardman.github.io/tags/android/"/>
    
      <category term="cocos2dx" scheme="https://hardman.github.io/tags/cocos2dx/"/>
    
  </entry>
  
  <entry>
    <title>详解intrinsicContentSize及约束优先级contentHugging&amp;contentCompressionResistance</title>
    <link href="https://hardman.github.io/2016/03/14/%E8%AF%A6%E8%A7%A3intrinsicContentSize%E5%8F%8A%E7%BA%A6%E6%9D%9F%E4%BC%98%E5%85%88%E7%BA%A7contentHugging-contentCompressionResistance/"/>
    <id>https://hardman.github.io/2016/03/14/详解intrinsicContentSize及约束优先级contentHugging-contentCompressionResistance/</id>
    <published>2016-03-14T09:38:59.000Z</published>
    <updated>2018-07-19T06:20:13.090Z</updated>
    
    <content type="html"><![CDATA[<p>在了解intrinsicContentSize之前，我们需要先了解2个概念：</p><ul><li>AutoLayout在做什么</li><li>约束优先级是什么意思。</li></ul><p>如果不了解这两个概念，看intinsic content size没有任何意义。<br>注：由于上面这几个概念都是针对UIView或其子类(UILabel，UIImageView等等)来说的。所以下文中都用UIView指代。</p><h2 id="AutoLayout在做什么"><a href="#AutoLayout在做什么" class="headerlink" title="AutoLayout在做什么"></a>AutoLayout在做什么</h2><p>一个UIView想要显示在屏幕中，仅须有2个需要确定的元素，一是位置，二是大小。只要2者确定，UIView就可以正确显示，至于显示的内容，则由UIView自己决定（drawRect）。</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314170635059" width="500" align="center"><br></div><br>没有AutoLayout的时候，我们需要通过 initWithFrame:(CGRect)这种方式来指定UIView的位置和大小。<br><br>而使用AutoLayout的过程，就是通过约束来确定UIView的位置和大小的过程。<br><br>约束优先级<br>–<br>为什么约束需要优先级？因为有的时候2个约束可能会有冲突。<br><br>比如：有一个UIView 距离父UIView的左右距离都是0，这是2个约束，此时再给此UIView加一个宽度约束，比如指定宽度为100，那么就会产生约束冲突了。<br><div align="center"><br><img src="http://img.blog.csdn.net/20160314171333593" width="500" align="center"><br></div><br>因为，这两种约束不可能同时存在，只能满足一个，那么满足谁呢？默认情况下给UIView加的这几个约束优先级都是1000，属于最高的优先级了，表示此约束必须满足。<br><br>所以这种冲突不能被iOS所允许。此时就需要修改优先级了。把其中任意一个约束的优先级改为小于1000的值即可。<br><br>&gt;iOS可以通过比较两个”相互冲突的约束”的优先级，从而忽略低优先级的某个约束，达到正确布局的目的。<br><br>用鼠标选中宽度约束，然后在屏幕右侧的菜单中，修改优先级，如下图：<br><div align="center"><br><img src="http://img.blog.csdn.net/20160314171442562" width="500" align="center"><br></div><br>这样就没有约束冲突了。因为如果一旦两个约束冲突，系统会自动忽略优先级低的约束。<br><div align="center"><br><img src="http://img.blog.csdn.net/20160314171524062" width="500" align="center"><br></div><br>上面举的这个例子有些极端，因为上面两个约束都是确定的值，而且是绝对冲突。所以如果遇到这种情况，可能选择删掉某个约束更为合适。<br><br>而约束优先级更多的时候用于解决模糊约束（相对于上面的确定值约束来说）的冲突的问题。<br>比如有这样一个问题：<br><br>- UIView1有四个约束：距离父UIView左和上确定，宽和高也确定。<br>- UIView2在UIView1的下面，约束也有4个：上面距离UIView1确定，左面同UIView1对齐，同UIView1等高且等宽。<br>此时这两个UIView应该像这样：<br><div align="center"><br><img src="http://img.blog.csdn.net/20160314171652109" width="500" align="center"><br></div><p>这是一个很普通的应用场景，假设我希望有这样一个效果： 我希望UIView2的宽度不能超过50。当UIView1宽度小于50的时候，二者等宽；当UIView1宽度大于50的时候，UIView2不受UIView1宽度的影响。<br>于是我给UIView2加上一条约束：宽度&lt;=50。这时候冲突来了：<br>因为UIView1的宽度是定好的，而UIView2和UIView1等宽。那么UIView2的宽度就是确定的。</p><p>很显然，分为两种情况（根据UIView1的宽度不同）：</p><ul><li>若UIView1的宽度大于50，UIView2的宽度也一定大于50，这跟新加的限制宽度&lt;=50的约束是冲突的。 <div align="center"><br><img src="http://img.blog.csdn.net/20160314171756501" width="500" align="center"><br></div></li><li>否则不冲突。<div align="center"><br><img src="http://img.blog.csdn.net/20160314171829329" width="500" align="center"><br></div></li></ul><p>更糟糕的是，实际情况中，UIView1的宽度可能不是一个确定的值。它有可能会被页面中的其他View所影响，可能还会在运行时产生变化，并不能保证它的实际宽度一定小于50。所以，一旦产生约束冲突，可能就会对应用产生不确定的影响：可能显示错乱，也可能程序崩溃。</p><p>所以我们为了得到正确的结果，应该这样处理：</p><ol><li>当UIView1宽度小于等于50的时候，约束不冲突，修改优先级与否都是一样结果。</li><li>当UIView1宽度大于50的时候，忽略等宽约束，也就是降低等宽约束优先级。</li></ol><p>所以我们把等宽约束的优先级修改为999。上面两条都满足，问题解决。</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314171952440" width="500" align="center"><br></div><p>说到模糊约束，content Hugging／content Compression Resistance就是2个UIView自带的模糊约束。<br>而这两个约束存在的条件则是UIView必须指定了 Intrinsic Content Size。<br>在了解这两个模糊约束之前，必须了解Intrinsic Content Size是什么东西。</p><h2 id="Intrinsic-Contenet-Size"><a href="#Intrinsic-Contenet-Size" class="headerlink" title="Intrinsic Contenet Size"></a>Intrinsic Contenet Size</h2><p>Intrinsic Content Size：固有大小。顾名思义，在AutoLayout中，它作为UIView的属性（不是语法上的属性），意思就是说我知道自己的大小，如果你没有为我指定大小，我就按照这个大小来。</p><p>比如：大家都知道在使用AutoLayout的时候，UILabel是不用指定尺寸大小的，只需指定位置即可，就是因为，只要确定了文字内容，字体等信息，它自己就能计算出大小来。</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314172131346" width="500" align="center"><br></div><br>UILabel，UIImageView，UIButton等这些组件及某些包含它们的系统组件都有 Intrinsic Content Size 属性。<br>也就是说，遇到这些组件，你只需要为其指定位置即可。大小就使用Intrinsic Content Size就行了。<br><br>在代码中，上述系统控件都重写了UIView 中的 -(CGSize)intrinsicContentSize: 方法。<br>并且在需要改变这个值的时候调用：invalidateIntrinsicContentSize 方法，通知系统这个值改变了。<br><br>所以当我们在编写继承自UIView的自定义组件时，也想要有Intrinsic Content Size的时候，就可以通过这种方法来轻松实现。<br><br>Intrinsic冲突<br>–<br><br>一个UIView有了 Intrinsic Content Size 之后，才可以只指定位置，而不用指定大小。并且才可能会触发上述两个约束。<br><br>但是问题又来了，对于上述这种UIView来说，只指定位置而不指定大小，有的时候会有问题。<br><br>我们用UILabel来举例吧（所有支持Intrinsic Content Size 的组件都有此问题）。<br>2个UILabel，UILabel1（文字内容：UILabel1）和UILabel2（文字内容：UILabel2），其内容按照下面说明布局：<br><br>- 2个UILabel距离上边栏为50点。<br>- UILabel1与左边栏距离为10，UILabel2左面距离UILabel1为10点。<br><br>因为都具有Intrinsic属性，所以不需要指定size。位置应该也明确了。<br><div align="center"><br><img src="http://img.blog.csdn.net/20160314172208377" width="500" align="center"><br></div><p>现在问题来了，再给UILabel2加一条约束，右侧距离右边栏为10点。</p><p>很明显，如果按照约束来布局，则没办法满足2个UIlabel都使用 Intrinsic Content Size，至少某个UILabel的宽度大于Intrinsic Content Size。这种情况，我们称之为2个组件之间的“Intrinsic冲突”。</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314172318414" width="500" align="center"><br></div><p>解决“Intrinsic冲突”的方案有2种：</p><ol><li>两个UIlabel都不使用Intrinsic Content Size。为两个UIlabel增加新的约束，来显式指定它们的大小。如：给2个UIlabel增加宽度和高度约束或等宽等高约束等等。<div align="center"><br><img src="http://img.blog.csdn.net/20160314172405019" width="500" align="center"><br></div></li><li>可以让其中一个UIlabel使用Intrinsic Content Size，另一个label则自动占用剩余的空间。这时候就需要用到 Content Hugging 和 Content Compression Resistance了！具体做法在下面介绍。</li></ol><blockquote><p>一句话总结“Intrinsic冲突”：两个或多个可以使用Intrinsic Content Size的组件，因为组件中添加的其他约束，而无法同时使用 intrinsic Content Size了。</p></blockquote><h2 id="content-Hugging／content-Compression-Resistance"><a href="#content-Hugging／content-Compression-Resistance" class="headerlink" title="content Hugging／content Compression Resistance"></a>content Hugging／content Compression Resistance</h2><p>首先，这两个概念都是UIView的属性。</p><p>假设两个组件产生了“Intrinsic冲突”：</p><ol><li>Content Hugging 约束（不想变大约束）表示：如果组件的此属性优先级比另一个组件此属性优先级高的话，那么这个组件就保持不变，另一个可以在需要拉伸的时候拉伸。属性分横向和纵向2个方向。</li><li>Content Compression Resistance 约束（不想变小约束）表示：如果组件的此属性优先级比另一个组件此属性优先级高的话，那么这个组件就保持不变，另一个可以在需要压缩的时候压缩。属性分横向和纵向2个方向。</li></ol><p>意思很明显。上面UIlabel这个例子中，很显然，如果某个UILabel使用Intrinsic Content Size的时候，另一个需要拉伸。<br>所以我们需要调整两个UILabel的 Content Hugging约束的优先级就可以啦。<br>在这个页面可以调整优先级（拉到最下面）。</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314172538285" width="500" align="center"><br></div><p>分别调整两个UILabel的 Content Hugging的优先级可以得到不同的结果：</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314172613903" width="500" align="center"><br></div><br><div align="center"><br><img src="http://img.blog.csdn.net/20160314172651785" width="500" align="center"><br></div><p>Content Compression Resistance 的情况就不多说了，原理相同。</p><h2 id="在代码中修改UIView的这两个优先级"><a href="#在代码中修改UIView的这两个优先级" class="headerlink" title="在代码中修改UIView的这两个优先级"></a>在代码中修改UIView的这两个优先级</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];</span><br><span class="line">[label setContentCompressionResistancePriority: UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];</span><br></pre></td></tr></table></figure><p>Priority是个enum：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">typedef float UILayoutPriority;</span><br><span class="line">static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint.  Do not exceed this.</span><br><span class="line">static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.</span><br><span class="line">static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.</span><br><span class="line">static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50; // When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed.  UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation.  It&apos;s quite low.  It is generally not appropriate to make a constraint at exactly this priority.  You want to be higher or lower.</span><br></pre></td></tr></table></figure></p><p>Axis表示横向及纵向：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) &#123;</span><br><span class="line">    UILayoutConstraintAxisHorizontal = 0,</span><br><span class="line">    UILayoutConstraintAxisVertical = 1</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h2 id="创建自定义具有-Intrinsic-Content-Size-功能的组件"><a href="#创建自定义具有-Intrinsic-Content-Size-功能的组件" class="headerlink" title="创建自定义具有 Intrinsic Content Size 功能的组件"></a>创建自定义具有 Intrinsic Content Size 功能的组件</h2><p>代码及注释如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">//IntrinsicView.h</span><br><span class="line">#import &lt;UIKit/UIKit.h&gt;</span><br><span class="line"></span><br><span class="line">@interface IntrinsicView : UIView</span><br><span class="line">@property (nonatomic) CGSize extendSize;</span><br><span class="line">@end</span><br></pre></td></tr></table></figure></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">//IntrinsicView.m</span><br><span class="line">#import &quot;IntrinsicView.h&quot;</span><br><span class="line"></span><br><span class="line">static bool closeIntrinsic = false;//测试关闭Intrinsic的影响</span><br><span class="line"></span><br><span class="line">@implementation IntrinsicView</span><br><span class="line"></span><br><span class="line">- (instancetype)init</span><br><span class="line">&#123;</span><br><span class="line">    self = [super init];</span><br><span class="line">    if (self) &#123;</span><br><span class="line">        //不兼容旧版Autoreizingmask，只使用AutoLayout</span><br><span class="line">        //如果为YES，在AutoLayout中则会自动将view的frame和bounds属性转换为约束。</span><br><span class="line">        self.translatesAutoresizingMaskIntoConstraints = NO;</span><br><span class="line">    &#125;</span><br><span class="line">    return self;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//当用户设置extendSize时，提示系统IntrinsicContentSize变化了。</span><br><span class="line">-(void)setExtendSize:(CGSize)extendSize&#123;</span><br><span class="line">    _extendSize = extendSize;</span><br><span class="line">    //如果不加这句话，在view显示之后（比如延时几秒），再设置extendSize不会有效果。</span><br><span class="line">    //本例中也就是testInvalidateIntrinsic的方法不会产生预期效果。</span><br><span class="line">    [self invalidateIntrinsicContentSize];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//通过覆盖intrinsicContentSize函数修改View的Intrinsic的大小</span><br><span class="line">-(CGSize)intrinsicContentSize&#123;</span><br><span class="line">    if (closeIntrinsic) &#123;</span><br><span class="line">        return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        return CGSizeMake(_extendSize.width, _extendSize.height);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">@end</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">//测试代码</span><br><span class="line">#import &quot;ViewController.h&quot;</span><br><span class="line">#import &quot;newViewCtlViewController.h&quot;</span><br><span class="line">#import &quot;IntrinsicView.h&quot;</span><br><span class="line"></span><br><span class="line">@interface ViewController ()</span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation ViewController</span><br><span class="line"></span><br><span class="line">-(void)viewDidLoad&#123;</span><br><span class="line">    [super viewDidLoad];</span><br><span class="line">    [self testIntrinsicView];</span><br><span class="line">&#125;+</span><br><span class="line">-(void) testIntrinsicView&#123;</span><br><span class="line">    IntrinsicView *intrinsicView1 = [[IntrinsicView alloc] init];</span><br><span class="line">    intrinsicView1.extendSize = CGSizeMake(100, 100);</span><br><span class="line">    intrinsicView1.backgroundColor = [UIColor greenColor];</span><br><span class="line">    [self.view addSubview:intrinsicView1];</span><br><span class="line">    [self.view addConstraints:@[</span><br><span class="line">                                //距离superview上方100点</span><br><span class="line">                                  [NSLayoutConstraint constraintWithItem:intrinsicView1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:100],</span><br><span class="line">                                  //距离superview左面10点</span><br><span class="line">                                  [NSLayoutConstraint constraintWithItem:intrinsicView1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:10],</span><br><span class="line">    ]];</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    IntrinsicView *intrinsicView2 = [[IntrinsicView alloc] init];</span><br><span class="line">    intrinsicView2.extendSize = CGSizeMake(100, 30);</span><br><span class="line">    intrinsicView2.backgroundColor = [UIColor redColor];</span><br><span class="line">    [self.view addSubview:intrinsicView2];</span><br><span class="line">    [self.view addConstraints:@[</span><br><span class="line">                                //距离superview上方220点</span><br><span class="line">                                [NSLayoutConstraint constraintWithItem:intrinsicView2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:220],</span><br><span class="line">                                //距离superview左面10点</span><br><span class="line">                                [NSLayoutConstraint constraintWithItem:intrinsicView2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:10],</span><br><span class="line">                                ]];</span><br><span class="line">    </span><br><span class="line">    [self performSelector:@selector(testInvalidateIntrinsic:) withObject:intrinsicView2 afterDelay:2];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">-(void) testInvalidateIntrinsic:(IntrinsicView *)view&#123;</span><br><span class="line">    view.extendSize = CGSizeMake(100, 80);</span><br><span class="line">&#125;</span><br><span class="line">@end</span><br></pre></td></tr></table></figure><p>代码效果如下：</p><div align="center"><br><img src="http://img.blog.csdn.net/20160314173711526" width="500" align="center"><br></div>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在了解intrinsicContentSize之前，我们需要先了解2个概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AutoLayout在做什么&lt;/li&gt;
&lt;li&gt;约束优先级是什么意思。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果不了解这两个概念，看intinsic content size没有
      
    
    </summary>
    
      <category term="iOS" scheme="https://hardman.github.io/categories/iOS/"/>
    
    
      <category term="iOS" scheme="https://hardman.github.io/tags/iOS/"/>
    
      <category term="autolayout" scheme="https://hardman.github.io/tags/autolayout/"/>
    
  </entry>
  
</feed>
