• How to implement a custom RandomAccessReadOptimizer class

    This tutorial demonstrates how you can implement your own custom RandomAccessReadOptimizer. The RandomAccessReadOptimizer class is an abstraction in the Wowza™ media server software that will attempt to optimize file read access when streaming using the HTTP streaming protocols (Apple HLS, Flash HDS and Smooth Streaming). It is possible to implement your own RandomAccessReadOptimizer to control how bytes are read from the file source when using these protocols to stream content.

    Note: Requires Wowza Media Server™ 3.5 or later.

    Contents



    External Requirements


    1. Wowza Media Server 3.5 or later.

    2. Knowledge of Wowza IDE and Java.

    Configuration


    Add the following property to MediaReader/Properties in the [install-dir]/conf/[application]/Application.xml file:
    <Property>
    	<Name>randomAccessReadOptimizerClass</Name>
    	<Value>[package-name].RandomAccessReadOptimizer</Value>
    </Property>

    Module Construction


    1. Implement the IRandomAccessReadOptimizer interface as follows:
      public class RandomAccessReadOptimizer implements IRandomAccessReadOptimizer
    2. Override the interface functions with the following code:
      public class RandomAccessReadOptimizer implements IRandomAccessReadOptimizer{
      
      	@Override
      	public void addRequest(long arg0, int arg1, byte[] arg2, int arg3) {}
      
      	@Override
      	public void flushReads() {}
      
      	@Override
      	public int getBufferSize() {	
      		return 0;
      	}
      
      	@Override
      	public String getContextStr() {
      		return null;
      	}
      
      	@Override
      	public IRandomAccessReader getReader() {
      		return null;
      	}
      
      	@Override
      	public void setBufferSize(int arg0) {}
      
      	@Override
      	public void setContextStr(String arg0) {}
      
      	@Override
      	public void setDebugLog(boolean arg0) {}
      
      	@Override
      	public void setReader(IRandomAccessReader arg0) {}
      }
      After receiving a VOD HTTP chunk request, the addRequest function is invoked for each block of the file that is read for that chunk.
      @Override
      public void addRequest(long foffset, int fsize, byte[] buffer, int offset){}
      After all blocks are accounted for, the flushReads function is called.
      @Override
      public void flushReads() {}
      Use this method to populate the buffers with the requested data.

    3. Finally, to provide the default functionality, implement the following code and complete the class:
      import com.wowza.io.IRandomAccessReadOptimizer;
      import com.wowza.io.IRandomAccessReader;
      import java.util.*;
      import com.wowza.wms.logging.*;
      
      public class RandomAccessReadOptimizer implements IRandomAccessReadOptimizer
      {
      	class ReadHolder
      	{
      		long foffset = 0;
      		int fsize = 0;
      		byte[] buffer = null;
      		int offset = 0;
      		
      		public ReadHolder(long foffset, int fsize, byte[] buffer, int offset)
      		{
      			this.foffset = foffset;
      			this.fsize = fsize;
      			this.buffer = buffer;
      			this.offset = offset;
      		}
      	}
      	
      	private int bufferSize = 48*1024;
      	private SortedMap<Long, ReadHolder> requests = new TreeMap<Long, ReadHolder>();
      	private IRandomAccessReader reader = null;
      	private String contextStr = "unknown";
      	private int optimizedReads = 0;
      	private int directReads = 0;
      	private boolean debugLog = false;
      	
      	public RandomAccessReadOptimizer()
      	{
      		if (WMSLoggerFactory.getLogger(RandomAccessReadOptimizer.class).isDebugEnabled())
      			debugLog = true;
      	}
      	
      	public void addRequest(long foffset, int fsize, byte[] buffer, int offset)
      	{
      		requests.put(new Long(foffset), new ReadHolder(foffset, fsize, buffer, offset));
      	}
      		
      	private void doRead(ReadHolder holder)
      	{
      		directReads++;
      		
      		int readSize = 0;
      		try
      		{
      			synchronized(this.reader)
      			{
      				this.reader.seek(holder.foffset);
      				readSize = this.reader.read(holder.buffer, holder.offset, holder.fsize);
      			}
      		}
      		catch(Exception e)
      		{
      			WMSLoggerFactory.getLogger(RandomAccessReadOptimizer.class).error("RandomAccessReadOptimizer.doRead["+contextStr+"]: "+e.toString());
      		}
      		
      		if (readSize != holder.fsize)
      			WMSLoggerFactory.getLogger(RandomAccessReadOptimizer.class).warn("RandomAccessReadOptimizer.doRead["+contextStr+"]: Read size does not match: "+readSize+":"+holder.fsize);
      	
      	}
      	
      	private void doReads(List<ReadHolder> holders, long foffset, int fsize, byte[] fbuffer)
      	{
      		if (holders.size() == 1)
      			doRead(holders.get(0));
      		else
      			doReadsBuffer(holders, foffset, fsize, fbuffer);
      	}
      	
      	private void doReadsBuffer(List<ReadHolder> holders, long foffset, int fsize, byte[] fbuffer)
      	{
      		optimizedReads++;
      
      		int readSize = 0;
      		try
      		{
      			synchronized(this.reader)
      			{
      				this.reader.seek(foffset);
      				readSize = this.reader.read(fbuffer, 0, fsize);
      			}
      		}
      		catch(Exception e)
      		{
      			WMSLoggerFactory.getLogger(RandomAccessReadOptimizer.class).error("RandomAccessReadOptimizer.doReads["+contextStr+"]: "+e.toString());
      		}
      		
      		if (readSize == fsize)
      		{
      			Iterator<ReadHolder> iter = holders.iterator();
      			int fpos = 0;
      			while(iter.hasNext())
      			{
      				ReadHolder readHolder = iter.next();
      				
      				if (readHolder.buffer != null)
      					System.arraycopy(fbuffer, fpos, readHolder.buffer, readHolder.offset, readHolder.fsize);
      				
      				fpos += readHolder.fsize;
      			}
      		}
      		else
      			WMSLoggerFactory.getLogger(RandomAccessReadOptimizer.class).warn("RandomAccessReadOptimizer.doReads["+contextStr+"]: Read size does not match: "+readSize+":"+fsize);
      	}
      	
      	public void flushReads()
      	{
      		byte[] fbuffer = new byte[bufferSize];
      		
      		long startFPos = -1;
      		int currFSize = 0;
      		
      		Iterator<ReadHolder> iter = requests.values().iterator();
      		List<ReadHolder> holders = new ArrayList<ReadHolder>();
      		while(iter.hasNext())
      		{
      			ReadHolder readHolder = iter.next();
      			
      			if ((startFPos >= 0 && (startFPos+currFSize) != readHolder.foffset) || (currFSize+readHolder.fsize) > bufferSize)
      			{
      				if (holders.size() > 0)
      					doReads(holders, startFPos, currFSize, fbuffer);
      				
      				holders.clear();
      				startFPos = -1;
      				currFSize = 0;
      			}
      
      			if (readHolder.fsize > bufferSize)
      				doRead(readHolder);
      			else
      			{
      				if (startFPos < 0)
      					startFPos = readHolder.foffset;
      				currFSize += readHolder.fsize;
      				holders.add(readHolder);
      			}
      		}
      		
      		if (holders.size() > 0)
      			doReads(holders, startFPos, currFSize, fbuffer);
      		
      		holders.clear();
      		
      		if (this.debugLog)
      			WMSLoggerFactory.getLogger(RandomAccessReadOptimizer.class).info("RandomAccessReadOptimizer.flushReads["+contextStr+"]: totalBlocks:"+requests.size()+" blockReads:"+this.optimizedReads+" directReads:"+directReads+" bufferSize:"+bufferSize);
      
      		requests.clear();
      	}
      
      	public String getContextStr()
      	{
      		return contextStr;
      	}
      
      	public void setContextStr(String contextStr)
      	{
      		this.contextStr = contextStr;
      	}
      
      	public int getBufferSize()
      	{
      		return bufferSize;
      	}
      
      	public void setBufferSize(int bufferSize)
      	{
      		this.bufferSize = bufferSize;
      	}
      
      	public void setDebugLog(boolean debugLog)
      	{
      		this.debugLog = debugLog;
      	}
      
      	public IRandomAccessReader getReader()
      	{
      		return reader;
      	}
      
      	public void setReader(IRandomAccessReader reader)
      	{
      		this.reader = reader;
      	}
      }

    Troubleshooting


    If the optimizer is not invoked for HDS or Smooth Streaming, add the following property in the HTTPStreamer/Properties section of your [install-dir]/conf/[appname]/Application.xml file:
    <Property>
     <Name>httpOptimizeFileReads</Name>
     <Value>true</Value>
     <Type>Boolean</Type>
    </Property>

    Originally Published: 11-19-2013.

    If you're having problems or want to discuss this article, post in our forum.