如何在Wowza Media Server上将影音媒体文件进行串流直播 — 如同电视台节目播送
此文章描述如何在Wowza Media Server的多媒体服务器上将视频/声音文件进行IPTV网络电视直播串流
Wowza本身支持的影音串流文件的格式:
-
Video and AudioFLV (Flash Video – .flv)
MP4 (QuickTime container – .mp4, .f4v, .mov, .m4v, .mp4a, .3gp, and .3g2)
MP3 (.mp3)
下载并解开Wowza Media Server的插件收集
点这里可以下载ZIP的压缩文档(wms-plugin-collection.zip)。
下载后进行解压缩此文档后,可参考README.html的说明:
This package contains one jar file that you can use to implement a number of extensions for Wowza Server To install 1. Copy the file jar file wms-plugin-collection.jar from [package]/lib/ into [install-dir]/lib/ For detailed description and implementation guide for each Module see this article on the Wowza Forum: Module Collection
其实很简单,就是把解开的文档下,/lib里面的文档 wms-plugin-collection.jar 拷贝到 [wowza安装的文件夹]/lib里面,这样就行了。
在这个插件里,包含了许多Wowza的模块,所有模块(modules)的说明(英文) 陈述在此,在此不多做翻译说明,若有兴趣,可在此出回复。
在Wowza Media Server进行设定
依照前面的说闽嗯安装插件模块之后,可依据后面的说明进行设定。或者参考“How to do scheduled streaming with Stream class streams”的详细说名。特别是想了解下面的源代码:
package com.wowza.wms.plugin.collection.serverlistener; import com.wowza.wms.application.*; import com.wowza.wms.server.*; import com.wowza.wms.vhost.*; import com.wowza.wms.stream.publish.*; import com.wowza.wms.logging.*; import java.io.File; import java.text.SimpleDateFormat; import java.util.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class ServerListenerStreamPublisher implements IServerNotify { WMSLogger log = WMSLoggerFactory.getLogger(null); Map streamMap = new HashMap(); Map playlistMap = new HashMap(); public void onServerInit(IServer server) { log.info("ServerListenerStreamPublisher Started."); IVHost vhost = null; IApplication app = null; try { vhost = VHostSingleton.getInstance(server.getProperties().getPropertyStr("PublishToVHost", "_defaultVHost_")); } catch (Exception evhost) { log.info("ServerListenerStreamPublisher: Failed to get Vhost can not run."); return; } try { app = vhost.getApplication(server.getProperties().getPropertyStr("PublishToApplication", "live")); } catch (Exception eapp) { log.info("ServerListenerStreamPublisher: Failed to get Application can not run."); return; } // Belt and Braces check for VHost and App if ( vhost == null || app == null ) { log.info("ServerListenerStreamPublisher: VHost or Application failed, not running."); return; } Boolean passThruMetaData = server.getProperties().getPropertyBoolean("PassthruMetaData", true); String storageDir = app.getAppInstance("_definst_").getStreamStorageDir(); try { String smilLoc = storageDir + "/streamschedule.smil"; File playlistxml = new File(smilLoc); if (playlistxml.exists() == false){ log.info("ServerListenerStreamPublisher: Could not find playlist file: " + smilLoc); return; } DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = null; Document document = null; try { db = dbf.newDocumentBuilder(); document = db.parse("file:///" + smilLoc); } catch (Exception e ) { log.info("ServerListenerStreamPublisher: XML Parse failed"); return; } document.getDocumentElement().normalize(); NodeList streams = document.getElementsByTagName("stream"); for (int i = 0; i < streams.getLength(); i++) { Node streamItem = streams.item(i); if (streamItem.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) streamItem; String streamName = e.getAttribute("name"); log.info("ServerListenerStreamPublisher: Streame name is '"+streamName+"'"); Stream stream = Stream.createInstance(vhost, app.getName(), streamName); streamMap.put(streamName, stream); app.getAppInstance("_definst_").getProperties().setProperty(streamName, stream); } } NodeList playList = document.getElementsByTagName("playlist"); if (playList.getLength() == 0){ log.info("ServerListenerStreamPublisher: No playlists defined in smil file"); return; } for (int i = 0; i < playList.getLength(); i++) { Node scheduledPlayList = playList.item(i); if (scheduledPlayList.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) scheduledPlayList; NodeList videos = e.getElementsByTagName("video"); if (videos.getLength() == 0){ log.info("ServerListenerStreamPublisher: No videos defined in stream"); return; } String streamName = e.getAttribute("playOnStream"); if (streamName.length()==0) continue; Playlist playlist = new Playlist(streamName); playlist.setRepeat((e.getAttribute("repeat").equals("false"))?false:true); playlistMap.put(e.getAttribute("name"), playlist); for (int j = 0; j < videos.getLength(); j++) { Node video = videos.item(j); if (video.getNodeType() == Node.ELEMENT_NODE) { Element e2 = (Element) video; String src = e2.getAttribute("src"); Integer start = Integer.parseInt(e2.getAttribute("start")); Integer length = Integer.parseInt(e2.getAttribute("length")); playlist.addItem(src, start, length); } } String scheduled = e.getAttribute("scheduled"); SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date startTime = null; try { startTime = parser.parse(scheduled); } catch (Exception z ) { log.info("Parsing schedule time failed."); return ; } Stream stream = streamMap.get(streamName); stream.setSendOnMetadata(passThruMetaData); ScheduledItem item = new ScheduledItem(startTime, playlist, stream); item.start(); IStreamActionNotify actionNotify = new StreamListener(app.getAppInstance("_definst_")); stream.addListener(actionNotify); log.info("ServerListenerStreamPublisher Scheduled: " + stream.getName() + " for: " + scheduled); } } } catch(Exception ex) { log.info("ServerListenerStreamPublisher: Error from playlist manager is '"+ex.getMessage()+"'"); } } private class ScheduledItem { public Timer mTimer; public TimerTask mTask; public Date mStart; public Playlist mPL; public Stream mStream; public ScheduledItem(Date d, Playlist pl, Stream s){ mStart = d; mPL = pl; mStream = s; mTask = new TimerTask(){ public void run() { //synchronized(mStream.getLock()) //{ mPL.open(mStream); //} log.info("ServerListenerStreamPublisher Scheduled stream is now live: " + mStream.getName()); } }; mTimer = new Timer(); } public void start(){ if (mTimer==null) mTimer = new Timer(); mTimer.schedule(mTask, mStart); log.info("scheduled playlist: "+mPL.getName()+ " on stream: "+mStream.getName()+ " for:"+mStart.toString()); } public void stop(){ if (mTimer != null){ mTimer.cancel(); mTimer=null; log.info("cancelled playlist: "+mPL.getName()+ " on stream: "+mStream.getName()+ " for:"+mStart.toString()); } } } public void onServerCreate(IServer server) { } public void onServerShutdownComplete(IServer server) { log.info("ServerListenerStreamPublisher: Shutdown server start"); for (Map.Entry entry : streamMap.entrySet()) { try { Stream stream = entry.getValue(); stream.close(); stream = null; log.info("ServerListenerStreamPublisher Closed Stream: " + entry.getKey()); } catch(Exception ex) { log.error(ex.getMessage()); } } for (Map.Entry entry : playlistMap.entrySet()) { try { Playlist pl = entry.getValue(); pl = null; } catch(Exception ex) { log.error(ex.getMessage()); } } } public void onServerShutdownStart(IServer server) { } class StreamListener implements IStreamActionNotify { StreamListener(IApplicationInstance appInstance) { } public void onPlaylistItemStop(Stream stream, PlaylistItem item) { if (item.getIndex() == (stream.getPlaylist().size() - 1)) { if (! stream.getRepeat()) { stream.close(); WMSLoggerFactory.getLogger(null).info("ServerListenerStreamPublisher: closing stream: " + stream.getName()); } } } public void onPlaylistItemStart(Stream stream, PlaylistItem item) { try { String name = stream.getCurrentItem().getName(); stream.getPublisher().getAppInstance().broadcastMsg("PlaylistItemStart", name); WMSLoggerFactory.getLogger(null).info("ServerListenerStreamPublisher PlayList Item Start: " + name); } catch(Exception ex) { WMSLoggerFactory.getLogger(null).info("ServerListenerStreamPublisher Get Item error: " + ex.getMessage()); } } } }
而这个源代码的位置,在之前下载的压缩文档里的 <<zip –> wms-plugin-collection -> src -> com -> wowza -> wms -> plugin -> collection -> serverlistener -> ServerListenerStreamPublisher.java >>. 如果您真的有兴趣学习这个源代码,在自行研究。否则,下面的说明是有关相关的设定了。
在[安装wowza的文件夹]/conf/Server.xml这个文档打开并进行编辑,然后找到这个内容 /ServerListeners
然后输入下面这些源代码:
<ServerListener> <BaseClass>com.wowza.wms.plugin.collection.serverlistener.ServerListenerStreamPublisher</BaseClass> </ServerListener>
如果原先就已经有设定live这个应用,而且没有其他特别的应用和设定,原则上,已经设定完毕了。否则,还得依据所需要的进行下面3个步骤的设定程序。
**********************************************************
非常重要:这一台服务器必须要有一个叫做”live“应用,因为要做直播串流,所以这是必须的。而且这将是串流源与播放清单(Streams and Playlist)所要放的地方。如果,你想用其他的名字,例如MyLiveApp来取代live,则在conf/Server.xml的 /Properties里面设定下面的源代码:
<Property> <Name>PublishToApplication</Name> <Value>MyLiveApp</Value> </Property>
另外,如果你想用发布这个直播串流到另外的虚拟主机(VHost),则在conf/Server.xml的/Properties里面加上下面的源代码:
<Property> <Name>PublishToVHost</Name> <Value>CustomVHost</Value> </Property>
在默认设定值,从这些影音文档里提供的中间数据(MetaData)是会被传送到直播串流上的;如果你不希望有这个功能,你必须在/conf/Server.xml的/Properties里面写入下面的源代码,就不会有此功能了。
<Property> <Name>PassthruMetaData</Name> <Value>false</Value> <Type>Boolean</Type> </Property>
在目标的应用Application.xml里面的/StreamType要加上 “live” 或 “live-lowlatency”,才会有直播的功能。
*****************************************************
在[安装文件夹]/content/里面要建立一个 the smil 文档,取名为 streamschedule.smil
至少必须建立一个串流(stream),然后也必须至少建立一个播放清单(playlist)。可以设定是否播完后重播,都可以在播放清单里面设定的。start=”-2″代表播放源就是直播(live),length=’-1’代表播放到媒体文档的结尾。
你可以建立一个时程表里面包含了多个串流与多个播放清单,这样就类似电视台一样,可以有不同的频道以及顺序轮流播放,例如下面的范例。
<smil> <head> </head> <body> <stream name="Stream1"></stream> <stream name="Stream2"></stream> <playlist name="pl1" playOnStream="Stream1" repeat="true" scheduled="2009-12-11 16:00:00"> <video src="mp4:sample.mp4" start="5" length="5"/> <video src="mp4:sample.mp4" start="50" length="5"/> <video src="mp4:sample.mp4" start="150" length="5"/> </playlist> <playlist name="pl2" playOnStream="Stream1" repeat="true" scheduled="2009-12-11 16:30:00"> <video src="mp4:sample.mp4" start="0" length="-1"/> </playlist> <playlist name="pl3" playOnStream="Stream2" repeat="true" scheduled="2009-12-11 16:00:00"> <video src="mp4:sample.mp4" start="30" length="5"/> </playlist> </body> </smil>
/content/streamschedule.smil的简单说明
- 所有的节目若播放完毕没有连接的其他节目,则会自动重播
- 串流名称(stream name) = “stream1”: stream1代表要被播放的串流
- “playlist name=”pl1″ playOnStream=”Stream1″ repeat=”true” scheduled=”2009-12-11 16:00:00″”: 在 2009/12/11 16:00:00 开始播放这个播放清单”pl1“到”Sream1″这个串流,会重播。
- “video src=”mp4:sample.mp4″ start=”5” length=”5″”:先在sample这个文档的第5秒开始播放,然后连续播放5秒钟
- “video src=”mp4:sample.mp4″ start=”50″ length=”5″”: 接着持续播放相同的文档,但是,是从第50秒钟播放5秒钟,播完后会从第50秒开始重播播5秒,如此一直重复
- “playlist name=”pl2″ playOnStream=”Stream1″ repeat=”true” scheduled=”2009-12-11 16:30:00″: 最后在 2009/12/11 16:30:00 开始播放这个pl2的播放清单,因为同一个stream1,所以在这个时间点,pl1就停止,改播pl2
- “video src=”mp4:sample.mp4″ start=”0″ length=”-1″”: 在pl2是从sample.mp4这个文档的一开始就播放,一直播放到结束,结束后继续重播。
- pl3播放清单是提供给stream2的,2009-12-11 16:00:00开始,会从sample.mp4的第30秒,播放5秒钟,然后持续重播。
客户端设置
服务器: rtmp://[wowza-address]/live
串流源: Stream1 (or Stream2. As set in smil file).
- 客户端可以设定 Server: rtmp://[wowza-address]/live, Stream: Stream1以及Server: rtmp://[wowza-address]/live,Stream: Stream2,来观看这2个频道。
参考文件
Latest Comments