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中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res