多协议、性能稳定、丰富API的流媒体服务器软件
通过API查询录制时长?
element('share');?>
这篇文章通过一个HTTPProvider展示了如何使用Wowza nDVR API 来检查DVR的流录制,这个API使用dvrTime、packetTime、 UTC time 三类时间来查询。

注意:这个功能需要Wowza Media Server® 3.0.3.12及以上版本的支持。

时间单位

每一个nDVR 的切片的时间有三类时间单位:
  • DVR time:这是一个以毫秒为单位的DVR录制时间。第一个切片的时间总是从0开始,以后切片的时间从这里开始。了解Wowza产品细节 如果输入流有空隙(间隔),它可能是因为编码器有一段时间停止了发送数据或者是因为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里面。了解更多Wowza产品细节 这个HTTPProvider应该被添加到ServiceVersion HTTPProvider的上面。 在用户使用指南上详细解释了如何开发和部署HTTPProvider。

Code:
<HTTPProvider>
	<BaseClass>com.wowza.wms.plugin.test.dvr.api.HTTPDvrStreamQuery</BaseClass>
	<RequestFilters>dvrstreamquery*</RequestFilters>
	<AuthenticationMethod>none</AuthenticationMethod>
</HTTPProvider>
<!-- default provider must go at the end -->
<HTTPProvider>
	<BaseClass>com.wowza.wms.http.HTTPServerVersion</BaseClass>
	<RequestFilters>*</RequestFilters>
	<AuthenticationMethod>none</AuthenticationMethod>
</HTTPProvider>

使用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.*;
import com.wowza.wms.logging.WMSLoggerFactory;
import com.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 extends HTTProvider2Base {
    private static final String CLASSNAME = "HTTPDvrStreamQuery";
    private static final Class<HTTPDvrStreamQuery> CLASS = HTTPDvrStreamQuery.class;    

    public void onHTTPRequest(IVHost vhost, IHTTPRequest req, IHTTPResponse resp) {
        if (!doHTTPAuthentication(vhost, req, resp)) {
            return;
        }

        WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " HTTPRequest");

        Map<String, List<String>> 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 += "<BR>" + "action" + " is required";
                }
                WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " action: " + action);

                if (params.containsKey("app")) {
                    app = params.get("app").get(0);
                } else {
                    report += "<BR>" + "app" + " is required";
                }
                WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME + " app: " + app);

                if (params.containsKey("streamname")) {
                    streamName = params.get("streamname").get(0);
                } else {
                    report += "<BR>" + "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 + "<br><p>\n" + result;

                } else {
                    report = "Action: "+action + " is not valid.";
                }
            }

        } catch (Exception e) {
            report = "Error: " + e.getMessage();
        }

        String retStr = "<html><head><title>HTTPProvider DvrStreamQuery</title></head><body><h1>" + report + "</h1></body></html>";

        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<IDvrStreamStore> 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("<BR>Store:%s", store.getStreamName()));

            String result = String.format("<BR>  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("<BR> 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("<BR> packetStart: %s  packetEnd: %s", formatPacketTime(packetTimes.start), formatPacketTime(packetTimes.end));
                sb.append(s);
            }

            StartEndTimes utcTimes = queryUtcStartStop(store);
            if (utcTimes != null) {
                String s = String.format("<BR> utcStart: %s  utcEnd: %s", formatUtcTime(utcTimes.start), formatUtcTime(utcTimes.end));
                sb.append(s);            
            }

            sb.append("<P>");
        }
        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(IDvrStreamStore store) {
        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产品细节