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

相关文章

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

MyBatis ParameterHandler的具体使用

《MyBatisParameterHandler的具体使用》本文主要介绍了MyBatisParameterHandler的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录一、概述二、源码1 关键属性2.setParameters3.TypeHandler1.TypeHa

Spring 中的切面与事务结合使用完整示例

《Spring中的切面与事务结合使用完整示例》本文给大家介绍Spring中的切面与事务结合使用完整示例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录 一、前置知识:Spring AOP 与 事务的关系 事务本质上就是一个“切面”二、核心组件三、完

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

使用Python实现Word文档的自动化对比方案

《使用Python实现Word文档的自动化对比方案》我们经常需要比较两个Word文档的版本差异,无论是合同修订、论文修改还是代码文档更新,人工比对不仅效率低下,还容易遗漏关键改动,下面通过一个实际案例... 目录引言一、使用python-docx库解析文档结构二、使用difflib进行差异比对三、高级对比方