这篇文章通过一个HTTPProvider展示了如何使用Wowza nDVR API 来检查DVR的流录制,这个API使用dvrTime、packetTime、 UTC time 三类时间来查询。
注意:这个功能需要Wowza Media Server® 3.0.3.12及以上版本的支持。
时间单位
每一个nDVR 的切片的时间有三类时间单位:
DVR time:这是一个以毫秒为单位的DVR录制时间。第一个切片的时间总是从0开始,以后切片的时间从这里开始。如果输入流有空隙(间隔),它可能是因为编码器有一段时间停止了发送数据或者是因为nDVR工作在append模式,但DVR时间不会包含空隙。
Packet time: 编码器对输出的音频和视频数据进行打包时的时间戳。nDVR切片将保留这个packet time.
UTC time. 当输入流进入nDVR 模块时,数据包会被打上当前的UTC时间。注意,这不是数据包离开编码器的时间,这是Wowza nDVR收到数据包时的时间。
主要的接口
每一个IDvrStreamStore 包含一个IDvrManifest, 它代表了DVR录制流的组成信息。
IDvrManifest 包含几个通道的信息,包括音频和视频通道。这个class 是DvrChannelManifest。这些通道由多个manifest记录组成,它包含了分别用三个时间单位表示的一个时间戳。
通过从IApplicationInstance 定位到音频和视频流,我们可以检测到第一条和最后一条记录信息,并确定3个时间单位的开始和结束的时间。
HTTP Provider的例子
下面的例子用 HTTPProvider 机制展示了如何查询一个流的所有相关DVR录制信息。 这个例子展示了DVR录制是否正在进行、是否包含音频或视频,并报告DVR的开始和结束时间、数据包和UTC时间标。
这里提供的代码只是一个示例,你可以很容易修改它以适应你的需求。
HTTPProvider是wowza media server的扩展,它可以监听来自[install-dir]/conf/VHost.xml中的定义的HostPort上的HTTP请求,你可以通过URL请求来控制它。 在用户使用指南的对应章节可以获得更多细节信息。
使用Wowza IDE将这个class编译为一个jar文件,并将其拷贝到[install-dir]/lib文件夹下。 然后将这个HTTPProvider添加到/conf/VHost.xml /HostPort (Port 8086)/HTTPProviders里面。这个HTTPProvider应该被添加到ServiceVersion HTTPProvider的上面。在用户使用指南上详细解释了如何开发和部署HTTPProvider。
Code:
com.wowza.wms.plugin.test.dvr.api.HTTPDvrStreamQuery
dvrstreamquery*
none
com.wowza.wms.http.HTTPServerVersion
*
none
使用HTTP Provider
nDVR的录制,可以通过URL来访问HTTPProvider的方式来进行控制。格式如下:
Code:
http://[wowza-ip-address]:8086/dvrstreamquery?action=query&app=[application-name]&streamname=[stream-name][&forceload=true]
其中:
[wowza-ip-address]: 运行Wowza Media Server的服务器IP地址
app: 承载输入流的应用的应用名称
streamname: 将要被录制的输入流的名称
forceload: (可选) 这个provider默认只检索已经被Wowza Media Server加载的DVR录制。添加这个参数可以强制加载这个流的DVR录制信息。
这个HTTP Provider将想浏览器返回一个HTML响应,如下:
Code:
query myStream:
Store:myStream.0
isLive: false hasAudio: true hasVideo: true
dvrStart: 0.000 dvrEnd: 48.466 duration:48.466
packetStart: 0 packetEnd: 48466
utcStart: 2011-Dec-20 14:37:20 utcEnd:2011-Dec-20 14:38:08
Store:myStream.1
isLive: true hasAudio: true hasVideo: true
dvrStart: 0.000 dvrEnd: 1.648 duration:1.648
packetStart: 1449298 packetEnd: 1450946
utcStart: 2012-Jan-03 17:06:06 utcEnd:2012-Jan-03 17:06:07
代理示例
下面是一个查询DVR存储信息的代码示例:
Code:
package com.wowza.wms.plugin.test.dvr.api;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import com.wowza.wms.application.IApplicationInstance;
import com.wowza.wms.dvr.*;
import com.wowza.wms.http.*;
importcom.wowza.wms.logging.WMSLoggerFactory;
importcom.wowza.wms.plugin.test.dvr.api.HTTPDvrStreamQuery.StartEndTimes;
import com.wowza.wms.stream.mediacaster.MediaStreamMediaCasterUtils;
import com.wowza.wms.vhost.*;
// Usage: http://[wowza-ip-address]:8086/dvrstreamquery?action=query&app=[application-name]&streamname=[stream-name][&forceload=true]
public class HTTPDvrStreamQuery extendsHTTProvider2Base {
private static final String CLASSNAME = "HTTPDvrStreamQuery";
private static final Class.class;
public void onHTTPRequest(IVHost vhost, IHTTPRequest req, IHTTPResponseresp) {
if (!doHTTPAuthentication(vhost, req, resp)) {
return;
}
WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+ " HTTPRequest");
Map> params = req.getParameterMap();
String action = "";
String app = "";
String streamName = "";
String report = "";
boolean forceLoad = false;
if (req.getMethod().equalsIgnoreCase("get") ||req.getMethod().equalsIgnoreCase("post")) {
req.parseBodyForParams(true);
try {
if(params.containsKey("action")) {
action =params.get("action").get(0);
} else {
report +="
" + "action" + " is required";
}
WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " action: "+ action);
if(params.containsKey("app")) {
app =params.get("app").get(0);
} else {
report +="
" + "app" + " is required";
}
WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " app: " +app);
if(params.containsKey("streamname")) {
streamName =params.get("streamname").get(0);
} else {
report +="
" + "streamname" + " is required";
}
WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " streamName:" + streamName);
if (params.containsKey("forceload")){
String forceLoadString =params.get("forceload").get(0);
forceLoad =Boolean.parseBoolean(forceLoadString);
WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " forceload:" + forceLoadString);
}
} catch (Exception ex) {
report = "Error: " +ex.getMessage();
}
} else {
report = "Nothing to do.";
}
try {
IApplicationInstance appInstance =vhost.getApplication(app).getAppInstance("_definst_");
// If no error
if (report.equalsIgnoreCase("")) {
if(action.equalsIgnoreCase("query")) {
WMSLoggerFactory.getLogger(CLASS).info(String.format("%s.%s:%s", CLASSNAME, "query", streamName));
String streamTypeStr =appInstance.getStreamType();
boolean isLiveRepeaterEdge= false;
while (true) {
StreamList streamDefs =appInstance.getVHost().getStreamTypes();
StreamItem streamDef =streamDefs.getStreamDef(streamTypeStr);
if (streamDef == null)
break;
isLiveRepeaterEdge =streamDef.getProperties().getPropertyBoolean("isLiveRepeaterEdge",isLiveRepeaterEdge);
break;
}
if (isLiveRepeaterEdge)
streamName =MediaStreamMediaCasterUtils.mapMediaCasterName(appInstance, null, streamName);
String result =queryDvrStreamInfo(appInstance, streamName, forceLoad);
report = action +" " + streamName + ":";
report = report +"
\n" + result;
} else {
report = "Action:"+action + " is not valid.";
}
}
} catch (Exception e) {
report = "Error: " + e.getMessage();
}
String retStr = "
try {
OutputStream out = resp.getOutputStream();
byte[] outBytes = retStr.getBytes();
out.write(outBytes);
} catch (Exception e) {
WMSLoggerFactory.getLogger(CLASS).error(CLASSNAME + ": " +e.toString());
}
}
public String queryDvrStreamInfo(IApplicationInstance appInstance,String streamName, boolean forceLoad) {
StringBuffer sb = new StringBuffer();
IDvrStreamManager dvrMgr =DvrStreamManagerUtils.getStreamManager(appInstance, IDvrConstants.DVR_STREAMING_PACKETIZER_ID,streamName, forceLoad);
if (dvrMgr == null) {
sb.append("Stream "+streamName+" not loaded.");
} else {
List stores = dvrMgr.getStreamStores();
for (IDvrStreamStore store : stores) {
sb.append(queryDvrStoreInfo(store));
}
}
return sb.toString();
}
public String queryDvrStoreInfo(IDvrStreamStore store) {
StringBuffer sb = new StringBuffer();
if (store != null) {
boolean hasAudio = store.hasAudio();
boolean hasVideo = store.hasVideo();
boolean isLive = store.isLive();
sb.append(String.format("
Store:%s",store.getStreamName()));
String result = String.format("
isLive: %s hasAudio: %s hasVideo: %s",isLive, hasAudio, hasVideo);
sb.append(result);
long duration = -1;
StartEndTimes dvrTimes = queryDvrStartStop(store);
if (dvrTimes != null) {
duration = dvrTimes.end -dvrTimes.start;
String s =String.format("
dvrStart: %s dvrEnd: %s duration: %s",formatDvrTime(dvrTimes.start), formatDvrTime(dvrTimes.end),formatDvrTime(duration));
sb.append(s);
}
StartEndTimes packetTimes = queryPacketStartStop(store);
if (packetTimes != null) {
String s =String.format("
packetStart: %s packetEnd: %s", formatPacketTime(packetTimes.start),formatPacketTime(packetTimes.end));
sb.append(s);
}
StartEndTimes utcTimes = queryUtcStartStop(store);
if (utcTimes != null) {
String s =String.format("
utcStart: %s utcEnd: %s", formatUtcTime(utcTimes.start),formatUtcTime(utcTimes.end));
sb.append(s);
}
sb.append("
");
}
return sb.toString();
}
protected StartEndTimes queryDvrStartStop(IDvrStreamStore store) {
if (store == null) {
return null;
}
DvrChannelManifest manifest = getVideoOrAudioManifest(store);
if (manifest == null) {
return null;
}
DvrManifestEntry firstEntry = manifest.getFirstEntry();
DvrManifestEntry lastEntry = manifest.getLastRecordedEntry();
long start = firstEntry.getStartTimecode();
long end = lastEntry.getStopTimecode();
return new StartEndTimes(start, end);
}
protected StartEndTimes queryPacketStartStop(IDvrStreamStore store) {
if (store == null) {
return null;
}
DvrChannelManifest manifest = getVideoOrAudioManifest(store);
if (manifest == null) {
return null;
}
DvrManifestEntry firstEntry = manifest.getFirstEntry();
DvrManifestEntry lastEntry = manifest.getLastRecordedEntry();
long start = firstEntry.getPacketStartTime();
long end = lastEntry.getPacketStartTime() + lastEntry.getDuration();
return new StartEndTimes(start, end);
}
protected StartEndTimes queryUtcStartStop(IDvrStreamStore store) {
if (store == null) {
return null;
}
DvrChannelManifest manifest = getVideoOrAudioManifest(store);
if (manifest == null) {
return null;
}
DvrManifestEntry firstEntry = manifest.getFirstEntry();
DvrManifestEntry lastEntry = manifest.getLastRecordedEntry();
long start = firstEntry.getUtcStartTime();
long end = lastEntry.getUtcStartTime() + lastEntry.getDuration();
return new StartEndTimes(start, end);
}
private DvrChannelManifest getVideoOrAudioManifest(IDvrStreamStorestore) {
IDvrManifest manifest = store.getManifest();
boolean hasAudio = store.hasAudio();
boolean hasVideo = store.hasVideo();
DvrChannelManifest channelManifest = null;
if (hasVideo) {
channelManifest = manifest.getManifestChannel(IVHost.CONTENTTYPE_VIDEO);
} else if (hasAudio) {
channelManifest = manifest.getManifestChannel(IVHost.CONTENTTYPE_AUDIO);
}
return channelManifest;
}
protected String formatUtcTime(long utc) {
final String UTC_FORMAT = "yyyy-MMM-dd HH:mm:ss";
SimpleDateFormat dateFormatLocal = new SimpleDateFormat(UTC_FORMAT);
return dateFormatLocal.format(new Date(utc));
}
protected String formatDvrTime(long t) {
return String.format("%.3f", t/1000.0);
}
protected String formatPacketTime(long t) {
return String.format("%s", t);
}
public class StartEndTimes {
long start = -1;
long end = -1;
public StartEndTimes(long start, long end) {
this.start = start;
this.end = end;
}
}
}
Wowza Streaming Engine 4是业界功能强大、API接口丰富的流媒体Server产品,采用它作为流媒体服务器产品的案例很多,直播、在线教育、IPTV都有它的用武之地。