多协议、性能稳定、丰富API的流媒体服务器软件
用RTSP拉流进入Wowza时,如何动态更新音视频同步方式(RTP/AVSyncMethod)?
在用RTSP拉流进入Wowza时,利用下面这个Wowza扩展模块,你可以为每一路输入流设置不同的音视频同步方式。你要做的就是创建一个文本文件,列出每一路流和对应的音视频同步方式,当Wowza启动拉流时会读取这个文件。下面的代码可以直接编译和使用,无需任何修改。

注意:需要Wowza IDE来编译这个模块

Wowza扩展模块的源代码

下面是这个模块的完整源代码:
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import com.wowza.wms.amf.*;
import com.wowza.wms.bootstrap.Bootstrap;
import com.wowza.wms.logging.WMSLoggerFactory;
import com.wowza.wms.module.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.media.model.*;
import com.wowza.wms.rtp.model.*;

public class ModuleIPCameraDynamicallySetAVSync extends ModuleBase {
	private String logPrefix = "ModuleIPCameraDynamicallySetAVSync::";
	
	class StreamListener implements IMediaStreamActionNotify3 {
		private String logPrefix = "ModuleIPCameraDynamicallySetAVSync::StreamListener::";
		public void onMetaData(IMediaStream stream, AMFPacket metaDataPacket) {
		}

		public void onPauseRaw(IMediaStream stream, boolean isPause,
				double location) {
		}

		public void onPause(IMediaStream stream, boolean isPause,
				double location) {
		}

		public void onPlay(IMediaStream stream, String streamName,
				double playStart, double playLen, int playReset) {
		}

		private int getAvsyncMethodPropertyInt(String name){
			if(name.toLowerCase().equalsIgnoreCase("rtptimecode")){
				return RTPStream.AVSYNCMETHODS_RTPTIMECODE;
			}
			else if(name.toLowerCase().equalsIgnoreCase("sendreport")){
				return RTPStream.AVSYNCMETHODS_SENDERREPORT;
			}
			else if(name.toLowerCase().equalsIgnoreCase("systemclock")){
				return RTPStream.AVSYNCMETHODS_SYSTEMCLOCK;
			}
			return RTPStream.AVSYNCMETHODS_UNKNOWN;
		}
		
		private int getAvsyncStreamMethod(String filePath, String streamName) {
			ArrayList<String> mediaCacheFiles = new ArrayList<String>();
			try {
				FileInputStream in = new FileInputStream(filePath);
				BufferedReader br = new BufferedReader(new InputStreamReader(in));
				String item = "";

				while ((item = br.readLine()) != null) {
					item = item.trim();
					if (!item.startsWith("#") && item.length() > 0) {
						String[] parts = item.split(" ");
						if(parts.length==2){
							String fileStreamName = parts[0].trim();
							String fileSyncMethod = parts[1].trim();
							if(fileStreamName.equals(streamName)){
								return this.getAvsyncMethodPropertyInt(fileSyncMethod);
							}
						}
					}
				}
			} catch (Exception e) {
				WMSLoggerFactory.getLogger(null).info(
						this.logPrefix + "getAvsyncStreamMethod::" + e.getMessage());
			}
			return -1;
		}
		
		public void onPublish(IMediaStream stream, String streamName,
				boolean isRecord, boolean isAppend) {

			String avSyncTxtFilePath = Bootstrap
					.getServerHome(Bootstrap.CONFIGHOME)
					+ "/conf/avsyncstreams.txt";
			
			getLogger().info(
					this.logPrefix+"#onPublish["
							+ stream.getContextStr() + "]: " + streamName+" :: "+avSyncTxtFilePath);

			
			int method = this.getAvsyncStreamMethod(avSyncTxtFilePath, streamName);
			if(method>=0){
				RTPStream rtpStream = stream.getRTPStream();
				if (rtpStream != null){
					getLogger().info(
							this.logPrefix+"#onPublish["
									+ stream.getContextStr() + "]: " + streamName +" set Avsync Method to "+method);
					rtpStream.setAVSyncMethod(method);
				}
				else{
					getLogger().info(
							this.logPrefix+"#onPublish["
									+ stream.getContextStr() + "]: " + streamName +" is not RTPStream");
				}
			}
			else{

				getLogger().info(
						this.logPrefix+"#onPublish["
								+ stream.getContextStr() + "]: " + streamName+" is not a valid stream");
			}
		}

		public void onSeek(IMediaStream stream, double location) {
		}

		public void onStop(IMediaStream stream) {
		}

		public void onUnPublish(IMediaStream stream, String streamName,
				boolean isRecord, boolean isAppend) {
		}

		public void onCodecInfoVideo(IMediaStream stream,
				MediaCodecInfoVideo codecInfoVideo) {
		}

		public void onCodecInfoAudio(IMediaStream stream,
				MediaCodecInfoAudio codecInfoAudio) {
		}
	}

	public void onStreamCreate(IMediaStream stream) {
		IMediaStreamActionNotify2 actionNotify = new StreamListener();
		stream.addClientListener(actionNotify);
	}

}
		
publish事件被触发后,这个模块就会在avsyncstreams.txt文件中查找对应的streamName,如果找到,就获得对应的音视频同步方式,也就是AVSync参数的配置。

下面对AVSync参数做个说明:

  • senderreport - 使用RTCP协议中传送的 Sender Report (SR) 数据。这是默认设置。

  • rtptimecode - 假定 RTP 的时间戳 是绝对时间,用这个时间戳。

  • systemclock - 使用系统时钟。

配置

  1. 使用Wowza IDE编译这个模块,将生成的.jar文件拷贝到Wowza的[install-dir]/lib目录下.

  2. 将这个模块配置到你的应用上(也可以用管理界面来配置),在[install-dir]/conf/[application]/Application.xml文件的Root/Application/Modules部分添加如下配置:
    <Module>
             <Name>ModuleIPCameraDynamicallySetAVSync</Name>
             <Description>ModuleIPCameraDynamicallySetAVSync</Description>
             <Class>com.wowza.wms.plugin.test.module.ModuleIPCameraDynamicallySetAVSync</Class>
    </Module>
    		
  3. avsyncstreams.txt文件放在[install-dir]/conf目录下.

  4. 按下面的格式,在avsyncstreams.txt文件中为每一路输入流配置对应的音视频同步方式:
    #StreamName[space]AVSyncMethod
    #camera.stream syncmethod
    cam1.stream sendreport