Tomcat+red5+ffmpeg实现流媒体资源管理和在线直播
2016-12-30 11:34
337 查看
Red5的服务器搭建请看我的另外一篇文章:
http://blog.csdn.net/sunroyi666/article/details/52981639
最近正在开发基于流媒体的资源管理和直播系统,做了不少技术调查,框架的构思,也学到了很多东西。
现在空下来,把最近学到的技术知识和项目的架构分享一下。
现存的系统如下,可能大家遇到很多的系统都是这样。
新系统是这样的:
用户中心用CAS实现,这个以后再说。
资源管理系统实际上分两部分,系统部分用的Tomcat,流媒体服务器用的是Red5,两者分离。
实际上我已经把Red5和Tomcat整合好了,但是想想或许分开的话,资源的独立性更强,所以也就分开放了。
整合在一起的话,可以更方便的去控制用户连接到Red5,断开连接时的一些操作。
相互独立的话,很多操作就要放到系统中进行判断控制,或者在Red5这边单独控制。
但总体影响不大。
网上看到很多系统用的是Red5+Nginx而不是+Tomcat,这里感觉也没必要特地使用Nginx。
其他就是将上传的路径指向Red5服务器的目录而已(我这里是将资源管理系统和Red5服务器部署在一起的),然后将Red5服务器上的资源路径返回给用户自己保存。
RTMP是Adobe的私有协议,保存在实现RTMP协议的Red5流媒体服务器上的资源,不会像在Tomcat下面那样输入文件在服务器上的地址就能下载,保证了资源的安全性。
2. 上传的视频资源可用ffmpeg转码成其他格式(如FLV),并截取固定时间点的图片作为封面。
现在Red5已经支持mp4了,所以一般没必要把mp4转FLV。
ffmpeg工具可以去网上自己搜。
3. 因为资源管理用的是Red5流媒体服务器,所以顺便把直播功能也一起实现了,正好也要做这样的系统。
直播界面用Adobe Flash Builder 4.7做的swf。
因为我也是第一次用FlashBuilder,
这里先普及一下Actionscript,AS3,MXML,Flex,FlexBuilder,FlashBuilder,Flash,AIR,FlashPlayer的关系。
Actionscript简称AS,是Flash平台语言,根javascript其实很挺像的。
AS3是Actionscript的3.0版,也是现在最流行的版本。
Flex是官方的开发框架。
MXML是基于XML语法的语言,有点像HTML,可以与AS混用,最终编译成SWF。
Flash是一个平台,包含众多产品,比如网页动画,工具等等。
FlexBuilder是一套基于Eclipse的IDE。FlexBuilder从4.0开始改名为FlashBuilder。
FlashPlayer是播放用Flash制作的swf动画的必需的插件。
AIR是一个Runtime,就像JVM。有了它,用AS3开发的软件就可以跨平台运行。
下面是MXML代码,实现了简单的摄像头画面显示,开始直播和停止直播。
根据不同的用户,直播流的名字肯定是不一样的,这个是从HTML页面传参数给SWF。
当开始直播后,SWF会调用外部方法,去更新直播房间的状态。
用FlashBuilder编译以后,在bin-dug目录下会生成一些js和swf文件,一起拷出来,在HTML中调用:
把参数fileName和roomId传入flashvars,swf就可以获取到外部参数了。
直播,更新房间状态的功能就此完成。
想在PC上看直播的话,我用的是jwplayer,代码如下:
4. 如果在手机观看直播的话,直播端推荐用Adobe自己的直播软件Adobe Flash Media Live Encoder 3.2。
前面说过,RTMP是Adobe的私有协议,而ios和安卓手机都不支持Adobe,也就无法使用RTMP进行直播了。
现在主流做法是将RTMP用ffmpeg推流成为HLS,在手机端观看。HLS只需要H5的video标签就能播放。
FFMPEG命令如下:
①推正在直播的流的话,rtmp这段里面,"live=1"必须加,且必须用双引号,与rtmp地址包含在一起。
②中间是视频音频的参数设置
③最后"D:\apache-tomcat-8.0.9\webapps\resourcemanager\streams\room\hls\hls.m3u8"是hls协议的视频文件。
它会产生1个m3u8文件(ts文件列表),和N个ts文件(视频片段)
有人会问,既然可以用ffmpeg推流成为hls,那为什么还需要直播软件,直接pc端用swf直播,手机端不就可以看了吗?
请看下图ffmpeg转码时的参数:
这个是用Adobe Flash Media Live Encoder 3.2
这个是用swf直播的时候:
注意音频和视频Stream格式的区别。
用直播软件转码后,视频格式是vp6f,音频格式是mp3。而FlashBuilder并不支持这样的格式,它生成的是flv1和nellymoser。
其结果是,不转码,用swf播放带声音的视频时,会放1秒就卡死。不带声音的话OK,但是直播怎么能没声音呢?
我尝试了一下Red5的官方Demo直播,用手机观看也是同样的卡死。
我觉得肯定是音频格式有问题,所以尝试了下面的方法,但仍然不行:
① 修改音频的输入格式:mic.codec = SoundCodec.SPEEX
FlashBuilder只支持nellymoser和speex两种格式。
② 将视频和音频分两个流,然后用ffmpeg合并,视频和音频会出现延迟
这个就比较奇怪了。难道nellymoser和speex在手机上只能单独播放吗?
如果有人可以在FlashBuilder里面将音频转成mp3格式输入应该就没问题了,可是我不知道怎么转mp3呀
估计单用FlashBuilder是做不到直接转mp3的,需要C或者C++。
其实直播软件也就是将视频音频转码的作用。
参考了一下其他直播网站,他们似乎也推荐使用直播软件进行直播,那算了,我也别费工夫了,大家都用直播软件吧。
虽然最终没能解决音频转码的问题,但是在研究过程中对于ffmpeg的使用,以及视频音频的知识有了更加深入的了解。
系统中还整合了shiro,cas,以及手机端使用的APICloud,等以后有时间再继续分享吧。
http://blog.csdn.net/sunroyi666/article/details/52981639
最近正在开发基于流媒体的资源管理和直播系统,做了不少技术调查,框架的构思,也学到了很多东西。
现在空下来,把最近学到的技术知识和项目的架构分享一下。
首先
系统的开发目的是为了将用户和资源从现在的系统分离出来,为了以后系统扩展,更好的实现统一用户管理和资源管理这一概念。现存的系统如下,可能大家遇到很多的系统都是这样。
新系统是这样的:
用户中心用CAS实现,这个以后再说。
资源管理系统实际上分两部分,系统部分用的Tomcat,流媒体服务器用的是Red5,两者分离。
实际上我已经把Red5和Tomcat整合好了,但是想想或许分开的话,资源的独立性更强,所以也就分开放了。
整合在一起的话,可以更方便的去控制用户连接到Red5,断开连接时的一些操作。
相互独立的话,很多操作就要放到系统中进行判断控制,或者在Red5这边单独控制。
但总体影响不大。
网上看到很多系统用的是Red5+Nginx而不是+Tomcat,这里感觉也没必要特地使用Nginx。
接下来是系统功能和实现:
1. 资源管理这边提供了两种形式的上传,一种是上传界面,一种是Restful接口。其他就是将上传的路径指向Red5服务器的目录而已(我这里是将资源管理系统和Red5服务器部署在一起的),然后将Red5服务器上的资源路径返回给用户自己保存。
RTMP是Adobe的私有协议,保存在实现RTMP协议的Red5流媒体服务器上的资源,不会像在Tomcat下面那样输入文件在服务器上的地址就能下载,保证了资源的安全性。
2. 上传的视频资源可用ffmpeg转码成其他格式(如FLV),并截取固定时间点的图片作为封面。
现在Red5已经支持mp4了,所以一般没必要把mp4转FLV。
ffmpeg工具可以去网上自己搜。
/** * 视频转码 (PC端FLV) * @param ffmpegPath 转码工具的存放路径 * @param upFilePath 用于指定要转换格式的文件,要截图的视频源文件 * @param codcFilePath 格式转换后的的文件保存路径 * @param mediaPicPath 截图保存路径 * @return * @throws Exception */ public boolean exchangeToFlv(String ffmpegPath, String upFilePath, String codcFilePath, String mediaPicPath) throws Exception { // 创建一个List集合来保存转换视频文件为flv格式的命令 List<String> convert = new ArrayList<String>(); convert.add(ffmpegPath); // 添加转换工具路径 convert.add("-i"); // 添加参数"-i",该参数指定要转换的文件 convert.add(upFilePath); // 添加要转换格式的视频文件的路径 convert.add("-qscale"); //指定转换的质量 convert.add("6"); convert.add("-ab"); //设置音频码率 convert.add("64"); convert.add("-ac"); //设置声道数 convert.add("2"); convert.add("-ar"); //设置声音的采样频率 convert.add("22050"); convert.add("-r"); //设置帧频 convert.add("24"); convert.add("-y"); // 添加参数"-y",该参数指定将覆盖已存在的文件 convert.add(codcFilePath); // 创建一个List集合来保存从视频中截取图片的命令 List<String> cutpic = new ArrayList<String>(); cutpic.add(ffmpegPath); cutpic.add("-i"); cutpic.add(upFilePath); // 同上(指定的文件即可以是转换为flv格式之前的文件,也可以是转换的flv文件) cutpic.add("-y"); cutpic.add("-f"); cutpic.add("image2"); cutpic.add("-ss"); // 添加参数"-ss",该参数指定截取的起始时间 cutpic.add("16"); // 添加起始时间为第16秒 cutpic.add("-t"); // 添加参数"-t",该参数指定持续时间 cutpic.add("0.001"); // 添加持续时间为1毫秒 cutpic.add("-s"); // 添加参数"-s",该参数指定截取的图片大小 cutpic.add("350*240"); // 添加截取的图片大小为350*240 cutpic.add(mediaPicPath); // 添加截取的图片的保存路径 boolean mark = true; ProcessBuilder builder = new ProcessBuilder(); try { builder.command(convert); builder.redirectErrorStream(true); builder.start(); builder.command(cutpic); builder.redirectErrorStream(true); // 如果此属性为 true,则任何由通过此对象的 start() 方法启动的后续子进程生成的错误输出都将与标准输出合并, //因此两者均可使用 Process.getInputStream() 方法读取。这使得关联错误消息和相应的输出变得更容易 builder.start(); } catch (Exception e) { mark = false; System.out.println(e); e.printStackTrace(); } return mark; }
3. 因为资源管理用的是Red5流媒体服务器,所以顺便把直播功能也一起实现了,正好也要做这样的系统。
直播界面用Adobe Flash Builder 4.7做的swf。
因为我也是第一次用FlashBuilder,
这里先普及一下Actionscript,AS3,MXML,Flex,FlexBuilder,FlashBuilder,Flash,AIR,FlashPlayer的关系。
Actionscript简称AS,是Flash平台语言,根javascript其实很挺像的。
AS3是Actionscript的3.0版,也是现在最流行的版本。
Flex是官方的开发框架。
MXML是基于XML语法的语言,有点像HTML,可以与AS混用,最终编译成SWF。
Flash是一个平台,包含众多产品,比如网页动画,工具等等。
FlexBuilder是一套基于Eclipse的IDE。FlexBuilder从4.0开始改名为FlashBuilder。
FlashPlayer是播放用Flash制作的swf动画的必需的插件。
AIR是一个Runtime,就像JVM。有了它,用AS3开发的软件就可以跨平台运行。
下面是MXML代码,实现了简单的摄像头画面显示,开始直播和停止直播。
根据不同的用户,直播流的名字肯定是不一样的,这个是从HTML页面传参数给SWF。
当开始直播后,SWF会调用外部方法,去更新直播房间的状态。
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="init()" > <fx:Script> <![CDATA[ import mx.controls.Alert; import mx.core.FlexGlobals; private var nc:NetConnection; private var cam:Camera ; private var mic:Microphone; private var ns:NetStream; //private var room_id:String = FlexGlobals.topLevelApplication.parameters.roomid; /*** *** 初始化的方法,默认显示摄像头实时图像,不发布和录像 * ***/ private function init():void { cam = Camera.getCamera(); var vi:Video = new Video(); vi.width = 313; vi.height = 194; vi.attachCamera(cam); videoDisplay.addChild(vi); } /*** *** 发布直播并录制的方法 ***/ private function playClick():void { playButton.enabled = false; stopButton.enabled = true; recordCheck.enabled = false; clickHandler("1"); //之前有连接,先关闭 try{ if(nc.connected){ nc.close(); } }catch(e:Error){} nc = new NetConnection(); nc.addEventListener(NetStatusEvent.NET_STATUS,connectHandler); nc.client = this; // Red5的RTMP协议地址需要自己修改 nc.connect("rtmp://192.168.3.100:1936/oflaDemo"); } private function connectHandler(evt:NetStatusEvent):void{ trace(evt.info.code); //由于flash的异步机制,连接成功后才能做处理,否则NetStream会因为conn没能连接报参数错误#2126 var params:Object = FlexGlobals.topLevelApplication.parameters; var player:String = params.player; if (evt.info.code == 'NetConnection.Connect.Success') { ns = new NetStream(nc); cam = Camera.getCamera(); if(cam == null) { Alert.show("没有发现摄像头","提示") } mic = Microphone.getMicrophone(); setupCameraMic(); ns.attachAudio(mic); ns.attachCamera(cam); var vi:Video = new Video(); vi.width = videoDisplay.width; vi.height = videoDisplay.height; vi.attachCamera(cam); videoDisplay.addChild(vi); // live 直播不录制, record 边直播边录制 ns.publish(player, recordCheck.selected?"record":"live"); } else { Alert.show("直播服务器连接已断开","提示") } } /*** ***设置摄像头和麦克风的参数 ***/ private function setupCameraMic():void { cam = Camera.getCamera(); cam.setMode(320, 240, 30); cam.setQuality(0,70); mic = Microphone.getMicrophone(); mic.rate = 11; mic.setSilenceLevel(0); } /*** ***停止直播 ***/ private function stopClick():void { clickHandler("0"); nc.close(); ns.close(); playButton.enabled = true; stopButton.enabled = false; recordCheck.enabled = true; } public function onBWCheck(...arg):void { //do nothing } public function onBWDone(...arg):void { //do nothing } //更新直播状态 public function clickHandler(status:String):void { if(FlexGlobals.topLevelApplication.parameters.roomid==""){ Alert.show("用户名或密码不能为空","提示");return; } statusService.request.roomId=FlexGlobals.topLevelApplication.parameters.roomid; statusService.request.status=status; statusService.send(); } ]]> </fx:Script> <fx:Declarations> <s:HTTPService id="statusService" url="/mediamanager/play/mediaPlay/updateStatus" method="get"> <mx:request xmlns=""> </mx:request> </s:HTTPService> </fx:Declarations> <s:Panel x="66" y="29" title="发布直播" width="534" height="319"> <s:VideoDisplay x="6" y="10" width="370" height="262" id="videoDisplay" /> <s:Button label="开始" id="playButton" click="playClick();" x="384" y="24"/> <s:Button label="停止" x="384" y="68" id="stopButton" click="stopClick();"/> <s:CheckBox x="469" y="25" label="录制" width="53" id="recordCheck"/> </s:Panel> </s:Application>选择录制的话,会在Red5服务器的streams目录下生成“直播流名.flv”文件。
用FlashBuilder编译以后,在bin-dug目录下会生成一些js和swf文件,一起拷出来,在HTML中调用:
把参数fileName和roomId传入flashvars,swf就可以获取到外部参数了。
<script type="text/javascript" src="<c:url value="/scripts/play/server/swfobject.js"/>"></script> <script type="text/javascript"> //<!-- For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection. --> var swfVersionStr = "11.1.0"; // To use express install, set to playerProductInstall.swf, otherwise the empty string. var xiSwfUrlStr = "<c:url value='/scripts/play/server/playerProductInstall.swf'/>"; var flashvars ={}; flashvars.player=$("#fileName").val(); flashvars.roomid=$("#roomId").val(); var params = {}; //params.quality = "high"; //params.bgcolor = "#ffffff"; //params.allowscriptaccess = "sameDomain"; //params.allowfullscreen = "true"; var attributes = {}; attributes.id = "server"; attributes.name = "server"; attributes.align = "middle"; swfobject.embedSWF( "<c:url value='/scripts/play/server/server.swf'/>", "flashContent", "100%", "100%", swfVersionStr, xiSwfUrlStr, flashvars, params, attributes); //<!-- JavaScript enabled so display the flashContent div in case it is not replaced with a swf object. --> swfobject.createCSS("#flashContent", "display:block;text-align:left;"); </script>
直播,更新房间状态的功能就此完成。
想在PC上看直播的话,我用的是jwplayer,代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ include file="../jsp/include/taglibs.jsp"%> <html> <head> <title>JWPlayer</title> <script type="text/javascript" src="./scripts/jwplayer/jwplayer.js"></script> </head> <body> <div id="player"> <script type="text/javascript"> jwplayer("player").setup({ flashplayer: "./scripts/jwplayer/player.swf", image:"preview.jpg", file: "stream1477358653196", streamer: "rtmp://192.168.3.100:1936/oflaDemo" }); </script> </div> </body> </html>上面的file:"stream1477358653196"是直播流的名字,如果是播放资源的话,改成file:"video.mp4"即可。
4. 如果在手机观看直播的话,直播端推荐用Adobe自己的直播软件Adobe Flash Media Live Encoder 3.2。
前面说过,RTMP是Adobe的私有协议,而ios和安卓手机都不支持Adobe,也就无法使用RTMP进行直播了。
现在主流做法是将RTMP用ffmpeg推流成为HLS,在手机端观看。HLS只需要H5的video标签就能播放。
FFMPEG命令如下:
ffmpeg -i "rtmp://192.168.8.79:1937/red5/play live=1" -strict -2 -c:v libx264 -c:a aac -f hls "D:\apache-tomcat-8.0.9\webapps\resourcemanager\streams\room\hls\hls.m3u8"注意:
①推正在直播的流的话,rtmp这段里面,"live=1"必须加,且必须用双引号,与rtmp地址包含在一起。
②中间是视频音频的参数设置
③最后"D:\apache-tomcat-8.0.9\webapps\resourcemanager\streams\room\hls\hls.m3u8"是hls协议的视频文件。
它会产生1个m3u8文件(ts文件列表),和N个ts文件(视频片段)
有人会问,既然可以用ffmpeg推流成为hls,那为什么还需要直播软件,直接pc端用swf直播,手机端不就可以看了吗?
请看下图ffmpeg转码时的参数:
这个是用Adobe Flash Media Live Encoder 3.2
这个是用swf直播的时候:
注意音频和视频Stream格式的区别。
用直播软件转码后,视频格式是vp6f,音频格式是mp3。而FlashBuilder并不支持这样的格式,它生成的是flv1和nellymoser。
其结果是,不转码,用swf播放带声音的视频时,会放1秒就卡死。不带声音的话OK,但是直播怎么能没声音呢?
我尝试了一下Red5的官方Demo直播,用手机观看也是同样的卡死。
我觉得肯定是音频格式有问题,所以尝试了下面的方法,但仍然不行:
① 修改音频的输入格式:mic.codec = SoundCodec.SPEEX
FlashBuilder只支持nellymoser和speex两种格式。
② 将视频和音频分两个流,然后用ffmpeg合并,视频和音频会出现延迟
ffmpeg.exe -i "rtmp://192.168.8.79:1936/vod/video live=1" -i "rtmp://192.168.8.79:1936/vod/audio live=1" -strict -2 -c:v libx264 -c:a aac -f hls D:\apache-tomcat-8.0.9\webapps\mediamanager\streams\room\sun\sun.m3u8③ 在手机上放两个播放器,一个播放音频,一个播放视频,还是卡死。
这个就比较奇怪了。难道nellymoser和speex在手机上只能单独播放吗?
如果有人可以在FlashBuilder里面将音频转成mp3格式输入应该就没问题了,可是我不知道怎么转mp3呀
估计单用FlashBuilder是做不到直接转mp3的,需要C或者C++。
其实直播软件也就是将视频音频转码的作用。
参考了一下其他直播网站,他们似乎也推荐使用直播软件进行直播,那算了,我也别费工夫了,大家都用直播软件吧。
虽然最终没能解决音频转码的问题,但是在研究过程中对于ffmpeg的使用,以及视频音频的知识有了更加深入的了解。
系统中还整合了shiro,cas,以及手机端使用的APICloud,等以后有时间再继续分享吧。
相关文章推荐
- 基于Spring+JMX+Tomcat实现资源动态管理
- Red5流服务器搭建(实现在线直播,流媒体视频播放和在线视频会议)
- Red5流服务器搭建(实现在线直播,流媒体视频播放和在线视频会议)
- Tomcat如何实现资源安全管理
- Red5流服务器搭建(实现在线直播,流媒体视频播放和在线视频会议)
- Tomcat如何实现资源安全管理
- Red5流服务器搭建(实现在线直播,流媒体视频播放和在线视频会议)
- Hybrid模式-利用AssetsManager实现在线更新脚本文件lua、js、图片等资源(免去平台审核周期)
- 前端httpd+keepalived加后端heartbeat+nfs+drbd实现httpd服务的高效应用及资源统一管理
- 【COCOS2DX-LUA 脚本开发之十二】Hybrid模式-利用AssetsManager实现在线更新脚本文件lua、js、图片等资源(免去平台审核周期)
- Centos6.0之httpd+heartbeat+nfs实现httpd热备及资源统一管理
- Centos6.0之httpd+heartbeat+nfs实现httpd热备及资源统一管理
- tomcat 使用 memcached管理session ,并且实现统一登录
- 利用AssetsManager实现在线更新脚本文件lua、js、图片等资源(免去平台审核周期)
- Aspx实现在线用户管理
- 用PHP实现Ftp用户的在线管理
- 前端httpd+heepalived加后端heartbeat+nfs+drbd实现httpd服务的高效应用及资源统一管理
- 嵌入式LINUX下I/O资源的实现、管理和操作
- SpringBoard云峰会 SAP推在线资源管理方案
- 在线信息资源管理(章节一)