com.wowza.wms.stream
Interface IMediaWriter


public interface IMediaWriter

IMediaWriter: generic media writer interface. The flv recording system using this interface to persist .flv data captured from the Flash client. These classes are referenced in [install-dir]/conf/MediaWriters.xml.

Example IMediaWriter implementation: MediaWriterFLVBasic

This is a basic IMediaWriter implementation that can handle record and append.

import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;

import com.wowza.util.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.amf.AMFData;
import com.wowza.wms.logging.*;

public class MediaWriterFLV implements IMediaWriter
{
        private IMediaStream parent = null;
        private MediaWriterItem mediaWriterItem = null;
        private long[] currentTCs = new long[3];
        private long duration = 0;
        private Map extraMetadata = new HashMap();
        private boolean versionFile = false;

        public void setMediaWriterItem(MediaWriterItem mediaWriterItem)
        {
                this.mediaWriterItem = mediaWriterItem;
        }

        public void setParent(IMediaStream parent)
        {
                this.parent = parent;
        }

        public void writePackets(List audioPackets, List videoPackets,
                        List dataPackets, List audioTCs, List videoTCs, List dataTCs, List dataTypes,
                        boolean isFirst, boolean isLast)
        {
                File newFile = this.parent.getStreamFile();

                boolean localAppend = this.parent.isAppend();

                if (isFirst)
                {
                        long startTC = 0;
                        if (newFile.exists())
                        {
                                if (localAppend)
                                        startTC = FLVUtils.getLastTC(newFile);
                                else
                                {
                                        if (versionFile)
                                                FileUtils.versionFile(newFile);
                                        else
                                        {
                                                try
                                                {
                                                        newFile.delete();
                                                }
                                                catch (Exception e)
                                                {
                                                }
                                        }
                                }
                        }
                        else
                                localAppend = false;

                        this.currentTCs[FLVUtils.FLV_TCINDEXAUDIO] = startTC;
                        this.currentTCs[FLVUtils.FLV_TCINDEXVIDEO] = startTC;
                        this.currentTCs[FLVUtils.FLV_TCINDEXDATA] = startTC;
                }
                else
                        localAppend = true;

                try
                {
                        if (newFile.getParentFile() == null)
                                WMSLoggerFactory.getLogger(MediaWriterFLV.class).warn("MediaWriterFLV: File path does not exist: "+newFile.getPath());
                        else if (!newFile.getParentFile().exists())
                                WMSLoggerFactory.getLogger(MediaWriterFLV.class).warn("MediaWriterFLV: Folder does not exist: "+newFile.getParentFile().getPath());
                        else if (newFile.exists() && !newFile.canWrite())
                                WMSLoggerFactory.getLogger(MediaWriterFLV.class).warn("MediaWriterFLV: Cannot write to file (permission error): "+newFile.getPath());

                        FileOutputStream ds = new FileOutputStream(newFile, localAppend);

                        if (isFirst)
                        {
                                if (!localAppend)
                                {
                                        FLVUtils.writeHeader(ds, 0.0, extraMetadata);

                                        boolean writeZeroPacket = true;
                                        while(true)
                                        {
                                                if (audioPackets.size() == 0)
                                                        break;

                                                ByteBuffer data = (ByteBuffer)audioPackets.get(0);
                                        long tcA = ((Long)audioTCs.get(0)).longValue();

                                        if (tcA == 0 && data.limit() == 0)
                                                writeZeroPacket = false;

                                                break;
                                        }

                                        if (writeZeroPacket)
                                        {
                                                FLVUtils.writeChunk(ds, null, 0,
                                                                        this.currentTCs[FLVUtils.FLV_TCINDEXAUDIO],
                                                                        (byte) 0x08); // write zero length audio block
                                        }
                                }
                        }

                        FLVUtils.writePackets(ds, audioPackets, videoPackets, dataPackets,
                                        audioTCs, videoTCs, dataTCs, dataTypes, currentTCs);

                        ds.flush();
                        ds.close();
                }
                catch (Exception e)
                {
                        WMSLoggerFactory.getLogger(MediaWriterFLV.class).error(
                                        "MediaWriterFLV: Error writing to file: "+newFile.getPath()+" :"+e.toString());
                        e.printStackTrace();
                }

                if (isLast)
                {
                        duration = Math.max(Math.max(currentTCs[FLVUtils.FLV_TCINDEXAUDIO],
                                        currentTCs[FLVUtils.FLV_TCINDEXVIDEO]),
                                        currentTCs[FLVUtils.FLV_TCINDEXDATA]);
                        double durationSecs = ((double)duration) / 1000.0;

                        FLVUtils.writeDuration(newFile, durationSecs);
                }
        }

        public Map getExtraMetadata()
        {
                return extraMetadata;
        }

        public void setExtraMetadata(Map extraMetadata)
        {
                this.extraMetadata = extraMetadata;
        }

        public boolean isVersionFile()
        {
                return versionFile;
        }

        public void setVersionFile(boolean versionFile)
        {
                this.versionFile = versionFile;
        }

        public void putMetaData(String name, AMFData value)
        {
                this.extraMetadata.put(name, value);
        }

}

To use this class, edit [install-dir]/conf/MediaWriter and replace the definition for the flv MediaWriter:

<MediaWriter>
        <Name>flv</Name>
        <Description>FLV Media Writer</Description>
        <FileExtension>flv</FileExtension>
        <ClassBase>com.wowza.wms.plugin.mediawriter.flv.MediaWriterFLVBasic</ClassBase>
</MediaWriter>

Example IMediaWriter implementation: MediaWriterFLVMetadata

This example illustrates how to write custom metadata into the recorded flv file on the fly.

public class MediaWriterFLVMetadata implements IMediaWriter
{
        private IMediaStream parent = null;
        private MediaWriterItem mediaWriterItem = null;
        private long[] currentTCs = new long[3];
        private long duration = 0;
        private File tmpFile = null;
        private Map extraMetadata = new HashMap();
        private boolean versionFile = false;

        public void setMediaWriterItem(MediaWriterItem mediaWriterItem)
        {
                this.mediaWriterItem = mediaWriterItem;
        }

        public void setParent(IMediaStream parent)
        {
                this.parent = parent;
        }

        public void writePackets(List audioPackets, List videoPackets,
                        List dataPackets, List audioTCs, List videoTCs, List dataTCs,
                        boolean isFirst, boolean isLast)
        {
                File newFile = this.parent.getStreamFile();
                try
                {
                        if (tmpFile == null)
                                tmpFile = File.createTempFile("wowza", "flv");
                }
                catch (Exception e)
                {
                        WMSLoggerFactory.getLogger(MediaWriterFLVBasic.class).error(
                                        "MediaWriterFLVMetadata: Error createTempFile: "+
                                        tmpFile+" :"+e.toString());
                }

                boolean localAppend = this.parent.isAppend();
                if (isFirst)
                {
                        AMFDataArray keyFrames = null;

                        long startTC = 0;
                        if (newFile.exists())
                        {
                                if (localAppend)
                                {
                                        startTC = FLVUtils.getLastTC(newFile);
                                        keyFrames = getKeyFrames(newFile);
                                        copyPacketsToTmpFile(newFile, tmpFile);
                                }

                                if (versionFile)
                                        FileUtils.versionFile(newFile);
                                else
                                {
                                        try
                                        {
                                                newFile.delete();
                                        }
                                        catch (Exception e)
                                        {
                                        }
                                }
                        }
                        else
                                localAppend = false;

                        if (keyFrames == null)
                                keyFrames = new AMFDataArray();
                        extraMetadata.put("keyFrames", keyFrames);

                        this.currentTCs[FLVUtils.FLV_TCINDEXAUDIO] = startTC;
                        this.currentTCs[FLVUtils.FLV_TCINDEXVIDEO] = startTC;
                        this.currentTCs[FLVUtils.FLV_TCINDEXDATA] = startTC;
                }
                else
                        localAppend = true;

                AMFDataArray keyFrames = (AMFDataArray)extraMetadata.get("keyFrames");
        long timecode = this.currentTCs[FLVUtils.FLV_TCINDEXVIDEO];
                int size = videoPackets.size();
                for(int i=0;i<size;i++)
                {
                        ByteBuffer data = (ByteBuffer)videoPackets.get(i);
                        int firstByte = data.get(0);
                        timecode += ((Long)videoTCs.get(i)).longValue();
                        if (FLVUtils.getFrameType(firstByte) == FLVUtils.FLV_KFRAME)
                        {
                                double durationSecs = ((double)timecode) / 1000.0;
                                AMFDataObj dataObj = new AMFDataObj();
                                dataObj.put("name", new AMFDataItem("keyframe "+durationSecs));
                                dataObj.put("time", new AMFDataItem(durationSecs));
                                keyFrames.add(dataObj);
                        }
                }

                try
                {
                        FileOutputStream ds = new FileOutputStream(tmpFile, localAppend);
                        FLVUtils.writePackets(ds, audioPackets, videoPackets, dataPackets,
                                        audioTCs, videoTCs, dataTCs, currentTCs);
                        ds.flush();
                        ds.close();
                }
                catch (Exception e)
                {
                        WMSLoggerFactory.getLogger(MediaWriterFLVBasic.class).error(
                                        "MediaWriterFLVMetadata: Error writing to tmp file: "+
                                        newFile.getPath()+" :"+e.toString());
                }

                if (isLast)
                {
                        duration = Math.max(Math.max(currentTCs[FLVUtils.FLV_TCINDEXAUDIO],
                                        currentTCs[FLVUtils.FLV_TCINDEXVIDEO]),
                                        currentTCs[FLVUtils.FLV_TCINDEXDATA]);
                        double durationSecs = ((double)duration) / 1000.0;

                        try
                        {
                                AMFPacket packet = null;
                                FileOutputStream ds = new FileOutputStream(newFile);

                                FileInputStream di = new FileInputStream(tmpFile);
                                FLVUtils.writeHeader(ds, durationSecs, extraMetadata);
                                while((packet = FLVUtils.readChunk(di)) != null)
                                {
                                        FLVUtils.writeChunk(ds, packet.getDataBuffer(), packet.getSize(), 
                                                        packet.getTimecode(), (byte)packet.getType());
                                }
                                di.close();

                                ds.flush();
                                ds.close();

                                tmpFile.delete();
                        }
                        catch (Exception e)
                        {
                                WMSLoggerFactory.getLogger(MediaWriterFLVBasic.class).error(
                                                "MediaWriterFLVMetadata: Error tmp writing to file: "+
                                                newFile.getPath()+" :"+e.toString());
                        }
                }
        }

        private void copyPacketsToTmpFile(File newFile, File tmpFile)
        {
                AMFDataArray keyFrames = null;
                try
                {
                        AMFPacket packet = null;
                        FileOutputStream ds = new FileOutputStream(tmpFile);

                        FileInputStream di = new FileInputStream(newFile);
                        FLVUtils.readHeader(di);
                        FLVUtils.readChunk(di); // skip metaData packet
                        while((packet = FLVUtils.readChunk(di)) != null)
                        {
                                FLVUtils.writeChunk(ds, packet.getDataBuffer(), packet.getSize(), 
                                                packet.getTimecode(), (byte)packet.getType());
                        }
                        di.close();

                        ds.flush();
                        ds.close();
                }
                catch (Exception e)
                {
                        WMSLoggerFactory.getLogger(MediaWriterFLVBasic.class).error(
                                        "MediaWriterFLVMetadata: Error copyPacketsToTmpFile: "+
                                        newFile.getPath()+" :"+e.toString());
                }
        }

        private AMFDataArray getKeyFrames(File newFile)
        {
                AMFDataArray keyFrames = null;
                try
                {
                        BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(newFile));
                        FLVUtils.readHeader(inStream);
                        AMFPacket packet = FLVUtils.readChunk(inStream);
                        if (packet.getType() == IVHost.CONTENTTYPE_DATA0 || packet.getType() == IVHost.CONTENTTYPE_DATA3)
                        {
                                byte[] mbytes = packet.getData();
                                int moffset = 0;
                                if (packet.getType() == IVHost.CONTENTTYPE_DATA3 && mbytes.length > 0)
                                {
                                        if (mbytes[0] == 0)
                                                moffset = 1;
                                }

                                AMFDataList dataList = new AMFDataList(mbytes, moffset, mbytes.length-moffset);
                                if (dataList.size() > 1)
                                {
                                        if (dataList.get(1).getType() == AMFData.DATA_TYPE_MIXED_ARRAY)
                                        {
                                                AMFDataMixedArray metaValues = (AMFDataMixedArray)dataList.get(1);
                                                if (metaValues.containsKey("keyFrames"))
                                                        keyFrames = (AMFDataArray)metaValues.get("keyFrames");
                                        }
                                }
                        }
                        inStream.close();
                }
                catch (Exception e)
                {
                        WMSLoggerFactory.getLogger(MediaWriterFLVBasic.class).error(
                                        "MediaWriterFLVMetadata: Error getKeyFrames: "+
                                        newFile.getPath()+" :"+e.toString());
                }

                return keyFrames;
        }

        public boolean isVersionFile()
        {
                return versionFile;
        }

        public void setVersionFile(boolean versionFile)
        {
                this.versionFile = versionFile;
        }

        public void putMetaData(String name, AMFData value)
        {
                this.extraMetadata.put(name, value);
        }
}

To use this class, edit [install-dir]/conf/MediaWriter and replace the definition for the flv MediaWriter:

<MediaWriter>
        <Name>flv</Name>
        <Description>FLV Media Writer</Description>
        <FileExtension>flv</FileExtension>
        <ClassBase>com.wowza.wms.plugin.mediawriter.flv.MediaWriterFLVMetadata</ClassBase>
</MediaWriter>


Method Summary
 long getDuration()
          Get the recorded duration of the file in seconds
 boolean isVersionFile()
          Return true if the old file is to be versioned
 boolean isWaitForVideoKeyFrame()
          get wait for key frame
 void putMetaData(String name, AMFData value)
          Add metadata to the metadata packet.
 void setMediaWriterItem(com.wowza.wms.stream.MediaWriterItem mediaWriterItem)
          Set the media write definition
 void setParent(IMediaStream parent)
          Set the parent stream for this media write object
 void setVersionFile(boolean versionFile)
          Set to true if the old file is to be versioned
 void setWaitForVideoKeyFrame(boolean waitForVideoKeyFrame)
          Set to true if you want the recorder to skip opening frames until it hits a key frame
 void writePackets(java.util.List audioPackets, java.util.List videoPackets, java.util.List dataPackets, java.util.List audioTCs, java.util.List videoTCs, java.util.List dataTCs, java.util.List dataTypes, boolean isFirst, boolean isLast)
          Invoked each time a set of packets are ready to be presisted.
 

Method Detail

getDuration

long getDuration()
Get the recorded duration of the file in seconds

Returns:
recorded duration of the file in seconds

isVersionFile

boolean isVersionFile()
Return true if the old file is to be versioned

Returns:
true if the old file is to be versioned

isWaitForVideoKeyFrame

boolean isWaitForVideoKeyFrame()
get wait for key frame

Returns:
wait for key frame

putMetaData

void putMetaData(String name,
                 AMFData value)
Add metadata to the metadata packet. Only metadata added before the first call to writePackets will be included in the file

Parameters:
name - field name
value - metadata value

setMediaWriterItem

void setMediaWriterItem(com.wowza.wms.stream.MediaWriterItem mediaWriterItem)
Set the media write definition

Parameters:
mediaWriterItem - media write definition

setParent

void setParent(IMediaStream parent)
Set the parent stream for this media write object

Parameters:
parent -

setVersionFile

void setVersionFile(boolean versionFile)
Set to true if the old file is to be versioned

Parameters:
versionFile -

setWaitForVideoKeyFrame

void setWaitForVideoKeyFrame(boolean waitForVideoKeyFrame)
Set to true if you want the recorder to skip opening frames until it hits a key frame

Parameters:
waitForVideoKeyFrame - wait for key frame

writePackets

void writePackets(java.util.List audioPackets,
                  java.util.List videoPackets,
                  java.util.List dataPackets,
                  java.util.List audioTCs,
                  java.util.List videoTCs,
                  java.util.List dataTCs,
                  java.util.List dataTypes,
                  boolean isFirst,
                  boolean isLast)
Invoked each time a set of packets are ready to be presisted.

Parameters:
audioPackets - List of audio packets
videoPackets - List of video packets
dataPackets - List of data packets
audioTCs - List of audio timecodes
videoTCs - List of video timecodes
dataTCs - List of data timecodes
dataTypes - list of integer packets types (IVHost.CONTENTTYPE_DATA0, IVHost.CONTENTTYPE_DATA3) - if null assumed to be IVHost.CONTENTTYPE_DATA0
isFirst - true if first packet to be written
isLast - false if last packet to be written