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-09-2012.

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