使用juv-client-client.jar主要是尽快地完成毕业设计里面手机端向网页端发送实时视频的功能,由于实习和做毕业设计的时间冲突,因此完成毕业设计只花了1个多月时间。

(万恶的形式主义,论文格式改了我老久老久)因此代码上面会存在一些问题,并且也是单纯的实现了摄像头视频的实时传输,麦克风的实时语音没有实现。

自我感觉这个毕业设计没有多大价值,但是有参考意义,特把实现记录一下,用作纪念!

原理:

juv-client-client.jar提供了很多与Red5的交互操作,比如连接,流数据发布,方法互相调用等等。

在发布实时视频数据的之前,我们需要建立手机端和服务器端的RTMP连接。

使用类库里的NetConnection类:

关键代码如下:

  1. private void connectRed5() {
  2. //key的值官方网站上可以申请到免费试用版本:http://www.smaxe.com/order.jsf#request_evaluation_key
  3. License.setKey("63140-D023C-D7420-00B15-91FC7");
  4. connection = new NetConnection();
  5. //对连接进行配置
  6. connection.configuration().put(NetConnection.Configuration.INACTIVITY_TIMEOUT, -1);
  7. connection.configuration().put(NetConnection.Configuration.RECEIVE_BUFFER_SIZE, 256 * 1024);
  8. connection.configuration().put(NetConnection.Configuration.SEND_BUFFER_SIZE, 256 * 1024);
  9. connection.client(new ClientHandler());
  10. connection.addEventListener(new NetConnectionListener());
  11. connection.connect(red5_url);
  12. }

其中new ClientHandler类是继承Object,里面写的方法可以被服务器调用。

new NetConnectionListener可以继承NetConnection.ListenerAdapter或者实现Listener接口,用于显示处理建立RTMP连接时候的一些网络状况。

例如:

  1. private class ClientHandler extends Object {
  2. public ClientHandler() {}
  3. public void fun1() {}
  4. public void fun2() {}
  5. }
  6. private class NetConnectionListener extends NetConnection.ListenerAdapter {
  7. public NetConnectionListener() {}
  8. @Override
  9. public void onAsyncError(final INetConnection source, final String message, final Exception e) {
  10. System.out.println("NetConnection#onAsyncError: " + message + " "+ e);
  11. }
  12. @Override
  13. public void onIOError(final INetConnection source, final String message) {
  14. System.out.println("NetConnection#onIOError: " + message);
  15. }
  16. @Override
  17. public void onNetStatus(final INetConnection source, final Map<String, Object> info) {
  18. System.out.println("NetConnection#onNetStatus: " + info);
  19. final Object code = info.get("code");
  20. if (NetConnection.CONNECT_SUCCESS.equals(code)) {}
  21. }
  22. }

以上就是建立连接的过程,判断是否建立了连接在

  1. System.out.println("NetConnection#onNetStatus: " + info);

是会有消息打出来的。

建立RTMP连接以后我们就可以通过Android的Camera类进行视频的采集,然后进行实时发送。

这里我不得不说的是,实现Android端的视频采集比网页端的复杂,因为这个类库提供的摄像头类和麦克风类都是两个抽象类或者是接口,必须要自己实现它。而网页端却有封装好的摄像头和麦克风,调用简单。

我的方法是实现类库里的AbstractCamera抽象类,想到Android里面自己也提供了一个摄像头的Camera类,于是我想到了用面向对象的组合和多接口实现,于是我打算实现一个AndroidCamera类。

这里有个问题:为什么要实现AbstractCamera类?

因为这个类里面有一个protected的fireOnVideoData方法。可以给继承它的类使用,该方法的作用,我猜想是把一个个数据包封装成流数据。

继续实现AndroidCamera类,用类图表示我的实现方案:

可以看到我用Android里的Camera类、SurfaceView类、SurfaceHolder类组成了我自己的AndroidCamera类,并且需要实现SurfaceHolder.CallBack接口以及Camera的PreviewCallBack接口。

这么做的原因有两个:1、实现预览。2、预览的同时通过Camera的PreviewCallBack接口里的onPreviewFrame方法获取到实时帧数据,进而转码打包生成流数据。(注意我这里并没有进行视频的编码压缩,时间和能力有限)

直接上代码了:

  1. <pre name="code" class="java">    public class AndroidCamera extends AbstractCamera implements SurfaceHolder.Callback, Camera.PreviewCallback {
  2. private SurfaceView surfaceView;
  3. private SurfaceHolder surfaceHolder;
  4. private Camera camera;
  5. private int width;
  6. private int height;
  7. private boolean init;
  8. int blockWidth;
  9. int blockHeight;
  10. int timeBetweenFrames; // 1000 / frameRate
  11. int frameCounter;
  12. byte[] previous;
  13. public AndroidCamera(Context context) {
  14. surfaceView = (SurfaceView)((Activity) context).findViewById(R.id.surfaceView);
  15. //我是把Activity里的context传进入然后获取到SurfaceView,也可以之间传入SurfaceView进行实例
  16. surfaceHolder = surfaceView.getHolder();
  17. surfaceHolder.addCallback(AndroidCamera.this);
  18. surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  19. width = 320;
  20. height = 240;
  21. init = false;
  22. Log.d("DEBUG", "AndroidCamera()");
  23. }
  24. private void startVideo() {
  25. Log.d("DEBUG", "startVideo()");
  26. netStream = new NetStream(connection);
  27. netStream.addEventListener(new NetStream.ListenerAdapter() {
  28. @Override
  29. public void onNetStatus(final INetStream source, final Map<String, Object> info){
  30. System.out.println("Publisher#NetStream#onNetStatus: " + info);
  31. Log.d("DEBUG", "Publisher#NetStream#onNetStatus: " + info);
  32. final Object code = info.get("code");
  33. if (NetStream.PUBLISH_START.equals(code)) {
  34. if (aCamera != null) {
  35. netStream.attachCamera(aCamera, -1 /*snapshotMilliseconds*/);
  36. Log.d("DEBUG", "aCamera.start()");
  37. aCamera.start();
  38. } else {
  39. Log.d("DEBUG", "camera == null");
  40. }
  41. }
  42. }
  43. });
  44. netStream.publish(VideoName, NetStream.RECORD);
  45. }
  46. public void start() {
  47. camera.startPreview();
  48. }
  49. @Override
  50. public void onPreviewFrame(byte[] arg0, Camera arg1) {
  51. // TODO Auto-generated method stub
  52. if (!active) return;
  53. if (!init) {
  54. blockWidth = 32;
  55. blockHeight = 32;
  56. timeBetweenFrames = 100; // 1000 / frameRate
  57. frameCounter = 0;
  58. previous = null;
  59. init = true;
  60. }
  61. final long ctime = System.currentTimeMillis();
  62. byte[] current = RemoteUtil.decodeYUV420SP2RGB(arg0, width, height);
  63. try {
  64. final byte[] packet = RemoteUtil.encode(current, previous, blockWidth, blockHeight, width, height);
  65. Log.d("DEBUG", packet.toString());
  66. fireOnVideoData(new MediaDataByteArray(timeBetweenFrames, new ByteArray(packet)));
  67. previous = current;
  68. if (++frameCounter % 10 == 0) previous = null;
  69. }
  70. catch (Exception e) {
  71. e.printStackTrace();
  72. }
  73. final int spent = (int) (System.currentTimeMillis() - ctime);
  74. try {
  75. Thread.sleep(Math.max(0, timeBetweenFrames - spent));
  76. } catch (InterruptedException e) {
  77. // TODO Auto-generated catch block
  78. e.printStackTrace();
  79. }
  80. }
  81. @Override
  82. public void surfaceChanged(SurfaceHolder holder, int format, int width,
  83. int height) {
  84. // TODO Auto-generated method stub
  85. startVideo();
  86. }
  87. @Override
  88. public void surfaceCreated(SurfaceHolder holder) {
  89. // TODO Auto-generated method stub
  90. camera = Camera.open();
  91. try {
  92. camera.setPreviewDisplay(surfaceHolder);
  93. camera.setPreviewCallback(this);
  94. Camera.Parameters params = camera.getParameters();
  95. params.setPreviewSize(width, height);
  96. camera.setParameters(params);
  97. } catch (IOException e) {
  98. // TODO Auto-generated catch block
  99. e.printStackTrace();
  100. camera.release();
  101. camera = null;
  102. }
  103. }
  104. @Override
  105. public void surfaceDestroyed(SurfaceHolder holder) {
  106. // TODO Auto-generated method stub
  107. if (camera != null) {
  108. camera.stopPreview();
  109. camera.release();
  110. camera = null;
  111. }
  112. }
  113. } //AndroidCamera</pre><br>
  114. <br>
  115. <pre></pre>
  116. <p></p>
  117. <pre></pre>
  118. 上面的实现原理是基于类库自带的ExDesktopPublisher.java实现的,因此有些我自己也无法看懂。(因为我不懂多媒体)
  119. <p></p>
  120. <p>值得说明的是在发布实时视频的时候是通过类库里的NetStream的publish方法进行发布的,在这之前需要先用attachCamera方法给他设置视频源(代码里有)。</p>
  121. <p></p>
  122. <pre name="code" class="java"><pre name="code" class="java"> RemoteUtil.decodeYUV420SP2RGB</pre>
  123. <pre></pre>
  124. <p></p>
  125. <pre></pre>
  126. 是对onPreviewFrame获取到的YUV420视频源数据进行转换,转到RGB的,不然显示也许会有问题。算法如下:
  127. <p></p>
  128. <p></p>
  129. <pre name="code" class="java">public static byte[] decodeYUV420SP2RGB(byte[] yuv420sp, int width, int height) {
  130. final int frameSize = width * height;
  131. byte[] rgbBuf = new byte[frameSize * 3];
  132. // if (rgbBuf == null) throw new NullPointerException("buffer 'rgbBuf' is null");
  133. if (rgbBuf.length < frameSize * 3) throw new IllegalArgumentException("buffer 'rgbBuf' size "  + rgbBuf.length + " < minimum " + frameSize * 3);
  134. if (yuv420sp == null) throw new NullPointerException("buffer 'yuv420sp' is null");
  135. if (yuv420sp.length < frameSize * 3 / 2) throw new IllegalArgumentException("buffer 'yuv420sp' size " + yuv420sp.length + " < minimum " + frameSize * 3 / 2);
  136. int i = 0, y = 0;
  137. int uvp = 0, u = 0, v = 0;
  138. int y1192 = 0, r = 0, g = 0, b = 0;
  139. for (int j = 0, yp = 0; j < height; j++) {
  140. uvp = frameSize + (j >> 1) * width;
  141. u = 0;
  142. v = 0;
  143. for (i = 0; i < width; i++, yp++) {
  144. y = (0xff & ((int) yuv420sp[yp])) - 16;
  145. if (y < 0) y = 0;
  146. if ((i & 1) == 0) {
  147. v = (0xff & yuv420sp[uvp++]) - 128;
  148. u = (0xff & yuv420sp[uvp++]) - 128;
  149. }
  150. y1192 = 1192 * y;
  151. r = (y1192 + 1634 * v);
  152. g = (y1192 - 833 * v - 400 * u);
  153. b = (y1192 + 2066 * u);
  154. if (r < 0) r = 0; else if (r > 262143) r = 262143;
  155. if (g < 0) g = 0; else if (g > 262143) g = 262143;
  156. if (b < 0) b = 0; else if (b > 262143) b = 262143;
  157. rgbBuf[yp * 3] = (byte)(r >> 10);
  158. rgbBuf[yp * 3 + 1] = (byte)(g >> 10);
  159. rgbBuf[yp * 3 + 2] = (byte)(b >> 10);
  160. }
  161. }//for
  162. return rgbBuf;
  163. }// decodeYUV420Sp2RGB</pre><pre name="code" class="java"><pre name="code" class="java">RemoteUtil.encode</pre>
  164. <pre></pre>
  165. <p></p>
  166. <pre></pre>
  167. 的算法取之于ExDesktopPublisher.java,应该是对视频数据的RTMP封装。这里就不贴代码了,可以到sample的文件里拿来用。
  168. <p></p>
  169. <p>以上文字组织很乱,因为我是在答辩的前一个晚上才实现的,因此代码也很乱,很难组织清楚,不过原理就是这样。最后的确是实现了实时视频,然而可能由于转码算法问题,实时视频的颜色是有问题的。</p>
  170. <p>为自己的大学生活里最后一次软件功能实现留给纪念吧!<br>
  171. </p>
  172. <p><br>
  173. </p>
  174. </pre></pre>
 

Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据的更多相关文章

  1. android 导入自己的生成的jar,老是 could not find class

    最近开始学习android,开发一个小项目,功能很简单,就是从服务器上获取数据,之后显示在手机上.打算把访问服务器的功能打包成一个jar文件.然后android 引入jar包. 在eclipse 里 ...

  2. Android Error:Could not find lottie.jar

    Android Error:Could not find lottie.jar 今天遇到了一个及其头疼的问题 同事的工程导到我的电脑里却报错,错误是找不到jcenter仓库里的lottie.jar包 ...

  3. 【Android端】代码打包成jar包/aar形式

    Android端代码打包成jar包和aar形式: 首先,jar包的形式和aar形式有什么区别? 1.打包之后生成的文件地址: *.jar:库/build/intermediates/bundles/d ...

  4. Android开发者的演示工具——asm.jar

    作为Android开发者,我们有时候需要给客户或者其他人演示我们的Android作品.我们可以使用类似豌豆荚.360手机助手这样的软件,今天我来介绍一个Android开发者的演示工具--asm.jar ...

  5. 使用基于Android网络通信的OkHttp库实现Get和Post方式简单操作服务器JSON格式数据

     目录 前言 1 Get方式和Post方式接口说明 2 OkHttp库简单介绍及环境配置 3 具体实现 前言 本文具体实现思路和大部分代码参考自<第一行代码>第2版,作者:郭霖:但是文中讲 ...

  6. Android BLE与终端通信(四)——实现服务器与客户端即时通讯功能

    Android BLE与终端通信(四)--实现服务器与客户端即时通讯功能 前面几篇一直在讲一些基础,其实说实话,蓝牙主要为多的还是一些概念性的东西,当你把概念都熟悉了之后,你会很简单的就可以实现一些逻 ...

  7. 解决Android与服务器交互大容量数据问题

    对于目前的状况来说,移动终端的网络状况没有PC网络状况那么理想.在一个Android应用中,如果需要接收来自服务器的大容量数据,那么就不得不考虑客户的流量问题.本文根据笔者的一个项目实战经验出发,解决 ...

  8. Android(java)学习笔记210:采用post请求提交数据到服务器(qq登录案例)

    1.POST请求:  数据是以流的方式写给服务器 优点:(1)比较安全 (2)长度不限制 缺点:编写代码比较麻烦   2.我们首先在电脑模拟下POST请求访问服务器的场景: 我们修改之前编写的logi ...

  9. Android(java)学习笔记209:采用get请求提交数据到服务器(qq登录案例)

    1.GET请求:    组拼url的路径,把提交的数据拼装url的后面,提交给服务器. 缺点:(1)安全性(Android下提交数据组拼隐藏在代码中,不存在安全问题)  (2)长度有限不能超过4K(h ...

随机推荐

  1. 如何在winform DataGridView控件的DataGridViewButtonColumn按钮列中禁用按钮

    原文:http://msdn.microsoft.com/en-us/library/ms171619(v=vs.85).ASPX public class DataGridViewDisableBu ...

  2. How to define Servlet filter order of execution using annotations

    If we define Servlet filters in web.xml, then the order of execution of the filters will be the same ...

  3. POJ2186 Popular Cows 强连通分量tarjan

    做这题主要是为了学习一下tarjan的强连通分量,因为包括桥,双连通分量,强连通分量很多的求法其实都可以源于tarjan的这种方法,通过一个low,pre数组求出来. 题意:给你许多的A->B ...

  4. java基础知识回顾之---java StringBuffer类

    /*         * StringBuffer:就是字符串缓冲区,线程安全.         * 用于存储数据的容器.         * 特点:         * 1,长度的可变的.      ...

  5. YUM详解

    用YUM升级软件打开终端,切换到root用户,yum的操作大都须有超级用户的权限.首 先,yum update,这一步是必须的,yum会从服务器的header目录下载rpm的header,放在本地的缓 ...

  6. Python批量读取人脸图片与数据互相转换

    读取部分结果 程序 # -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt from PIL import ...

  7. 《HTTP权威指南》笔记

    http://blog.csdn.net/sunorry?viewmode=contents有些笔记 MIME 类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型,中间由一条斜杠来分隔:te ...

  8. SqlDBHelper常用方法

    /*============================================================= *.net连接数据库常用方法 *Author : dongny,Li * ...

  9. JAVA+ Proxool+ SQLserver 2008 “signer information does not match signer information of other classes in the same package”

    1. Proxool+SQLserver2008(sqljdbc4.jar)集成问题最近在项目中遇到个问题:我用的是Proxool连接池,连接SQLserver2008数据库,控制台报错:签名信息和同 ...

  10. shell编程基础(2)---&&与||

    shell 编程重要的应用就是管理系统,对于管理系统中成千上万的程序而言,查询某个文件名是否存在,并且获取该文件名所指代文件基本信息是系统管理员的基本任务.shell命令可以很轻松的完成这项任务. # ...