多协议、性能稳定、丰富API的流媒体服务器软件
如何动态地调整RTSP流的音视频同步方式?

使用这个模块可以动态的调整每一个RTSP直播流的RTP/AVSyncMethod(例如一个从IP摄像头或mpeg-ts编码器输出的流)。 只要你设置一个文本文件来记录每一个RTSP流和它们希望的音视频同步方式,当这个流输入到Wowza时,这个模块就会解析这个文件。 下面的代码,你无需修改就可以直接编译使用,或者你可以根据你的需求修改它。

请先准备Wowza IDE

下面是这个Module的完整的代码示例:

import java.io.BufferedReader;
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事件中,这个代码尝试用/conf目录下的avsyncstreams.txt文件来匹配一个输入流。如果匹配到了,就会接着去找到对应的音视频同步的参数,然后将其设置到setAVSyncmethod方法中。

可以使用以下几种音视频同步方式:

  • senderreport – 使用Real Time Transport Control Protocol (RTCP)协议的Sender Report (SR) 数据包。这是默认的方式。
  • rtptimecode – 假定RTP timecodes 是绝对的时间戳
  • systemclock – 用系统时钟来进行同步。


配置

  1. 使用Wowza IDE编译这个模块的源代码(Module 部分)

  2. 将下面的Module添加到你的[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. [install-dir]/conf目录下添加avsyncstreams.txt文件

  4. 按下面的格式添加流的名字和对应的音视频同步方式:

    #StreamName[space]AVSyncMethod
    #camera.stream syncmethod
    cam1.stream sendreport