书途
人无书而涂,因书而智,终老以书为伴,是为书途
2018-09-25T03:30:18.495Z
http://www.raomengyang.com/
Codios
Hexo
何为人生意义?
http://www.raomengyang.com/2018/09/25/何为人生意义?/
2018-09-25T03:23:54.000Z
2018-09-25T03:30:18.495Z
<ul>
<li>最近有个人生的哲学问题一直困扰着我,有时候忽然觉得一切都是毫无意义可言的,因为地球上再伟大的人,参照宏大的宇宙观来说,近乎颗粒微乎其微,可是我们作为人类连尘埃都算不上,甚至有时候觉得一切所谓美好的人生都是人把自己想象的太过于重要罢了。曾努力了一辈子的伟人,为世人所记住千百年,可当有一天我们突然意识到自己的渺小,在世上本就是可有可无的“尘埃”的时候,一刻的毁灭则将我们看来如此漫长的几千年甚至几十万年的发展史轻松抹去,而在“真”的世界中,或许没有“意识物体”能够注意到对我们这么重要的时刻。所以,我们活着,美好的过着每一天,究竟是为了什么呢?</li>
</ul>
<ul>
<li>每个人的人生经历是完全不同的,对于好吃懒做的人,可能会很颓废的消磨一辈子;勤勤勉勉上进的人,会把自己的光环放大,让更多的人记住他;老的一辈为了造福子孙后代,建设发展城市,便利后人的生活,让大家都很开心的过着自己的生活,可难道这就是所谓的意义么?我总觉得自己要活的有意义,可马上二十七了,再没两年也将三十而立,买车买房生孩子孝顺父母,眨眼几十年一过,经历了生离死别,孤老病苦,直到我闭眼的那时,这“人生意义”我能参悟透么?又可能遇到各种灾害活不到那么长的时间,如果在意外的那一刻,我又能做些什么,想些什么,“魂”归何处呢?所以就算是“死”,意义又在哪呢?按照能量守恒的定律,我不过是一堆发热的肉体渐渐散去我的热度,然后身体腐烂、体液挥发,最终化为一堆白骨,一堆灰烬,如我们脚下的蝼蚁、枪口下的猎物、屠夫刀下的猪羊一般,死后不具任何“意义”。还有很多人会因为各种原由轻生,可是死亡后就能所谓的解脱吗?一样的是折磨自己。</li>
</ul>
<ul>
<li>最近有个人生的哲学问题一直困扰着我,有时候忽然觉得一切都是毫无意义可言的,因为地球上再伟大的人,参照宏大的宇宙观来说,近乎颗粒微乎其微,可是我们作为人类连尘埃都算不上,甚至有时候觉得一切所谓美好的人生都是人把自己想象的太过于重要罢了。曾努力了一辈子的伟人,为世人
git 使用分享
http://www.raomengyang.com/2018/04/25/git-使用分享/
2018-04-25T02:40:34.000Z
2018-04-25T02:40:34.413Z
<h1 id="git-使用分享"><a href="#git-使用分享" class="headerlink" title="git 使用分享"></a>git 使用分享</h1><ul>
<li><h5 id="git-命令行必备"><a href="#git-命令行必备" class="headerlink" title="git 命令行必备"></a>git 命令行必备</h5><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"># 设置 alias 别名</span><br><span class="line">git config --global alias.st "status"</span><br><span class="line">git config --global alias.ci "commit"</span><br><span class="line">git config --global alias.co "checkout"</span><br><span class="line">git config --global alias.br "branch"</span><br><span class="line">git config --global alias.ps "push"</span><br><span class="line">git config --global alias.pl "pull"</span><br><span class="line">git config --global alias.bra "branch -a"</span><br><span class="line">git config --global alias.cob "checkout -b"</span><br><span class="line">git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"</span><br><span class="line"></span><br><span class="line"># 配置用户名</span><br><span class="line">git config --global user.name "your name"</span><br><span class="line">git config --global user.email "ericrao@welove-inc.com"</span><br><span class="line"></span><br><span class="line"># 查看 git 配置</span><br><span class="line">$ cat .gitconfig</span><br><span class="line">[alias]</span><br><span class="line"> co = checkout</span><br><span class="line"> ci = commit</span><br><span class="line"> br = branch</span><br><span class="line"> st = status</span><br><span class="line">[user]</span><br><span class="line"> name = Your Name</span><br><span class="line"> email = your@email.com</span><br></pre></td></tr></table></figure>
</li>
<li><p>配置 credential,避免每次都输入用户名和密码</p>
<ul>
<li><code>git config --global credential.helper store <file path></code></li>
</ul>
</li>
<li><p>忽略无用文件:</p>
<ul>
<li><p>推荐插件 .ignore on IDEA</p>
</li>
<li><p><code>.gitignore</code> templates :<a href="https://github.com/github/gitignore" target="_blank" rel="external">https://github.com/github/gitignore</a></p>
</li>
</ul>
</li>
</ul>
<ul>
<li>常用命令 <code>git merge</code> 和 <code>git fecth / pull</code> 的区别</li>
<li>巧用保存进度 <code>git stash</code> 和 <code>git stash pop / apply</code></li>
<li>每次发版需要打标签:<ul>
<li><code>git tag 1.0.0 -m "tag message"</code></li>
<li>查看 TAG: <code>git tag --list</code> 、<code>git show 1.0.0</code></li>
</ul>
</li>
</ul>
<a id="more"></a>
<h2 id="问题合集"><a href="#问题合集" class="headerlink" title="问题合集"></a>问题合集</h2><ul>
<li>问题 1:如何使用 submodule<ul>
<li>代码演示</li>
</ul>
</li>
</ul>
<ul>
<li><strong>问题 2 :如何使用 subtree</strong><ul>
<li>比 submodule 更建议使用,具体使用方法待更新</li>
</ul>
</li>
<li>问题3 :避免分支合并错误<ul>
<li>修改分支名 : master_stable / master_dev / master_debug / raomengyang_dev/ master_beta</li>
<li><strong>使用 git hook</strong> :<a href="https://gist.github.com/mwise/69ec35b646b52d98050d" target="_blank" rel="external">https://gist.github.com/mwise/69ec35b646b52d98050d</a> </li>
</ul>
</li>
</ul>
<blockquote>
<p>To use this hook:</p>
<ul>
<li>add the <code>prepare-commit-msg</code> file at <code>.git/hooks/prepare-commit-msg</code> and edit as needed</li>
<li>make it executable: <code>chmod +x .git/hooks/prepare-commit-msg</code></li>
<li>disable fast-forward merges: <code>git config branch.master.mergeoptions "--no-ff"</code></li>
<li>that’s it!</li>
</ul>
<p>NOTE: after a failed merge from a forbidden branch, the working tree will still be in a MERGING state. To discard the local working copy state, run: <code>git reset --merge</code></p>
</blockquote>
<p><a href="https://gist.github.com/mwise/69ec35b646b52d98050d#file-prepare-commit-msg" target="_blank" rel="external"><strong>prepare-commit-msg </strong></a></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></pre></td><td class="code"><pre><span class="line">#!/usr/bin/env ruby</span><br><span class="line"># This git hook will prevent merging specific branches into master</span><br><span class="line"># Put this file in your local repo, in the .git/hooks folder</span><br><span class="line"># and make sure it is executable.</span><br><span class="line"># The name of the file *must* be "prepare-commit-msg" for Git to pick it up.</span><br><span class="line"></span><br><span class="line">FORBIDDEN_BRANCHES = ["test"]</span><br><span class="line"></span><br><span class="line">def merge?</span><br><span class="line"> ARGV[1] == "merge"</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">def into_master?</span><br><span class="line"> current_branch = `git branch | grep '*' | sed 's/* //'`.chop</span><br><span class="line"> current_branch == "master"</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">def merge_msg</span><br><span class="line"> @msg ||= `cat .git/MERGE_MSG`</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">def from_branch</span><br><span class="line"> @from_branch = merge_msg.match(/Merge branch '(.*?)'/)[1]</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">def from_forbidden_branch?</span><br><span class="line"> FORBIDDEN_BRANCHES.include?(from_branch)</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">if merge? && into_master? && from_forbidden_branch?</span><br><span class="line"> out = `git reset --merge`</span><br><span class="line"> puts</span><br><span class="line"> puts " STOP THE PRESSES!"</span><br><span class="line"> puts " You are trying to merge #{from_branch} into the *master* branch."</span><br><span class="line"> puts " Surely you don't mean that?"</span><br><span class="line"> puts</span><br><span class="line"> puts " run the following command now to discard your working tree changes:"</span><br><span class="line"> puts</span><br><span class="line"> puts " git reset --merge"</span><br><span class="line"> puts</span><br><span class="line"> exit 1</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<ul>
<li>问题4 :</li>
</ul>
<blockquote>
<p>我本来打算把代码提交到branch1,但是我提交的时候忘了切过去, 提交到branch2了( 未推送或者已经推送 ), 这时候怎么回到我提交前的状态, 切个分支就可以再提交到branch1</p>
</blockquote>
<p> 代码演示</p>
<ul>
<li><p>iOS 项目分支出的问题</p>
<p></p>
</li>
</ul>
<h1 id="git-使用分享"><a href="#git-使用分享" class="headerlink" title="git 使用分享"></a>git 使用分享</h1><ul>
<li><h5 id="git-命令行必备"><a href="#git-命令行必备" class="headerlink" title="git 命令行必备"></a>git 命令行必备</h5><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"># 设置 alias 别名</span><br><span class="line">git config --global alias.st "status"</span><br><span class="line">git config --global alias.ci "commit"</span><br><span class="line">git config --global alias.co "checkout"</span><br><span class="line">git config --global alias.br "branch"</span><br><span class="line">git config --global alias.ps "push"</span><br><span class="line">git config --global alias.pl "pull"</span><br><span class="line">git config --global alias.bra "branch -a"</span><br><span class="line">git config --global alias.cob "checkout -b"</span><br><span class="line">git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"</span><br><span class="line"></span><br><span class="line"># 配置用户名</span><br><span class="line">git config --global user.name "your name"</span><br><span class="line">git config --global user.email "ericrao@welove-inc.com"</span><br><span class="line"></span><br><span class="line"># 查看 git 配置</span><br><span class="line">$ cat .gitconfig</span><br><span class="line">[alias]</span><br><span class="line"> co = checkout</span><br><span class="line"> ci = commit</span><br><span class="line"> br = branch</span><br><span class="line"> st = status</span><br><span class="line">[user]</span><br><span class="line"> name = Your Name</span><br><span class="line"> email = your@email.com</span><br></pre></td></tr></table></figure>
</li>
<li><p>配置 credential,避免每次都输入用户名和密码</p>
<ul>
<li><code>git config --global credential.helper store <file path></code></li>
</ul>
</li>
<li><p>忽略无用文件:</p>
<ul>
<li><p>推荐插件 .ignore on IDEA</p>
</li>
<li><p><code>.gitignore</code> templates :<a href="https://github.com/github/gitignore">https://github.com/github/gitignore</a></p>
</li>
</ul>
</li>
</ul>
<ul>
<li>常用命令 <code>git merge</code> 和 <code>git fecth / pull</code> 的区别</li>
<li>巧用保存进度 <code>git stash</code> 和 <code>git stash pop / apply</code></li>
<li>每次发版需要打标签:<ul>
<li><code>git tag 1.0.0 -m "tag message"</code></li>
<li>查看 TAG: <code>git tag --list</code> 、<code>git show 1.0.0</code></li>
</ul>
</li>
</ul>
Android设计模式 -- 巧用策略模式告别繁琐的 if...else...
http://www.raomengyang.com/2018/02/28/Android设计模式-巧用策略模式告别繁琐的-if-else/
2018-02-28T10:56:43.000Z
2018-02-28T10:56:43.430Z
<h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><p>根据后台配置进行不同的广告加载策略,例如有广告 A / B / C,某个时段后台配置播放广告 C,默认播放 A;</p>
<h3 id="普通的实现方式"><a href="#普通的实现方式" class="headerlink" title="普通的实现方式"></a>普通的实现方式</h3><ol>
<li>创建广告管理类,实现广告加载/播放的控制:</li>
</ol>
<figure class="highlight kotlin"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AdManager</span></span>(adName: String) {</span><br><span class="line"> <span class="keyword">var</span> ad = adName</span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">initAd</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">when</span> (ad) {</span><br><span class="line"> <span class="string">"A"</span> -> initA()</span><br><span class="line"> <span class="string">"B"</span> -> initB()</span><br><span class="line"> <span class="string">"C"</span> -> initC()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">playAd</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">when</span> (ad) {</span><br><span class="line"> <span class="string">"A"</span> -> initA()</span><br><span class="line"> <span class="string">"B"</span> -> initB()</span><br><span class="line"> <span class="string">"C"</span> -> initC()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">stopAd</span><span class="params">()</span></span> {<span class="string">"A"</span></span><br><span class="line"> <span class="keyword">when</span> (ad) {</span><br><span class="line"> <span class="string">"A"</span> -> initA()</span><br><span class="line"> <span class="string">"B"</span> -> initB()</span><br><span class="line"> <span class="string">"C"</span> -> initC()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">initA</span><span class="params">()</span></span> {</span><br><span class="line"> print(<span class="string">"init A"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 代码省略...</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">playA</span><span class="params">()</span></span> {</span><br><span class="line"> print(<span class="string">"play A"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 代码省略...</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">stopA</span><span class="params">()</span></span> {</span><br><span class="line"> print(<span class="string">"stop A"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 代码省略...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol>
<li>使用方式:</li>
</ol>
<figure class="highlight kotlin"><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"><span class="class"><span class="keyword">class</span> <span class="title">ConcreteAd</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> adManager = AdManager(<span class="string">"A"</span>)</span><br><span class="line"> adManager.initAd()</span><br><span class="line"> adManager.playAd()</span><br><span class="line"> adManager.stopAd()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从上面看到,充斥着大量的if…else…(Kotlin 中用的 when 语法),并且耦合度很高,想要增加广告就要在同一个类中进行修改。那么使用策略模式来进行优化后,大致是这样的:</p>
<ol>
<li>创建广告策略接口 IAdStrategy 类:</li>
</ol>
<figure class="highlight kotlin"><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"><span class="class"><span class="keyword">interface</span> <span class="title">IAdStrategy</span> </span>{</span><br><span class="line"> <span class="keyword">abstract</span> <span class="function"><span class="keyword">fun</span> <span class="title">initAd</span><span class="params">()</span></span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="function"><span class="keyword">fun</span> <span class="title">playAd</span><span class="params">()</span></span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="function"><span class="keyword">fun</span> <span class="title">stopAd</span><span class="params">()</span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol>
<li>针对不同的广告进行策略的实现(下面以广告A为例):</li>
</ol>
<figure class="highlight kotlin"><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"><span class="class"><span class="keyword">class</span> <span class="title">A</span> : <span class="type">IAdStrategy {</span></span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">initAd</span><span class="params">()</span></span> {</span><br><span class="line"> print(<span class="string">"init A"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">playAd</span><span class="params">()</span></span> {</span><br><span class="line"> print(<span class="string">"play A"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">stopAd</span><span class="params">()</span></span> {</span><br><span class="line"> print(<span class="string">"stop A"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol>
<li>创建策略相关的Context来绑定广告和策略接口之间的关系:</li>
</ol>
<figure class="highlight kotlin"><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"><span class="class"><span class="keyword">class</span> <span class="title">AdStrategyContext</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> lateinit <span class="keyword">var</span> mAdStrategy: IAdStrategy</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(adStrategy: IAdStrategy) {</span><br><span class="line"> <span class="keyword">this</span>.mAdStrategy = adStrategy</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">initAd</span><span class="params">()</span></span> {</span><br><span class="line"> mAdStrategy.initAd()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">playAd</span><span class="params">()</span></span> {</span><br><span class="line"> mAdStrategy.playAd()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol>
<li>使用方式:</li>
</ol>
<figure class="highlight kotlin"><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"><span class="class"><span class="keyword">class</span> <span class="title">ConcreteAd</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 普通</span></span><br><span class="line"> <span class="keyword">var</span> adManager = AdManager(<span class="string">"A"</span>)</span><br><span class="line"> adManager.initAd()</span><br><span class="line"> adManager.playAd()</span><br><span class="line"> adManager.stopAd()</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 策略模式</span></span><br><span class="line"> <span class="keyword">var</span> adCtx = AdStrategyContext(A())</span><br><span class="line"> adCtx.initAd()</span><br><span class="line"> adCtx.playAd()</span><br><span class="line"> adCtx.stopAd()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的例子可以看到,一旦需要增加广告D的话,去实现对应的 IAdStrategy 接口即可,和之前的代码没有耦合,符合设计原则。但缺点就是如果广告很多的话,会存在很多类文件,并且一旦接口需要补充和修改,那么所有的实现类都会变动。</p>
<h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><p>根据后台配置进行不同的广告加载策略,例如有广告 A / B / C,某个时段后台配置播放广告 C,默认播放 A;</p>
<h3 id="普
2017年终总结附2018小目标
http://www.raomengyang.com/2018/01/18/2017年终总结附2018小目标/
2018-01-18T10:00:00.000Z
2018-01-19T02:26:58.423Z
<p>匆匆2017年过了,这一年发生了很多事,工作也好,生活也罢,总是往好的方向前进的。</p>
<p>16年末定了一些2017的小目标,现在该给自己一个交代:</p>
<h2 id="跳槽-√"><a href="#跳槽-√" class="headerlink" title="跳槽 √"></a>跳槽 √</h2><p>这件事对我现在仍旧意义深刻。在2017年一月的时候先去了美图面试,过程发挥还好,但跪在了最终的业务熟练度上,还记得那个问题:</p>
<ul>
<li>Gradle 中 Flavor 是干嘛的?</li>
<li>Build.gradle 文件中 compile 和 compileXX 有何区别?</li>
</ul>
<p>失败告终后,潜心准备了两个月,于三月在铁蕾哥的帮助下来到发现角面试。表现不好,但公司还是给我了工作的机会。自己也没闲着,一个月就提前转正了,在圣诞还涨了一次薪,提前实现自己的预期目标,再次感激~ 更换工作后环境也变了,不再如从前那么压抑。喜欢发现角的开放、自由,还有这里的各位也都很逗比。</p>
<p>这里也要感谢前东家的 CTO 老魏,每次想到你招人的时候说遇到了像我一样的人都很激动很开心,这是对我最大的肯定了,你也如我老师一样给予我偌大帮助,在云拓的一年半虽然时间很紧,但也非常充实,学到不少东西,无论是技术还是工作所需。也感谢老高的挽留,但世界还很大,我不想就这么沉寂下去。</p>
<p>所以,这个目标完美的达成 √。</p>
<a id="more"></a>
<h2 id="夯实基础-√"><a href="#夯实基础-√" class="headerlink" title="夯实基础 √"></a>夯实基础 √</h2><p>今年可算是技术方向扩展和打基础的一年,以前节奏太快,很多细节没有注意到,在今年都有所弥补,并且除 Android 之外接触了后端 Java 的代码,虽然还在熟悉业务中。而做了做自动化相关的事情:APK 自动化部署、自动化测试等提高大伙工作效率的事,抛开技术不说,能够实现自动化真的省了不少时间,例如打一个 APK 签名包曾经就要一个多小时,一旦任何问题需要修改又得重新打包,自动化部署后每次全渠道包一次构建也就 5 分钟左右。</p>
<p>Android 方向的话,入了 Kotlin 的坑,但还未曾接入项目的开发中,仅仅把基础语法过了一遍,写了些 Demo。</p>
<p>不过基础相关的事情有些还是没有办妥,例如加强自己的算法能力,设计模式的更深层次的运用,都还有不少路要走。技术切不可停留于表面,知其然还要知所以然。所以扎实算法、自我的业务扩展、熟练不同的编程语言和设计模式这些细节的地方便是 2018 年的目标之一。</p>
<h2 id="写博客、编写开源库-✘"><a href="#写博客、编写开源库-✘" class="headerlink" title="写博客、编写开源库 ✘"></a>写博客、编写开源库 ✘</h2><p>这个只能说是虎头蛇尾,写了 ScreenRecorder 系列以后就太监了。途中造了不少轮子但都没有进行技术的总结。原因还是懒,自从换了工作后,公开的技术博客写的少了。计划今年把自己的个人博客树立起来,并结合自己的微信公众号,能够作为推广自己的工具。目标是简洁到位。技术层面有料,对别人有帮助,能够如铁蕾哥那样,既有生活,有想法,也有 NB 的技术。</p>
<h2 id="成为高级-Android-开发者-?"><a href="#成为高级-Android-开发者-?" class="headerlink" title="成为高级 Android 开发者 ?"></a>成为高级 Android 开发者 ?</h2><p>暂未达成,虽然从2015年开始正式接触编程已经有快三年了,已经不是那个畏惧胆怯的新人,现在工作经验应该属于 3 ~ 5 年这个层段,但技术上个人感觉还是属于中上层,与任玉刚、郭霖、张鸿洋这些大神比起来差距还太大,所以当年定的为期三年成为高级开发者还需要再努力呐!</p>
<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>以上就是曾经定的 2017 年目标达成情况,然而这一年其实不仅仅是这样的,来到发现角,老大是位资深驴友,这一年跟着他去了不少地方:</p>
<ul>
<li>察哈尔火山群</li>
<li>蔚县茶山村</li>
<li>国庆自驾北京 - 成都</li>
</ul>
<p>每次旅途都结识了不一样的人,看到了自己不曾见过的风景。</p>
<p>而我自己也去了两次上海,配女票逛了迪士尼、东方明珠、外滩、城隍庙。年末的时候,公司报销车旅费,独自前往了在上海举办的 Google Developer Days 2017。才发现上海并不是曾经所想的那样,这里有着与北京截然不同的城市风貌和气息。</p>
<p>2018年的元旦,新年第一天在土豪炜家,老大和我还有土豪炜的妹子朋友一起迎接了新年的到来,自己操刀的这顿火锅很爽,也收到了土豪炜送的新年礼物😜 还有很搞笑的是那晚上回去的途中电动车居然没电了,最后寒风中找了一个货拉拉连车带人一起拖回家,哈哈哈。</p>
<p>最后很感谢老大的各种帮助和带我一起嗨~ 让我明白除了技术,还有生活。</p>
<p>[]~( ̄▽ ̄)~*干杯</p>
<p>以上的经历我竟然都没有记录成一篇篇博客,这是 2017 年唯一的遗憾,希望后续能够一点点回忆起来补上。</p>
<h2 id="2018-年开端与小目标"><a href="#2018-年开端与小目标" class="headerlink" title="2018 年开端与小目标"></a>2018 年开端与小目标</h2><p>2018一月才刚过半就经历了一些记忆深刻的事:</p>
<ul>
<li>参加 Google Android Things 物联网沙龙,并上台分享,途中认识了 GDE 王玉成大哥,又抽到一套NXP 的 Android Things 开发板</li>
<li>开始接触了区块链</li>
</ul>
<p>上述的事情单独会写博客,这里不细说了。谈谈目标吧:</p>
<h3 id="TensorFlow-入门"><a href="#TensorFlow-入门" class="headerlink" title="TensorFlow 入门"></a>TensorFlow 入门</h3><p>GDD 过后,自己玩了玩 TensorFlow,才发现 AI 已经如此的不得了,今年自己重点会把业余时间放在人工智能机器学习这方面,AI 不仅仅是趋势,而是人类一次技术变革的到来。</p>
<h3 id="算法、语言基础"><a href="#算法、语言基础" class="headerlink" title="算法、语言基础"></a>算法、语言基础</h3><p>之前提到了2017还有一些事情没做完,把 Python 和算法的坑填了。</p>
<h3 id="筹办婚事、领证"><a href="#筹办婚事、领证" class="headerlink" title="筹办婚事、领证"></a>筹办婚事、领证</h3><p>是的,我计划2018年年底领证结婚,和女票已经7年长跑,是时候有一个交代了。</p>
<h3 id="买车"><a href="#买车" class="headerlink" title="买车"></a>买车</h3><p>从去年就一直心痒痒,从小喜欢车现在也到年龄了,准备今年择机入手,因为还有三年才能摇号,而且搞不好要摇一辈子,所以放弃京牌准备办离周边省市的牌照。你说限行?在帝都谁会开车上班呐[🤣笑哭]</p>
<p>其他想到再补充吧。</p>
<p>匆匆2017年过了,这一年发生了很多事,工作也好,生活也罢,总是往好的方向前进的。</p>
<p>16年末定了一些2017的小目标,现在该给自己一个交代:</p>
<h2 id="跳槽-√"><a href="#跳槽-√" class="headerlink" title="跳槽 √"></a>跳槽 √</h2><p>这件事对我现在仍旧意义深刻。在2017年一月的时候先去了美图面试,过程发挥还好,但跪在了最终的业务熟练度上,还记得那个问题:</p>
<ul>
<li>Gradle 中 Flavor 是干嘛的?</li>
<li>Build.gradle 文件中 compile 和 compileXX 有何区别?</li>
</ul>
<p>失败告终后,潜心准备了两个月,于三月在铁蕾哥的帮助下来到发现角面试。表现不好,但公司还是给我了工作的机会。自己也没闲着,一个月就提前转正了,在圣诞还涨了一次薪,提前实现自己的预期目标,再次感激~ 更换工作后环境也变了,不再如从前那么压抑。喜欢发现角的开放、自由,还有这里的各位也都很逗比。</p>
<p>这里也要感谢前东家的 CTO 老魏,每次想到你招人的时候说遇到了像我一样的人都很激动很开心,这是对我最大的肯定了,你也如我老师一样给予我偌大帮助,在云拓的一年半虽然时间很紧,但也非常充实,学到不少东西,无论是技术还是工作所需。也感谢老高的挽留,但世界还很大,我不想就这么沉寂下去。</p>
<p>所以,这个目标完美的达成 √。</p>
续厨师与母亲的厨艺问题的一些想法
http://www.raomengyang.com/2017/11/01/续厨师与母亲的厨艺问题的一些想法/
2017-10-31T16:14:50.000Z
2017-10-31T16:27:01.000Z
<p>之前看到一个小问题:</p>
<ul>
<li>为什么初学两年的厨师比20年经验的母亲做的饭好吃的多?</li>
</ul>
<p>把问题迁移到编程上来说也一样,相关的问题可以参考:</p>
<ul>
<li>一年工作经验十年用</li>
</ul>
<p>今天看了一本讲解学习技巧的 cook book,里面也提到了这个问题:</p>
<ul>
<li>驾龄5年的司机和20年驾龄的司机谁的车技更好?</li>
<li>3、4年工作经验的医生和30年从医经历的老医师谁的医术更高?</li>
</ul>
<p>其实大部分都是前者有优势,因为后者常常是重复做了几十年同样的事,而并无太大的长进。写代码也一样,一年的时间学会基础语法、业务编写,然后从业十年编程一直处于业务需求的修改,可能技术和业界大牛比起来有天壤之别。这其中的道理又是为什么呢?</p>
<p>我自己总结的大概有这么几个部分:</p>
<h2 id="技术深度"><a href="#技术深度" class="headerlink" title="技术深度"></a>技术深度</h2><p>所谓的技术深度,就是前面提到的语法、业务,不同的业务涉及到的技术含量是不一样的, 如果是简单的社交软件,可能对 API 和数据的处理多一些,让用户的体验用起来更棒,基本就达到了这个产品的需求,而如果从事的是卫星遥感,舰船识别,那么就要对自身的算法能力加强,不仅是专业软件的熟练掌握,对相关的算法做到得心应手那么同样三年工作经验,后者的工资可能是前者的好几倍(行业领先的水平),所以这也说明了选工作和业务的重要性。比如获得了几家公司的 Offer,业务的侧重点不同,对个人的发展影响也极大,去微信开发组的肯定比去易信的要幸运得多(当然还有别的很多关键因素)。</p>
<h2 id="舒适区"><a href="#舒适区" class="headerlink" title="舒适区"></a>舒适区</h2><p>每个人都会遇到瓶颈,在瓶颈的时候其实也算是处于舒适区,当没有突破的时候,这种状态会一直伴随着自己长存,时间再长,可能会忘记自己追求的是什么,我是谁?我在哪?我要做什么?参见铁蕾哥的博客《<a href="http://zhangtielei.com/posts/blog-growth-curve.html" target="_blank" rel="external">技术的成长曲线</a>》。但我个人体会是,瓶颈区有两种类型,一种是舒适区,一种是紧迫区,紧迫区不同于舒适区的是,在这段时间会特别难熬,尤其自己某项技术掌握的不到位,或者不熟练,但是工作上、业务上却急需使用,自己有种心有余而力不足的感觉,无力感随身而出,睡也睡不好,吃也吃不香,工作效率还很低下,这种状态会严重影响自己的自信心,怀疑自己的能力。然而往往紧迫区过后就是一段快速上升的时期,某一天头疼不已的 BUG、技术难点突然解决,或是自己领悟都真谛了,喜悦感,成就感油然而生。</p>
<h2 id="临界值"><a href="#临界值" class="headerlink" title="临界值"></a>临界值</h2><p>人的天性都是懒惰的(说的是我自己么?)每当某件事做到一定程度时(也可以说达成最低目标),人就开始懈怠了,成就非凡的人一般来说都会把活干的很漂亮,技术能力也一样,或许我们不停的突破自己的极限,但就像打字一样,每个人都有自己的一个“基本极限”,我初三学习的盲打,从刚开始练习的每分钟30多字到后来每分钟的最快180字,渐渐落回150以下,从此再无心关注这个问题,至此我的极限就是100多字每分钟。而专注练习的打字员基本每分钟几百字不是问题(可能方法也不同,还有使用的是专用打字机)。相信大家最快也都最多200吧?编程似乎也一样,一旦陷入了这个临界值,我们就需要考虑下一步如何突破成为真正的打字高手了</p>
<h2 id="突破"><a href="#突破" class="headerlink" title="突破"></a>突破</h2><p>既然达到了舒适区、紧迫区、极限,那就要考虑突破,而往往这个问题是最为关键又无解的,因为每个人的状态不同,单一的方法不一定适用每个人,想要寻找一种有效的方法,可能会消耗很多精力、时间,还有可能走了一圈弯路。就个人情况具体问题具体分析吧,我也只是在不停的尝试,不敢妄言。</p>
<p>之前看到一个小问题:</p>
<ul>
<li>为什么初学两年的厨师比20年经验的母亲做的饭好吃的多?</li>
</ul>
<p>把问题迁移到编程上来说也一样,相关的问题可以参考:</p>
<ul>
<li>一年工作经验十年用</li>
</ul>
<p>今天看了一本讲解
求职,离职,入职
http://www.raomengyang.com/2017/05/16/求职,离职,入职/
2017-05-15T16:15:03.000Z
2017-05-15T16:15:03.000Z
<p>从 3 月底 4 月初一直拖到今天 5.15 ,早想把这一个月前前后后发生的事做个总结,把心里所想的表述出来,无奈各种琐事,或是心情五味陈杂,一拖拖了大半月。也正好,来了新公司工作了两周,前后一条线也够啰嗦小篇了。</p>
<p>主要谈及自己的三件事:</p>
<ul>
<li>求职</li>
<li>离职</li>
<li>入职</li>
</ul>
<h2 id="求职"><a href="#求职" class="headerlink" title="求职"></a>求职</h2><p>2015 年到今为止,毕业入职也有一年加半载有余,关于在前任公司的事儿也就不多说了。总而言之就是加了不少班,涨了不少肉,废了不少神,一大活人再待下去肯定要废了。为了成长,我选择从这个坑中跳了出来。于是迎来了求职的个人需求。</p>
<p>不过理应先离职再求职,无奈互联网(尤其是 Android / iOS 开发)这行近年来不景气,好多同行出去了才发现工作没想象的那么好找。在培训班对整个行业、市场的狂轰滥炸之下,管你什么语言什么技能什么职业,钱赚了就行。身边已经不少培训出来找不到工作还面临高额贷款的朋友了,大多人都眼红所谓阿猫阿狗都能年薪 xx 万的工作,可是阿猫阿狗这么多,岗位却是有限的啊。所以,我们程序猿不仅不是人,还连猫狗都不如。扯这么多就是想说,工作不好找,得骑驴找马。此次工作我也是请人帮忙内推才有的宝贵机会,机不可失时不再来。还好新公司挺近情面,容我一个月的时间做好帮老东家招兵买马工作交接的事,也证明了我的选择没错。</p>
<h2 id="离职"><a href="#离职" class="headerlink" title="离职"></a>离职</h2><p>3 月底定了工作,4 月清明节后回来立马向老板、老大他们提了离职这件事儿。意料之中,找我谈了两三天,我还是婉拒了,心似嫁出去的女儿泼出去的水,已经没有理由留于此。我能做的就是尽量把招人、交接这两件事做好。至于项目,我已经尽我最大的可能完善了,但求无愧于心。实话说,提离职的那几天,真不好受。想的最多的就是刚入职的那段苦逼又奋斗向前的日子。感谢老大曾经给我的这份工作机会,大恩不言谢。以后能做的就是用自己有限的能力尽量帮助需要帮助的人们。循环往复,技术也是这样传承的吧。很快的两周不到就招入了两位新人。</p>
<p>一个月的时间,感觉过了大半年似的。</p>
<h2 id="入职"><a href="#入职" class="headerlink" title="入职"></a>入职</h2><p>新公司不大,但是氛围属于我喜欢的那种创业公司。同事 NICE,老大和 CEO 人都很不错(面试的时候我就确定这点了),总而言之一切都如意。入职这两周也有压力,还好都在自己的掌控之中,剩下的就看自己的造化吧。不过还是很坚信,<strong>努力的人,运气一般都不会差。</strong></p>
<p>从 3 月底 4 月初一直拖到今天 5.15 ,早想把这一个月前前后后发生的事做个总结,把心里所想的表述出来,无奈各种琐事,或是心情五味陈杂,一拖拖了大半月。也正好,来了新公司工作了两周,前后一条线也够啰嗦小篇了。</p>
<p>主要谈及自己的三件事:</p>
<ul>
<
Android实现录屏直播(三)MediaProjection VirtualDisplay librtmp MediaCodec实现视频编码并推流到rtmp服务器
http://www.raomengyang.com/2017/04/18/Android实现录屏直播(三)MediaProjection VirtualDisplay librtmp MediaCodec实现视频编码并推流到rtmp服务器/
2017-04-18T02:45:33.000Z
2017-04-18T02:45:46.000Z
<p>请尊重分享成果,转载请注明出处,本文来自<a href="http://my.csdn.net/zxccxzzxz" target="_blank" rel="external">Coder包子哥</a>,原文链接:<a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272" target="_blank" rel="external">http://blog.csdn.net/zxccxzzxz/article/details/55230272</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396" target="_blank" rel="external">Android实现录屏直播(一)ScreenRecorder的简单分析</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244" target="_blank" rel="external">Android实现录屏直播(二)需求才是硬道理之产品功能调研</a></p>
<a id="more"></a>
<p>看到有网友在后台私信和询问录屏这部分推流相关的问题,感觉这篇博客早该写完了。事实上除了繁忙的工作加上春节假期一下子拖了近一个月之久。近期更新了Demo,加入了视频帧推流,需要的朋友可以看看Demo。</p>
<p>无论是音频还是视频编码,我们都需要原始的数据源,拿视频举例子,实际录屏直播也只是将屏幕的每一个视图间接的获取充当原始视频帧,和摄像头获取视频帧原理区别不大。如今Android设备几乎都已经满足硬编码的条件了(虽然有好有坏),所以我们假装曾经那些兼容性问题都不存在。</p>
<p>我们向服务器发送视频数据的时候,还需要先发送视频参数的数据给服务器以区分我们的视频源的格式、类型及FLV封装的一些关键信息(File Header / File TAG等如sps / pps等相关的meta data)给解码器。(PS: 关于FLV格式封装等视频编解码的分析推荐看看<a href="http://blog.csdn.net/leixiaohua1020" target="_blank" rel="external">雷博</a>的相关博文)MediaCodec的细节可以自行查阅官方API。</p>
<p>并且推荐这些对我极为有用的文章资料,感谢这些作者的无私分享:</p>
<ul>
<li><a href="https://github.com/lakeinchina/librestreaming" target="_blank" rel="external">librestreaming</a></li>
</ul>
<p>作者自行封装及实现的一个Android实施滤镜、RTMP推流的类库。代码结构需要花点时间理解和读懂,值得深入学习其中的实现,因为使用MediaProjection / VirtualDisplay 来进行录屏的话,官方并不提供帧率的控制,这需要用到OpenGL ES将VirtualDisplay中的surface进行绘制到MediaCodec中的surface。然而在这个库中作者已经实现了全部的操作,<strong>本篇文章也会围绕该库进行大致的分析</strong>。</p>
<ul>
<li><a href="http://www.jianshu.com/p/b61ea6783b07" target="_blank" rel="external">屏幕录制(二)——帧率控制</a></li>
</ul>
<p>在找到上面的类库之前,还是这位作者给出的思路才能够一点点往OpenGL ES这个坑里跳,越入越深,差点没爬出来。由于作者不方便放出源码,我只能通过他的描述一点点的实现。并且StackOverFlow中网友fadden也给出了相关的思路:<a href="http://stackoverflow.com/questions/31527134/controlling-frame-rate-of-virtualdisplay" target="_blank" rel="external">controlling-frame-rate-of-virtualdisplay</a></p>
<ul>
<li><a href="https://github.com/google/grafika" target="_blank" rel="external">grafika</a></li>
</ul>
<p>Google官方给的Demo,基本涵盖了OpenGL的各类用法,好好看看吧。</p>
<ul>
<li><a href="http://bigflake.com/mediacodec/" target="_blank" rel="external">Android MediaCodec stuff</a></li>
</ul>
<p>一个Android MediaCodec的超详细的博客,实例Demo很明确。</p>
<p>问题的缘由来自工作中某些需求所引起的,接下来我一一描述。</p>
<h2 id="需求一-帧率控制"><a href="#需求一-帧率控制" class="headerlink" title="需求一 帧率控制"></a>需求一 帧率控制</h2><p>好不容易开发完成录屏直播,结果在低码率或者网络波动大的情况下,很多机型(尤其是小米)在60帧满帧的条件打出来的视频是那样的酸爽,动态的画面简直眼瞎。老板要求改帧率!降低帧率到30看看什么情况,最后实际选择使用了15FPS。</p>
<p>在快速滑动屏幕或者画面变换频繁的情况下改善视频模糊的做法:(参见<a href="https://github.com/lakeinchina/librestreaming/issues/11)" target="_blank" rel="external">https://github.com/lakeinchina/librestreaming/issues/11)</a></p>
<ul>
<li>提高码率BPS</li>
<li>降低帧率</li>
<li>调小关键帧间隔(MediaFormat.KEY_I_FRAME_INTERVAL)</li>
<li>提高AVCProfile(Android默认使用的是BaseLine模式)</li>
<li>编码器的好坏也有关系</li>
</ul>
<h2 id="需求二-稳定性(保活)"><a href="#需求二-稳定性(保活)" class="headerlink" title="需求二 稳定性(保活)"></a>需求二 稳定性(保活)</h2><p>性能比起Bilibili还是要差一些,这里挖个坑,之后再填。</p>
<p>后期设定的方案是将推流放到remote service当中,该service为前台独立进程的service,对主进程的依赖性减弱一些(虽然APP在被杀死的时候也可能被杀死)</p>
<p>通过开启远程服务并与APP的进程进行进程间通信(IPC),寻求保活的方式花了一段时间,最后对MIUI的系统机制还是无果,Debug的时候发现MIUI拥有一个PowerKeeper,一旦触发就会对任何后台进程的APP(据说有白名单)进行KillApplication操作,在我的压力测试下,无一应用幸免(包括优化得极其稳定的Bilibili,GooglePlay录屏APP排行第一的AZ ScreenRecorder)。</p>
<h2 id="近期涉及到的技术知识点:"><a href="#近期涉及到的技术知识点:" class="headerlink" title="近期涉及到的技术知识点:"></a>近期涉及到的技术知识点:</h2><ul>
<li>Java多线程并发(线程之间的通信,并发时锁的使用)</li>
<li>OpenGL ES(对Surface与Surface之间的数据传递)</li>
<li>Android消息机制的深入理解(Handler内部实现及熟练运用) </li>
</ul>
<h1 id="Updated-3-12"><a href="#Updated-3-12" class="headerlink" title="Updated 3.12"></a>Updated 3.12</h1><p>之前阿里云搞活动,12块买了个1核2G / 1M 半年的服务器,正好一直闲置没用,为了完成这篇博客我也真是够拼的了,先按照上述链接搭建一个基于Nginx + RTMP协议的流媒体服务器。搭服务器的目的是为了完成推流的操作,毕竟不想用公司的资源来进行私人的活动。</p>
<p>很多朋友都问推流什么时候才有,那么今天我就完完整整的将录屏推流这块完善,Android客户端的Demo + 推流服务器的步骤实现,时间有限,只注重实现,代码质量之后重构。</p>
<p>丑话再说在前头,我本着一颗开源分享和学习的心来写博客和Demo,认为好的点赞、评论大家随意,但是本人能力和精力有限,这本属于一个Demo,如果认为太烂没参考价值,那么还请留点口德,默默关闭本页即可,有问题提出来我会回复并以改正,请求勿喷,谢谢~</p>
<h2 id="Demo结构"><a href="#Demo结构" class="headerlink" title="Demo结构"></a>Demo结构</h2><p>大概将会包含几个部分:</p>
<ol>
<li>录屏 : 客户端实现(官方API,目前仅支持Android 5.0以上)</li>
<li>原始帧的格式转换,分辨率、方向转换:libyuv,这里没有使用librestreaming中的封装方法。(录屏在使用OpenGL绘制之前直接使用的是API配置的参数,故暂时<strong>跳过该步骤</strong>)</li>
<li>FLV的格式封装: 套用librestreaming中的算法(Java),实际我的项目中是通过一种不巧妙地算法将sps / pps 硬拼凑出来的。虽然也是同样的原理,但个人更推荐该库的处理方式,代码清晰易懂。</li>
<li>rtmp推流:前期先准备好相应的librtmp的源码,构建项目的同时进行引用编译成静态库,本次直接引用librestreaming中的代码稍作修改,有关librtmp更详细的内容可参见雷博的文章: <a href="http://blog.csdn.net/leixiaohua1020/article/details/42104945" target="_blank" rel="external">http://blog.csdn.net/leixiaohua1020/article/details/42104945</a></li>
</ol>
<p><strong>录屏推流的流程:</strong></p>
<p>客户端原始帧编码为H264裸流(MediaCodec,Android API) —> 再封装为FLV格式的视频流(Java) —> 按照rtmp流媒体协议通过librtmp(JNI + C)推流到RTMP流媒体服务器 —> 客户端播放器解码观看</p>
<p>注意:</p>
<p>Demo省略了librestreaming中的OpenGL处理帧率的过程,也就意味着我们使用的是MediaCodec直接编码后的数据,并没有OpenGL绘制VirtualDisplay映射给MediaCodec.createInputSurface中Surface的这个过程,目的是将流程简单化。OpenGL方面的使用之后我也会介绍说明。</p>
<p>可以看到实现录屏到<strong>本地</strong>的ScreenRecorder直接通过下面的方法进行音视频写入,而推流的话需要做以修改。</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">mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);</span><br></pre></td></tr></table></figure>
<h2 id="FLV-Header的获取-SPS-PPS"><a href="#FLV-Header的获取-SPS-PPS" class="headerlink" title="FLV Header的获取(SPS PPS)"></a>FLV Header的获取(SPS PPS)</h2><p>FLV的头文件信息发送给服务器后,就可以将我们的关键帧发送,注意流媒体服务器解析的时候首先要先得到第一帧关键帧才会开始解析后面的视频帧,所以我们还需要在编码器获取IDR帧的时候进行发送。MediaCodec的<code>INFO_OUTPUT_FORMAT_CHANGED</code>这个状态可以获取sps / pps,再将数据处理包装后打到FLV的TAG中。</p>
<figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sendAVCDecoderConfigurationRecord</span><span class="params">(<span class="keyword">long</span> tms, MediaFormat format)</span> </span>{</span><br><span class="line"> <span class="keyword">byte</span>[] AVCDecoderConfigurationRecord = Packager.H264Packager.generateAVCDecoderConfigurationRecord(format);</span><br><span class="line"> <span class="keyword">int</span> packetLen = Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH +</span><br><span class="line"> AVCDecoderConfigurationRecord.length;</span><br><span class="line"> <span class="keyword">byte</span>[] finalBuff = <span class="keyword">new</span> <span class="keyword">byte</span>[packetLen];</span><br><span class="line"> Packager.FLVPackager.fillFlvVideoTag(finalBuff,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="keyword">true</span>,</span><br><span class="line"> <span class="keyword">true</span>,</span><br><span class="line"> AVCDecoderConfigurationRecord.length);</span><br><span class="line"> System.arraycopy(AVCDecoderConfigurationRecord, <span class="number">0</span>,</span><br><span class="line"> finalBuff, Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH, AVCDecoderConfigurationRecord.length);</span><br><span class="line"> RESFlvData resFlvData = <span class="keyword">new</span> RESFlvData();</span><br><span class="line"> resFlvData.droppable = <span class="keyword">false</span>;</span><br><span class="line"> resFlvData.byteBuffer = finalBuff;</span><br><span class="line"> resFlvData.size = finalBuff.length;</span><br><span class="line"> resFlvData.dts = (<span class="keyword">int</span>) tms;</span><br><span class="line"> resFlvData.flvTagType = RESFlvData.FLV_RTMP_PACKET_TYPE_VIDEO;</span><br><span class="line"> resFlvData.videoFrameType = RESFlvData.NALU_TYPE_IDR;</span><br><span class="line"> dataCollecter.collect(resFlvData, RESRtmpSender.FROM_VIDEO);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><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"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] generateAVCDecoderConfigurationRecord(MediaFormat mediaFormat) {</span><br><span class="line"> ByteBuffer SPSByteBuff = mediaFormat.getByteBuffer(<span class="string">"csd-0"</span>);</span><br><span class="line"> SPSByteBuff.position(<span class="number">4</span>);</span><br><span class="line"> ByteBuffer PPSByteBuff = mediaFormat.getByteBuffer(<span class="string">"csd-1"</span>);</span><br><span class="line"> PPSByteBuff.position(<span class="number">4</span>);</span><br><span class="line"> <span class="keyword">int</span> spslength = SPSByteBuff.remaining();</span><br><span class="line"> <span class="keyword">int</span> ppslength = PPSByteBuff.remaining();</span><br><span class="line"> <span class="keyword">int</span> length = <span class="number">11</span> + spslength + ppslength;</span><br><span class="line"> <span class="keyword">byte</span>[] result = <span class="keyword">new</span> <span class="keyword">byte</span>[length];</span><br><span class="line"> SPSByteBuff.get(result, <span class="number">8</span>, spslength);</span><br><span class="line"> PPSByteBuff.get(result, <span class="number">8</span> + spslength + <span class="number">3</span>, ppslength);</span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * UB[8]configurationVersion</span><br><span class="line"> * UB[8]AVCProfileIndication</span><br><span class="line"> * UB[8]profile_compatibility</span><br><span class="line"> * UB[8]AVCLevelIndication</span><br><span class="line"> * UB[8]lengthSizeMinusOne</span><br><span class="line"> */</span></span><br><span class="line"> result[<span class="number">0</span>] = <span class="number">0x01</span>;</span><br><span class="line"> result[<span class="number">1</span>] = result[<span class="number">9</span>];</span><br><span class="line"> result[<span class="number">2</span>] = result[<span class="number">10</span>];</span><br><span class="line"> result[<span class="number">3</span>] = result[<span class="number">11</span>];</span><br><span class="line"> result[<span class="number">4</span>] = (<span class="keyword">byte</span>) <span class="number">0xFF</span>;</span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * UB[8]numOfSequenceParameterSets</span><br><span class="line"> * UB[16]sequenceParameterSetLength</span><br><span class="line"> */</span></span><br><span class="line"> result[<span class="number">5</span>] = (<span class="keyword">byte</span>) <span class="number">0xE1</span>;</span><br><span class="line"> ByteArrayTools.intToByteArrayTwoByte(result, <span class="number">6</span>, spslength);</span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * UB[8]numOfPictureParameterSets</span><br><span class="line"> * UB[16]pictureParameterSetLength</span><br><span class="line"> */</span></span><br><span class="line"> <span class="keyword">int</span> pos = <span class="number">8</span> + spslength;</span><br><span class="line"> result[pos] = (<span class="keyword">byte</span>) <span class="number">0x01</span>;</span><br><span class="line"> ByteArrayTools.intToByteArrayTwoByte(result, pos + <span class="number">1</span>, ppslength);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在librestreaming中Packager.java这个类主要就是做了上面这些事。仅两个方法实现,作者代码逻辑清晰易懂,这里就不多说了,再次感谢Lake哥!</p>
<p>可以看到音视频编码线程共用同一个RESFlvDataCollecter接口,负责监听编码线程的一举一动,从接口中将音视频编码帧送到同一个帧队列,发送线程取数据时取到什么就把数据喂给RtmpStreamingSender发送出去。</p>
<figure class="highlight java"><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">dataCollecter = <span class="keyword">new</span> RESFlvDataCollecter() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">collect</span><span class="params">(RESFlvData flvData, <span class="keyword">int</span> type)</span> </span>{</span><br><span class="line"> rtmpSender.feed(flvData, type);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>在librestreaming中使用了HandlerThread为WorkHandler提供Looper,通过Handler的消息队列循环机制来控制数据的发送,实际也可以自定义线程,手动管理视频帧收发队列,但涉及到了并发抢占资源的问题,更推荐Handler这种方式,并且Handler处理除了效率高、逻辑清晰,易管理之外还有一个好处,如果使用OpenGL绘制Surface时,正好可以Handler处理其中的异步操作。</p>
<p>不过在Demo中我修改为了一个普通的Runnable任务,<code>run()</code>中循环处理<code>frameQueue</code>中的数据。代码如下:</p>
<figure class="highlight java"><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><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RtmpStreamingSender</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MAX_QUEUE_CAPACITY = <span class="number">50</span>;</span><br><span class="line"> <span class="keyword">private</span> AtomicBoolean mQuit = <span class="keyword">new</span> AtomicBoolean(<span class="keyword">false</span>);</span><br><span class="line"> <span class="keyword">private</span> LinkedBlockingDeque<RESFlvData> frameQueue = <span class="keyword">new</span> LinkedBlockingDeque<>(MAX_QUEUE_CAPACITY);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Object syncWriteMsgNum = <span class="keyword">new</span> Object();</span><br><span class="line"> <span class="keyword">private</span> FLvMetaData fLvMetaData;</span><br><span class="line"> <span class="keyword">private</span> RESCoreParameters coreParameters; </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">long</span> jniRtmpPointer = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> maxQueueLength = <span class="number">150</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> writeMsgNum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> String rtmpAddr = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">STATE</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> START = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> RUNNING = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> STOPPED = <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">RtmpStreamingSender</span><span class="params">()</span> </span>{</span><br><span class="line"> coreParameters = <span class="keyword">new</span> RESCoreParameters();</span><br><span class="line"> coreParameters.mediacodecAACBitRate = <span class="number">32</span> * <span class="number">1024</span>;</span><br><span class="line"> coreParameters.mediacodecAACSampleRate = <span class="number">44100</span>;</span><br><span class="line"> coreParameters.mediacodecAVCFrameRate = <span class="number">20</span>;</span><br><span class="line"> coreParameters.videoWidth = <span class="number">1280</span>;</span><br><span class="line"> coreParameters.videoHeight = <span class="number">720</span>;</span><br><span class="line"> fLvMetaData = <span class="keyword">new</span> FLvMetaData(coreParameters);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (!mQuit.get()) {</span><br><span class="line"> <span class="keyword">if</span> (frameQueue.size() > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">switch</span> (state) {</span><br><span class="line"> <span class="keyword">case</span> STATE.START:</span><br><span class="line"> LogTools.d(<span class="string">"RESRtmpSender,WorkHandler,tid="</span> + Thread.currentThread().getId());</span><br><span class="line"> <span class="keyword">if</span> (TextUtils.isEmpty(rtmpAddr)) {</span><br><span class="line"> LogTools.e(<span class="string">"rtmp address is null!"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> jniRtmpPointer = RtmpClient.open(rtmpAddr, <span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> openR = jniRtmpPointer == <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line"> String serverIpAddr = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (openR == <span class="number">0</span>) {</span><br><span class="line"> serverIpAddr = RtmpClient.getIpAddr(jniRtmpPointer);</span><br><span class="line"> LogTools.d(<span class="string">"server ip address = "</span> + serverIpAddr);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (jniRtmpPointer == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">byte</span>[] MetaData = fLvMetaData.getMetaData();</span><br><span class="line"> RtmpClient.write(jniRtmpPointer,</span><br><span class="line"> MetaData,</span><br><span class="line"> MetaData.length,</span><br><span class="line"> RESFlvData.FLV_RTMP_PACKET_TYPE_INFO, <span class="number">0</span>);</span><br><span class="line"> state = STATE.RUNNING;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> STATE.RUNNING:</span><br><span class="line"> <span class="keyword">synchronized</span> (syncWriteMsgNum) {</span><br><span class="line"> --writeMsgNum;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (state != STATE.RUNNING) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> RESFlvData flvData = frameQueue.pop();</span><br><span class="line"> <span class="keyword">if</span> (writeMsgNum >= (maxQueueLength * <span class="number">2</span> / <span class="number">3</span>) && flvData.flvTagType == RESFlvData.FLV_RTMP_PACKET_TYPE_VIDEO && flvData.droppable) {</span><br><span class="line"> LogTools.d(<span class="string">"senderQueue is crowded,abandon video"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> res = RtmpClient.write(jniRtmpPointer, flvData.byteBuffer, flvData.byteBuffer.length, flvData.flvTagType, flvData.dts);</span><br><span class="line"> <span class="keyword">if</span> (res == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (flvData.flvTagType == RESFlvData.FLV_RTMP_PACKET_TYPE_VIDEO) {</span><br><span class="line"> LogTools.d(<span class="string">"video frame sent = "</span> + flvData.size);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> LogTools.d(<span class="string">"audio frame sent = "</span> + flvData.size);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> LogTools.e(<span class="string">"writeError = "</span> + res);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> STATE.STOPPED:</span><br><span class="line"> <span class="keyword">if</span> (state == STATE.STOPPED || jniRtmpPointer == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> closeR = RtmpClient.close(jniRtmpPointer);</span><br><span class="line"> serverIpAddr = <span class="keyword">null</span>;</span><br><span class="line"> LogTools.e(<span class="string">"close result = "</span> + closeR);</span><br><span class="line"> <span class="keyword">break</span>;</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"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendStart</span><span class="params">(String rtmpAddr)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (syncWriteMsgNum) {</span><br><span class="line"> writeMsgNum = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.rtmpAddr = rtmpAddr;</span><br><span class="line"> state = STATE.START;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendStop</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (syncWriteMsgNum) {</span><br><span class="line"> writeMsgNum = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> state = STATE.STOPPED;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendFood</span><span class="params">(RESFlvData flvData, <span class="keyword">int</span> type)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (syncWriteMsgNum) {</span><br><span class="line"> <span class="comment">//LAKETODO optimize</span></span><br><span class="line"> <span class="keyword">if</span> (writeMsgNum <= maxQueueLength) {</span><br><span class="line"> frameQueue.add(flvData);</span><br><span class="line"> ++writeMsgNum;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> LogTools.d(<span class="string">"senderQueue is full,abandon"</span>);</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 class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">quit</span><span class="params">()</span> </span>{</span><br><span class="line"> mQuit.set(<span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>RtmpStreamingSender.java这个类的大部分方法都引入了librestreaming中RESRtmpSender.java类中的代码实现,只是将其改为了之前所说的线程循环机制,并没有用Handler。</p>
<h2 id="RTMP-package的封装与使用"><a href="#RTMP-package的封装与使用" class="headerlink" title="RTMP package的封装与使用"></a>RTMP package的封装与使用</h2><p>RtmpClient中包含了jni的native方法,可以看到有对应了screenrecorderrtmp.h中的几个方法:</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">long</span> <span class="title">open</span><span class="params">(String url, <span class="keyword">boolean</span> isPublishMode)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">read</span><span class="params">(<span class="keyword">long</span> rtmpPointer, <span class="keyword">byte</span>[] data, <span class="keyword">int</span> offset, <span class="keyword">int</span> size)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">write</span><span class="params">(<span class="keyword">long</span> rtmpPointer, <span class="keyword">byte</span>[] data, <span class="keyword">int</span> size, <span class="keyword">int</span> type, <span class="keyword">int</span> ts)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">close</span><span class="params">(<span class="keyword">long</span> rtmpPointer)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> String <span class="title">getIpAddr</span><span class="params">(<span class="keyword">long</span> rtmpPointer)</span></span>;</span><br></pre></td></tr></table></figure>
<p>我们可根据RtmpClient.java这个Jni入口类生成 jni的c文件screenrecorderrtmp.h,再到项目的java目录,使用以下命令在同级目录下创建一个jni/screenrecorderrtmp.h 文件。</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">javah -d jni net.yrom.screenrecorder.rtmp.RtmpClient</span><br></pre></td></tr></table></figure>
<p>接着对应h文件编写c的rtmp推流代码,screenrecorderrtmp.c 如下:</p>
<figure class="highlight c"><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><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><jni.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><screenrecorderrtmp.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><malloc.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="string">"rtmp.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">JNIEXPORT jlong JNICALL <span class="title">Java_net_yrom_screenrecorder_rtmp_RtmpClient_open</span></span><br><span class="line"> <span class="params">(JNIEnv * env, jobject thiz, jstring url_, jboolean isPublishMode)</span> </span>{</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">char</span> *url = (*env)->GetStringUTFChars(env, url_, <span class="number">0</span>);</span><br><span class="line"> LOGD(<span class="string">"RTMP_OPENING:%s"</span>,url);</span><br><span class="line"> RTMP* rtmp = RTMP_Alloc();</span><br><span class="line"> <span class="keyword">if</span> (rtmp == <span class="literal">NULL</span>) {</span><br><span class="line"> LOGD(<span class="string">"RTMP_Alloc=NULL"</span>);</span><br><span class="line"> return <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> RTMP_Init(rtmp);</span><br><span class="line"> <span class="keyword">int</span> ret = RTMP_SetupURL(rtmp, url);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!ret) {</span><br><span class="line"> RTMP_Free(rtmp);</span><br><span class="line"> rtmp=<span class="literal">NULL</span>;</span><br><span class="line"> LOGD(<span class="string">"RTMP_SetupURL=ret"</span>);</span><br><span class="line"> return <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (isPublishMode) {</span><br><span class="line"> RTMP_EnableWrite(rtmp);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ret = RTMP_Connect(rtmp, NULL);</span><br><span class="line"> <span class="keyword">if</span> (!ret) {</span><br><span class="line"> RTMP_Free(rtmp);</span><br><span class="line"> rtmp=<span class="literal">NULL</span>;</span><br><span class="line"> LOGD(<span class="string">"RTMP_Connect=ret"</span>);</span><br><span class="line"> return <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> ret = RTMP_ConnectStream(rtmp, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!ret) {</span><br><span class="line"> ret = RTMP_ConnectStream(rtmp, <span class="number">0</span>);</span><br><span class="line"> RTMP_Close(rtmp);</span><br><span class="line"> RTMP_Free(rtmp);</span><br><span class="line"> rtmp=<span class="literal">NULL</span>;</span><br><span class="line"> LOGD(<span class="string">"RTMP_ConnectStream=ret"</span>);</span><br><span class="line"> return <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> (*env)->ReleaseStringUTFChars(env, url_, url);</span><br><span class="line"> LOGD(<span class="string">"RTMP_OPENED"</span>);</span><br><span class="line"> return rtmp;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span><br><span class="line"> * Class: net_yrom_screenrecorder_rtmp_RtmpClient</span><br><span class="line"> * Method: read</span><br><span class="line"> * Signature: (J[BII)I</span><br><span class="line"> */</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL <span class="title">Java_net_yrom_screenrecorder_rtmp_RtmpClient_read</span></span><br><span class="line"><span class="params">(JNIEnv * env, jobject thiz,jlong rtmp, jbyteArray data_, jint offset, jint size)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span>* data = <span class="built_in">malloc</span>(size*sizeof(char));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> readCount = RTMP_Read((RTMP*)rtmp, data, size);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (readCount > <span class="number">0</span>) {</span><br><span class="line"> (*env)->SetByteArrayRegion(env, data_, offset, readCount, data); <span class="comment">// copy</span></span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">free</span>(data);</span><br><span class="line"></span><br><span class="line"> return readCount;</span><br><span class="line">}</span><br><span class="line"><span class="comment">/*</span><br><span class="line"> * Class: net_yrom_screenrecorder_rtmp_RtmpClient</span><br><span class="line"> * Method: write</span><br><span class="line"> * Signature: (J[BIII)I</span><br><span class="line"> */</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL <span class="title">Java_net_yrom_screenrecorder_rtmp_RtmpClient_write</span></span><br><span class="line"><span class="params">(JNIEnv * env, jobject thiz,jlong rtmp, jbyteArray data, jint size, jint type, jint ts)</span> </span>{</span><br><span class="line"> LOGD(<span class="string">"start write"</span>);</span><br><span class="line"> jbyte *buffer = (*env)->GetByteArrayElements(env, data, NULL);</span><br><span class="line"> RTMPPacket *packet = (RTMPPacket*)<span class="built_in">malloc</span>(sizeof(RTMPPacket));</span><br><span class="line"> RTMPPacket_Alloc(packet, size);</span><br><span class="line"> RTMPPacket_Reset(packet);</span><br><span class="line"> <span class="keyword">if</span> (type == RTMP_PACKET_TYPE_INFO) { <span class="comment">// metadata</span></span><br><span class="line"> packet->m_nChannel = <span class="number">0x03</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (type == RTMP_PACKET_TYPE_VIDEO) { <span class="comment">// video</span></span><br><span class="line"> packet->m_nChannel = <span class="number">0x04</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (type == RTMP_PACKET_TYPE_AUDIO) { <span class="comment">//audio</span></span><br><span class="line"> packet->m_nChannel = <span class="number">0x05</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> packet->m_nChannel = <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> packet->m_nInfoField2 = ((RTMP*)rtmp)->m_stream_id;</span><br><span class="line"></span><br><span class="line"> LOGD(<span class="string">"write data type: %d, ts %d"</span>, type, ts);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">memcpy</span>(packet->m_body, buffer, size);</span><br><span class="line"> packet->m_headerType = RTMP_PACKET_SIZE_LARGE;</span><br><span class="line"> packet->m_hasAbsTimestamp = FALSE;</span><br><span class="line"> packet->m_nTimeStamp = ts;</span><br><span class="line"> packet->m_packetType = type;</span><br><span class="line"> packet->m_nBodySize = size;</span><br><span class="line"> <span class="keyword">int</span> ret = RTMP_SendPacket((RTMP*)rtmp, packet, <span class="number">0</span>);</span><br><span class="line"> RTMPPacket_Free(packet);</span><br><span class="line"> <span class="built_in">free</span>(packet);</span><br><span class="line"> (*env)->ReleaseByteArrayElements(env, data, buffer, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (!ret) {</span><br><span class="line"> LOGD(<span class="string">"end write error %d"</span>, sockerr);</span><br><span class="line"> return sockerr;</span><br><span class="line"> }<span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> LOGD(<span class="string">"end write success"</span>);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">/*</span><br><span class="line"> * Class: net_yrom_screenrecorder_rtmp_RtmpClient</span><br><span class="line"> * Method: close</span><br><span class="line"> * Signature: (J)I</span><br><span class="line"> */</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL <span class="title">Java_net_yrom_screenrecorder_rtmp_RtmpClient_close</span></span><br><span class="line"> <span class="params">(JNIEnv * env,jlong rtmp, jobject thiz)</span> </span>{</span><br><span class="line"> RTMP_Close((RTMP*)rtmp);</span><br><span class="line"> RTMP_Free((RTMP*)rtmp);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"><span class="comment">/*</span><br><span class="line"> * Class: net_yrom_screenrecorder_rtmp_RtmpClient</span><br><span class="line"> * Method: getIpAddr</span><br><span class="line"> * Signature: (J)Ljava/lang/String;</span><br><span class="line"> */</span></span><br><span class="line"><span class="function">JNIEXPORT jstring JNICALL <span class="title">Java_net_yrom_screenrecorder_rtmp_RtmpClient_getIpAddr</span></span><br><span class="line"> <span class="params">(JNIEnv * env,jobject thiz,jlong rtmp)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(rtmp!=<span class="number">0</span>){</span><br><span class="line"> RTMP* r= (RTMP*)rtmp;</span><br><span class="line"> return (*env)->NewStringUTF(env, r->ipaddr);</span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> return (*env)->NewStringUTF(env, <span class="string">""</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>更多的请看Demo源码,说下目前未实现的功能和问题:</p>
<ul>
<li>音频编码还没有,只有视频的部分。考虑到音频可以通过FFmpeg等lib进行软编码,也可以MediaCodec硬编,需要的话可以自行加入</li>
<li>有一定延迟,发送队列需要优化</li>
<li>录屏的API有个坑,就是在屏幕内容没有变化的时候,是不会刷新绘制帧率的,也就是如果最后一帧是主页,而此时主页没有任何UI变化,那么MediaCodec会停止编码工作,直到屏幕上有任何UI变化(包括一像素)则继续编码。遇到这个问题当时想了一些办法感觉太复杂,所以我则在悬浮窗加了个闪烁的提示条,只要在录屏过程中提示条就会闪动,这样就巧妙的避免了上述问题的发生</li>
<li>不可帧率控制,其实Demo大部分还是引用的是librestreaming的代码,因为时间有限,就不做过多的开发,还是推荐使用和理解该库的原理,并不复杂,并且已经实现了帧率控制,丢帧等处理。对OpenGL不熟悉的朋友可以先忽略这部分的实现,只看原始帧采集,编码器编码,FLV封装,推流这几个部分即可</li>
</ul>
<h2 id="RTMP服务器"><a href="#RTMP服务器" class="headerlink" title="RTMP服务器"></a>RTMP服务器</h2><p>参照Nginx + rtmp搭建了流媒体服务器用来测试,Demo中键入以下地址便可推流,yourstramingkey自定义,</p>
<figure class="highlight java"><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">rtmp:<span class="comment">//59.110.159.133/live/<yourstramingkey></span></span><br><span class="line">例如:rtmp:<span class="comment">//59.110.159.133/live/test</span></span><br></pre></td></tr></table></figure>
<h2 id="效果图"><a href="#效果图" class="headerlink" title="效果图"></a>效果图</h2><p><img src="https://raw.githubusercontent.com/eterrao/ScreenRecorder/master/images/ScreenRecorderDemo.jpeg" alt="demo"></p>
<h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><p>传送门:<a href="https://github.com/eterrao/ScreenRecorder" target="_blank" rel="external">GitHub</a></p>
<h2 id="更多参考文章:"><a href="#更多参考文章:" class="headerlink" title="更多参考文章:"></a>更多参考文章:</h2><ul>
<li><a href="http://stackoverflow.com/questions/31527134/controlling-frame-rate-of-virtualdisplay" target="_blank" rel="external">controlling-frame-rate-of-virtualdisplay</a></li>
<li><a href="http://blog.csdn.net/ray_chou/article/details/48416467" target="_blank" rel="external">TextureView+SurfaceTexture+OpenGL ES来播放视频系列</a></li>
<li><a href="https://github.com/GH-HOME/DecodeEncodeMP4" target="_blank" rel="external">DecodeEncodeMP4</a></li>
<li><a href="http://blog.csdn.net/leixiaohua1020/article/details/42104945" target="_blank" rel="external">最简单的基于librtmp的示例:发布(FLV通过RTMP发布)</a></li>
<li><a href="http://blog.csdn.net/leixiaohua1020/article/details/39803457" target="_blank" rel="external">最简单的基于FFmpeg的推流器(以推送RTMP为例)</a></li>
<li><a href="http://www.cnblogs.com/cocoajin/p/4353767.html" target="_blank" rel="external">在Ubuntu 14 上安装 Nginx-RTMP 流媒体服务器</a></li>
</ul>
<p>请尊重分享成果,转载请注明出处,本文来自<a href="http://my.csdn.net/zxccxzzxz">Coder包子哥</a>,原文链接:<a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272">http://blog.csdn.net/zxccxzzxz/article/details/55230272</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396">Android实现录屏直播(一)ScreenRecorder的简单分析</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244">Android实现录屏直播(二)需求才是硬道理之产品功能调研</a></p>
个人对面试者和面试官的几点经验总结
http://www.raomengyang.com/2017/04/18/个人对面试者和面试官的几点经验总结/
2017-04-18T02:39:33.000Z
2017-04-18T02:39:33.000Z
<p>近期因为公司招人的原因做了回面试官,做了些许总结以备自戒。</p>
<h2 id="面试者:"><a href="#面试者:" class="headerlink" title="面试者:"></a>面试者:</h2><ul>
<li><p>大部分面试者都有些紧张,其实很正常,我自己面试的时候感觉比他们紧张多了,而且刚开始的时候我自己虽然是面试官,也会感觉很紧张,生怕因为自己的不足导致面试者失去一次宝贵的工作机会,但多面几次就逐渐适应了,所以紧张还是因为自己经历得比较少</p>
</li>
<li><p>面试者简历投递的时候,一定不要超过 3 页,并且把你熟悉掌握的技术从前往后依次排列</p>
</li>
<li><p>工作经历很重要,但是感觉很多面试者的简历上面并没有突出面试官想看到的重点:</p>
<ul>
<li><p>是否项目的负责人,或负责项目的比重,承担那些重要职责</p>
</li>
<li><p>在项目中攻克了哪些难关,复杂度如何,我很在意面试者有没有对疑难杂症有更好的思路,如果出现问题第一时间如何去解决,有没有责任感,但是往往这一点很难从简短的面试中体现出来</p>
</li>
</ul>
</li>
<li><p>最好提供自己的技术博客和 GitHub,有助于面试官多了解你个人的技术能力</p>
</li>
<li><p>简历如实写作,遇到过简历上写了自己并不掌握的知识点,这样面试官问到但无法回答的时候会冷场很尴尬的</p>
</li>
</ul>
<h2 id="面试官:"><a href="#面试官:" class="headerlink" title="面试官:"></a>面试官:</h2><ul>
<li><p>在面试前要看看将面试的面试者的简历,做个准备</p>
</li>
<li><p>除了简历上的点,还需要准备一些必问的题,不能临时想什么问什么,这样会让人感觉不专业不负责,再根据面试者简历准备一下适合他的问题</p>
</li>
<li><p>对面试者的时候态度要谨慎,毕竟很多面试者难免都会很紧张,适当的暖暖场可以让双方的交流更顺畅,面试者也能充分发挥自己的实力</p>
</li>
<li><p>掌握时间,技术能力强的,值得面的人可以多花时间深入了解,技术一般的。不合适的人,需要的话可以给予建议,根据自己的时间适时而止</p>
</li>
<li><p>切记,不要把你熟悉掌握的某一个专一技术点拿来抨击和当作评判别人的标准,很可能别人比你牛的多,只是这部分没接触过罢了</p>
</li>
<li><p>懂得尊重,谁都是从菜鸟过来的</p>
</li>
</ul>
<p>后续想到再补充。。。</p>
<p>近期因为公司招人的原因做了回面试官,做了些许总结以备自戒。</p>
<h2 id="面试者:"><a href="#面试者:" class="headerlink" title="面试者:"></a>面试者:</h2><ul>
<li><p>大部分面试者都有些紧张,其实很正
http://www.raomengyang.com/2017/02/24/如果当时没有选择,那么我会在哪?/
2017-02-23T17:23:52.000Z
2017-02-23T17:23:52.000Z
<p>如果当时没有选择,那么我会在哪?</p>
<p>如今毕业已快两年已久,两年前的今天我还在初学Java,刚通过师兄接触Android,从此踏入编程这条不归路。</p>
<p>很感激自己当初的选择,让我能够在一个单纯又靠自己双手创造价值的环境中工作。</p>
<p>最近对自己这一两年的得失反思总结了下,回首看着这踏过的一步步脚印,这一路以来也有不少坎坷和艰辛。</p>
<p>两年的分水岭,似乎我没有达到预期的及格线。虽然之前一直强调基础,但是基础真的太重要了。没有绝对坚固的知识基础,很难在一个领域有所深入的建树,尤其在阅读《Android内核剖析》作者柯元旦对自己曾经学习和创业之事叙述的时候,明显感觉到了高校之间的巨大差距。</p>
<p>好学校是存在学渣,但如果学霸之间的比较呢?大家的平均水平真的不是一个档次的,如果可以的话很希望能够经历一次真正的奋斗考研去往一所自己梦想的大学读研。可惜现实如此,只能不甘的苟活并在技术上能够让自己变得异常的强大才行。</p>
<p>最近投简历的时候也感觉自己的不争气,其实两年应该可以证明一个人的技术能力了,可是自己浪荡,没有充分利用时间成长达到应有的水平,很是失望。</p>
<p>希望剩下的这关键的一年能够放下心结,努力拼搏对未来的自己有一个交代。不想成为三年技术还是渣的人。</p>
<p>不要总是给自己找理由!</p>
<p>如果当时没有选择,那么我会在哪?</p>
<p>如今毕业已快两年已久,两年前的今天我还在初学Java,刚通过师兄接触Android,从此踏入编程这条不归路。</p>
<p>很感激自己当初的选择,让我能够在一个单纯又靠自己双手创造价值的环境中工作。</p>
<p>最近对自己这
Android libyuv应用系列(二)libyuv的使用
http://www.raomengyang.com/2017/01/15/Android-libyuv应用系列(二)libyuv的使用/
2017-01-15T14:02:37.000Z
2017-01-15T14:04:20.000Z
<p>上篇文章<a href="http://blog.csdn.net/zxccxzzxz/article/details/53982849" target="_blank" rel="external">Android libyuv使用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别</a>中我们了解了YUV相关的知识,而本篇文章我会介绍libyuv是什么,以及如何使用libyuv进行相应的图像数据处理。</p>
<p>当我们在 Android 中处理 Image 时,常因为 Java 性能和效率问题导致达不到我们期望的效果,例如进行Camera 采集视频流的原始帧时我们需要每秒能够获取足够的帧率才能流畅的显示出来,这也是为什么美颜 SDK 和图像识别等这类 SDK 都是基于 C / C++ 的原因之一。语言的特性也是关键因素点,所以常常会在 Java 中调用 C / C++ 的 API 来进行相关操作。</p>
<p>因最近工作需求是替代 Camera 的原始打视频流,数据源是 Bitmap 格式的,如果使用 Java 的方法来进行Bitmap 的旋转,转换为 YUV 类型的 NV21 、YV12 数据的话,那么少说也要 15FPS 的视频就尴尬的变成了5FPS的PPT幻灯片了。关于YUV的各种格式区别请见我的博客:<a href="http://www.cnblogs.com/raomengyang/p/5582270.html" target="_blank" rel="external">直播必备之YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别</a>,而Google提供了一套Image处理的开源库<a href="git clone https://chromium.googlesource.com/libyuv/libyuv">libyuv</a>(科学上网),可高效的对各类Image进行Rotate(旋转)、Scale(拉伸)和Convert(格式转换)等操作。</p>
<a id="more"></a>
<h3 id="libyuv官方说明"><a href="#libyuv官方说明" class="headerlink" title="libyuv官方说明"></a><a href="https://chromium.googlesource.com/libyuv/libyuv" target="_blank" rel="external">libyuv</a>官方说明</h3><blockquote>
<p><strong>libyuv</strong> is an open source project that includes YUV scaling and conversion functionality.</p>
<ul>
<li>Scale YUV to prepare content for compression, with point, bilinear or box filter.</li>
<li>Convert to YUV from webcam formats.</li>
<li>Convert from YUV to formats for rendering/effects.</li>
<li>Rotate by 90/180/270 degrees to adjust for mobile devices in portrait mode.</li>
<li>Optimized for SSE2/SSSE3/AVX2 on x86/x64.</li>
<li>Optimized for Neon on Arm.</li>
<li>Optimized for DSP R2 on Mips.</li>
</ul>
</blockquote>
<p>简单来讲,libyuv 就是一个具有可以对 YUV 进行拉伸和转换等操作的工具库。</p>
<p>几个重要的功能:</p>
<ul>
<li>可以使用 point,bilinear 或 box 三种类型的压缩方法进行YUV的拉伸</li>
<li>旋转 90/180/270 的角度以适配设备的竖屏模式</li>
<li>可将 webcam 转换为 YUV</li>
<li>还有一些列的平台性能优化等等</li>
</ul>
<p>大概了解了libyuv的功能后,我们来看看普通方式和libyuv之间的差距。</p>
<h2 id="系统环境"><a href="#系统环境" class="headerlink" title="系统环境"></a>系统环境</h2><p>我的硬件环境是Macbook Pro和PC,硬件环境如下:</p>
<table>
<thead>
<tr>
<th style="text-align:center">Hardware</th>
<th style="text-align:center">Macbook Pro Retina, 13-inch, Early 2015</th>
<th style="text-align:center">PC</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">OS</td>
<td style="text-align:center">MacOS Sierra 10.12</td>
<td style="text-align:center">Windows 10</td>
</tr>
<tr>
<td style="text-align:center">CPU</td>
<td style="text-align:center">2.7 GHz Intel Core i5</td>
<td style="text-align:center">i5 6500</td>
</tr>
<tr>
<td style="text-align:center">RAM</td>
<td style="text-align:center">8 GB 1867 MHz DDR3</td>
<td style="text-align:center">16G 2400MHz DDR4</td>
</tr>
<tr>
<td style="text-align:center">HDD</td>
<td style="text-align:center">128 SSD</td>
<td style="text-align:center">256 SSD</td>
</tr>
</tbody>
</table>
<p>我们使用一张XXX的Bitmap来做一下对比测试,看看不同的系统环境下,效果如何。</p>
<h2 id="Bitmap和YUV的转换"><a href="#Bitmap和YUV的转换" class="headerlink" title="Bitmap和YUV的转换"></a>Bitmap和YUV的转换</h2><p>数据源是 Bitmap,项目中会涉及以下几种格式:</p>
<table>
<thead>
<tr>
<th style="text-align:center">Bitmap</th>
<th style="text-align:center">YUV</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">ARGB_8888</td>
<td style="text-align:center">NV21 (YUV420SP)</td>
</tr>
<tr>
<td style="text-align:center">RGB_565</td>
<td style="text-align:center">YV12 (YUV420P)</td>
</tr>
</tbody>
</table>
<p>StackOverFlow上有网友给出了手动转换BitmapToYuv的方式:</p>
<figure class="highlight java"><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><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span><br><span class="line"> * Bitmap转换成Drawable</span><br><span class="line"> * Bitmap bm = xxx; //xxx根据你的情况获取</span><br><span class="line"> * BitmapDrawable bd = new BitmapDrawable(getResource(), bm);</span><br><span class="line"> * 因为BtimapDrawable是Drawable的子类,最终直接使用bd对象即可。</span><br><span class="line"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] getNV21(<span class="keyword">int</span> inputWidth, <span class="keyword">int</span> inputHeight, Bitmap srcBitmap) {</span><br><span class="line"> <span class="keyword">int</span>[] argb = <span class="keyword">new</span> <span class="keyword">int</span>[inputWidth * inputHeight];</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != srcBitmap) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> srcBitmap.getPixels(argb, <span class="number">0</span>, inputWidth, <span class="number">0</span>, <span class="number">0</span>, inputWidth, inputHeight);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2];</span></span><br><span class="line"> <span class="comment">// encodeYUV420SP(yuv, argb, inputWidth, inputHeight);</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != srcBitmap && !srcBitmap.isRecycled()) {</span><br><span class="line"> srcBitmap.recycle();</span><br><span class="line"> srcBitmap = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> colorconvertRGB_IYUV_I420(argb, inputWidth, inputHeight);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">encodeYUV420SP</span><span class="params">(<span class="keyword">byte</span>[] yuv420sp, <span class="keyword">int</span>[] argb, <span class="keyword">int</span> width, <span class="keyword">int</span> height)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> frameSize = width * height;</span><br><span class="line"> <span class="keyword">int</span> yIndex = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> uvIndex = frameSize;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> a, R, G, B, Y, U, V;</span><br><span class="line"> <span class="keyword">int</span> index = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < height; j++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < width; i++) {</span><br><span class="line"></span><br><span class="line"> a = (argb[index] & <span class="number">0xff000000</span>) >> <span class="number">24</span>; <span class="comment">// a is not used obviously</span></span><br><span class="line"> R = (argb[index] & <span class="number">0xff0000</span>) >> <span class="number">16</span>;</span><br><span class="line"> G = (argb[index] & <span class="number">0xff00</span>) >> <span class="number">8</span>;</span><br><span class="line"> B = (argb[index] & <span class="number">0xff</span>) >> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// well known RGB to YUV algorithm</span></span><br><span class="line"> Y = ((<span class="number">66</span> * R + <span class="number">129</span> * G + <span class="number">25</span> * B + <span class="number">128</span>) >> <span class="number">8</span>) + <span class="number">16</span>;</span><br><span class="line"> U = ((-<span class="number">38</span> * R - <span class="number">74</span> * G + <span class="number">112</span> * B + <span class="number">128</span>) >> <span class="number">8</span>) + <span class="number">128</span>;</span><br><span class="line"> V = ((<span class="number">112</span> * R - <span class="number">94</span> * G - <span class="number">18</span> * B + <span class="number">128</span>) >> <span class="number">8</span>) + <span class="number">128</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2 meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every otherpixel AND every other scanline.*/</span></span><br><span class="line"> yuv420sp[yIndex++] = (<span class="keyword">byte</span>) ((Y < <span class="number">0</span>) ? <span class="number">0</span> : ((Y > <span class="number">255</span>) ? <span class="number">255</span> : Y));</span><br><span class="line"> <span class="keyword">if</span> (j % <span class="number">2</span> == <span class="number">0</span> && index % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line"> yuv420sp[uvIndex++] = (<span class="keyword">byte</span>) ((V < <span class="number">0</span>) ? <span class="number">0</span> : ((V > <span class="number">255</span>) ? <span class="number">255</span> : V));</span><br><span class="line"> yuv420sp[uvIndex++] = (<span class="keyword">byte</span>) ((U < <span class="number">0</span>) ? <span class="number">0</span> : ((U > <span class="number">255</span>) ? <span class="number">255</span> : U));</span><br><span class="line"> }</span><br><span class="line"> index++;</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 class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] colorconvertRGB_IYUV_I420(<span class="keyword">int</span>[] aRGB, <span class="keyword">int</span> width, <span class="keyword">int</span> height) {</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> frameSize = width * height;</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> chromasize = frameSize / <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> yIndex = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> uIndex = frameSize;</span><br><span class="line"> <span class="keyword">int</span> vIndex = frameSize + chromasize;</span><br><span class="line"> <span class="keyword">byte</span>[] yuv = <span class="keyword">new</span> <span class="keyword">byte</span>[width * height * <span class="number">3</span> / <span class="number">2</span>];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> a, R, G, B, Y, U, V;</span><br><span class="line"> <span class="keyword">int</span> index = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < height; j++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < width; i++) {</span><br><span class="line"> <span class="comment">//a = (aRGB[index] & 0xff000000) >> 24; //not using it right now</span></span><br><span class="line"> R = (aRGB[index] & <span class="number">0xff0000</span>) >> <span class="number">16</span>;</span><br><span class="line"> G = (aRGB[index] & <span class="number">0xff00</span>) >> <span class="number">8</span>;</span><br><span class="line"> B = (aRGB[index] & <span class="number">0xff</span>) >> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> Y = ((<span class="number">66</span> * R + <span class="number">129</span> * G + <span class="number">25</span> * B + <span class="number">128</span>) >> <span class="number">8</span>) + <span class="number">16</span>;</span><br><span class="line"> U = ((-<span class="number">38</span> * R - <span class="number">74</span> * G + <span class="number">112</span> * B + <span class="number">128</span>) >> <span class="number">8</span>) + <span class="number">128</span>;</span><br><span class="line"> V = ((<span class="number">112</span> * R - <span class="number">94</span> * G - <span class="number">18</span> * B + <span class="number">128</span>) >> <span class="number">8</span>) + <span class="number">128</span>;</span><br><span class="line"></span><br><span class="line"> yuv[yIndex++] = (<span class="keyword">byte</span>) ((Y < <span class="number">0</span>) ? <span class="number">0</span> : ((Y > <span class="number">255</span>) ? <span class="number">255</span> : Y));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (j % <span class="number">2</span> == <span class="number">0</span> && index % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line"> yuv[vIndex++] = (<span class="keyword">byte</span>) ((U < <span class="number">0</span>) ? <span class="number">0</span> : ((U > <span class="number">255</span>) ? <span class="number">255</span> : U));</span><br><span class="line"> yuv[uIndex++] = (<span class="keyword">byte</span>) ((V < <span class="number">0</span>) ? <span class="number">0</span> : ((V > <span class="number">255</span>) ? <span class="number">255</span> : V));</span><br><span class="line"> }</span><br><span class="line"> index++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> yuv;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>上面的方式如果在不苛求性能的情况下是可以满足使用的,然而每秒也就能够达到5~8FPS的水平(与设备的硬件配置也有关系),显然达不到我的需求。那么使用libyuv后的结果如何呢?别着急,我们先看看如何编译libyuv。</p>
<h2 id="获取libyuv"><a href="#获取libyuv" class="headerlink" title="获取libyuv"></a>获取libyuv</h2><p>将<a href="https://chromium.googlesource.com/libyuv/libyuv" target="_blank" rel="external">libyuv</a> <code>git clone</code>下来后,我们可以看到结构目录如下:</p>
<p><img src="https://raw.githubusercontent.com/eterrao/AndroidLibyuvImageUtils/master/images/libyuv_dir.png" alt="libyuv_dir"></p>
<p>libyuv给出了三个平台的MakeFile文件,可以Build出Windows / Mac OS / Linux三种平台的资源包。因为我使用的是Android,这里以Android.mk为例:</p>
<figure class="highlight"><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></pre></td><td class="code"><pre><span class="line"># This is the Android makefile for libyuv for both platform and NDK.</span><br><span class="line">LOCAL_PATH:= $(call my-dir)</span><br><span class="line"></span><br><span class="line">include $(CLEAR_VARS)</span><br><span class="line"></span><br><span class="line">LOCAL_CPP_EXTENSION := .cc</span><br><span class="line"></span><br><span class="line">LOCAL_SRC_FILES := \</span><br><span class="line"> source/compare.cc \</span><br><span class="line"> source/compare_common.cc \</span><br><span class="line"> source/compare_neon64.cc \</span><br><span class="line"> source/compare_gcc.cc \</span><br><span class="line"> source/convert.cc \</span><br><span class="line"> source/convert_argb.cc \</span><br><span class="line"> source/convert_from.cc \</span><br><span class="line"> source/convert_from_argb.cc \</span><br><span class="line"> source/convert_to_argb.cc \</span><br><span class="line"> source/convert_to_i420.cc \</span><br><span class="line"> source/cpu_id.cc \</span><br><span class="line"> source/planar_functions.cc \</span><br><span class="line"> source/rotate.cc \</span><br><span class="line"> source/rotate_argb.cc \</span><br><span class="line"> source/rotate_mips.cc \</span><br><span class="line"> source/rotate_neon64.cc \</span><br><span class="line"> source/row_any.cc \</span><br><span class="line"> source/row_common.cc \</span><br><span class="line"> source/row_mips.cc \</span><br><span class="line"> source/row_neon64.cc \</span><br><span class="line"> source/row_gcc.cc \</span><br><span class="line"> source/scale.cc \</span><br><span class="line"> source/scale_any.cc \</span><br><span class="line"> source/scale_argb.cc \</span><br><span class="line"> source/scale_common.cc \</span><br><span class="line"> source/scale_mips.cc \</span><br><span class="line"> source/scale_neon64.cc \</span><br><span class="line"> source/scale_gcc.cc \</span><br><span class="line"> source/video_common.cc</span><br><span class="line"></span><br><span class="line"># TODO(fbarchard): Enable mjpeg encoder.</span><br><span class="line"># source/mjpeg_decoder.cc</span><br><span class="line"># source/convert_jpeg.cc</span><br><span class="line"># source/mjpeg_validate.cc</span><br><span class="line"></span><br><span class="line">ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)</span><br><span class="line"> LOCAL_CFLAGS += -DLIBYUV_NEON</span><br><span class="line"> LOCAL_SRC_FILES += \</span><br><span class="line"> source/compare_neon.cc.neon \</span><br><span class="line"> source/rotate_neon.cc.neon \</span><br><span class="line"> source/row_neon.cc.neon \</span><br><span class="line"> source/scale_neon.cc.neon</span><br><span class="line">endif</span><br><span class="line"></span><br><span class="line">LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include</span><br><span class="line">LOCAL_C_INCLUDES += $(LOCAL_PATH)/include</span><br><span class="line"></span><br><span class="line">LOCAL_MODULE := libyuv_static</span><br><span class="line">LOCAL_MODULE_TAGS := optional</span><br><span class="line"></span><br><span class="line">include $(BUILD_STATIC_LIBRARY)</span><br></pre></td></tr></table></figure>
<h2 id="使用NDK编译libyuv"><a href="#使用NDK编译libyuv" class="headerlink" title="使用NDK编译libyuv"></a>使用NDK编译libyuv</h2><p>常用两种编译方式:</p>
<ul>
<li>直接引入源码</li>
</ul>
<p>通过Gradle使用脚本的方式代替手动编译。在构建项目的同时将libyuv编译引入,通过Gradle来构建编译,具体方法是在app层级的build.gradle中加入对应的Build Task,指定相关路径,同时构建项目和编译。</p>
<ul>
<li>预先手动将libyuv编译成动态库so文件,放入对应的jniLibs目录下</li>
</ul>
<p>使用<code>ndk-build</code>命令进行编译,每次执行<code>ndk-build</code>之前都需要<code>ndk-build clean</code>一遍才行,不然不会将新的改动编译进去。</p>
<p><img src="https://raw.githubusercontent.com/eterrao/AndroidLibyuvImageUtils/master/images/libyuv_ndk_build.png" alt="libyuv_ndk_build"></p>
<p>如果使用JNI并且要在 c / c++ 层使用libyuv的话,上述两种方式都需要在项目中的 Android.mk 文件中加入 libyuv 的引用,如:</p>
<figure class="highlight bash"><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">LOCAL_PATH := $(call my-dir)</span><br><span class="line"></span><br><span class="line">include $(CLEAR_VARS)</span><br><span class="line">LOCAL_LDLIBS := -llog</span><br><span class="line">LOCAL_LDFLAGS += -ljnigraphics</span><br><span class="line">LOCAL_SHARED_LIBRARIES := libyuv</span><br><span class="line">LOCAL_MODULE := yuv_utils</span><br><span class="line">LOCAL_SRC_FILES := com_rayclear_jni_YuvUtils.c</span><br><span class="line">include $(BUILD_SHARED_LIBRARY)</span><br><span class="line"></span><br><span class="line">include $(CLEAR_VARS)</span><br><span class="line">LOCAL_MODULE := yuv</span><br><span class="line">LOCAL_SRC_FILES := $(LOCAL_PATH)/libyuv.so</span><br><span class="line">include $(PREBUILT_SHARED_LIBRARY)</span><br></pre></td></tr></table></figure>
<p>到这里libyuv的编译工作就基本完成了,准备工作做完后,在需要使用的Activity或者Application初始化的时候添加如下代码进行引入:</p>
<figure class="highlight java"><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"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyApplication</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * so文件默认前缀带lib,在此引用时需要去掉"lib"和后缀".so"</span><br><span class="line"> * */</span></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> System.loadLibrary(<span class="string">"yuv_utils"</span>);</span><br><span class="line"> System.loadLibrary(<span class="string">"yuv"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Context sContext;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate();</span><br><span class="line"> initContext();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initContext</span><span class="params">()</span> </span>{</span><br><span class="line"> sContext = getApplicationContext();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Context <span class="title">getContext</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> sContext;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h2><p>先上图看看区别:</p>
<p><img src="https://raw.githubusercontent.com/eterrao/AndroidLibyuvImageUtils/master/images/libyuv_java_convert.jpeg" alt="java_convert_bitmap_to_yuv"><img src="https://raw.githubusercontent.com/eterrao/AndroidLibyuvImageUtils/master/images/libyuv_convert.jpeg" alt="libyuv_convert_bitmap_to_yuv"></p>
<p>使用Java进行Bitmap转换为YUV时,一张1440 x 900 的Bitmap耗时大概35 ~ 45ms左右,而使用libyuv则花费14~22 ms左右,性能提升一倍,而更暴力的来了,如果同时进行拉伸缩放和格式转换,例如1440 x 90 —> 480 x 270,可以实现 5 ~ 13 ms,性能提升了3 ~ 6倍。这意味着1000 ms可以满足我们不低于25FPS的需求。</p>
<p><img src="https://raw.githubusercontent.com/eterrao/AndroidLibyuvImageUtils/master/images/libyuv_convert_scale.jpeg" alt="libyuv_bitmap_scale_convert_to_yuv"></p>
<h2 id="Rawviewer查看YUV文件"><a href="#Rawviewer查看YUV文件" class="headerlink" title="Rawviewer查看YUV文件"></a>Rawviewer查看YUV文件</h2><p>上篇文章中提供了<a href="http://download.csdn.net/detail/zxccxzzxz/9508288" target="_blank" rel="external">RawViewer的下载地址</a>,但是具体的使用方式还没说,在Demo中有方法<code>FileUtil.saveYuvToSdCardStorage(dstYuv)</code>用于保存YUV文件(.jpeg为后缀的)到存储中,从设备中取到这个文件使用RawViewer打开,打开前先进行RawViewer的参数配置,否则可能会闪退。我们预先设定分辨率及格式后打开即可。如下图所示:</p>
<p><img src="https://raw.githubusercontent.com/eterrao/AndroidLibyuvImageUtils/master/images/libyuvPreview.png" alt="libyuvPreview"></p>
<h2 id="Demo源码"><a href="#Demo源码" class="headerlink" title="Demo源码"></a><a href="https://github.com/eterrao/AndroidLibyuvImageUtils" target="_blank" rel="external">Demo源码</a></h2><ul>
<li><a href="https://github.com/eterrao/AndroidLibyuvImageUtils" target="_blank" rel="external">GitHub地址</a></li>
</ul>
<p>简单的Demo结果因为有事拖拖拉拉搞了一下午,不得不对着产率低下感到头疼啊。Demo源码在我的GitHub仓库,这篇文章中介绍的不详细的地方可以从项目中看看实现即可,后续有时间我会将这个库做个简单的工具类封装。有问题的朋友请随时留言指错或者提问,如果觉得对你有帮助的话请顺手点个Star,谢谢大家的支持!</p>
<p>上篇文章<a href="http://blog.csdn.net/zxccxzzxz/article/details/53982849">Android libyuv使用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别</a>中我们了解了YUV相关的知识,而本篇文章我会介绍libyuv是什么,以及如何使用libyuv进行相应的图像数据处理。</p>
<p>当我们在 Android 中处理 Image 时,常因为 Java 性能和效率问题导致达不到我们期望的效果,例如进行Camera 采集视频流的原始帧时我们需要每秒能够获取足够的帧率才能流畅的显示出来,这也是为什么美颜 SDK 和图像识别等这类 SDK 都是基于 C / C++ 的原因之一。语言的特性也是关键因素点,所以常常会在 Java 中调用 C / C++ 的 API 来进行相关操作。</p>
<p>因最近工作需求是替代 Camera 的原始打视频流,数据源是 Bitmap 格式的,如果使用 Java 的方法来进行Bitmap 的旋转,转换为 YUV 类型的 NV21 、YV12 数据的话,那么少说也要 15FPS 的视频就尴尬的变成了5FPS的PPT幻灯片了。关于YUV的各种格式区别请见我的博客:<a href="http://www.cnblogs.com/raomengyang/p/5582270.html">直播必备之YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别</a>,而Google提供了一套Image处理的开源库<a href="git clone https://chromium.googlesource.com/libyuv/libyuv">libyuv</a>(科学上网),可高效的对各类Image进行Rotate(旋转)、Scale(拉伸)和Convert(格式转换)等操作。</p>
Android libyuv应用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
http://www.raomengyang.com/2017/01/15/Android-libyuv应用系列(一)Android常用的几种格式:NV21-NV12-YV12-YUV420P的区别/
2017-01-15T13:58:55.000Z
2017-01-15T14:04:36.000Z
<p>项目中接触到图像处理这部分,需求是将手机摄像头采集的原始帧进行 Rotate (旋转)、Scale(拉伸)和 format convert(格式转换),无奈对此的了解甚少于是网上查阅资料恶补了一顿,完事后将最近所学总结一下以方便之后的人别踩太多。</p>
<p>首先想要了解YUV为何物,请猛戳:<a href="https://msdn.microsoft.com/en-us/library/aa904813(VS.80" target="_blank" rel="external">Video Rendering with 8-Bit YUV Formats</a>.aspx) 链接中微软已经写的很详细了,国内大部分文章都是翻译这篇文章的,如果还有疑问的同学可以参考下面这些大神的博客:</p>
<ul>
<li><a href="http://blog.csdn.net/leixiaohua1020/article/details/42134965" target="_blank" rel="external">最简单的基于FFmpeg的libswscale的示例(YUV转RGB)</a></li>
<li><a href="http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.htm" target="_blank" rel="external">图文详解YUV420数据格式</a></li>
<li><a href="http://tangzm.com/blog/?p=18" target="_blank" rel="external">ANDROID 高性能图形处理 之 一. RENDERSCRIPT</a></li>
</ul>
<a id="more"></a>
<p>从上面的文章中应该都会对YUV有所了解和认识了。需要注意的是,在 Android SDK <= 20 Android5.0 LOLLIPOP 版本中 Google 支持的 Camera Preview Callback 的YUV常用格式有两种:</p>
<ul>
<li><p><a href="#NV21">NV21</a></p>
</li>
<li><p><a href="#YV12">YV12</a></p>
</li>
</ul>
<p>先贴一段微软的叙述:</p>
<blockquote>
<h3 id="4-2-0-Formats-12-Bits-per-Pixel"><a href="#4-2-0-Formats-12-Bits-per-Pixel" class="headerlink" title="4:2:0 Formats, 12 Bits per Pixel"></a>4:2:0 Formats, 12 Bits per Pixel</h3><p> Four 4:2:0 12-bpp formats are recommended, with the following FOURCC codes:</p>
<ul>
<li><p>IMC2</p>
</li>
<li><p>IMC4</p>
</li>
<li>YV12</li>
<li>NV12</li>
</ul>
<p> In all of these formats, the chroma channels are subsampled by a factor of two in both the horizontal and vertical dimensions. </p>
<h3 id="YV12"><a href="#YV12" class="headerlink" title="YV12"></a>YV12</h3><p> All of the Y samples appear first in memory as an array of unsigned char values. This array is followed immediately by all of the V (Cr) samples. The stride of the V plane is half the stride of the Y plane, and the V plane contains half as many lines as the Y plane. The V plane is followed immediately by all of the U (Cb) samples, with the same stride and number of lines as the V plane (Figure 12).</p>
<h3 id="NV12"><a href="#NV12" class="headerlink" title="NV12"></a>NV12</h3><p> All of the Y samples are found first in memory as an array of unsigned char values with an even number of lines. The Y plane is followed immediately by an array of unsigned char values that contains packed U (Cb) and V (Cr) samples, as shown in Figure 13. When the combined U-V array is addressed as an array of little-endian WORD values, the LSBs contain the U values, and the MSBs contain the V values. NV12 is the preferred 4:2:0 pixel format for DirectX VA. It is expected to be an intermediate-term requirement for DirectX VA accelerators supporting 4:2:0 video.</p>
</blockquote>
<p>从上可知 YV12 和 NV12 所占内存是 12bits / Pixel,每个 Y 就是一个像素点,注意红色加粗的叙述,YUV 值在内存中是按照数组的形式存放的,而由于 YV12 和 NV21 都是属于 Planar 格式,也就是 Y 值和 UV 值是独立采样的:</p>
<blockquote>
<p>In a planar format, the Y, U, and V components are stored as three separate planes.</p>
<p>在 planar 的格式中, Y, U, V 值是单独存储在三个分离的平面中的。</p>
</blockquote>
<p>既然 Y、U、V 值都是独立的,那就意味着我们可以分别处理相应的值,比如在YV12中,排列方式如下表所示,每4个 Y 共用一对 UV 值,而 U、V 值又是按照横排排列(下面是 YV12 格式中,宽为16,高为4像素的排列)。</p>
<h3 id="YV12-中-16-x-4-像素排列"><a href="#YV12-中-16-x-4-像素排列" class="headerlink" title="YV12 中 16 x 4 像素排列"></a>YV12 中 16 x 4 像素排列</h3><table>
<thead>
<tr>
<th style="text-align:center">行 \ 列</th>
<th style="text-align:center">1</th>
<th style="text-align:center">2</th>
<th style="text-align:center">3</th>
<th style="text-align:center">4</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">Y 第一行</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
</tr>
<tr>
<td style="text-align:center">Y 第二行</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
</tr>
<tr>
<td style="text-align:center">Y 第三行</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
</tr>
<tr>
<td style="text-align:center">Y 第三行</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
<td style="text-align:center">Y Y</td>
</tr>
<tr>
<td style="text-align:center">V第一行</td>
<td style="text-align:center">V0</td>
<td style="text-align:center">V1</td>
<td style="text-align:center">V2</td>
<td style="text-align:center">V3</td>
</tr>
<tr>
<td style="text-align:center">U第一行</td>
<td style="text-align:center">U0</td>
<td style="text-align:center">U1</td>
<td style="text-align:center">U2</td>
<td style="text-align:center">U3</td>
</tr>
<tr>
<td style="text-align:center">V第二行</td>
<td style="text-align:center">V4</td>
<td style="text-align:center">V5</td>
<td style="text-align:center">V6</td>
<td style="text-align:center">V7</td>
</tr>
<tr>
<td style="text-align:center">U第二行</td>
<td style="text-align:center">U4</td>
<td style="text-align:center">U5</td>
<td style="text-align:center">U6</td>
<td style="text-align:center">U7</td>
</tr>
</tbody>
</table>
<p>了解了 YUV 值的结构我们就可以任性的对此图像做 Rotate,scale等等。这里我以480*270 (16:9)的一张原始帧图像举例,贴出部分代码示例:<br>CameraPreviewFrame.java:<br><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span><br><span class="line">* 获取preview的原始帧 </span><br><span class="line">* 这里有个前提,因为Android camera preview默认格式为NV21的,所以需要</span><br><span class="line">* 调用setPreviewFormat()方法设置为我们需要的格式</span><br><span class="line">*/</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPreviewFrame</span><span class="params">(<span class="keyword">byte</span>[] data, Camera camera)</span> </span>{<span class="comment">// 假设这里的data为480x270原始帧</span></span><br><span class="line"> String SRC_FRAME_WIDTH = <span class="number">480</span>;</span><br><span class="line"> String SRC_FRAME_HEIGHT = <span class="number">270</span>;</span><br><span class="line"> String DES_FRAME_WIDTH = <span class="number">480</span>;</span><br><span class="line"> String DES_FRAME_HEIGHT = <span class="number">270</span>;</span><br><span class="line"> <span class="comment">// 此处将data数组保存在了指定的路径,保存类型为jpeg格式,但是普通的图片浏</span></span><br><span class="line"> <span class="comment">// 览器是无法打开的,需要使用RawViewer等专业的工具打开。</span></span><br><span class="line"> <span class="comment">// 定义与原始帧大小一样的outputData,因为YUV420所占内存是12Bits/Pixel,</span></span><br><span class="line"> <span class="comment">// 每个Y为一个像素8bit=1Byte,U=2bit=1/4(Byte),V= 2bit =1/4(Byte),</span></span><br><span class="line"> <span class="comment">// Y值数量为480*270,则U=V=480*270*(1/4)</span></span><br><span class="line"> <span class="keyword">byte</span>[] outputData = <span class="keyword">new</span> <span class="keyword">byte</span>[DES_FRAME_WIDTH * DES_FRAME_HEIGHT * <span class="number">3</span> / <span class="number">2</span>]; </span><br><span class="line"> <span class="comment">// call the JNI method to rotate frame data clockwise 90 degrees</span></span><br><span class="line"> YuvUtil.DealYV12(data, outputData, SRC_FRAME_WIDTH, SRC_FRAME_HEIGHT, <span class="number">90</span>);</span><br><span class="line"> saveImageData(outputData);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// save image to sdcard path: Pictures/MyTestImage/</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">saveImageData</span><span class="params">(<span class="keyword">byte</span>[] imageData)</span> </span>{</span><br><span class="line"> File imageFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);</span><br><span class="line"> <span class="keyword">if</span> (imageFile == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> FileOutputStream fos = <span class="keyword">new</span> FileOutputStream(imageFile);</span><br><span class="line"> fos.write(imageData);</span><br><span class="line"> fos.close();</span><br><span class="line"> } <span class="keyword">catch</span> (FileNotFoundException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> Log.e(TAG, <span class="string">"File not found: "</span> + e.getMessage());</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> Log.e(TAG, <span class="string">"Error accessing file: "</span> + e.getMessage());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> File <span class="title">getOutputMediaFile</span><span class="params">(<span class="keyword">int</span> type)</span> </span>{</span><br><span class="line"> File imageFileDir = <span class="keyword">new</span> File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), <span class="string">"MyYuvImage"</span>);</span><br><span class="line"> <span class="keyword">if</span> (!imageFileDir.exists()) {</span><br><span class="line"> <span class="keyword">if</span> (!imageFileDir.mkdirs()) {</span><br><span class="line"> Log.e(TAG, <span class="string">"can't makedir for imagefile"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Create a media file name</span></span><br><span class="line"> String timeStamp = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyyMMdd_HHmmss"</span>).format(<span class="keyword">new</span> Date());</span><br><span class="line"> File imageFile;</span><br><span class="line"> <span class="keyword">if</span> (type == MEDIA_TYPE_IMAGE) {</span><br><span class="line"> imageFile = <span class="keyword">new</span> File(imageFileDir.getPath() + File.separator +</span><br><span class="line"> <span class="string">"IMG_"</span> + timeStamp + <span class="string">".jpg"</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (type == MEDIA_TYPE_VIDEO) {</span><br><span class="line"> imageFile = <span class="keyword">new</span> File(imageFileDir.getPath() + File.separator +</span><br><span class="line"> <span class="string">"VID_"</span> + timeStamp + <span class="string">".mp4"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> imageFile;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>上面的代码中可以看到我调用了JNI的方法<code>YuvUtil.RotateYV12()</code></p>
<p>YuvUtil.java</p>
<figure class="highlight java"><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"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">YuvUtil</span> </span>{</span><br><span class="line"> <span class="comment">// 初始化,为data分配相应大小的内存</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">initYV12</span><span class="params">(<span class="keyword">int</span> length, <span class="keyword">int</span> scale_length)</span></span>;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">DealYV12</span><span class="params">(<span class="keyword">byte</span>[] src_data, <span class="keyword">byte</span>[] dst_data, <span class="keyword">int</span> width, <span class="keyword">int</span> height, <span class="keyword">int</span> rotation)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对应的Jni的C代码如下:<br>com_example_jni_YuvUtil.h<br><figure class="highlight c"><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"><span class="comment">/* DO NOT EDIT THIS FILE - it is machine generated */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><jni.h></span></span></span><br><span class="line"><span class="comment">/* Header for class _Included_com_example_jni_YuvUtil */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> _Included_com_example_jni_YuvUtil</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _Included_com_example_jni_YuvUtil</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> __cplusplus</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="comment">/*</span><br><span class="line">* Class: com_example_jni_YuvUtil</span><br><span class="line">* Method: initYV12</span><br><span class="line">* Signature: (II)V</span><br><span class="line"> */</span></span><br><span class="line"> <span class="function">JNIEXPORT <span class="keyword">void</span> JNICALL <span class="title">Java_com_example_jni_YuvUtil_initYV12</span></span><br><span class="line"> <span class="params">(JNIEnv *, jclass, jint, jint)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span><br><span class="line">* Class: com_example_jni_YuvUtil</span><br><span class="line">* Method: DealYV12</span><br><span class="line">* Signature: ([B[BIIIII)V</span><br><span class="line"> */</span></span><br><span class="line"> <span class="function">JNIEXPORT <span class="keyword">void</span> JNICALL <span class="title">Java_com_example_jni_YuvUtil_DealYV12</span></span><br><span class="line"> <span class="params">(JNIEnv *, jclass, jbyteArray, jbyteArray, jint, jint, jint, jint, jint)</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> __cplusplus</span></span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure></p>
<p>com_example_jni_YuvUtil.c<br><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="string">"com_example_jni_YuvUtil.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><android/log.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><jni.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TAG <span class="string">"jni-log-jni"</span> <span class="comment">// 这个是自定义的LOG的标识</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) <span class="comment">// 定义LOGD类型</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) <span class="comment">// 定义LOGI类型</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) <span class="comment">// 定义LOGW类型</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) <span class="comment">// 定义LOGE类型</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) <span class="comment">// 定义LOGF类型</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> *input_src_data, *output_src_data, *src_y_data,</span><br><span class="line"> *src_u_data, *src_v_data, *dst_y_data, *dst_v_data;</span><br><span class="line"><span class="keyword">int</span> src_data_width, src_data_height, len_src;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span><br><span class="line">* Class: com_example_jni_YuvUtil</span><br><span class="line">*/</span></span><br><span class="line"><span class="function">JNIEXPORT <span class="keyword">void</span> JNICALL <span class="title">Java_com_example_jni_YuvUtil_initYV12</span></span><br><span class="line"> <span class="params">(JNIEnv *env, jclass jcls, jint length, jint scaleDataLength)</span> </span>{</span><br><span class="line"> len_src = length;</span><br><span class="line"> len_scale = scaleDataLength;</span><br><span class="line"> LOGD(<span class="string">"########## len_src = %d, len_scale = %d \n"</span>, len_src, len_scale);</span><br><span class="line"></span><br><span class="line">input_src_data = <span class="built_in">malloc</span>(sizeof(char) * len_src);</span><br><span class="line">LOGD(<span class="string">"########## input_src_data = %d \n"</span>, input_src_data);</span><br><span class="line"></span><br><span class="line">src_y_data = <span class="built_in">malloc</span>(sizeof(char) * (len_src * <span class="number">2</span> / <span class="number">3</span>));</span><br><span class="line">src_u_data = <span class="built_in">malloc</span>(sizeof(char) * (len_src / <span class="number">6</span>));</span><br><span class="line">src_v_data = <span class="built_in">malloc</span>(sizeof(char) * (len_src / <span class="number">6</span>));</span><br><span class="line"></span><br><span class="line">dst_y_data = <span class="built_in">malloc</span>(sizeof(char) * (len_src * <span class="number">2</span> / <span class="number">3</span>));</span><br><span class="line">dst_u_data = <span class="built_in">malloc</span>(sizeof(char) * (len_src / <span class="number">6</span>));</span><br><span class="line">dst_v_data = <span class="built_in">malloc</span>(sizeof(char) * (len_src / <span class="number">6</span>));</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">JNIEXPORT <span class="keyword">void</span> JNICALL <span class="title">Java_com_example_jni_YuvUtil_DealYV12</span></span><br><span class="line"><span class="params">(JNIEnv *env, jclass jcls, jbyteArray src_data,</span><br><span class="line"> jbyteArray dst_data, jint width, jint height, jint rotation, jint dst_width, jint dst_height)</span> </span>{</span><br><span class="line">src_data_width = width;</span><br><span class="line">src_data_height = height;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将src_data的数据传给input_src_data</span></span><br><span class="line">(*env)->GetByteArrayRegion (env, src_data, <span class="number">0</span>, len_src, (jbyte*)(input_src_data));</span><br><span class="line"></span><br><span class="line"><span class="comment">/*以下三个memcpy分别将Y、U、V值从src_data中提取出来,将YUV值分别scale或者rotate,则可得到对应格式的图像数据*/</span></span><br><span class="line"><span class="comment">// get y plane</span></span><br><span class="line"><span class="built_in">memcpy</span>(src_y_data, input_src_data , (len_src * <span class="number">2</span> /<span class="number">3</span>));</span><br><span class="line"><span class="comment">// get u plane</span></span><br><span class="line"><span class="built_in">memcpy</span>(src_u_data, input_src_data + (len_src * <span class="number">2</span> / <span class="number">3</span>), len_src / <span class="number">6</span>);</span><br><span class="line"><span class="comment">// get v plane</span></span><br><span class="line"><span class="built_in">memcpy</span>(src_v_data, input_src_data + (len_src * <span class="number">5</span> / <span class="number">6</span> ), len_src / <span class="number">6</span>);</span><br><span class="line"><span class="comment">/*获取yuv三个值的数据可以做相应操作*/</span></span><br><span class="line"><span class="comment">// ......... </span></span><br><span class="line"><span class="comment">// .........</span></span><br><span class="line"><span class="comment">// 例:将Y值置为0,则得到没有灰度的图像;</span></span><br><span class="line"><span class="built_in">memset</span>(input_src_data + src_data_width * src_data_height, <span class="number">0</span>, src_data_width * src_data_height);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将input_src_data的数据返回给dst_data输出</span></span><br><span class="line"><span class="comment">// output to the dst_data</span></span><br><span class="line">(*env)->SetByteArrayRegion (env, dst_data, <span class="number">0</span>, len_src, (jbyte*)(input_src_data));</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span><br><span class="line">* free memory</span><br><span class="line">*/</span></span><br><span class="line"><span class="function">JNIEXPORT <span class="keyword">void</span> JNICALL <span class="title">Java_com_example_jni_YuvUtil_ReleaseYV12</span></span><br><span class="line"><span class="params">(JNIEnv *env , jclass jcls)</span> </span>{</span><br><span class="line"><span class="built_in">free</span>(output_src_data);</span><br><span class="line"><span class="built_in">free</span>(input_src_data);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<h2 id="RawViewer"><a href="#RawViewer" class="headerlink" title="RawViewer"></a><a href="http://download.csdn.net/detail/zxccxzzxz/9508288" target="_blank" rel="external">RawViewer</a></h2><p>一个查看YUV原始帧文件的工具,可以根据自定义的宽高、YUV格式显示出当前YUV的图像,对分析当前视频帧的结构和数据类型还是挺有帮助的。</p>
<h2 id="Demo源码"><a href="#Demo源码" class="headerlink" title="Demo源码"></a><a href="https://github.com/eterrao/AndroidLibyuvImageUtils" target="_blank" rel="external">Demo源码</a></h2><ul>
<li><a href="https://github.com/eterrao/AndroidLibyuvImageUtils" target="_blank" rel="external">GitHub地址</a></li>
</ul>
<p>后续有时间我会将这个库做个简单的工具类封装。有问题的朋友请随时留言指错或者提问,如果觉得对你有帮助的话请顺手点个Star,谢谢大家的支持!</p>
<p>项目中接触到图像处理这部分,需求是将手机摄像头采集的原始帧进行 Rotate (旋转)、Scale(拉伸)和 format convert(格式转换),无奈对此的了解甚少于是网上查阅资料恶补了一顿,完事后将最近所学总结一下以方便之后的人别踩太多。</p>
<p>首先想要了解YUV为何物,请猛戳:<a href="https://msdn.microsoft.com/en-us/library/aa904813(VS.80">Video Rendering with 8-Bit YUV Formats</a>.aspx) 链接中微软已经写的很详细了,国内大部分文章都是翻译这篇文章的,如果还有疑问的同学可以参考下面这些大神的博客:</p>
<ul>
<li><a href="http://blog.csdn.net/leixiaohua1020/article/details/42134965">最简单的基于FFmpeg的libswscale的示例(YUV转RGB)</a></li>
<li><a href="http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.htm">图文详解YUV420数据格式</a></li>
<li><a href="http://tangzm.com/blog/?p=18">ANDROID 高性能图形处理 之 一. RENDERSCRIPT</a></li>
</ul>
Android实现录屏直播(二)需求才是硬道理之产品功能调研
http://www.raomengyang.com/2017/01/09/Android实现录屏直播(二)需求才是硬道理之产品功能调研/
2017-01-08T16:24:46.000Z
2017-03-13T12:31:05.000Z
<p>请尊重分享成果,转载请注明出处,本文来自<a href="http://my.csdn.net/zxccxzzxz" target="_blank" rel="external">Coder包子哥</a>,原文链接:<a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244" target="_blank" rel="external">http://blog.csdn.net/zxccxzzxz/article/details/54254244</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396" target="_blank" rel="external">Android实现录屏直播(一)ScreenRecorder的简单分析</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244" target="_blank" rel="external">Android实现录屏直播(二)需求才是硬道理之产品功能调研</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272" target="_blank" rel="external">Android实现录屏直播(三)MediaProjection + VirtualDisplay + librtmp + MediaCodec实现视频编码并推流到rtmp服务器</a></p>
<p>前面的<a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396" target="_blank" rel="external">Android实现录屏直播(一)ScreenRecorder的简单分析</a>一文中我们对 ScreenRecorder 这个开源 Demo 中的实现机制大概有了了解,但在继续写这个系列文章的时候发现每一个细节都太紧密了,稍微不注意就会深入每个知识点的细节导致文章又臭又长还表述不清晰,于是我决定把这7天实现该功能的整个流程重新梳理一遍,按照我开发和研究学习的步骤来写,大致过程如下:</p>
<ol>
<li><a href="#产品功能调研">产品功能调研</a></li>
<li>Bilibili 的反编译及 UI 的编写</li>
<li><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396" target="_blank" rel="external">ScreenRecorder 等 Demo 的代码分析</a></li>
<li>对 H264 结构、FLV 格式封装的研究学习</li>
<li>sps pps avcc 关键帧等视频封装原理的学习与分析</li>
<li>MediaProjection 实现录屏中 MediaCodec 的详细用法</li>
<li><a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272" target="_blank" rel="external">编码后的帧进行推流</a></li>
</ol>
<a id="more"></a>
<h1 id="产品功能调研"><a href="#产品功能调研" class="headerlink" title="产品功能调研"></a>产品功能调研</h1><p>我们作为技术开发人员,任务下发的时候首先要与产品经理进行需求的深入了解,只有了解对方想要的是什么后我们功能实现才能达到他们最大的期望值。当然一旦确定需求后把菜刀亮出来,然后就轻松愉快的写代码吧😜。嗯,本次任务就是尽可能的还原Bilibili的录屏直播功能,汗颜,无需设计,无需讨论,我自己研究吧,反正项目一直都是我一人开发,也习惯了(PS: 尽管是Bilibili的忠实用户,当然不能在工作的时候看番剧啦),废话不多说,瞄准几个功能:</p>
<ul>
<li>直播悬浮窗</li>
<li>直播提示通知栏</li>
<li>录屏直播的实现机制</li>
</ul>
<h2 id="直播悬浮窗"><a href="#直播悬浮窗" class="headerlink" title="直播悬浮窗"></a>直播悬浮窗</h2><p>说起悬浮窗不得不感谢郭神的博客 <a href="http://blog.csdn.net/guolin_blog/article/details/8689140" target="_blank" rel="external">Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果</a>,就是懒直接拿来修修改改就行(郭神别打我~),实现效果如图</p>
<p><img src="https://raw.githubusercontent.com/eterrao/ScreenRecorder/master/images/floating_window.png" alt="直播悬浮窗"></p>
<p>悬浮窗包含一个自定义的View(继承自FrameLayout)、服务和自定义WindowManager,更多细节请见源码,代码就不贴出来了,太占地方。值得注意的是,这里的悬浮窗我添加了一个弹幕显示的ListView面板用于显示用户的发言。</p>
<h2 id="直播提示通知栏"><a href="#直播提示通知栏" class="headerlink" title="直播提示通知栏"></a>直播提示通知栏</h2><p>由于直接录制桌面涉及到用户的个人隐私问题,如果没有一个标识让用户看着那问题就麻烦了(亲测过通过这种录屏方式可以捕获桌面所有的内容,无论是键盘输入状态、摄像头拍照摄像及输入银行卡密码等等),所以Android系统虽然会提示需要进行屏幕录像,但还是有一定的风险!通知栏属于常驻类型,内容如下:</p>
<p><img src="https://raw.githubusercontent.com/eterrao/ScreenRecorder/master/images/notification.png" alt="录屏通知栏"></p>
<h3 id="通知栏的实现"><a href="#通知栏的实现" class="headerlink" title="通知栏的实现"></a>通知栏的实现</h3><p>先看看下面这个服务类,主要功能就是开启通知并创建悬浮窗两个功能,注意创建悬浮窗时判断当前设备是否为我们的APP授权了<strong>悬浮窗权限</strong>这个功能我还没有来得及实现,小米、魅族华为等系统需要手动去APP设置中授权,之后有时间再补上。如需此服务与Activity之间有通信机制的话,还需要自定义Binder,在启动服务的Activity中通过bindService()与ServiceConnection来实现数据的传递,这里先挖一个坑,后续我完善功能后再回来填上。</p>
<figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.app.Notification;</span><br><span class="line"><span class="keyword">import</span> android.app.NotificationManager;</span><br><span class="line"><span class="keyword">import</span> android.app.PendingIntent;</span><br><span class="line"><span class="keyword">import</span> android.os.Binder;</span><br><span class="line"><span class="keyword">import</span> android.os.Handler;</span><br><span class="line"><span class="keyword">import</span> android.support.v4.app.NotificationCompat;</span><br><span class="line"><span class="keyword">import</span> android.app.Service;</span><br><span class="line"><span class="keyword">import</span> android.content.Intent;</span><br><span class="line"><span class="keyword">import</span> android.os.IBinder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ScreenRecordListenerService</span> <span class="keyword">extends</span> <span class="title">Service</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"ScreenRecordListenerService, "</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> PENDING_REQUEST_CODE = <span class="number">0x01</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> NOTIFICATION_ID = <span class="number">3</span>;</span><br><span class="line"> <span class="keyword">private</span> NotificationManager mNotificationManager;</span><br><span class="line"> <span class="keyword">private</span> NotificationCompat.Builder builder;</span><br><span class="line"> <span class="keyword">private</span> Handler handler = <span class="keyword">new</span> Handler();</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate();</span><br><span class="line"> initNotification();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> IBinder <span class="title">onBind</span><span class="params">(Intent intent)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">onStartCommand</span><span class="params">(Intent intent, <span class="keyword">int</span> flags, <span class="keyword">int</span> startId)</span> </span>{</span><br><span class="line"> <span class="comment">// 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。</span></span><br><span class="line"> <span class="keyword">if</span> (!MyWindowManager.isWindowShowing()) {</span><br><span class="line"> handler.post(<span class="keyword">new</span> Runnable() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> MyWindowManager.createSmallWindow(getApplicationContext());</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.onStartCommand(intent, flags, startId);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initNotification</span><span class="params">()</span> </span>{</span><br><span class="line"> builder = <span class="keyword">new</span> NotificationCompat.Builder(<span class="keyword">this</span>)</span><br><span class="line"> .setSmallIcon(R.drawable.ic_launcher)</span><br><span class="line"> .setContentTitle(getResources().getString(R.string.app_name))</span><br><span class="line"> .setContentText(<span class="string">"您正在录制视频内容哦"</span>)</span><br><span class="line"> .setOngoing(<span class="keyword">true</span>)</span><br><span class="line"> .setDefaults(Notification.DEFAULT_VIBRATE);</span><br><span class="line"></span><br><span class="line"> Intent backIntent = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, RecordActivityV2.class);</span><br><span class="line"> PendingIntent pendingIntent = PendingIntent.getActivity(<span class="keyword">this</span>, PENDING_REQUEST_CODE, backIntent, PendingIntent.FLAG_UPDATE_CURRENT);</span><br><span class="line"> builder.setContentIntent(pendingIntent);</span><br><span class="line"> mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);</span><br><span class="line"> mNotificationManager.notify(NOTIFICATION_ID, builder.build());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDestroy</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onDestroy();</span><br><span class="line"> <span class="keyword">if</span> (mNotificationManager != <span class="keyword">null</span>) {</span><br><span class="line"> mNotificationManager.cancel(NOTIFICATION_ID);</span><br><span class="line"> }</span><br><span class="line"> MyWindowManager.removeSmallWindow(getApplicationContext());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的代码我们可以看出服务启动后进行了Notification的初始化,我这里按照Bilibili的样式去实现的。在Activity中当启动直播时,Bilibili是通过<code>moveTaskToBack(true)</code>直接回到桌面的,所以我猜想开启通知栏和悬浮窗服务应该是在Activity生命周期的<code>onStop()</code>启动和<code>onResume()</code>关闭,当然这些都是业务逻辑可自己定义。</p>
<figure class="highlight java"><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"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onResume</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onResume();</span><br><span class="line"> <span class="keyword">if</span> (isRecording) stopScreenRecordService();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onStop</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onStop();</span><br><span class="line"> <span class="keyword">if</span> (isRecording) startScreenRecordService();</span><br><span class="line">}</span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">startScreenRecordService</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (mRecorder != <span class="keyword">null</span> && mRecorder.getStatus()) {</span><br><span class="line"> Intent runningServiceIT = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, ScreenRecordListenerService.class);</span><br><span class="line"> <span class="comment">// bindService(runningServiceIT, connection, BIND_AUTO_CREATE); </span></span><br><span class="line"> startService(runningServiceIT);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">stopScreenRecordService</span><span class="params">()</span> </span>{</span><br><span class="line"> Intent runningServiceIT = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, ScreenRecordListenerService.class);</span><br><span class="line"> stopService(runningServiceIT);</span><br><span class="line"> <span class="keyword">if</span> (mRecorder != <span class="keyword">null</span> && mRecorder.getStatus()) {</span><br><span class="line"> Toast.makeText(<span class="keyword">this</span>, <span class="string">"现在正在进行录屏直播哦"</span>, Toast.LENGTH_SHORT).show();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>最后别忘了在AndroidManifest.xml中进行Service的注册和对悬浮窗的授权。</p>
<figure class="highlight"><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"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /></span><br><span class="line"><application</span><br><span class="line"> <service</span><br><span class="line"> android:name=".service.ScreenRecordListenerService"</span><br><span class="line"> android:enabled="true"</span><br><span class="line"> android:exported="false" /></span><br><span class="line"></application></span><br></pre></td></tr></table></figure>
<p>关于悬浮窗与通知栏差不多就介绍到这里了,更多细节请查看源码,本Demo源码都是基于上篇博客提到的ScreenRecorder实现的。</p>
<p>我们来看看最终效果吧:</p>
<p><img src="https://raw.githubusercontent.com/eterrao/ScreenRecorder/master/images/screenRecorderDemo.gif" alt="效果图"></p>
<h2 id="录屏直播的实现机制"><a href="#录屏直播的实现机制" class="headerlink" title="录屏直播的实现机制"></a>录屏直播的实现机制</h2><p>使用MediaProjection与VirtualDisplay等Android 5.0以上的API实现的,在此就不多说了,请看<a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396" target="_blank" rel="external">上篇博客</a>吧。</p>
<h1 id="尾语"><a href="#尾语" class="headerlink" title="尾语"></a>尾语</h1><p>果然还是有个提纲要好下笔的多,至少知道写些什么。因为最近文章写的都有点赶,有疑问的朋友还请留言指正!接下来准备写一下<strong>MediaProjection 实现录屏中 MediaCodec 的详细用法</strong>,至于反编译Bilibili的过程就不写了,反编译为的是参考别人的思路,并不是为了做代码小偷,这点职业操守还是有的,如果说业务上有盗窃之意请出门左转找我们的产品经理,我是无辜的码农[笑]。之后会在别的文章中提及一下我个人使用反编译时的一些心得和小技巧吧。别的主题都还需要花些时间再看看,毕竟都是工作时候囫囵吞枣接触的,理论知识可不能瞎糊弄。</p>
<h2 id="Demo源码"><a href="#Demo源码" class="headerlink" title="Demo源码"></a><a href="https://github.com/eterrao/ScreenRecorder" target="_blank" rel="external">Demo源码</a></h2><p>Demo源码都在我的<a href="https://github.com/eterrao/ScreenRecorder" target="_blank" rel="external">GitHub仓库</a>中,有需要的朋友可以去 <code>clone</code> 或 <code>fork</code>,如对您有帮助还请给个Star,十分感谢~</p>
<h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><ul>
<li><a href="http://blog.csdn.net/guolin_blog/article/details/8689140" target="_blank" rel="external">Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果</a></li>
<li><a href="https://developer.android.google.cn/reference/android/app/Notification.html" target="_blank" rel="external">Android Notification</a></li>
<li><a href="http://blog.csdn.net/zxccxzzxz/article/details/52377191" target="_blank" rel="external">Android面试一天一题(1Day)IntentService作用是什么</a></li>
<li><a href="http://www.cnblogs.com/plokmju/p/android_Notification.html" target="_blank" rel="external">Android–通知之Notification</a></li>
</ul>
<p>请尊重分享成果,转载请注明出处,本文来自<a href="http://my.csdn.net/zxccxzzxz">Coder包子哥</a>,原文链接:<a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244">http://blog.csdn.net/zxccxzzxz/article/details/54254244</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396">Android实现录屏直播(一)ScreenRecorder的简单分析</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244">Android实现录屏直播(二)需求才是硬道理之产品功能调研</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272">Android实现录屏直播(三)MediaProjection + VirtualDisplay + librtmp + MediaCodec实现视频编码并推流到rtmp服务器</a></p>
<p>前面的<a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396">Android实现录屏直播(一)ScreenRecorder的简单分析</a>一文中我们对 ScreenRecorder 这个开源 Demo 中的实现机制大概有了了解,但在继续写这个系列文章的时候发现每一个细节都太紧密了,稍微不注意就会深入每个知识点的细节导致文章又臭又长还表述不清晰,于是我决定把这7天实现该功能的整个流程重新梳理一遍,按照我开发和研究学习的步骤来写,大致过程如下:</p>
<ol>
<li><a href="#产品功能调研">产品功能调研</a></li>
<li>Bilibili 的反编译及 UI 的编写</li>
<li><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396">ScreenRecorder 等 Demo 的代码分析</a></li>
<li>对 H264 结构、FLV 格式封装的研究学习</li>
<li>sps pps avcc 关键帧等视频封装原理的学习与分析</li>
<li>MediaProjection 实现录屏中 MediaCodec 的详细用法</li>
<li><a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272">编码后的帧进行推流</a></li>
</ol>
Android实现录屏直播(一)ScreenRecorder的简单分析
http://www.raomengyang.com/2017/01/07/Android实现录屏直播(一)ScreenRecorder的简单分析/
2017-01-06T16:53:49.000Z
2017-03-12T17:19:47.000Z
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396" target="_blank" rel="external">Android实现录屏直播(一)ScreenRecorder的简单分析</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244" target="_blank" rel="external">Android实现录屏直播(二)需求才是硬道理之产品功能调研</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272" target="_blank" rel="external">Android实现录屏直播(三)MediaProjection + VirtualDisplay + librtmp + MediaCodec实现视频编码并推流到rtmp服务器</a></p>
<p>应项目需求瞄准了Bilibili的录屏直播功能,基本就仿着做一个吧。研究后发现Bilibili是使用的MediaProjection 与 VirtualDisplay结合实现的,需要 Android 5.0 Lollipop API 21以上的系统才能使用。</p>
<p>其实官方提供的<a href="https://github.com/googlesamples/android-ScreenCapture" target="_blank" rel="external">android-ScreenCapture</a>这个Sample中已经有了MediaRecorder的实现与使用方式,还有使用MediaRecorder实现的录制屏幕到本地文件的Demo,从中我们都能了解这些API的使用。</p>
<p>而如果需要直播推流的话就需要自定义MediaCodec,再从MediaCodec进行编码后获取编码后的帧,免去了我们进行原始帧的采集的步骤省了不少事。可是问题来了,因为之前没有仔细了解H264文件的结构与FLV封装的相关技术,其中爬了不少坑,此后我会一一记录下来,希望对用到的朋友有帮助。</p>
<p>项目中对我参考意义最大的一个Demo是网友Yrom的GitHub项目<a href="https://github.com/yrom/ScreenRecorder" target="_blank" rel="external">ScreenRecorder</a>,Demo中实现了录屏并将视频流存为本地的MP4文件(咳咳,其实Yrom就是Bilibili的员工吧?( ゜- ゜)つロ)😏。在此先大致分析一下该Demo的实现,之后我会再说明我的实现方式。</p>
<a id="more"></a>
<h1 id="ScreenRecorder"><a href="#ScreenRecorder" class="headerlink" title="ScreenRecorder"></a><a href="https://github.com/yrom/ScreenRecorder" target="_blank" rel="external">ScreenRecorder</a></h1><p>具体的<strong>原理</strong>在Demo的README中已经说得很明白了:</p>
<blockquote>
<ul>
<li><code>Display</code> 可以“投影”到一个 <code>VirtualDisplay</code></li>
<li>通过 <code>MediaProjectionManager</code> 取得的 <code>MediaProjection</code>创建<code>VirtualDisplay</code></li>
<li><code>VirtualDisplay</code> 会将图像渲染到 <code>Surface</code>中,而这个<code>Surface</code>是由<code>MediaCodec</code>所创建的</li>
</ul>
<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">> mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);</span><br><span class="line">> ...</span><br><span class="line">> mSurface = mEncoder.createInputSurface();</span><br><span class="line">> ...</span><br><span class="line">> mVirtualDisplay = mMediaProjection.createVirtualDisplay(name, mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mSurface, null, null);</span><br><span class="line">></span><br></pre></td></tr></table></figure>
</blockquote>
<p>></p>
<blockquote>
<ul>
<li><code>MediaMuxer</code> 将从 <code>MediaCodec</code> 得到的图像元数据封装并输出到MP4文件中</li>
</ul>
<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">> int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);</span><br><span class="line">> ...</span><br><span class="line">> ByteBuffer encodedData = mEncoder.getOutputBuffer(index);</span><br><span class="line">> ...</span><br><span class="line">> mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);</span><br><span class="line">></span><br><span class="line">></span><br></pre></td></tr></table></figure>
</blockquote>
<p>></p>
<blockquote>
<p>所以其实在<strong>Android 4.4</strong>上可以通过<code>DisplayManager</code>来创建<code>VirtualDisplay</code>也是可以实现录屏,但因为权限限制需要<strong>ROOT</strong>。 (see <a href="https://developer.android.com/reference/android/hardware/display/DisplayManager.html" target="_blank" rel="external">DisplayManager.createVirtualDisplay()</a>)</p>
</blockquote>
<p>Demo很简单,两个Java文件:</p>
<ul>
<li>MainActivity.java</li>
<li>ScreenRecorder.java</li>
</ul>
<h2 id="MainActivity"><a href="#MainActivity" class="headerlink" title="MainActivity"></a>MainActivity</h2><p>类中仅仅是实现的入口,最重要的方法是<code>onActivityResult</code>,因为MediaProjection就需要从该方法开启。但是别忘了先进行MediaProjectionManager的初始化</p>
<figure class="highlight java"><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"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onActivityResult</span><span class="params">(<span class="keyword">int</span> requestCode, <span class="keyword">int</span> resultCode, Intent data)</span> </span>{</span><br><span class="line"> MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);</span><br><span class="line"> <span class="keyword">if</span> (mediaProjection == <span class="keyword">null</span>) {</span><br><span class="line"> Log.e(<span class="string">"@@"</span>, <span class="string">"media projection is null"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// video size</span></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> width = <span class="number">1280</span>;</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> height = <span class="number">720</span>;</span><br><span class="line"> File file = <span class="keyword">new</span> File(Environment.getExternalStorageDirectory(),</span><br><span class="line"> <span class="string">"record-"</span> + width + <span class="string">"x"</span> + height + <span class="string">"-"</span> + System.currentTimeMillis() + <span class="string">".mp4"</span>);</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> bitrate = <span class="number">6000000</span>;</span><br><span class="line"> mRecorder = <span class="keyword">new</span> ScreenRecorder(width, height, bitrate, <span class="number">1</span>, mediaProjection, file.getAbsolutePath());</span><br><span class="line"> mRecorder.start();</span><br><span class="line"> mButton.setText(<span class="string">"Stop Recorder"</span>);</span><br><span class="line"> Toast.makeText(<span class="keyword">this</span>, <span class="string">"Screen recorder is running..."</span>, Toast.LENGTH_SHORT).show();</span><br><span class="line"> moveTaskToBack(<span class="keyword">true</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="ScreenRecorder-1"><a href="#ScreenRecorder-1" class="headerlink" title="ScreenRecorder"></a>ScreenRecorder</h2><p>这是一个线程,结构很清晰,<code>run()</code>方法中完成了MediaCodec的初始化,VirtualDisplay的创建,以及循环进行编码的全部实现。</p>
<h3 id="线程主体"><a href="#线程主体" class="headerlink" title="线程主体"></a>线程主体</h3><figure class="highlight java"><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"> <span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> prepareEncoder();</span><br><span class="line"> mMuxer = <span class="keyword">new</span> MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line"> }</span><br><span class="line"> mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + <span class="string">"-display"</span>,</span><br><span class="line"> mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,</span><br><span class="line"> mSurface, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> Log.d(TAG, <span class="string">"created virtual display: "</span> + mVirtualDisplay);</span><br><span class="line"> recordVirtualDisplay();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> release();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="MediaCodec的初始化"><a href="#MediaCodec的初始化" class="headerlink" title="MediaCodec的初始化"></a>MediaCodec的初始化</h3><p>方法中进行了编码器的参数配置与启动、Surface的创建两个关键的步骤</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">prepareEncoder</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);</span><br><span class="line"> format.setInteger(MediaFormat.KEY_COLOR_FORMAT,</span><br><span class="line"> MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); <span class="comment">// 录屏必须配置的参数</span></span><br><span class="line"> format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);</span><br><span class="line"> format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);</span><br><span class="line"> format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);</span><br><span class="line"> Log.d(TAG, <span class="string">"created video format: "</span> + format);</span><br><span class="line"> mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);</span><br><span class="line"> mEncoder.configure(format, <span class="keyword">null</span>, <span class="keyword">null</span>, MediaCodec.CONFIGURE_FLAG_ENCODE);</span><br><span class="line"> mSurface = mEncoder.createInputSurface(); <span class="comment">// 需要在createEncoderByType之后和start()之前才能创建,源码注释写的很清楚</span></span><br><span class="line"> Log.d(TAG, <span class="string">"created input surface: "</span> + mSurface);</span><br><span class="line"> mEncoder.start();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="编码器实现循环编码"><a href="#编码器实现循环编码" class="headerlink" title="编码器实现循环编码"></a>编码器实现循环编码</h3><p>下面的代码就是编码过程,由于作者使用的是Muxer来进行视频的采集,所以在resetOutputFormat方法中实际意义是将编码后的视频参数信息传递给Muxer并启动Muxer。</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">recordVirtualDisplay</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (!mQuit.get()) {</span><br><span class="line"> <span class="keyword">int</span> index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);</span><br><span class="line"> Log.i(TAG, <span class="string">"dequeue output buffer index="</span> + index);</span><br><span class="line"> <span class="keyword">if</span> (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {</span><br><span class="line"> resetOutputFormat();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (index == MediaCodec.INFO_TRY_AGAIN_LATER) {</span><br><span class="line"> Log.d(TAG, <span class="string">"retrieving buffers time out!"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// wait 10ms</span></span><br><span class="line"> Thread.sleep(<span class="number">10</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (index >= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (!mMuxerStarted) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"MediaMuxer dose not call addTrack(format) "</span>);</span><br><span class="line"> }</span><br><span class="line"> encodeToVideoTrack(index);</span><br><span class="line"> mEncoder.releaseOutputBuffer(index, <span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><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"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">resetOutputFormat</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// should happen before receiving buffers, and should only happen once</span></span><br><span class="line"> <span class="keyword">if</span> (mMuxerStarted) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"output format already changed!"</span>);</span><br><span class="line"> }</span><br><span class="line"> MediaFormat newFormat = mEncoder.getOutputFormat();</span><br><span class="line"> <span class="comment">// 在此也可以进行sps与pps的获取,获取方式参见方法getSpsPpsByteBuffer()</span></span><br><span class="line"> Log.i(TAG, <span class="string">"output format changed.\n new format: "</span> + newFormat.toString());</span><br><span class="line"> mVideoTrackIndex = mMuxer.addTrack(newFormat);</span><br><span class="line"> mMuxer.start();</span><br><span class="line"> mMuxerStarted = <span class="keyword">true</span>;</span><br><span class="line"> Log.i(TAG, <span class="string">"started media muxer, videoIndex="</span> + mVideoTrackIndex);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>获取sps pps的ByteBuffer,注意此处的sps pps都是read-only只读状态</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">getSpsPpsByteBuffer</span><span class="params">(MediaFormat newFormat)</span> </span>{</span><br><span class="line"> ByteBuffer rawSps = newFormat.getByteBuffer(<span class="string">"csd-0"</span>); </span><br><span class="line"> ByteBuffer rawPps = newFormat.getByteBuffer(<span class="string">"csd-1"</span>); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="录屏视频帧的编码过程"><a href="#录屏视频帧的编码过程" class="headerlink" title="录屏视频帧的编码过程"></a>录屏视频帧的编码过程</h3><p>BufferInfo.flags表示当前编码的信息,如源码注释:</p>
<figure class="highlight java"><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 class="comment">/**</span><br><span class="line"> * This indicates that the (encoded) buffer marked as such contains</span><br><span class="line"> * the data for a key frame.</span><br><span class="line"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> BUFFER_FLAG_KEY_FRAME = <span class="number">1</span>; <span class="comment">// 关键帧</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span><br><span class="line"> * This indicated that the buffer marked as such contains codec</span><br><span class="line"> * initialization / codec specific data instead of media data.</span><br><span class="line"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> BUFFER_FLAG_CODEC_CONFIG = <span class="number">2</span>; <span class="comment">// 该状态表示当前数据是avcc,可以在此获取sps pps</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span><br><span class="line"> * This signals the end of stream, i.e. no buffers will be available</span><br><span class="line"> * after this, unless of course, {<span class="doctag">@link</span> #flush} follows.</span><br><span class="line"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> BUFFER_FLAG_END_OF_STREAM = <span class="number">4</span>;</span><br></pre></td></tr></table></figure>
<p>实现编码:</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">encodeToVideoTrack</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> ByteBuffer encodedData = mEncoder.getOutputBuffer(index);</span><br><span class="line"> <span class="keyword">if</span> ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// The codec config data was pulled out and fed to the muxer when we got</span></span><br><span class="line"> <span class="comment">// the INFO_OUTPUT_FORMAT_CHANGED status.</span></span><br><span class="line"> <span class="comment">// Ignore it.</span></span><br><span class="line"> <span class="comment">// 大致意思就是配置信息(avcc)已经在之前的resetOutputFormat()中喂给了Muxer,此处已经用不到了,然而在我的项目中这一步却是十分重要的一步,因为我需要手动提前实现sps, pps的合成发送给流媒体服务器</span></span><br><span class="line"> Log.d(TAG, <span class="string">"ignoring BUFFER_FLAG_CODEC_CONFIG"</span>);</span><br><span class="line"> mBufferInfo.size = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (mBufferInfo.size == <span class="number">0</span>) {</span><br><span class="line"> Log.d(TAG, <span class="string">"info.size == 0, drop it."</span>);</span><br><span class="line"> encodedData = <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Log.d(TAG, <span class="string">"got buffer, info: size="</span> + mBufferInfo.size</span><br><span class="line"> + <span class="string">", presentationTimeUs="</span> + mBufferInfo.presentationTimeUs</span><br><span class="line"> + <span class="string">", offset="</span> + mBufferInfo.offset);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (encodedData != <span class="keyword">null</span>) {</span><br><span class="line"> encodedData.position(mBufferInfo.offset);</span><br><span class="line"> encodedData.limit(mBufferInfo.offset + mBufferInfo.size); <span class="comment">// encodedData是编码后的视频帧,但注意作者在此并没有进行关键帧与普通视频帧的区别,统一将数据写入Muxer</span></span><br><span class="line"> mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);</span><br><span class="line"> Log.i(TAG, <span class="string">"sent "</span> + mBufferInfo.size + <span class="string">" bytes to muxer..."</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以上就是对ScreenRecorder这个Demo的大体分析,由于总结时间仓促,很多细节部分我也没有进行深入的发掘研究,所以请大家抱着怀疑的态度阅读,如果说明有误或是理解不到位的地方,希望大家帮忙指出,谢谢!</p>
<h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p>在功能的开发中还参考了很多有价值的资料与文章:</p>
<ol>
<li><a href="http://www.dobest.me/blog/2016/06/17/Android%E5%B1%8F%E5%B9%95%E7%9B%B4%E6%92%AD%E6%96%B9%E6%A1%88/" target="_blank" rel="external">Android屏幕直播方案</a></li>
<li><a href="https://android.googlesource.com/platform/cts/+/kitkat-release/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java" target="_blank" rel="external">Google官方的EncodeVirtualDisplayTest</a></li>
<li><a href="https://segmentfault.com/a/1190000007361184" target="_blank" rel="external">FLV文件格式解析</a></li>
<li><a href="http://www.codeman.net/2014/01/439.html" target="_blank" rel="external">使用librtmp进行H264与AAC直播</a></li>
<li>后续更新…</li>
</ol>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54150396">Android实现录屏直播(一)ScreenRecorder的简单分析</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/54254244">Android实现录屏直播(二)需求才是硬道理之产品功能调研</a></p>
<p><a href="http://blog.csdn.net/zxccxzzxz/article/details/55230272">Android实现录屏直播(三)MediaProjection + VirtualDisplay + librtmp + MediaCodec实现视频编码并推流到rtmp服务器</a></p>
<p>应项目需求瞄准了Bilibili的录屏直播功能,基本就仿着做一个吧。研究后发现Bilibili是使用的MediaProjection 与 VirtualDisplay结合实现的,需要 Android 5.0 Lollipop API 21以上的系统才能使用。</p>
<p>其实官方提供的<a href="https://github.com/googlesamples/android-ScreenCapture">android-ScreenCapture</a>这个Sample中已经有了MediaRecorder的实现与使用方式,还有使用MediaRecorder实现的录制屏幕到本地文件的Demo,从中我们都能了解这些API的使用。</p>
<p>而如果需要直播推流的话就需要自定义MediaCodec,再从MediaCodec进行编码后获取编码后的帧,免去了我们进行原始帧的采集的步骤省了不少事。可是问题来了,因为之前没有仔细了解H264文件的结构与FLV封装的相关技术,其中爬了不少坑,此后我会一一记录下来,希望对用到的朋友有帮助。</p>
<p>项目中对我参考意义最大的一个Demo是网友Yrom的GitHub项目<a href="https://github.com/yrom/ScreenRecorder">ScreenRecorder</a>,Demo中实现了录屏并将视频流存为本地的MP4文件(咳咳,其实Yrom就是Bilibili的员工吧?( ゜- ゜)つロ)😏。在此先大致分析一下该Demo的实现,之后我会再说明我的实现方式。</p>
2016年终总结
http://www.raomengyang.com/2016/12/31/2016年终总结/
2016-12-31T05:10:40.000Z
2016-12-31T05:12:44.000Z
<p><img src="https://raw.githubusercontent.com/eterrao/BlogExamples/master/personalWorkSummary.jpg" alt="img"></p>
<p>在月初的时候我就写了一些年终总结,比较糙,也偏向于个人生活的事情。今天趁着2016的最后一天再将我的工作状态做个全面总结。其实在回想的时候真不敢相信一年内能做这么多事,我从一个外行到编程菜鸟彻彻底底的踏上了程序员的不归路。</p>
<a id="more"></a>
<h1 id="我的2016"><a href="#我的2016" class="headerlink" title="我的2016"></a>我的2016</h1><h3 id="项目转型"><a href="#项目转型" class="headerlink" title="项目转型"></a>项目转型</h3><p>去年来到现公司以后我就从菜鸟的水平进行了项目建立,今年年初产品有了个形状后公司业务突然从自媒体直播转向教育直播,于是换了一下包装重新将各种功能和UI进行了大范围的调整。此次调整也使得我成长了不少。</p>
<p>第一次实现交易订单,凡是跟钱挂钩的业务,必须要谨慎对待马虎不得。但是偏偏有一次粗心犯了错,损失了2000多RMB的订单,问题源于字符串精度转换的问题,第二天上线后吓尿了😱紧急修复再上线。感谢服务器的同事的救援才挽回了不少的损失,运营同事也受了不少用户的抱怨。庆幸的是当时公司的业务量还不大,不然就像日本的那位同行一下子让公司蒙受损失400个亿,这400个小目标打几辈子工才能还得起?没有经历就不会长记性,从那次起我对代码的测试和鲁棒性就十分重视。代码质量代表了个人的水平,切勿贪图速度不保证代码的严密性和健壮性!</p>
<p>除了业务,我和同事还一起将大部分的代码进行了一次重构,此次调整为的将代码的耦合度降低。所以最终基于MVP的框架确实省了不少事。所以设计模式真的很重要,再次重复面试官的那句话,想起了一本设计模式的书中也有类似的话语。</p>
<p>后期同事离职后剩我一人孤军奋战,完善了Android客户端的两个核心功能,感觉技能提升了不少。不过一个人闭门造车的日子真的很难熬,遇到瓶颈或是深入思考细节的时候可能会越陷越深无法自拔,特别希望有人能够探讨或者指点迷津,毕竟是一个Team,生产效率要高得多。</p>
<h3 id="开始写博客"><a href="#开始写博客" class="headerlink" title="开始写博客"></a>开始写博客</h3><p>对于我这种文笔不好的人,写一篇像样的博客真的很难,既要言简意赅,还要图文并茂的把概念说明白是一件很要命的事。博客重要性我从15年刚开始学编程的时候就意识到了,真正开始写一写东西是从16年才动手的,搭了<a href="http://www.shutu.tech" target="_blank" rel="external">个人博客</a>,又将<a href="http://www.cnblogs.com/raomengyang" target="_blank" rel="external">博客园</a>搬家到<a href="http://blog.csdn.net/zxccxzzxz" target="_blank" rel="external">CSDN</a>,瞎搞半天最后三个博客都在维护。然而这一年来也没有什么产出,这是我最大的遗憾,看到别人一天天在进步,自己的内心也十分的浮躁总想一口吃个胖子,导致了这样一种情况,<strong>刚想好了一个主题写了一半不到因为工作或别的事情耽搁了就忘记这个事了,下次再想起来就不写篇文章了又重新开启另一篇博客</strong>,这样死循环下去最后没有一篇正式的文章发布。我个人也早就意识到这个很严重的问题而一直没有改正。要么就是<strong>总觉得这个主题是否太简单,别人都写过了自己就不写了</strong>。最近看到一位大神曾经的博文,原来他也遇到过类似的问题,他得出的结论是:</p>
<blockquote>
<p>对于coding这件事,不止于会问,如果想要有提高,夯实基础和总结积累是两个很重要的方面。夯实基础即是要多看书,看好书,看经典。把基本的原理,概念要理解透。总结积累即是要在实践的过程中,对每次遇到的问题、困难进行总结提炼,遇到的问题是什么,自己是怎么解决的,总结的一个好方式就是做笔记写备忘,所谓好记性不如烂笔头,与其相同的问题一次次重复地遇到不如把每次遇到的问题及解决方法都记录下来,一是加深了理解,二来把东西放在自家后院,那才是自己的,用起来或者平时翻出来看看也是方便。</p>
<p>刚刚开始写笔记的时候常常会觉得,这个东西太简单了,用一次就记住了,没有必要去写。其实并不是这样。简单又何妨,就我个人经验而言,只要是一开始把你难住的问题,往往还会有第二次,第三次。所以,多思考,勤笔记,下次再遇则有迹可循。只有足够努力,方显毫不费力。想做一件事,任何时候都不算太晚,除非只是想想而已。</p>
</blockquote>
<p>所以无论知识点的简单与否,写的清晰连贯,总结的到位让自己理解更深刻才是最重要的。明确我们写博客的最终目的,<strong>不要为了写博客而写博客</strong>!</p>
<h3 id="参加技术开发大会"><a href="#参加技术开发大会" class="headerlink" title="参加技术开发大会"></a>参加技术开发大会</h3><p>11月底参加了<a href="http://blog.csdn.net/zxccxzzxz/article/details/53240815" target="_blank" rel="external">北京的GDG DevFest大会</a>,虽然12月初的GDD谷歌开发者大会没去成(悔恨死了😭)。但此次大会应该算是我Android开发之路的一个转折点。作为一名Android开发者,理应多参加这种有意义的活动,不仅能够涨知识涨眼界了解当前热门的技术点,还能接触到行业趋势的变化顺便积累人脉,让自己的平台上升一个新高度。技术最可怕的是做井底之蛙,闭门造车,轮子再造也是圆的。</p>
<p>最后给自己一个期限,<strong>最近的两三年能够亲自参加Google I/O大会,见识一下真正的硅谷。</strong></p>
<h1 id="2017的小目标"><a href="#2017的小目标" class="headerlink" title="2017的小目标"></a>2017的小目标</h1><h3 id="跳槽"><a href="#跳槽" class="headerlink" title="跳槽"></a>跳槽</h3><p>此次目标是中大型互联网行业的公司。因为体验过了小公司病,希望能够去一家大公司开拓自己的视野,能够跟随更多的大神学习得到快速成长。</p>
<h3 id="夯实基础"><a href="#夯实基础" class="headerlink" title="夯实基础"></a>夯实基础</h3><p>恶补科班必修的基础。我对编程的追求不止局限于只会写业务代码,毕竟码农和优秀的程序员是两种职业,既然都选择了编程生涯,那么必然要爱一行干一行,干一行精一行。计算机基础一直是我的软肋,直到现今我也只是将必学的皮毛囫囵吞枣了一下,平时工作忙的时候没有太多精力放在上面,只好睡前或者起床的半小时翻翻书做做笔记。点滴的积累也稍微有了点感觉对很多曾经不明白的知识点有了初步的掌握。想起那位面试官说的话,“前人花了几十年的时间走过的路必然是有意义的“,基础不扎实盖不成高楼。新年来要重新调整学习状态和习惯,提高效率。</p>
<h3 id="写博客"><a href="#写博客" class="headerlink" title="写博客"></a>写博客</h3><p>保证至少一月一篇高质量技术文章。自己遇到的各种问题在上面已经说明就不多说了。接下来的博客需要开启自己的系列专栏,深入浅出的分析叙述。还要锻炼写作能力和提高语言组织的技巧。</p>
<h3 id="编写开源库"><a href="#编写开源库" class="headerlink" title="编写开源库"></a>编写开源库</h3><p>贡献自己的开源库到GitHub。初学Android的时候就很崇拜那些贡献开源库的大神们,感觉能够写这些代码的人都很牛,他们不仅为开源社区做了贡献,还帮助了一波又一波的菜鸟实现了相关功能。编程的好处就是这样,前人种树后人乘凉。拿来主义这么多年,我也希望成为一名合格的开源社区参与者,将自己的微薄力量奉献出来为别人来带帮助。</p>
<h3 id="成为高级Android开发者"><a href="#成为高级Android开发者" class="headerlink" title="成为高级Android开发者"></a>成为高级Android开发者</h3><p>一转眼2015年入门以来已经经历了接近两年的时间,没有达到自己预期的目标。2017年这一年意味着是Android开发的第三年,三年足以让一个人从门外汉变成一个行业精英,在这个关键的分水岭一切都靠自己把握了,<strong>优秀的人都拥有很强的自驱力</strong>。</p>
<p><img src="https://raw.githubusercontent.com/eterrao/BlogExamples/master/personalWorkSummary.jpg" alt="img"></p>
<p>在月初的时候我就写了一些年终总结,比较糙,也偏向于个人生活的事情。今天趁着2016的最后一天再将我的工作状态做个全面总结。其实在回想的时候真不敢相信一年内能做这么多事,我从一个外行到编程菜鸟彻彻底底的踏上了程序员的不归路。</p>
工作一年后的状态与总结
http://www.raomengyang.com/2016/12/18/工作一年后的状态与总结/
2016-12-18T09:43:56.000Z
2016-12-31T05:27:01.000Z
<h1 id="2016-发生了什么个人大事?"><a href="#2016-发生了什么个人大事?" class="headerlink" title="2016 发生了什么个人大事?"></a>2016 发生了什么个人大事?</h1><a id="more"></a>
<h2 id="回家"><a href="#回家" class="headerlink" title="回家"></a>回家</h2><p>曾经绝对不会相信,大一那次过年回家再一走就是三年之久。因私人原因无法吐露,但一个人距千里大陆离乡在外三年的苦衷,在这个交通便捷的时代似乎也是让人无法理解。时间越长,我越能理解古代诗人写诗时那种惆怅的心情。“独在异乡为异客,每逢佳节倍思亲。”每句好诗词中又掺杂了作者的多少辛酸?</p>
<p>##好兄弟结婚,生孩子</p>
<p>最值得开心的是十多年的好兄弟S的婚事。我俩从初一以来都是知心知己的好基友,去年一起毕业后他回家考了事业编,我辗转帝都过上了北漂生活。然而这家伙居然没几个月就结束了二十几年的单身并成功的在春节后把生米煮成了熟饭。借他的光中秋我又回了老家一趟,并以伴郎的身份见证了这场幸福美满的婚事。昨日与我微信说,再过几天宝宝就要出生了,要当爹的他现在心里可激动了。看着他已为人父,打心里为他高兴。</p>
<h2 id="涨工资、提期权等待遇的事"><a href="#涨工资、提期权等待遇的事" class="headerlink" title="涨工资、提期权等待遇的事"></a>涨工资、提期权等待遇的事</h2><p>每次都想提毕业的那些事,略显矫情。毕业到重新找到工作的两个月确实有点辛酸,不过已成过去式,日子不是一天天变好吗。我一直相信着只要努力绝不会白费,所以在二月老板和老大找我谈加薪给期权的事也算是对自己的认可。不过创业公司,难免存在或多或少的小公司病,不多说了都是泪。</p>
<h2 id="离与欢,孤身一人独自奋战"><a href="#离与欢,孤身一人独自奋战" class="headerlink" title="离与欢,孤身一人独自奋战"></a>离与欢,孤身一人独自奋战</h2><p>有两个人,一位是刚进公司带我的师父L,一位是与我同期进入公司奋战的同事小G。</p>
<p>师父在二月就离职了,但那几个月他对我工作上的帮助和指引可以说对我的整个职业生涯都有莫大的影响。滴水之恩必当涌泉相报,所以感激一路以来对我帮助甚多的人们。</p>
<p>小G毕业后待了两个月也离职了,后来重新找了一家创业公司。我俩前些日子还一起去了GDG的技术分享会。毕业这一年来,工作上我们一起并肩奋战,谈框架,论代码,指点江山,激扬文字,有基情没节操。所以朋友就是这样一个个相聚再离别,多少也有点感伤。</p>
<h2 id="搬家"><a href="#搬家" class="headerlink" title="搬家"></a>搬家</h2><p>说起搬家,如果是自己在北京买的房子,那么肯定谁都是幸福感满满的吧,但是北漂的我们,对于望而止步的房价,只能老实做蚁族,蜗居。现实很残酷,但是生活还要继续,那句话说得好,<strong>房子是租来的,但生活不是</strong>。</p>
<p>近一周的折腾,工作、清洁和搬家,对于行李少的人来说当然不费事,但无奈我的行李毕业一年不知不觉已经堆成了山(有很多不要但舍不得扔的),结果一天的时间居然搬完了!在贤内助女友的打理下,似乎把这座山搬完变成了可能。</p>
<h2 id="面试"><a href="#面试" class="headerlink" title="面试"></a>面试</h2><p>都怪自己浪,把这个珍贵的机会浪费了😭。详情见<a href="http://blog.csdn.net/zxccxzzxz/article/details/52733726" target="_blank" rel="external">这篇博文</a>。</p>
<h1 id="2015年定的工作、学习计划"><a href="#2015年定的工作、学习计划" class="headerlink" title="2015年定的工作、学习计划"></a>2015年定的工作、学习计划</h1><blockquote>
<p>2016年:</p>
<p>上半年:</p>
<ul>
<li>优化公司产品,做到拥有优秀的功能和体验</li>
<li>开发自己的一款上线产品</li>
<li>将LeetCode的算法题刷完</li>
<li>Java:基础过关(熟练运用线程的调度,常用语法,网络请求的类库)</li>
<li>研究VR方面的开发 (openCV + VR 着装) </li>
</ul>
<p><strong>下半年:</strong></p>
<ul>
<li>基础攻破后技术应该会有质的提高,后半年根据公司业务开发出自己承认的产品,学以致用。</li>
</ul>
</blockquote>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>公司产品前后也删删改改了很多,框架上从强耦合在Activity中的代码,改为了MVP。使用MVP后确实感觉比之前的代码好多了,层次清晰了许多,但近期发现复杂的代码量依旧留存在Presenter中,并没有真正的解决<strong>代码量重</strong>的问题。最近研究MVVM,希望在剩余不多的时间内进行代码重构。</p>
<p>计划中的优秀功能和体验确实有突破,无论是UI也好,功能也罢,都已经慢慢成型为一款产品。对自定义View和动效有了更多的理解。对代码的健壮和容错性变得很在意,中途有一次发生了对精度转换的出错,导致交易的订单金额有损失,还好公司的业务量较小,如果换在大型的商业平台上我就成罪人了。所以细心是做事的关键因素,并且这件事后发现单元测试和集成测试是必不可少的环节。因为项目较小所以从来没有写过测试代码,这犯了Coding的大忌。希望以后能够完善这些不足,避免此类悲剧再次重演。在2017年要提升自己的代码质量和把控能力。</p>
<p>自己的上线产品,目前只有Alpha版本的一个小工具,10月拖到现在两个月了还么有完全提交,侧面反映出我做事不果断、拖拉的性格,这也是很严重的问题。无论在写公司的项目代码,还是写写小Demo,其实对自己的代码能力都有一定的提升,切勿认为看到别人的源码后自己就能够写一套出来,就算一模一样的功能,不自己实践一遍是不知道其中有多少坑的。所以很简单的代码,自己能独立实践就一定要做。</p>
<p>关于数据结构与算法,延伸到自己的计算机基础一直都是软肋。自己本来就是非科班的,在上次面试后面试官指点后就知道这个坑不填不行了,老实把操作系统、数据结构与算法和网络通信协议都好好学学才是硬道理。刷题仅仅是面试中的一个必备的环节。刷完题不代表自己就有能力拿到心仪的Offer。</p>
<p>Java基础倒是补了不少,重新过了很多细节部分。但代码量还是不够,没有达到期望的水准。并发这块经验也不足,遇到高并发的时候线程各种调度控制还没有实际的解决方案。而且感觉面向对象的编程思想还是没有根深蒂固在心中,对于万物皆对象的这门高级程序设计语言来说,使用的时候就应该用正确的打开方式。</p>
<h2 id="面临的问题"><a href="#面临的问题" class="headerlink" title="面临的问题"></a>面临的问题</h2><ul>
<li>技术博客没有什么含量,并且写的少</li>
<li>写博客的时候,不知道想些什么,或者表述不清晰,理解不透彻,废话稍多</li>
<li>面试准备不充分</li>
<li>平时积累没有进行有效的总结,导致了有输入没输出</li>
<li>做事拖拉,想法偏激有问题</li>
<li>对当前工作产生了厌倦,对公司环境产生了不可控的情绪</li>
<li>工作重心不明确,项目之间的重心划分明显有偏差</li>
<li>对新事物学习的激情少了许多,感觉自己的能力没有充分发挥,成长太慢</li>
<li>在变胖的同时更懒了,不愿多做多想</li>
</ul>
<h2 id="2017,未来"><a href="#2017,未来" class="headerlink" title="2017,未来"></a>2017,未来</h2><p>明显感觉去年的计划不够细,并且没有一个明确的规划,都是跟随自己的时间随心而定。未来可不能再这样放荡下去了。</p>
<p>初定一下2017年的工作学习计划:</p>
<ul>
<li>写自定义View的博客专栏</li>
<li>写源码分析的博客专栏</li>
<li>继续学习数据结构与算法,刷题,把每一个点都用博客记录下来</li>
<li>深入探索操作系统的基本原理,网络通信协议等科班必修内容,根基扎实了才能建成高楼大厦</li>
<li>写自己的开源库</li>
<li>学习更多的语言(C++、JavaScript),让自己的知识广度和眼界更开阔</li>
</ul>
<p>在大前提不便的情况下,尽量实现自己的五年职业规划</p>
<h4 id="2017关键技术要点:"><a href="#2017关键技术要点:" class="headerlink" title="2017关键技术要点:"></a>2017关键技术要点:</h4><ul>
<li>数据结构与算法</li>
<li>设计模式</li>
<li>Android源码分析</li>
<li>网络通信协议</li>
<li>操作系统原理</li>
</ul>
<h1 id="2016-发生了什么个人大事?"><a href="#2016-发生了什么个人大事?" class="headerlink" title="2016 发生了什么个人大事?"></a>2016 发生了什么个人大事?</h1>
2016 Beijing GDG DevFest大会参后感
http://www.raomengyang.com/2016/11/25/2016-Beijing-GDG-DevFest大会参后感/
2016-11-25T15:43:50.000Z
2016-11-25T15:43:50.000Z
<p><img src="https://raw.githubusercontent.com/eterrao/BlogExamples/master/GDGTicket.jpg" alt="GDG DevFest 2016 Beijing"></p>
<h2 id="前话"><a href="#前话" class="headerlink" title="前话"></a>前话</h2><p>15年初的时候参加过一次GDG线下举办的一次分享会,因为当时是实习的公司提供的活动场地。有了那次机会后,就一直关注了GDG的活动。</p>
<p>参加的目的最重要的是本次大会是比较盛大的一场技术大会,在经过一年的创业公司的洗礼以后,成长正处于瓶颈期,迷茫却不知所措,本着充电的初衷报了名。</p>
<h3 id="大会还赠送了礼品:"><a href="#大会还赠送了礼品:" class="headerlink" title="大会还赠送了礼品:"></a>大会还赠送了礼品:</h3><ul>
<li>Android小机器人</li>
<li>Google Developer的纪念衫一件</li>
<li>提供了午餐,茶歇有零食</li>
</ul>
<a id="more"></a>
<h2 id="技术简要总结:"><a href="#技术简要总结:" class="headerlink" title="技术简要总结:"></a>技术简要总结:</h2><p>本次大会分享的主题有好几类,但我感觉收获较多的有这么几个:</p>
<ul>
<li>XiaoyuWang(CastBox的创始人)分享的Firebase相关使用和数据分析,近期关注海外产品较多也接触了Firebase,正遇到一些疑惑在这个会上全解决了。<ul>
<li>TestLab:可以进行模拟测试,免费且方便迅速</li>
<li>Admob的填充率高</li>
<li>凡是Google的服务,国内产品反正都是用不上的。</li>
</ul>
</li>
<li>黄帅(博客:猴子搬来的救兵)的Android安全问题,作为开发者的我一直没有考虑过相关的技术点,导致听完分享以后发现漏洞这么多,而自己却什么都不知道。。</li>
<li>任玉刚(《Android开发艺术探索》一书的作者)老师这次的分享还是基于《Android开发艺术探索》中的知识点讨论的,第一次见到本人兴奋ing。。。</li>
<li>说实话,闫国跃的主题xlog太深奥,听的时候懵懵懂懂的,最后完了只有两个字在心中“这人好牛X!” 不过话说回来,深深佩服微信开发团队,一个Mars框架平台承载了这么多的业务开发,一个框架全搞定。妈蛋自己弱爆了,苦笑。。</li>
<li>张铁蕾的异步处理也收获不少。</li>
<li>剩下的蓝牙接触不多,所以没仔细听。</li>
<li>技术小黑屋分享的Memory Leak是我很关注的主题,可是分享过后有些失望,感觉作者没有准备充分,并且了解也不够深刻,说的都是一些很浅的知识点,准备深入又被一笔带过。</li>
<li>最后大神秋百万的闪电分享提到的各项代码优化处理对我帮助很大,曾经总觉得代码算法对UI层的效果影响不大,现在设备的硬件配置都那么高了,还谈什么细节,可听完分享后对自己这种不负责的态度感到羞愧,这就是和优秀程序员的差距!</li>
</ul>
<p>会上的技术分享资料都会分享公布,公众号和微博都可以索取。</p>
<h2 id="感受"><a href="#感受" class="headerlink" title="感受"></a>感受</h2><p>总的来说这次参与完全出乎我的意料,一直膜拜的技术大神:廖祜秋、任玉刚、黄帅(猴子搬来的救兵)等都亲临现场做分享,还有很多不认识的大牛。曾经在我看来他们就是在我所接触的技术界里面遥不可及的神人,可是见到真人后发现都是很谦逊低调却很牛逼的人,一股“我也要变牛X”的想法油然而生,这才是我想要的人生。仅仅是为了自己能够走得更远,看得更高,那时候或许名利早已抛之脑后了吧。</p>
<p>反过来思考了当前自身的现况,只能一个字形容:惨!</p>
<ul>
<li>自身技术成长的障碍短期内一直没有攻破,牢牢地被套在圈子里死循环。</li>
<li>当前的工作环境已经不适合自己的发展,本以为为了参加本次请一天假(本周六加班)会赢得Boss大力支持,倒相反的认为是在浪费工作时间,这样的Boss还有什么盼头。一个人的Android开发,成天就是业务需求,大事没多少,小事没完了。心中无奈只能两个字表达:呵呵!</li>
</ul>
<p>吐槽不快之后更多的是陷入未来技术发展的沉思,半路出家本就基础不扎实,距离年终也就两个月的时间,希望在16年内剩下的这一个多月把欠的技术债尽量弥补。</p>
<p><img src="https://raw.githubusercontent.com/eterrao/BlogExamples/master/GDGTicket.jpg" alt="GDG DevFest 2016 Beijing"></p>
<h2 id="前话"><a href="#前话" class="headerlink" title="前话"></a>前话</h2><p>15年初的时候参加过一次GDG线下举办的一次分享会,因为当时是实习的公司提供的活动场地。有了那次机会后,就一直关注了GDG的活动。</p>
<p>参加的目的最重要的是本次大会是比较盛大的一场技术大会,在经过一年的创业公司的洗礼以后,成长正处于瓶颈期,迷茫却不知所措,本着充电的初衷报了名。</p>
<h3 id="大会还赠送了礼品:"><a href="#大会还赠送了礼品:" class="headerlink" title="大会还赠送了礼品:"></a>大会还赠送了礼品:</h3><ul>
<li>Android小机器人</li>
<li>Google Developer的纪念衫一件</li>
<li>提供了午餐,茶歇有零食</li>
</ul>
修复ijkPlayer进行m3u8 hls流播放时seek进度条拖动不准确的问题
http://www.raomengyang.com/2016/11/09/修复ijkPlayer进行m3u8-hls流播放时seek进度条拖动不准确的问题/
2016-11-09T08:21:35.000Z
2016-11-09T08:21:35.000Z
<p>项目中使用的播放器是ijkPlayer,发现播放切片特点的hls流(m3u8格式的视频)拖动seekBar的时候会莫名的跳转或者seek不到准确的位置,发现网友也遇到了同样的问题,ijk的开发者也说明了是因为UI层的问题导致的,需要自己排查。涉及到该问题的链接:</p>
<ul>
<li><p>通过ijkPlayer播放m3u8视频时快进不准确的解决方案</p>
<p><a href="http://www.jianshu.com/p/bc42ba6e4bf2" target="_blank" rel="external">http://www.jianshu.com/p/bc42ba6e4bf2</a></p>
</li>
<li><p>为什么Sample里面的进度条,往前拖动进度条后,还会往后退几秒</p>
<p><a href="https://github.com/Bilibili/ijkplayer/issues/834" target="_blank" rel="external">https://github.com/Bilibili/ijkplayer/issues/834</a></p>
</li>
</ul>
<ul>
<li><p>向前拖动,进度条会往回跳</p>
<p><a href="https://github.com/Bilibili/ijkplayer/issues/313" target="_blank" rel="external">https://github.com/Bilibili/ijkplayer/issues/313</a></p>
<blockquote>
<p><strong>bbcallen </strong>commented <a href="https://github.com/Bilibili/ijkplayer/issues/313#issuecomment-119404669" target="_blank" rel="external">on Jul 8, 2015</a></p>
<p>UI部分seekbar的回调处理得不太合理,如果放手很快,最后一个位置不会被传给播放器,建议自行修改。</p>
</blockquote>
</li>
</ul>
<a id="more"></a>
<p>既然开发者都说了,那么就老实分析代码吧。因为项目中用到的<code>MediaController</code>继承自Android系统的<code>MediaController</code>,所以还得看看源码,分析得出系统中实现是将seek的listener监听器放在<code>onProgressChanged</code>这个方法中,这也是为什么我们断断续续拖动的时候播放器也会播放,知道这点就够了,把<code>onProgressChanged</code>中的<code>mPlayer.seekTo((int) newposition);</code>放到<code>onStopTrackingTouch</code>方法中。</p>
<p>执行顺序是:</p>
<p>onStartTrackingTouch(执行一次) —> onProgressChanged(拖动就会不停的执行) —> onStopTrackingTouch(停止后最后执行一次)</p>
<p>实现代码如下:</p>
<figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomMediaController</span> <span class="keyword">extends</span> <span class="title">MediaController</span> <span class="keyword">implements</span> <span class="title">ICustomMediaController</span> </span>{</span><br><span class="line"> <span class="comment">// ....................代码省略.............................</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// There are two scenarios that can trigger the seekbar listener to trigger:</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// The first is the user using the touchpad to adjust the posititon of the</span></span><br><span class="line"> <span class="comment">// seekbar's thumb. In this case onStartTrackingTouch is called followed by</span></span><br><span class="line"> <span class="comment">// a number of onProgressChanged notifications, concluded by onStopTrackingTouch.</span></span><br><span class="line"> <span class="comment">// We're setting the field "mDragging" to true for the duration of the dragging</span></span><br><span class="line"> <span class="comment">// session to avoid jumps in the position in case of ongoing playback.</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// The second scenario involves the user operating the scroll ball, in this</span></span><br><span class="line"> <span class="comment">// case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,</span></span><br><span class="line"> <span class="comment">// we will simply apply the updated position without suspending regular updates.</span></span><br><span class="line"> <span class="keyword">private</span> OnSeekBarChangeListener mSeekListener=<span class="keyword">new</span> OnSeekBarChangeListener(){</span><br><span class="line"> <span class="keyword">long</span> newposition;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartTrackingTouch</span><span class="params">(SeekBar bar)</span></span>{</span><br><span class="line"> show(<span class="number">3600000</span>);</span><br><span class="line"> mDragging=<span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span>(seekerBarDraggingListener!=<span class="keyword">null</span>)</span><br><span class="line"> seekerBarDraggingListener.getCurrentDraggingstatus(mDragging);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// By removing these pending progress messages we make sure</span></span><br><span class="line"> <span class="comment">// that a) we won't update the progress while the user adjusts</span></span><br><span class="line"> <span class="comment">// the seekbar and b) once the user is done dragging the thumb</span></span><br><span class="line"> <span class="comment">// we will post one of these messages to the queue again and</span></span><br><span class="line"> <span class="comment">// this ensures that there will be exactly one message queued up.</span></span><br><span class="line"> mHandler.removeMessages(SHOW_PROGRESS);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProgressChanged</span><span class="params">(SeekBar bar,<span class="keyword">int</span> progress,<span class="keyword">boolean</span> fromuser)</span></span>{</span><br><span class="line"> <span class="keyword">if</span>(!fromuser){</span><br><span class="line"> <span class="comment">// We're not interested in programmatically generated changes to</span></span><br><span class="line"> <span class="comment">// the progress bar's position.</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">long</span> duration=mPlayer.getDuration();</span><br><span class="line"> newposition=(duration*progress)/<span class="number">1000L</span>;</span><br><span class="line"> <span class="comment">// 系统原来的实现是在progress改变的时候时刻都在进行videoplayer的seek</span></span><br><span class="line"> <span class="comment">//这会导致seek m3u8切片文件的时候拖动seek时不准确,所以需要在拖动完成后才进行播放器的seekTo()</span></span><br><span class="line"> <span class="comment">// mPlayer.seekTo((int) newposition);</span></span><br><span class="line"> <span class="keyword">if</span>(mCurrentTime!=<span class="keyword">null</span>)</span><br><span class="line"> mCurrentTime.setText(stringForTime((<span class="keyword">int</span>)newposition));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStopTrackingTouch</span><span class="params">(SeekBar bar)</span></span>{</span><br><span class="line"> mDragging=<span class="keyword">false</span>;</span><br><span class="line"> mPlayer.seekTo((<span class="keyword">int</span>)newposition);</span><br><span class="line"> <span class="keyword">if</span>(seekerBarDraggingListener!=<span class="keyword">null</span>)</span><br><span class="line"> seekerBarDraggingListener.getCurrentDraggingstatus(mDragging);</span><br><span class="line"> setProgress();</span><br><span class="line"> updatePausePlay();</span><br><span class="line"> <span class="keyword">if</span>(isntNeedStayShowAfterDrag){</span><br><span class="line"> show(sDefaultTimeout);</span><br><span class="line"> <span class="comment">// Ensure that progress is properly updated in the future,</span></span><br><span class="line"> <span class="comment">// the call to show() does not guarantee this because it is a</span></span><br><span class="line"> <span class="comment">// no-op if we are already showing.</span></span><br><span class="line"> mHandler.sendEmptyMessage(SHOW_PROGRESS);</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 class="comment">// ....................代码省略.............................</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>项目中使用的播放器是ijkPlayer,发现播放切片特点的hls流(m3u8格式的视频)拖动seekBar的时候会莫名的跳转或者seek不到准确的位置,发现网友也遇到了同样的问题,ijk的开发者也说明了是因为UI层的问题导致的,需要自己排查。涉及到该问题的链接:</p>
<ul>
<li><p>通过ijkPlayer播放m3u8视频时快进不准确的解决方案</p>
<p><a href="http://www.jianshu.com/p/bc42ba6e4bf2">http://www.jianshu.com/p/bc42ba6e4bf2</a></p>
</li>
<li><p>为什么Sample里面的进度条,往前拖动进度条后,还会往后退几秒</p>
<p><a href="https://github.com/Bilibili/ijkplayer/issues/834">https://github.com/Bilibili/ijkplayer/issues/834</a></p>
</li>
</ul>
<ul>
<li><p>向前拖动,进度条会往回跳</p>
<p><a href="https://github.com/Bilibili/ijkplayer/issues/313">https://github.com/Bilibili/ijkplayer/issues/313</a></p>
<blockquote>
<p><strong>bbcallen </strong>commented <a href="https://github.com/Bilibili/ijkplayer/issues/313#issuecomment-119404669">on Jul 8, 2015</a></p>
<p>UI部分seekbar的回调处理得不太合理,如果放手很快,最后一个位置不会被传给播放器,建议自行修改。</p>
</blockquote>
</li>
</ul>
mac linux批量修改文件名
http://www.raomengyang.com/2016/11/05/mac-linux批量修改文件名/
2016-11-05T08:19:51.000Z
2016-11-05T08:19:51.000Z
<p>我的mac使用命令行批量修改名字时发现居然没有rename的指令:<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">zsh: command not found: rename</span><br></pre></td></tr></table></figure></p>
<p>所以使用HomeBrew先安装一下:<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">➜ ~ brew install rename</span><br></pre></td></tr></table></figure></p>
<a id="more"></a>
<p>完后可以直接使用简单的一行命令进行多个文件的命名修改,大致格式如下:<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">➜ ~ rename 's/old/new/' *.files</span><br></pre></td></tr></table></figure></p>
<p>例如:</p>
<h4 id="修改批量的png文件的前缀由’ic-’改为’icsetting‘"><a href="#修改批量的png文件的前缀由’ic-’改为’icsetting‘" class="headerlink" title="修改批量的png文件的前缀由’ic_’改为’icsetting‘ :"></a>修改批量的png文件的前缀由’ic_’改为’ic<em>setting</em>‘ :</h4><p>(ic_launcher.png -> ic_setting_launcher.png)</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">➜ ~ rename 's/ic_/ic_setting_/' *.png</span><br></pre></td></tr></table></figure>
<h4 id="修改批量文件的后缀:"><a href="#修改批量文件的后缀:" class="headerlink" title="修改批量文件的后缀:"></a>修改批量文件的后缀:</h4><p>同上</p>
<h4 id="修改部分命名:"><a href="#修改部分命名:" class="headerlink" title="修改部分命名:"></a>修改部分命名:</h4><p>同上</p>
<p>我的mac使用命令行批量修改名字时发现居然没有rename的指令:<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">zsh: command not found: rename</span><br></pre></td></tr></table></figure></p>
<p>所以使用HomeBrew先安装一下:<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">➜ ~ brew install rename</span><br></pre></td></tr></table></figure></p>
面试经验--乐视
http://www.raomengyang.com/2016/10/04/面试经验-乐视/
2016-10-04T06:03:56.000Z
2017-01-08T15:24:38.000Z
<p>此次投的是三年经验的Android开发,最后反而因为自己的失误,没有准备充分而导致结果很悲剧,以此告诫自己千万不能疏忽大意。</p>
<a id="more"></a>
<h2 id="面试过程"><a href="#面试过程" class="headerlink" title="面试过程"></a>面试过程</h2><p>第一次去大公司面试,心里不是一般的激动和紧张,来到乐视大厦门口,感觉这一切都不像是真实的,这才是北漂的我现在想去的地方。向一楼客服MM询问了下要了临时工卡直接上了10F,电梯有点挤。找到HR后她帮我联系了面试官。(PS: 网友说挤,现在看到真实环境还真不是盖的,HR都是挨着坐,研发这边都是各种大电视并排的,但是这种环境工作也正是我所期待的)</p>
<p>面试官貌似很严肃的样子,让我更多了几分不自然。在工位上直接开始给我一套题先做,题数不多只有5题:</p>
<ol>
<li>实现单例模式;题解:<a href="http://blog.csdn.net/zxccxzzxz/article/details/52520021" target="_blank" rel="external">Android设计模式 – 单例模式总结</a></li>
<li>循环和递归实现N阶阶乘 (0 != 1),输入目标值可以输出结果;</li>
<li>二分法查找目标值;</li>
<li>两个栈实现队列,栈的基本方法给出:pop(),push(),isEmpty();题解:<a href="http://blog.csdn.net/zxccxzzxz/article/details/53984292" target="_blank" rel="external">LeetCode Stack Design专题</a></li>
<li>100层跳台阶,一次可以跳123步,总共跳多少步;</li>
</ol>
<p>后面的题解属于我个人博客总结,如有疑点请帮忙指出,谢谢!</p>
<p>由于快到中午开始的,写完正好面试官买饭去了,回来端着饭问我是否写完了,结果看了看答卷瞬间黑脸直接说,你这写的答案感觉不像工作三年的啊,我老实实话实说去年2月才开始自学的编程,9月正式工作的,现在工作一年多了,因为怕简历被刷才写的三年。于是开始问我的答卷,就不一一细说了,想找个缝钻进去。。</p>
<p>数据结构与算法被完爆后,又问了问关于Android方面的知识点,其实这些问题基本都是聊工作接触到的东西,然后面试官试探你入的有多深</p>
<ul>
<li>Volley有哪些类型的网络请求;</li>
<li>Volley的RetryPolicy方法,其中问到了设置超时时间,原理等</li>
<li>Get / Post的请求方式,两者的区别</li>
<li>Android Studio查看线程状态的方法</li>
<li>线程有哪些状态</li>
<li>如何分析Debug ANR的问题</li>
<li>从traces.txt文件中怎么分析ANR</li>
</ul>
<p>记得的也就大概是上面这些问题,但是一般面试官问的时候都是看你的简历问的问题,看看你的简历写的是否真实,千万不要把不会的写的熟练,不然自己打脸。</p>
<p>此次面试最大的感触,是后面面试官问我的优势在哪,我说我很拼,可被痛骂一顿,说没看到我拼的结果,并且我只有输入没有输出,这样是不行的。因为不是科班生的缘故,他建议我把相关的教材找找,把基础好好看看,前人几十年的知识积累都记录在里面,不要忽略这些东西。</p>
<p>虽然此次以GG告终,却改变了我曾经很多幼稚的想法。感谢生命中给予我帮助甚多的人们。</p>
<h2 id="最后总结了几点:"><a href="#最后总结了几点:" class="headerlink" title="最后总结了几点:"></a>最后总结了几点:</h2><ul>
<li>无论是否正在工作还是要多去面试,毕竟能知道自己的不足,能尽快改进</li>
<li>基础很重要!这决定了自己以后成长的深度,不要认为数据结构与算法貌似工作中都用不到</li>
<li>面试前先练练手写代码,确保自己写的清晰、熟练,写的同时注意鲁棒问题,边界问题,答完题后将几个值带进去看看是否正确</li>
<li>写博客,平时工作用到的都尽量记录下来,并且细心一些。这次被问到DDMS中查看线程状态时就有细节被问到而我一问三不知</li>
<li>简历要真实,大部分面试的内容都是简历写的内容</li>
</ul>
<p>此次投的是三年经验的Android开发,最后反而因为自己的失误,没有准备充分而导致结果很悲剧,以此告诫自己千万不能疏忽大意。</p>
Android5.0以下NoClassDefFoundError的Bug
http://www.raomengyang.com/2016/09/11/Android5-0以下NoClassDefFoundError的Bug/
2016-09-11T15:46:28.000Z
2016-09-11T16:19:58.000Z
<p>大周末的,突然接到老大的电话说很多用户无法安装新上线的APK,让我紧急Fix(现Android项目就我一己之力)。但奇怪的是也没有Bug Reporter,而且开发过程中也一直没问题。根据上报的几个用户的机型,我初步推断都是5.0以下的设备无法启动App,通过云测和优测的真机模拟(在此强烈推荐这两个测试平台,前者是腾讯的,机型较新,后者做的规模还不错,测试较为准确,并且每天都有免费兼容测试的资格和限时免费的机型)打印出Log后得出判断,错误异常居然是这个:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.lang.NoClassDefFoundError</span><br></pre></td></tr></table></figure>
<p>这可就折腾了,因为Log打印的是某个类没有找到,开始以为是该类的代码问题,仔细检查后发现根本不应该是这个类引发的问题。而分别打的两个正式包(Mac和PC环境)设备报错都不是同一个类,刚开始Google的时候,关键字是<code>NoClassDefFoundError android</code>,SF上网友遇到的是Eclipse的路径配置问题,和我遇到的不是同一个Bug,就在希望快崩溃的时候,再次Google<code>NoClassDefFoundError android studio</code>发现<a href="http://stackoverflow.com/questions/27698287/noclassdeffounderror-with-android-studio-on-android-4" target="_blank" rel="external">该问题描述</a>和我遇到的一模一样,回想前些日子遇到过<a href="https://developer.android.com/studio/build/multidex.html" target="_blank" rel="external">Configure Apps with Over 64K Methods</a>该问题,检查后发现Application并没继承<a href="https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html" target="_blank" rel="external">MultiDexApplication</a>,而build.gradle中依赖也没了,仅仅剩余下述开关<code>multiDexEnabled true</code> 。重新打包测试后果然是这个问题引发的血案,而出现这个问题是因为我和同事merge代码时候丢失部分修改导致的。</p>
<p>解决方法:</p>
<ol>
<li>配置build.gradle (app)</li>
</ol>
<figure class="highlight xml"><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">android {</span><br><span class="line"> compileSdkVersion 21</span><br><span class="line"> buildToolsVersion "21.1.0"</span><br><span class="line"></span><br><span class="line"> defaultConfig {</span><br><span class="line"> ...</span><br><span class="line"> minSdkVersion 14</span><br><span class="line"> targetSdkVersion 21</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> // Enabling multidex support. 开关</span><br><span class="line"> multiDexEnabled true</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">dependencies {</span><br><span class="line"> // 添加依赖</span><br><span class="line"> compile 'com.android.support:multidex:1.0.0'</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol>
<li>使用自定义的Application继承 <code>MultiDexApplication</code> 这个类,或者重写Application的方法attachBaseContext(),并调用<code>MultiDex.install()</code></li>
</ol>
<p>大周末的,突然接到老大的电话说很多用户无法安装新上线的APK,让我紧急Fix(现Android项目就我一己之力)。但奇怪的是也没有Bug Reporter,而且开发过程中也一直没问题。根据上报的几个用户的机型,我初步推断都是5.0以下的设备无法启动App,通过云测和优测的真