Specify the A/V synchronization method when re-streaming RTSP streams with the Wowza Streaming Engine Java API

Use this Wowza Streaming Engine™ Java API application module to set the RTP/AVSyncMethod for live RTSP streams, such as IP cameras or MPEG-TS encoders, on a per-stream basis. If you add a text file with a list of streams and their intended sync methods, the module deciphers this list when published. The code can be compiled and used without modification or extended by developers and implemented within a custom workflow.

Contents


Requirements
Module
Configuration

Requirements


  • Wowza Streaming Engine 3.6.2 or later
  • Wowza IDE

Module


This is the complete source:

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("senderreport")){
				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);
	}

}

After a publish event, this code tries to match the stream to one of the streams listed in your local avsyncstreams.txt file. If matched, this module further tries to match the corresponding native sync property that subsequently sets the AVSync method.

The following AVSync methods are used:

  • senderreport - Uses the Sender Report (SR) packets that are sent over the Real Time Transport Control Protocol (RTCP) channel. This is the default value.
     
  • rtptimecode - Assumes that RTP timecodes are absolute timecode values.
     
  • systemclock - Synchronizes based on the system clock.

Configuration


  1. Compile the module (use the complete source from the Module section) by using the Wowza IDE and including the .jar file in the [install-dir]/lib directory.
     
  2. Add the following module to your [install-dir]/conf/[application]/Application.xml file in the Root/Application/Modules section as follows:
    <Module>
             <Name>ModuleIPCameraDynamicallySetAVSync</Name>
             <Description>ModuleIPCameraDynamicallySetAVSync</Description>
             <Class>com.wowza.wms.plugin.test.module.ModuleIPCameraDynamicallySetAVSync</Class>
    </Module>
  3. Include the avsyncstreams.txt file in your [install-dir]/conf directory.
     
  4. Add a list of streams and sync methods to avsyncstreams.txt in the following format:
    #StreamName[space]AVSyncMethod
    #camera.stream syncmethod
    cam1.stream senderreport