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

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo