DJI_Mobile_SDK是大疆为开发者提供的开发无人机应用的开发接口,可以实现对无人机飞行的控制,也可以利用无人机相机完成一些视觉任务。目前网上的开发教程主要集中于DJI 开发者社区网上的资源非常少。废话不多说~~,现在将在Android项目中学习到的东西总结一下。

使用大疆无人机做计算机视觉项目,第一步就是要将从云台相机中获取的视频流解析成图像帧,DJI在github上提供了视频解码成图像帧的Demo程序。官网说明文档并没有对如何将这个解码Demo集成进自己的项目进行说明,只是简单说明了DJIVideoStreamDecoder和NativeHelper类的主要用途。附上解码的源程序

Android源代码地址https://github.com/DJI-Mobile-SDK-Tutorials/Android-VideoStreamDecodingSample.git

下面就将对如何使用这个模块进行说明

一、模块结构

首先要说明的是,整个解码过程是通过FFmpeg和MediaCodec实现,按照官网的教程,DJIVideoStreamDecoder.java和NativeHelper.java是实现解码的关键类。按照官网的教程分为以下步骤:

1. 初始化一个NativeHelper的实例对象,来监听来自无人机高空的视频数据。

2.将原始的H.264视频数据送入FFmpeg中解析。

3.将解析完成的视频数据从FFmpeg中取出,并将解析后的数据缓存到图像帧序列中

4.将MediaCodec作为一个解码器,然后对视频中的I帧进行捕获。

5.解码完成后,可为MediaCodec的输出数据配置一个TextureView或SurfaceView用来对视频画面进行预览,或者调用监听器对解码数据进行监听完成其他操作。

6.释放FFmpeg和MediaCodec资源。

二、解码调用

看完上述步骤,我们对解码过程有了初步的认识,以下是DJIVideoStreamDecoder类中的变量。其中instance是解码类的实例,解码出的视频帧会存放在frameQueue中。handle类涉及线程控制,如果需要了解HandleThread的用法,请点击此链接。在Demo中解码线程已经全部实现,不需要我们再做任何处理。

1.DJIVideoStreamDecoder.java

    private static DJIVideoStreamDecoder instance;
private Queue<DJIFrame> frameQueue;
private HandlerThread dataHandlerThread;
private Handler dataHandler;
private HandlerThread callbackHandlerThread;
private Handler callbackHandler;
private Context context;
private MediaCodec codec;
private Surface surface; public int frameIndex = -1;
private long currentTime;
public int width;
public int height;
private boolean hasIFrameInQueue = false;
private boolean hasIFrameInCodec;
private ByteBuffer[] inputBuffers;
private ByteBuffer[] outputBuffers;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
LinkedList<Long> bufferChangedQueue=new LinkedList<Long>(); private long createTime;

2.Mainactivity.java

实现流数据转换为图像的关键步骤在MainActivity.java中实现,值得注意的是在Android系统中,图像是以YUVImage的格式传递,因此,在存储数据的时候要使用YUV图像格式,对于每秒解析的图像帧数量,通过DJIVIdeoStreamDecoder.getInstance().frameIndex控制,比如Demo中对30取余,表示仅对序号为30的倍数的图像帧存储,如果每秒帧率为30,则每秒只取一帧图像。进而可通过调节分母的大小实现取帧频率的控制。

  将raw数据解析成YUV格式图像的源代码

@Override
public void onYuvDataReceived(byte[] yuvFrame, int width, int height) {
//In this demo, we test the YUV data by saving it into JPG files.
if (DJIVideoStreamDecoder.getInstance().frameIndex % 30 == 0) {
byte[] y = new byte[width * height];
byte[] u = new byte[width * height / 4];
byte[] v = new byte[width * height / 4];
byte[] nu = new byte[width * height / 4]; //
byte[] nv = new byte[width * height / 4];
System.arraycopy(yuvFrame, 0, y, 0, y.length);
for (int i = 0; i < u.length; i++) {
v[i] = yuvFrame[y.length + 2 * i];
u[i] = yuvFrame[y.length + 2 * i + 1];
}
int uvWidth = width / 2;
int uvHeight = height / 2;
for (int j = 0; j < uvWidth / 2; j++) {
for (int i = 0; i < uvHeight / 2; i++) {
byte uSample1 = u[i * uvWidth + j];
byte uSample2 = u[i * uvWidth + j + uvWidth / 2];
byte vSample1 = v[(i + uvHeight / 2) * uvWidth + j];
byte vSample2 = v[(i + uvHeight / 2) * uvWidth + j + uvWidth / 2];
nu[2 * (i * uvWidth + j)] = uSample1;
nu[2 * (i * uvWidth + j) + 1] = uSample1;
nu[2 * (i * uvWidth + j) + uvWidth] = uSample2;
nu[2 * (i * uvWidth + j) + 1 + uvWidth] = uSample2;
nv[2 * (i * uvWidth + j)] = vSample1;
nv[2 * (i * uvWidth + j) + 1] = vSample1;
nv[2 * (i * uvWidth + j) + uvWidth] = vSample2;
nv[2 * (i * uvWidth + j) + 1 + uvWidth] = vSample2;
}
}
//nv21test
byte[] bytes = new byte[yuvFrame.length];
System.arraycopy(y, 0, bytes, 0, y.length);
for (int i = 0; i < u.length; i++) {
bytes[y.length + (i * 2)] = nv[i];
bytes[y.length + (i * 2) + 1] = nu[i];

   将Buffer中的raw数据整理成jpeg图像

    /* Save the buffered data into a JPG image file*/
private void screenShot(byte[] buf, String shotDir) {
File dir = new File(shotDir);
if (!dir.exists() || !dir.isDirectory()) {
dir.mkdirs();
}
YuvImage yuvImage = new YuvImage(buf,
ImageFormat.NV21,
DJIVideoStreamDecoder.getInstance().width,
DJIVideoStreamDecoder.getInstance().height,
null);
OutputStream outputFile;
final String path = dir + "/ScreenShot_" + System.currentTimeMillis() + ".jpg";
try {
outputFile = new FileOutputStream(new File(path));
} catch (FileNotFoundException e) {
Log.e(TAG, "test screenShot: new bitmap output file error: " + e);
return;
}
if (outputFile != null) {
yuvImage.compressToJpeg(new Rect(0,
0,
DJIVideoStreamDecoder.getInstance().width,
DJIVideoStreamDecoder.getInstance().height), 100, outputFile);
}
try {
outputFile.close();
} catch (IOException e) {
Log.e(TAG, "test screenShot: compress yuv image error: " + e);
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
displayPath(path);
}
});
} public void onClick(View v) {
if (screenShot.isSelected()) {
screenShot.setText("Screen Shot");
screenShot.setSelected(false);
if (useSurface) {
DJIVideoStreamDecoder.getInstance().changeSurface(videostreamPreviewSh.getSurface());
}
savePath.setText("");
savePath.setVisibility(View.INVISIBLE);
} else {
screenShot.setText("Live Stream");
screenShot.setSelected(true);
if (useSurface) {
DJIVideoStreamDecoder.getInstance().changeSurface(null);
}
savePath.setText("");
savePath.setVisibility(View.VISIBLE);
pathList.clear();
}
} private void displayPath(String path){
path = path + "\n\n";
if(pathList.size() < 6){
pathList.add(path);
}else{
pathList.remove(0);
pathList.add(path);
}
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0 ;i < pathList.size();i++){
stringBuilder.append(pathList.get(i));
}
savePath.setText(stringBuilder.toString());
}

  在大疆的Demo程序中,选择采用存储磁盘的方式来获取是各帧。处理函数为Mainactivity类中screenShot(byte[] buf, String shotDir)方法在此方法中使用Android内置类YUVImage的compressToJpeg()方法以流的方式进行存储,存储路径通过shotDir传入。

  以上就是关于DJI 无人机截取取图像帧的介绍,获取图像帧之后就可进行各式各样的图像任务了。

  小菜鸟一个,大家一起学习交流咯。

大疆无人机 Android 开发总结——视频解码的更多相关文章

  1. 化学专业大二转战Android开发,终于拥有了鹅厂暑期实习offer

    我是双非学校,应用化学专业,一年前我大二,现在我大三.一年前我两手空空,现在我拥有了鹅厂暑期实习的offer. 虽然结果是好的,但我春招实习的道路远没有这么简单和辉煌,它是无比坎坷的:每个人应该量力而 ...

  2. 大三小学期 Android开发的一些经验

    1.同一个TextView几种颜色的设置: build=(TextView)findViewById(R.id.building); SpannableStringBuilder style = ne ...

  3. 大疆无人机M100相关问题解决过程

    1.遥控器升级问题 iOS端使用app升级,重复尝试了5次+,还是无法升级.卸载app重新安装,依旧是无法升级.使用Android app升级,一次搞定. 2.飞行器固件升级(云台别选错了) http ...

  4. 2019大疆PC软件开发笔试——开关和灯泡两个电路板

    题目描述: 小A是一名DIY爱好者,经常制作一些有趣的东西. 今天,小A突然想要来做这样一个东西.小A现在有两块同样大小为n×m,有n×m块大小为1×1小电路板拼成的矩形电路板,假设叫做电路板A和电路 ...

  5. Android开发用过的十大框架

    http://blog.csdn.net/u011200604/article/details/51695096 本文系多方综合与转载整合,意在Android开发中能够知道和使用一些好用的第三方支持, ...

  6. Xamarin Android开发实战(上册)大学霸内部资料

    Xamarin Android开发实战(上册)大学霸内部资料   试读文档下载地址:http://pan.baidu.com/s/1jGEHhhO 密码:vcfm 介绍: 本教程是国内唯一的Xamar ...

  7. 【转】Android开发学习笔记:5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

  8. Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?

    0.写在前面 本文涉及到屏幕密度的讨论,这里先要搞清楚 DisplayMetrics 的两个变量,摘录官方文档的解释: density:The logical density of the displ ...

  9. Android开发5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

随机推荐

  1. luogu P2824 [HEOI2016/TJOI2016]排序

    题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行 ...

  2. CoderForces999D-Equalize the Remainders

    D. Equalize the Remainders time limit per test 3 seconds memory limit per test 256 megabytes input s ...

  3. python sympy evalf()函数

    SymPy是一个符号计算的Python库.它的目标是成为一个全功能的计算机代数系统,同时保持代码简 洁.易于理解和扩展.它完全由Python写成,不依赖于外部库.SymPy支持符号计算.高精度计算.模 ...

  4. 10分钟搞定nginx实现负载均衡

    10.1 负载均衡的概念 对用户请求的数据进行调度的作用 对用户访问的请求网站可以进行压力的分担 10.2 常见的代理方式 10.2.1 正向代理 10.2.2 反向代理 10.3 负载均衡的部署环节 ...

  5. 【新手必学】Python爬虫之多线程实战

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:清风化煞_   正文 新手注意:如果你学习遇到问题找不到人解答,可以点 ...

  6. ARTS-S golang panic返回默认值

    package main import "fmt" func fn_test_panic() (a int) { a = 2 panic("This is panic&q ...

  7. windows程序设计03_读取utf8文件

    这里用到的读取utf8文件的思路特别朴素.先把utf8文件按char读取到内存里.因为utf8是变长的,为了处理方便,在内存里把char转化成wchar_t,这样一个字符就是一个wchar_t.把ut ...

  8. Orleans在.net core的开发

    Goods 服务 启动 using System; using System.Collections.Generic; using System.Linq; using System.Net; usi ...

  9. 如何禁止chrome浏览器http自动转成https

    Chrome 浏览器 地址栏中输入 chrome://net-internals/#hsts 在 Delete domain security policies 中输入项目的域名,并 Delete 删 ...

  10. 深入浅出分析 PriorityQueue

    一.摘要 在前几篇文章中,咱们了解到,Queue 的实现类有 ArrayDeque.LinkedList.PriorityQueue. 在上一章节中,陆续的介绍到 ArrayDeque 和 Linke ...