哪吒鬧海靈珠子兄弟 發表於 2019-8-31 18:35:00

大疆无人机 Android 开发总结——视频解码

<p>&nbsp; &nbsp; &nbsp; &nbsp; DJI_Mobile_SDK是大疆为开发者提供的开发无人机应用的开发接口,可以实现对无人机飞行的控制,也可以利用无人机相机完成一些视觉任务。目前网上的开发教程主要集中于<strong>DJI 开发者社区</strong>,网上的资源非常少。废话不多说~~,现在将在Android项目中学习到的东西总结一下。</p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp; &nbsp; 使用大疆无人机做计算机视觉项目,第一步就是要将从云台相机中获取的视频流解析成图像帧,DJI在github上提供了视频解码成图像帧的Demo程序。官网说明文档并没有对如何将这个解码Demo集成进自己的项目进行说明,只是简单说明了DJIVideoStreamDecoder和NativeHelper类的主要用途。附上解码的源程序</p>
<p><span style="color: rgba(51, 102, 255, 1)"><strong>Android</strong><strong>源代码地址</strong>:https://github.com/DJI-Mobile-SDK-Tutorials/Android-VideoStreamDecodingSample.git</span></p>
<p><span style="color: rgba(51, 102, 255, 1)">&nbsp;</span></p>
<p>下面就将对如何使用这个模块进行说明</p>
<p>一、模块结构</p>
<p>&nbsp; &nbsp; &nbsp; 首先要说明的是,整个解码过程是通过FFmpeg和MediaCodec实现,按照官网的教程,DJIVideoStreamDecoder.java和NativeHelper.java是实现解码的关键类。按照官网的教程分为以下步骤:</p>
<p>&nbsp;</p>
<blockquote>
<p>1. 初始化一个NativeHelper的实例对象,来监听来自无人机高空的视频数据。</p>
<p>2.将原始的H.264视频数据送入FFmpeg中解析。</p>
<p>3.将解析完成的视频数据从FFmpeg中取出,并将解析后的数据缓存到图像帧序列中</p>
<p>4.将MediaCodec作为一个解码器,然后对视频中的I帧进行捕获。</p>
<p>5.解码完成后,可为MediaCodec的输出数据配置一个TextureView或SurfaceView用来对视频画面进行预览,或者调用监听器对解码数据进行监听完成其他操作。</p>
<p>6.释放FFmpeg和MediaCodec资源。</p>
</blockquote>
<p>&nbsp;</p>
<p>二、解码调用</p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;看完上述步骤,我们对解码过程有了初步的认识,以下是DJIVideoStreamDecoder类中的变量。其中instance是解码类的实例,解码出的视频帧会存放在frameQueue中。handle类涉及线程控制,如果需要了解HandleThread的用法,请点击此链接。在Demo中解码线程已经全部实现,不需要我们再做任何处理。</p>
<p>&nbsp; &nbsp; &nbsp; 1.DJIVideoStreamDecoder.java</p>
<div class="cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_wrapper_has cke_widget_selected" data-cke-widget-wrapper="1" data-cke-filter="off" data-cke-display-name="代码段" data-cke-widget-id="5">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">    private</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> DJIVideoStreamDecoder instance;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> Queue&lt;DJIFrame&gt;<span style="color: rgba(0, 0, 0, 1)"> frameQueue;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> HandlerThread dataHandlerThread;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Handler dataHandler;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> HandlerThread callbackHandlerThread;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Handler callbackHandler;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Context context;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> MediaCodec codec;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Surface surface;

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> frameIndex = -1<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> currentTime;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> width;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> height;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> hasIFrameInQueue = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasIFrameInCodec;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> ByteBuffer[] inputBuffers;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> ByteBuffer[] outputBuffers;
    MediaCodec.BufferInfo bufferInfo </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MediaCodec.BufferInfo();
    LinkedList</span>&lt;Long&gt; bufferChangedQueue=<span style="color: rgba(0, 0, 255, 1)">new</span> LinkedList&lt;Long&gt;<span style="color: rgba(0, 0, 0, 1)">();

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> createTime;</pre>
</div>
</div>
<p>2.Mainactivity.java</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;实现流数据转换为图像的关键步骤在MainActivity.java中实现,值得注意的是在Android系统中,图像是以YUVImage的格式传递,因此,在存储数据的时候要使用YUV图像格式,对于每秒解析的图像帧数量,通过DJIVIdeoStreamDecoder.getInstance().frameIndex控制,比如Demo中对30取余,表示仅对序号为30的倍数的图像帧存储,如果每秒帧率为30,则每秒只取一帧图像。进而可通过调节分母的大小实现取帧频率的控制。</p>
<p>&nbsp;</p>
<p>  将raw数据解析成YUV格式图像的源代码</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onYuvDataReceived(<span style="color: rgba(0, 0, 255, 1)">byte</span>[] yuvFrame, <span style="color: rgba(0, 0, 255, 1)">int</span> width, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> height) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">In this demo, we test the YUV data by saving it into JPG files.</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (DJIVideoStreamDecoder.getInstance().frameIndex % 30 == 0<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] y = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>;
            </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] u = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>;
            </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] v = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>;
            </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] nu = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>; <span style="color: rgba(0, 128, 0, 1)">//
</span>            <span style="color: rgba(0, 0, 255, 1)">byte</span>[] nv = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>;
            System.arraycopy(yuvFrame, </span>0, y, 0<span style="color: rgba(0, 0, 0, 1)">, y.length);
            </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; u.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
                v </span>= yuvFrame;
                u </span>= yuvFrame;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> uvWidth = width / 2<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> uvHeight = height / 2<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> j = 0; j &lt; uvWidth / 2; j++<span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; uvHeight / 2; i++<span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">byte</span> uSample1 = u;
                  </span><span style="color: rgba(0, 0, 255, 1)">byte</span> uSample2 = u;
                  </span><span style="color: rgba(0, 0, 255, 1)">byte</span> vSample1 = v[(i + uvHeight / 2) * uvWidth +<span style="color: rgba(0, 0, 0, 1)"> j];
                  </span><span style="color: rgba(0, 0, 255, 1)">byte</span> vSample2 = v[(i + uvHeight / 2) * uvWidth + j + uvWidth / 2<span style="color: rgba(0, 0, 0, 1)">];
                  nu[</span>2 * (i * uvWidth + j)] =<span style="color: rgba(0, 0, 0, 1)"> uSample1;
                  nu[</span>2 * (i * uvWidth + j) + 1] =<span style="color: rgba(0, 0, 0, 1)"> uSample1;
                  nu[</span>2 * (i * uvWidth + j) + uvWidth] =<span style="color: rgba(0, 0, 0, 1)"> uSample2;
                  nu[</span>2 * (i * uvWidth + j) + 1 + uvWidth] =<span style="color: rgba(0, 0, 0, 1)"> uSample2;
                  nv[</span>2 * (i * uvWidth + j)] =<span style="color: rgba(0, 0, 0, 1)"> vSample1;
                  nv[</span>2 * (i * uvWidth + j) + 1] =<span style="color: rgba(0, 0, 0, 1)"> vSample1;
                  nv[</span>2 * (i * uvWidth + j) + uvWidth] =<span style="color: rgba(0, 0, 0, 1)"> vSample2;
                  nv[</span>2 * (i * uvWidth + j) + 1 + uvWidth] =<span style="color: rgba(0, 0, 0, 1)"> vSample2;
                }
            }
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">nv21test</span>
            <span style="color: rgba(0, 0, 255, 1)">byte</span>[] bytes = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">;
            System.arraycopy(y, </span>0, bytes, 0<span style="color: rgba(0, 0, 0, 1)">, y.length);
            </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; u.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
                bytes =<span style="color: rgba(0, 0, 0, 1)"> nv;
                bytes = nu;</pre>
</div>
<p>  &nbsp;将Buffer中的raw数据整理成jpeg图像</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">    /*</span><span style="color: rgba(0, 128, 0, 1)"> Save the buffered data into a JPG image file</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> screenShot(<span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] buf, String shotDir) {
      File dir </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File(shotDir);
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!dir.exists() || !<span style="color: rgba(0, 0, 0, 1)">dir.isDirectory()) {
            dir.mkdirs();
      }
      YuvImage yuvImage </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> YuvImage(buf,
                ImageFormat.NV21,
                DJIVideoStreamDecoder.getInstance().width,
                DJIVideoStreamDecoder.getInstance().height,
                </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
      OutputStream outputFile;
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> String path = dir + "/ScreenShot_" + System.currentTimeMillis() + ".jpg"<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            outputFile </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> FileOutputStream(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File(path));
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (FileNotFoundException e) {
            Log.e(TAG, </span>"test screenShot: new bitmap output file error: " +<span style="color: rgba(0, 0, 0, 1)"> e);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (outputFile != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            yuvImage.compressToJpeg(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Rect(0<span style="color: rgba(0, 0, 0, 1)">,
                  </span>0<span style="color: rgba(0, 0, 0, 1)">,
                  DJIVideoStreamDecoder.getInstance().width,
                  DJIVideoStreamDecoder.getInstance().height), </span>100<span style="color: rgba(0, 0, 0, 1)">, outputFile);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            outputFile.close();
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
            Log.e(TAG, </span>"test screenShot: compress yuv image error: " +<span style="color: rgba(0, 0, 0, 1)"> e);
            e.printStackTrace();
      }
      runOnUiThread(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
            @Override
            </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
                displayPath(path);
            }
      });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (screenShot.isSelected()) {
            screenShot.setText(</span>"Screen Shot"<span style="color: rgba(0, 0, 0, 1)">);
            screenShot.setSelected(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (useSurface) {
                DJIVideoStreamDecoder.getInstance().changeSurface(videostreamPreviewSh.getSurface());
            }
            savePath.setText(</span>""<span style="color: rgba(0, 0, 0, 1)">);
            savePath.setVisibility(View.INVISIBLE);
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            screenShot.setText(</span>"Live Stream"<span style="color: rgba(0, 0, 0, 1)">);
            screenShot.setSelected(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (useSurface) {
                DJIVideoStreamDecoder.getInstance().changeSurface(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
            }
            savePath.setText(</span>""<span style="color: rgba(0, 0, 0, 1)">);
            savePath.setVisibility(View.VISIBLE);
            pathList.clear();
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> displayPath(String path){
      path </span>= path + "\n\n"<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(pathList.size() &lt; 6<span style="color: rgba(0, 0, 0, 1)">){
            pathList.add(path);
      }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
            pathList.remove(</span>0<span style="color: rgba(0, 0, 0, 1)">);
            pathList.add(path);
      }
      StringBuilder stringBuilder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuilder();
      </span><span style="color: rgba(0, 0, 255, 1)">for</span>(<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0 ;i &lt; pathList.size();i++<span style="color: rgba(0, 0, 0, 1)">){
            stringBuilder.append(pathList.get(i));
      }
      savePath.setText(stringBuilder.toString());
    }</span></pre>
</div>
<p>  在大疆的Demo程序中,选择采用存储磁盘的方式来获取是各帧。处理函数为Mainactivity类中screenShot(byte[] buf, String shotDir)方法在此方法中使用Android内置类YUVImage的compressToJpeg()方法以流的方式进行存储,存储路径通过shotDir传入。</p>
<p>  以上就是关于DJI 无人机截取取图像帧的介绍,获取图像帧之后就可进行各式各样的图像任务了。</p>
<p>  小菜鸟一个,大家一起学习交流咯。&nbsp;</p><br><br>
来源:https://www.cnblogs.com/fancy-li/p/11439985.html
頁: [1]
查看完整版本: 大疆无人机 Android 开发总结——视频解码