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服务器发布实时视频数据相关推荐
- 目前最好的,以rtmp发送264到red5服务器的代码,darkdragonking亲测可用--《到H264视频通过RTMP直播》
前面的文章中提到了通过RTSP(Real Time Streaming Protocol)的方式来实现视频的直播,但RTSP方式的一个弊端是如果需要支持客户端通过网页来访问,就需要在在页面中嵌入一个A ...
- 【Android RTMP】RTMP 直播推流阶段总结 ( 服务器端搭建 | Android 手机端编码推流 | 电脑端观看直播 | 服务器状态查看 )
文章目录 安卓直播推流专栏博客总结 一. 服务器搭建 二. 手机端推流 三. 电脑端观看直播 四. RTMP 服务器端状态 安卓直播推流专栏博客总结 Android RTMP 直播推流技术专栏 : 0 ...
- 如何实现Android端获取RTSP|RTMP流转推RTMP
技术背景 最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在Android板件拉取本地的RTSP或RTMP流,然后对外推送RTMP出去,亦或内部启个轻量级RTSP服务,提供个对外对接的媒 ...
- android studio 集成 第三方sdk,Android FrameWork集成第三方SDK的jar包和so庫
本文講解的如何在Android FrameWork如何集成XXXsdk的jar包和so庫 首先在framework/opt/建立XXSDK的文件夾講jar包和so庫拷貝進去 1:framework/o ...
- Android Error:Could not find lottie.jar
Android Error:Could not find lottie.jar 今天遇到了一个及其头疼的问题 同事的工程导到我的电脑里却报错,错误是找不到jcenter仓库里的lottie.jar包 ...
- android自定义控件 jar,Android Studio引用自定义的framework.jar包
1.在app/libs/目录下添加framework.jar包. 2.打开build->Edit Libraries and Dependencies,把libs/framework.jar放到 ...
- Android解决Could not find manifest-merger.jar问题
我弄了很久,原来发现很简单就可以解决了: 将项目的build.gradle jcenter() 和google()的位置调换一下就ok: 问题原因:jcenter已经删掉了android.arch.c ...
- Android Proguard 不混淆所有第三方jar(忽略配置设置)
今天给大家分享一个只混淆自己的项目代码而不混淆所有第三方jar的方法(没错,是所有的第三方都不进行混淆,无论来什么第三方,都不需要动混淆配置) 不混淆所有第三方重大突破 今天又被同事逼迫看了一下Pro ...
- Android 内置RTSP/RTMP服务器,实现局域网内视频推流与播放
1. 背景 工作中有一个需求,在同一个局域网内, 需要将Android平板端(车机)上的摄像头上的画面,实时传输到手机上进行播放. 对于这个需求,我们想到了用RTSP/RTMP进行推流,然后在手机端拉 ...
最新文章
- 主板上这家伙,要当 CPU 和内存的中间商!
- 前端利器—1—转型JS编程
- 学python要考什么证-这十个Python常用库,学习Python的你必须要知道!
- 第五章:条件、循环以及其他语句(上)
- go goroutine主死从随
- 西南科技计算机在线自测,西南科技大学2017春季第一学期高等数学1在线自测答案...
- Android之提示android.content.res.Resources$NotFoundException: Resource ID #0x7f08010a
- 乐高无限的服务器怎么建,乐高无限悬空房子怎么建造 建造方法介绍
- 浅谈程序员的职业规划
- mysql数据库的多实例_MySQL数据库多实例应用实战 - 橙子柠檬's Blog
- brew安装指定版本mysql,Mac 系统为 Valet 开发环境安装指定版本 MySQL
- java用for语句实现菱形_编写Java程序,应用for循环打印菱形、三角形
- 考研过程中最容易犯的八大错误
- android 说出密码,关于未来的住宅的作文400字5篇
- 【bzoj4695】最假女选手 线段树区间最值操作
- TCP协议与IP协议
- QAV250四轴穿越机安装全程详解(多图)
- Astronauts UVA - 1391(2-sat)
- 全网最详细elasticsearch7.10.2安装手册
- 计算机图形学方向的基本能力
热门文章
- 2019中兴捧月之智能音频处理参赛总结
- OpenCV3学习(9.2)连通域分析函数详解connectedComponents()和connectedComponentsWithStats()
- 7-1 一元多项式求导 (10 分)
- 7-125 切分表达式——写个tokenizer吧 (20 分)
- 浪漫的表白 (5 分)
- 交换最小值和最大值 (15 分)
- JS当中的无限分类递归树
- nodejs学习(一)--express+ejs生成项目
- 需求管理与分析——需求池
- 为什么工具监测不出内存泄漏