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. //key的值官方网站上可以申请到免费试用版本:http://www.smaxe.com/order.jsf#request_evaluation_key
  3. License.setKey("63140-D023C-D7420-00B15-91FC7");
  4. connection = new NetConnection();
  5. //对连接进行配置
  6. connection.configuration().put(NetConnection.Configuration.INACTIVITY_TIMEOUT, -1);
  7. connection.configuration().put(NetConnection.Configuration.RECEIVE_BUFFER_SIZE, 256 * 1024);
  8. connection.configuration().put(NetConnection.Configuration.SEND_BUFFER_SIZE, 256 * 1024);
  9. connection.client(new ClientHandler());
  10. connection.addEventListener(new NetConnectionListener());
  11. connection.connect(red5_url);
  12. }

其中new ClientHandler类是继承Object,里面写的方法可以被服务器调用。

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

例如:

[java] view plain copy

  1. private class ClientHandler extends Object {
  2. public ClientHandler() {}
  3. public void fun1() {}
  4. public void fun2() {}
  5. }
  6. private class NetConnectionListener extends NetConnection.ListenerAdapter {
  7. public NetConnectionListener() {}
  8. @Override
  9. public void onAsyncError(final INetConnection source, final String message, final Exception e) {
  10. System.out.println("NetConnection#onAsyncError: " + message + " "+ e);
  11. }
  12. @Override
  13. public void onIOError(final INetConnection source, final String message) {
  14. System.out.println("NetConnection#onIOError: " + message);
  15. }
  16. @Override
  17. public void onNetStatus(final INetConnection source, final Map<String, Object> info) {
  18. System.out.println("NetConnection#onNetStatus: " + info);
  19. final Object code = info.get("code");
  20. if (NetConnection.CONNECT_SUCCESS.equals(code)) {}
  21. }
  22. }

以上就是建立连接的过程,判断是否建立了连接在

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

Android 如何使用juv-rtmp-client.jar向Red5服务器发布实时视频数据的相关文章

CrtmpServer支持Android与IOS进行RTMP直播遇到的_checkbw问题

在进行移动端视频直播项目时遇到的问题,手机端在推的流时的是没问题的,主要现在是IOS和安卓连接CRtmpServer后进行播放时checkBW过不了,出现异常:NetConnection.Call.Failed,但连FMS服务器时能正常播放,由于用的是VLC的库,是封装好的,确定不了是哪个环节出了问题,以下是安卓与IOS开发报出来的异常截图. 然后,决定分析下CRtmpServer的日志及原码,发现在CrtmpServer的日志中发现了一条警告:Default implementation of

android 导入自己的生成的jar,老是 could not find class

最近开始学习android,开发一个小项目,功能很简单,就是从服务器上获取数据,之后显示在手机上.打算把访问服务器的功能打包成一个jar文件.然后android 引入jar包. 在eclipse 里 新建了一个java项目.之后完成代码测试,打包成jar文件 都很顺利.引用到android项目中,调试总报错,could not find class . 折腾了几天,都没有解决,几乎试遍 了,网上的所有方法都不行.后来突然意识到,是不是java项目的jar 不能引用到android 中.后来新建一

Android——eclipse共享library以及导出jar包

android的apk在在eclipse上进行开发的时候,有时候需要import其它包中的一些class,正常的方法就是在java build path中library 中添加 jar 包! 转载注明出处:http://blog.csdn.net/jscese/article/details/36627195 一.apk之间共享Class 在eclipse中的一个androoid工程想要使用另外一个工程的class,可以在工程的project.properties文件中添加: android.l

Android 混淆打包不混淆第三方jar包

项目因为要发布,所以要混淆打包. 混淆打包流程: 1.在proguard-project.txt文件中添加不需要混淆的类和第三方的jar包 这个是保持自己包中不需要混淆的类,如果有些类调用了jni也不需要混淆,不然会出错.还有如果项目中有其他项目作为library引入,那这些项目的一些类也不能混淆. 这个是保持项目中的第三方jar不混淆 另外加上上面几句话,不然会在控制台中报warning警告 2.在project.properties文件中把proguard.config=${sdk.dir}

【Android端】代码打包成jar包/aar形式

Android端代码打包成jar包和aar形式: 首先,jar包的形式和aar形式有什么区别? 1.打包之后生成的文件地址: *.jar:库/build/intermediates/bundles/debug(release)/classes.jar *.aar:库/build/outputs/aar/libraryname.aar 区别:jar包只包含了classes文件,不包含资源文件:aar不仅包含了classes文件,还包含资源文件 并且,aar的这个可以发布到maven库,然后使用者直

Android网络(3):HttpClient作客户端,Tomcat Servlet作服务器的交互示例

前面相继介绍了Android网络编程里的Socket传输图片.HttpURLConnection,今天看HttpClient. 第一部分:JavaEE版的Eclipse配置Tomcat [备注:开发后台服务器用Eclipse的JavaEE版最好的,但单就Tomcat来说(不写jsp之类的),本文下面的服务器方面操作在普通版的Eclipse也是可以的.我这里为了和ADT-bundle分开,特意重新安个JavaEE版的Eclipse.] 1.下载Eclipse的Tomcat插件:http://www

Android:解决客户端从服务器上获取数据乱码的方法

向服务器发送HTTP请求,接收到的JSON包为response,用String content = EntityUtils.toString(response.getEntity(),"utf-8");解码还是出现了中文乱码,在后面加了 String name = new String(response.getBytes("iso-8859-1"), "UTF-8"); 也无济于事.想到服务器好像是用URLENCODER编了码的,怀着试一试的态度

Android(java)学习笔记210:采用post请求提交数据到服务器

1.POST请求:  数据是以流的方式写给服务器 优点:(1)比较安全 (2)长度不限制 缺点:编写代码比较麻烦   2.我们首先在电脑模拟下POST请求访问服务器的场景: 我们修改之前编写的login.jsp代码,如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <%@ page language="java"

Android使用HttpClient实现文件上传到PHP服务器,并监控进度条

上传 服务器端PHP 代码如下 : <?php $target_path = "./tmp/";//接收文件目录 $target_path = $target_path.($_FILES['file']['name']); $target_path = iconv("UTF-8","gb2312", $target_path); if(move_uploaded_file($_FILES['file']['tmp_name'], $targ