Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据
使用juv-client-client.jar主要是尽快地完成毕业设计里面手机端向网页端发送实时视频的功能,由于实习和做毕业设计的时间冲突,因此完成毕业设计只花了1个多月时间。
(万恶的形式主义,论文格式改了我老久老久)因此代码上面会存在一些问题,并且也是单纯的实现了摄像头视频的实时传输,麦克风的实时语音没有实现。
自我感觉这个毕业设计没有多大价值,但是有参考意义,特把实现记录一下,用作纪念!
原理:
juv-client-client.jar提供了很多与Red5的交互操作,比如连接,流数据发布,方法互相调用等等。
在发布实时视频数据的之前,我们需要建立手机端和服务器端的RTMP连接。
使用类库里的NetConnection类:
关键代码如下:
- private void connectRed5() {
- //key的值官方网站上可以申请到免费试用版本:http://www.smaxe.com/order.jsf#request_evaluation_key
- License.setKey("63140-D023C-D7420-00B15-91FC7");
- connection = new NetConnection();
- //对连接进行配置
- connection.configuration().put(NetConnection.Configuration.INACTIVITY_TIMEOUT, -1);
- connection.configuration().put(NetConnection.Configuration.RECEIVE_BUFFER_SIZE, 256 * 1024);
- connection.configuration().put(NetConnection.Configuration.SEND_BUFFER_SIZE, 256 * 1024);
- connection.client(new ClientHandler());
- connection.addEventListener(new NetConnectionListener());
- connection.connect(red5_url);
- }
其中new ClientHandler类是继承Object,里面写的方法可以被服务器调用。
new NetConnectionListener可以继承NetConnection.ListenerAdapter或者实现Listener接口,用于显示处理建立RTMP连接时候的一些网络状况。
例如:
- private class ClientHandler extends Object {
- public ClientHandler() {}
- public void fun1() {}
- public void fun2() {}
- }
- private class NetConnectionListener extends NetConnection.ListenerAdapter {
- public NetConnectionListener() {}
- @Override
- public void onAsyncError(final INetConnection source, final String message, final Exception e) {
- System.out.println("NetConnection#onAsyncError: " + message + " "+ e);
- }
- @Override
- public void onIOError(final INetConnection source, final String message) {
- System.out.println("NetConnection#onIOError: " + message);
- }
- @Override
- public void onNetStatus(final INetConnection source, final Map<String, Object> info) {
- System.out.println("NetConnection#onNetStatus: " + info);
- final Object code = info.get("code");
- if (NetConnection.CONNECT_SUCCESS.equals(code)) {}
- }
- }
以上就是建立连接的过程,判断是否建立了连接在
- 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方法获取到实时帧数据,进而转码打包生成流数据。(注意我这里并没有进行视频的编码压缩,时间和能力有限)
直接上代码了:
- <pre name="code" class="java"> public class AndroidCamera extends AbstractCamera implements SurfaceHolder.Callback, Camera.PreviewCallback {
- private SurfaceView surfaceView;
- private SurfaceHolder surfaceHolder;
- private Camera camera;
- private int width;
- private int height;
- private boolean init;
- int blockWidth;
- int blockHeight;
- int timeBetweenFrames; // 1000 / frameRate
- int frameCounter;
- byte[] previous;
- public AndroidCamera(Context context) {
- surfaceView = (SurfaceView)((Activity) context).findViewById(R.id.surfaceView);
- //我是把Activity里的context传进入然后获取到SurfaceView,也可以之间传入SurfaceView进行实例
- surfaceHolder = surfaceView.getHolder();
- surfaceHolder.addCallback(AndroidCamera.this);
- surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- width = 320;
- height = 240;
- init = false;
- Log.d("DEBUG", "AndroidCamera()");
- }
- private void startVideo() {
- Log.d("DEBUG", "startVideo()");
- netStream = new NetStream(connection);
- netStream.addEventListener(new NetStream.ListenerAdapter() {
- @Override
- public void onNetStatus(final INetStream source, final Map<String, Object> info){
- System.out.println("Publisher#NetStream#onNetStatus: " + info);
- Log.d("DEBUG", "Publisher#NetStream#onNetStatus: " + info);
- final Object code = info.get("code");
- if (NetStream.PUBLISH_START.equals(code)) {
- if (aCamera != null) {
- netStream.attachCamera(aCamera, -1 /*snapshotMilliseconds*/);
- Log.d("DEBUG", "aCamera.start()");
- aCamera.start();
- } else {
- Log.d("DEBUG", "camera == null");
- }
- }
- }
- });
- netStream.publish(VideoName, NetStream.RECORD);
- }
- public void start() {
- camera.startPreview();
- }
- @Override
- public void onPreviewFrame(byte[] arg0, Camera arg1) {
- // TODO Auto-generated method stub
- if (!active) return;
- if (!init) {
- blockWidth = 32;
- blockHeight = 32;
- timeBetweenFrames = 100; // 1000 / frameRate
- frameCounter = 0;
- previous = null;
- init = true;
- }
- final long ctime = System.currentTimeMillis();
- byte[] current = RemoteUtil.decodeYUV420SP2RGB(arg0, width, height);
- try {
- final byte[] packet = RemoteUtil.encode(current, previous, blockWidth, blockHeight, width, height);
- Log.d("DEBUG", packet.toString());
- fireOnVideoData(new MediaDataByteArray(timeBetweenFrames, new ByteArray(packet)));
- previous = current;
- if (++frameCounter % 10 == 0) previous = null;
- }
- catch (Exception e) {
- e.printStackTrace();
- }
- final int spent = (int) (System.currentTimeMillis() - ctime);
- try {
- Thread.sleep(Math.max(0, timeBetweenFrames - spent));
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- // TODO Auto-generated method stub
- startVideo();
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- // TODO Auto-generated method stub
- camera = Camera.open();
- try {
- camera.setPreviewDisplay(surfaceHolder);
- camera.setPreviewCallback(this);
- Camera.Parameters params = camera.getParameters();
- params.setPreviewSize(width, height);
- camera.setParameters(params);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- camera.release();
- camera = null;
- }
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- // TODO Auto-generated method stub
- if (camera != null) {
- camera.stopPreview();
- camera.release();
- camera = null;
- }
- }
- } //AndroidCamera</pre><br>
- <br>
- <pre></pre>
- <p></p>
- <pre></pre>
- 上面的实现原理是基于类库自带的ExDesktopPublisher.java实现的,因此有些我自己也无法看懂。(因为我不懂多媒体)
- <p></p>
- <p>值得说明的是在发布实时视频的时候是通过类库里的NetStream的publish方法进行发布的,在这之前需要先用attachCamera方法给他设置视频源(代码里有)。</p>
- <p></p>
- <pre name="code" class="java"><pre name="code" class="java"> RemoteUtil.decodeYUV420SP2RGB</pre>
- <pre></pre>
- <p></p>
- <pre></pre>
- 是对onPreviewFrame获取到的YUV420视频源数据进行转换,转到RGB的,不然显示也许会有问题。算法如下:
- <p></p>
- <p></p>
- <pre name="code" class="java">public static byte[] decodeYUV420SP2RGB(byte[] yuv420sp, int width, int height) {
- final int frameSize = width * height;
- byte[] rgbBuf = new byte[frameSize * 3];
- // if (rgbBuf == null) throw new NullPointerException("buffer 'rgbBuf' is null");
- if (rgbBuf.length < frameSize * 3) throw new IllegalArgumentException("buffer 'rgbBuf' size " + rgbBuf.length + " < minimum " + frameSize * 3);
- if (yuv420sp == null) throw new NullPointerException("buffer 'yuv420sp' is null");
- if (yuv420sp.length < frameSize * 3 / 2) throw new IllegalArgumentException("buffer 'yuv420sp' size " + yuv420sp.length + " < minimum " + frameSize * 3 / 2);
- int i = 0, y = 0;
- int uvp = 0, u = 0, v = 0;
- int y1192 = 0, r = 0, g = 0, b = 0;
- for (int j = 0, yp = 0; j < height; j++) {
- uvp = frameSize + (j >> 1) * width;
- u = 0;
- v = 0;
- for (i = 0; i < width; i++, yp++) {
- y = (0xff & ((int) yuv420sp[yp])) - 16;
- if (y < 0) y = 0;
- if ((i & 1) == 0) {
- v = (0xff & yuv420sp[uvp++]) - 128;
- u = (0xff & yuv420sp[uvp++]) - 128;
- }
- y1192 = 1192 * y;
- r = (y1192 + 1634 * v);
- g = (y1192 - 833 * v - 400 * u);
- b = (y1192 + 2066 * u);
- if (r < 0) r = 0; else if (r > 262143) r = 262143;
- if (g < 0) g = 0; else if (g > 262143) g = 262143;
- if (b < 0) b = 0; else if (b > 262143) b = 262143;
- rgbBuf[yp * 3] = (byte)(r >> 10);
- rgbBuf[yp * 3 + 1] = (byte)(g >> 10);
- rgbBuf[yp * 3 + 2] = (byte)(b >> 10);
- }
- }//for
- return rgbBuf;
- }// decodeYUV420Sp2RGB</pre><pre name="code" class="java"><pre name="code" class="java">RemoteUtil.encode</pre>
- <pre></pre>
- <p></p>
- <pre></pre>
- 的算法取之于ExDesktopPublisher.java,应该是对视频数据的RTMP封装。这里就不贴代码了,可以到sample的文件里拿来用。
- <p></p>
- <p>以上文字组织很乱,因为我是在答辩的前一个晚上才实现的,因此代码也很乱,很难组织清楚,不过原理就是这样。最后的确是实现了实时视频,然而可能由于转码算法问题,实时视频的颜色是有问题的。</p>
- <p>为自己的大学生活里最后一次软件功能实现留给纪念吧!<br>
- </p>
- <p><br>
- </p>
- </pre></pre>
Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据的更多相关文章
- android 导入自己的生成的jar,老是 could not find class
最近开始学习android,开发一个小项目,功能很简单,就是从服务器上获取数据,之后显示在手机上.打算把访问服务器的功能打包成一个jar文件.然后android 引入jar包. 在eclipse 里 ...
- Android Error:Could not find lottie.jar
Android Error:Could not find lottie.jar 今天遇到了一个及其头疼的问题 同事的工程导到我的电脑里却报错,错误是找不到jcenter仓库里的lottie.jar包 ...
- 【Android端】代码打包成jar包/aar形式
Android端代码打包成jar包和aar形式: 首先,jar包的形式和aar形式有什么区别? 1.打包之后生成的文件地址: *.jar:库/build/intermediates/bundles/d ...
- Android开发者的演示工具——asm.jar
作为Android开发者,我们有时候需要给客户或者其他人演示我们的Android作品.我们可以使用类似豌豆荚.360手机助手这样的软件,今天我来介绍一个Android开发者的演示工具--asm.jar ...
- 使用基于Android网络通信的OkHttp库实现Get和Post方式简单操作服务器JSON格式数据
目录 前言 1 Get方式和Post方式接口说明 2 OkHttp库简单介绍及环境配置 3 具体实现 前言 本文具体实现思路和大部分代码参考自<第一行代码>第2版,作者:郭霖:但是文中讲 ...
- Android BLE与终端通信(四)——实现服务器与客户端即时通讯功能
Android BLE与终端通信(四)--实现服务器与客户端即时通讯功能 前面几篇一直在讲一些基础,其实说实话,蓝牙主要为多的还是一些概念性的东西,当你把概念都熟悉了之后,你会很简单的就可以实现一些逻 ...
- 解决Android与服务器交互大容量数据问题
对于目前的状况来说,移动终端的网络状况没有PC网络状况那么理想.在一个Android应用中,如果需要接收来自服务器的大容量数据,那么就不得不考虑客户的流量问题.本文根据笔者的一个项目实战经验出发,解决 ...
- Android(java)学习笔记210:采用post请求提交数据到服务器(qq登录案例)
1.POST请求: 数据是以流的方式写给服务器 优点:(1)比较安全 (2)长度不限制 缺点:编写代码比较麻烦 2.我们首先在电脑模拟下POST请求访问服务器的场景: 我们修改之前编写的logi ...
- Android(java)学习笔记209:采用get请求提交数据到服务器(qq登录案例)
1.GET请求: 组拼url的路径,把提交的数据拼装url的后面,提交给服务器. 缺点:(1)安全性(Android下提交数据组拼隐藏在代码中,不存在安全问题) (2)长度有限不能超过4K(h ...
随机推荐
- [C/CPP系列知识] 在C中使用没有声明的函数时将发生什么 What happens when a function is called before its declaration in C
http://www.geeksforgeeks.org/g-fact-95/ 1 在C语言中,如果函数在声明之前被调用,那么编译器假设函数的返回值的类型为INT型, 所以下面的code将无法通过编译 ...
- Linux下配置安装PHP环境
参考别人的做法,遇到问题上网查,下面就是安装步骤. 一.安装Apache2.2.221.到官网下载 http://httpd.apache.org/download.cgi 2.解压 t ...
- hdu 1056
水题 ~~ 按题目要求直接判断~. /************************************************************************* > A ...
- Linux重启inotify配置max_user_watches无效被恢复默认值8192的正确修改方法
Linux下Rsync+inotify-tools实现数据实时同步中有一个重要的配置就是设置Inotify的max_user_watches值,如果不设置,当遇到大量文件的时候就会出现出错的情况. 一 ...
- POJ 3461 Oulipo(字符串匹配,KMP算法)
题意:给出几组数据,每组有字符串W和T,问你W在T中出现几次. 思路:字符串长度很大,用KMP算法. 一开始写的是:调用KMP算法查找W在T中是否匹配,若匹配,则个数+1.则接下来T的索引移动相应的距 ...
- SQL server 复习一
第一天 下面我们从最基础的开始: 在运行里面输入:services.msc 一.启动服务 二.数据库登录的两种身份验证方式 另外一种身份验证方式就是SQL Server身份验证. sa不能使用的时候可 ...
- 一个很好的php分词类库
PHPAnalysis源程序下载与演示: PHP分词系统 V2.0 版下载 | PHP分词系统演示 | PHPAnalysis类API文档 原文连接地址:http://www.phpbone.co ...
- mysql小记--基础知识
一.事务 事务是由一组SQL语句组成的逻辑处理单元. 事务的特征ACID,即原子性.一致性.隔离性和持久性. 原子性(Atomicity)事务作为整体执行,操作要么全部执行.要么全部不执行. 一致性( ...
- Project Euler 81:Path sum: two ways 路径和:两个方向
Path sum: two ways In the 5 by 5 matrix below, the minimal path sum from the top left to the bottom ...
- 对于linux下system()函数的深度理解(整理)
原谅: http://blog.sina.com.cn/s/blog_8043547601017qk0.html 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同 ...