android视频开发之一Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据

本文主要是介绍android视频开发之一Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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

原理:

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

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

使用类库里的NetConnection类:

关键代码如下:

[java]  view plain copy
  1. private void connectRed5() {  
  2.           
  3.         //key的值官方网站上可以申请到免费试用版本:http://www.smaxe.com/order.jsf#request_evaluation_key  
  4.         License.setKey("63140-D023C-D7420-00B15-91FC7");  
  5.         connection = new NetConnection();  
  6.           
  7.         //对连接进行配置  
  8.         connection.configuration().put(NetConnection.Configuration.INACTIVITY_TIMEOUT, -1);  
  9.         connection.configuration().put(NetConnection.Configuration.RECEIVE_BUFFER_SIZE, 256 * 1024);  
  10.         connection.configuration().put(NetConnection.Configuration.SEND_BUFFER_SIZE, 256 * 1024);  
  11.           
  12.         connection.client(new ClientHandler());  
  13.         connection.addEventListener(new NetConnectionListener());  
  14.         connection.connect(red5_url);  
  15.     }  
其中new ClientHandler类是继承Object,里面写的方法可以被服务器调用。

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

例如:

[java]  view plain copy
  1. private class ClientHandler extends Object {  
  2.           
  3.         public ClientHandler() {}  
  4.           
  5.         public void fun1() {}  
  6.           
  7.         public void fun2() {}  
  8.     }  
  9.       
  10. private class NetConnectionListener extends NetConnection.ListenerAdapter {  
  11.           
  12.     public NetConnectionListener() {}  
  13.           
  14.     @Override  
  15.     public void onAsyncError(final INetConnection source, final String message, final Exception e) {  
  16.     System.out.println("NetConnection#onAsyncError: " + message + " "+ e);  
  17.     }  
  18.   
  19.     @Override  
  20.     public void onIOError(final INetConnection source, final String message) {  
  21.     System.out.println("NetConnection#onIOError: " + message);  
  22.     }  
  23.   
  24.     @Override  
  25.     public void onNetStatus(final INetConnection source, final Map<String, Object> info) {  
  26.     System.out.println("NetConnection#onNetStatus: " + info);  
  27.     final Object code = info.get("code");  
  28.     if (NetConnection.CONNECT_SUCCESS.equals(code)) {}  
  29.     }  
  30. }  
以上就是建立连接的过程,判断是否建立了连接在
[java]  view plain copy
  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方法获取到实时帧数据,进而转码打包生成流数据。(注意我这里并没有进行视频的编码压缩,时间和能力有限)

直接上代码了:

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

这篇关于android视频开发之一Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/625268

相关文章

mybatis-plus QueryWrapper中or,and的使用及说明

《mybatis-plusQueryWrapper中or,and的使用及说明》使用MyBatisPlusQueryWrapper时,因同时添加角色权限固定条件和多字段模糊查询导致数据异常展示,排查发... 目录QueryWrapper中or,and使用列表中还要同时模糊查询多个字段经过排查这就导致只要whe

Python使用openpyxl读取Excel的操作详解

《Python使用openpyxl读取Excel的操作详解》本文介绍了使用Python的openpyxl库进行Excel文件的创建、读写、数据操作、工作簿与工作表管理,包括创建工作簿、加载工作簿、操作... 目录1 概述1.1 图示1.2 安装第三方库2 工作簿 workbook2.1 创建:Workboo

使用Go实现文件复制的完整流程

《使用Go实现文件复制的完整流程》本案例将实现一个实用的文件操作工具:将一个文件的内容完整复制到另一个文件中,这是文件处理中的常见任务,比如配置文件备份、日志迁移、用户上传文件转存等,文中通过代码示例... 目录案例说明涉及China编程知识点示例代码代码解析示例运行练习扩展小结案例说明我们将通过标准库 os

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

SpringBoot改造MCP服务器的详细说明(StreamableHTTP 类型)

《SpringBoot改造MCP服务器的详细说明(StreamableHTTP类型)》本文介绍了SpringBoot如何实现MCPStreamableHTTP服务器,并且使用CherryStudio... 目录SpringBoot改造MCP服务器(StreamableHTTP)1 项目说明2 使用说明2.1

Spring Boot Maven 插件如何构建可执行 JAR 的核心配置

《SpringBootMaven插件如何构建可执行JAR的核心配置》SpringBoot核心Maven插件,用于生成可执行JAR/WAR,内置服务器简化部署,支持热部署、多环境配置及依赖管理... 目录前言一、插件的核心功能与目标1.1 插件的定位1.2 插件的 Goals(目标)1.3 插件定位1.4 核

如何使用Lombok进行spring 注入

《如何使用Lombok进行spring注入》本文介绍如何用Lombok简化Spring注入,推荐优先使用setter注入,通过注解自动生成getter/setter及构造器,减少冗余代码,提升开发效... Lombok为了开发环境简化代码,好处不用多说。spring 注入方式为2种,构造器注入和setter

基于Python开发一个图像水印批量添加工具

《基于Python开发一个图像水印批量添加工具》在当今数字化内容爆炸式增长的时代,图像版权保护已成为创作者和企业的核心需求,本方案将详细介绍一个基于PythonPIL库的工业级图像水印解决方案,有需要... 目录一、系统架构设计1.1 整体处理流程1.2 类结构设计(扩展版本)二、核心算法深入解析2.1 自

MySQL中比较运算符的具体使用

《MySQL中比较运算符的具体使用》本文介绍了SQL中常用的符号类型和非符号类型运算符,符号类型运算符包括等于(=)、安全等于(=)、不等于(/!=)、大小比较(,=,,=)等,感兴趣的可以了解一下... 目录符号类型运算符1. 等于运算符=2. 安全等于运算符<=>3. 不等于运算符<>或!=4. 小于运

使用zip4j实现Java中的ZIP文件加密压缩的操作方法

《使用zip4j实现Java中的ZIP文件加密压缩的操作方法》本文介绍如何通过Maven集成zip4j1.3.2库创建带密码保护的ZIP文件,涵盖依赖配置、代码示例及加密原理,确保数据安全性,感兴趣的... 目录1. zip4j库介绍和版本1.1 zip4j库概述1.2 zip4j的版本演变1.3 zip4