• How to implement a custom RandomAccessReadOptimizer class

    This article demonstrates how you can implement your own custom RandomAccessReadOptimizer. The RandomAccessReadOptimizer class is an abstraction in Wowza™ media server software that tries to optimize file read access when streaming using the HTTP streaming protocols (Adobe HDS, Apple HLS, MPEG-DASH, and Smooth Streaming). You can 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.0 or later, as well as knowledge of the Wowza IDE and Java.

    Contents


    Configuration
    Module Construction
    Troubleshooting

    Configuration


    Add the following property to <MediaReader>/<Properties> in the [install-dir]/conf/[vod-application-name]/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's 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 isn't invoked for Adobe HDS or Smooth Streaming, add the following property to the <HTTPStreamer>/<Properties> section of your [install-dir]/conf/[vod-application-name]/Application.xml file:
    <Property>
     <Name>httpOptimizeFileReads</Name>
     <Value>true</Value>
     <Type>Boolean</Type>
    </Property>

    Originally Published: For Wowza Media Server 3.5.0 on 11-08-2012.

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