最近因为工作需要,暂停手机视频直播系统的学习和开发,转为完成一个比较急的任务:将从DVR得到的视频通过网页的flash player插件来直播。
要完成这个任务,首先必须对基于RTMP的流媒体服务器有一定的了解。以前我有花了一段时间学Flash Media Server,不过因为FMS卖得很贵,所以还是用免费的Red5好了。
读了研究生之后,我就进入了图像处理领域,写程序都是用matlab、C、C++,本来以为本科时候写的7万行java代码要浪费了,现在发现还是挺有用的,因为Red5是java写的。Java代码看起来比C、C++容易。
下面开始介绍Red5:
Red5是建立在Jetty(servlet engine) , Mina (networking)基础之上的,参考了Terracotta Clustering技术理念,并通过使用Spring框架将起整合起来的。
Jetty是一款运行速度较快轻量级的开源servlet 容器,可以作为嵌入式服务使用。
Mina 是Apache组织下的一个基于 Java NIO 技术的新项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。
Terracotta是开源的Java集群技术。
Spring是轻量级Java企业级开发的事实标准,Red5采用Spring来管理使程序逻辑更加清晰,耦合性更低,扩展性更好。
Red5响应请求的流程
(1)Red5在启动时会调用RTMPMinaTransport的start()方法,该方法会开启rmtp的socket监听端口(默认是1935),然后使用Mina 的api 将RTMPMinaIoHandler 绑定到该端口。
(2)RTMPMinaIoHandler 上定义了messageReceived()、messageSent()、sessionOpened()和sessionClosed()等方法,当有socket请求时,相应的方法会被调用。这时RTMPMinaIoHandler 会使用当前的socket连接来创建一个(或者使用之前创建好的)RTMPMinaConnection,并将其作为参数传递给定义于RTMPHandler类上的相应的messageReceived()、messageSent()、connectionOpened()和connectionClosed()方法。
(3)RTMPHandler会调用Server类的lookupGlobal()方法获得当前的GlobalScope,然后再利用GlobalScope找到当前socket请求应该使用的WebScope(这个WebScope 由开发者在WEB-INF\red5-web.xml中定义)。最后,RTMPHandler会调用RTMPMinaConnection 的connect ()方法连接到相应的WebScope。
(4)通常来说,WebScope又会将请求转移给ApplicationAdapter,由它来最终响应请求,而项目中通过重载ApplicationAdapter的方法来实现自己的逻辑。至此,控制流进入了开发者的项目中。
ApplicationAdapter类介绍
MultiThreadedApplicationAdapter类,是Red5应用程序的基础类。它提供了操作SharedObjects 和 streams的方法,还有连接和服务列表,是一个应用程序基本的Iscope。它实现了IstreamAwareScopeHandler接口,提供了在应用程序种控制流的方法。它还提供了一个很有用的事件控制器,可以拦截流、授权用户访问等。可以在其子类中添加各种方法,在客户端上通过NetConnection.call()方法调用服务器端的方法。这与Flash Media Server不同,FMS需要将你的客户端方法保存在服务器端,Red5提供更方便的方式进行远程方法调用。
ApplicationAdapter类是MultiThreadedApplicationAdapter的子类。ApplicationAdapter使用单线程的方式进行方法调用的消息出来,所以其性能不如MultiThreadedApplicationAdapter。
这2个类的核心方法为:
public boolean appStart(IScope arg0):Red5应用程序启动时自动执行此方法,进行一些参数初始化,如声明连接数据库的资料等。
public void appStop(IScope arg0):Red5应用程序停止时自动执行此方法。
public boolean appConnect(IConnection arg0, Object[] arg1):当客户端连接本应用程序时自执行此方法。
public boolean appDisconnect(Iconnection conn):当客户端断开连接时自动执行此方法。(如关闭浏览器、关闭FLASH PLAYER等特殊情况,均会触发该方法)。
public boolean appJoin(IClient arg0, IScope arg1):当有新的连接加入进来时自动调用。
获取某客户在服务器端保留/设置的变量:首先要通过Red5.getConnectionLocal()方法获得Iconnection对象,然后调用它的getClient()方法获得Client对象,再通过Client对象的getAttributes(“var_name”) 方法获得对应的变量的值。
下面是继承ApplicationAdapter类实现自己的Red5应用程序的代码例子:
import org.red5.server.adapter.ApplicationAdapter; import org.red5.server.api.IClient; import org.red5.server.api.IConnection; import org.red5.server.api.IScope;
public class Application extends ApplicationAdapter { @Override public boolean appStart(IScope arg0) // 应用程序启动时自动执行 { return super.appStart(arg0); } @Override public void appStop(IScope arg0) // 应用程序停止时自动执行 { super.appStop(arg0); } @Override public boolean appConnect(IConnection arg0, Object[] arg1) // 客户端连接本应用程序时自执行 { if(arg1 != null){ for(Object o:arg1){ … } } return super.appConnect(arg0, arg1); } @Override public boolean appJoin(IClient arg0, IScope arg1) // 有新的连接加入进来时自动调用 { //client.setAttribute("username",username); //IConnection current = Red5.getConnectionLocal(); //current.getClient().getAttribute("username") //Iterator<IConnection> it=appScope.getConnections(); //var nc:NetConnection=new NetConnection(); //nc.connect("rtmp://127.0.0.1/your_app","your_vars"); return super.appJoin(arg0, arg1); } } |
主要API简介
Red5提供了很多接口供开发人员进行二次开发:
IConnection:连接对象。每个连接都有一个关联的客户端和域。连接可能是持续型、轮询型、或短暂型。建立此接口的目的,是为了给不同的子类,如 RTMPConnection,RemotingConnection,AJAXConnection, HttpConnection 等,提供基础通用的方法。它提供getClient()方法来获取客服端对象。
IScope :每个Red5应用程序至少有一个域,用来搭建处理器、环景、服务器之间的连接。域可以构成树形结构,所有客户端都可以作为其节点共享域内的对象(比如流和数据)。所有的客服端(client)通过连接(connection)连接到域中。对于单一域,每个连接对应一个客服端,每个客服端对应一个id,简单的应用,操作就针对一个id和一个连接进行。
IServiceCapableConnection :获取有效连接。代码中先获取到连接实例,然后判断是否是有效连接并强制类型转换,之后调取客户端相应函数。
IClient :客户端对象代表某单一客户端。一个客户端可以和同一主机下不同的域分别建立连接。客户端对象和HTTP session 很相像。可以使用IClientRegistry.newClient(Object[])方法来创建IClient对象。
ApplicationAdapter:ApplicationAdapter是应用层级的IScope。若要处理流进程,需实现 IStreamAwareScopeHandler接口中的相应处理方法。ApplicationAdapter还提供了有效的事件处理机制,来进行截取流、确认用户等操作。同时,其子类中引入的方法均可在客户端通过 NetConnection 调取。在Aodbe 的FMS 中必须在服务器端维护客户端对象,与之相较,Red5 为您的远程请求提供了更加方便快捷的操作方法。
Red5开发示例
开发一个Red5 应用的服务器项目,和一般的J2EE 程序相似。进入Red5的webapps目录下,参考doc/templates目录下的myapp 文件夹,新建一个以自己项目命名的文件夹,比如myFirst,并对配置文件做相应的修改。然后开始编写自己的服务器端程序:
public class MyApplication extends ApplicationAdapter { private IScope appScope; private String username=””;
// 取得本次连接的Iscope, appStart方法在连接开始时自动触发 public boolean appStart(IScope app) { appScope = app; return true; }
// 连接时触发的函数,定义本过程中的username public boolean appConnect(IConnection conn, Object[]params) { username = (String)params[0]; return true; } // 连接加入时触发的函数,写入username的值 public boolean appJoin(IClient client, IScope app) { client.setAttribute(”username”,username); return true; } // 客户端登录时调用函数,将返回目前登录的在线列表 public String login() { IConnection current = Red5.getConnectionLocal(); System.out.println(”<—”+current.getClient().getId()+”:” +current.getClient().getAttribute(”username”)); return getOnlineList(); } // 取得在线列表,对在线的客户端进行遍历,并显示。 public String getOnlineList() { Iteratorit=appScope.getConnections(); StringonLineList=””; while(it.hasNext()) { IConnectionthis_conn=it.next(); IClientic=this_conn.getClient(); Stringu=ic.getAttribute(”username”).toString(); onLineList+=ic.getId()+”,”+u+”;”; System.out.println(u); } return onLineList; }
|
我们还需要对刚刚完成的程序进行配置,编辑WEB-INF下的red5-web.xml,增加<bean id=”web.handler” class=”MyApplication”>这样本程序将以刚才写的Application来运行。编辑web.xml,找到webAppRootKey,将其下面的paramvalue标签中的值修改为/myFirst,编辑red5-web.properties,将第一句修改为webapp.contextPath=/myFirst。
这样,在客户端中就可以轻易的调用login()方法了。在FLASH 中新建一个文件,输入以下代码:
nc = new NetConnection(); nc.connect(”rtmp:// localhost/myFirst”); nc.onResult = function(obj) { trace(obj); } nc.call(”login”,nc); |
客户端登录服务器,需要调用login方法,服务器便能得到一份客户端的在线列表了。
至于建立和运行一个Red5应用程序的具体过程,这里就不讲了。