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 Details

    • writePackets

      void writePackets(List audioPackets, List videoPackets, List dataPackets, List audioTCs, List videoTCs, List dataTCs, 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
    • 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 -
    • isVersionFile

      boolean isVersionFile()
      Return true if the old file is to be versioned
      Returns:
      true if the old file is to be versioned
    • setVersionFile

      void setVersionFile(boolean versionFile)
      Set to true if the old file is to be versioned
      Parameters:
      versionFile -
    • isWaitForVideoKeyFrame

      boolean isWaitForVideoKeyFrame()
      get wait for key frame
      Returns:
      wait for key frame
    • 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
    • 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
    • getDuration

      long getDuration()
      Get the recorded duration of the file in seconds
      Returns:
      recorded duration of the file in seconds
    • isAudioWaitForVideoKeyFrame

      boolean isAudioWaitForVideoKeyFrame()
      Set to true if waiting for first video key frame to start recording audio
      Returns:
      true if waiting for first video key frame to start recording audio
    • setAudioWaitForVideoKeyFrame

      void setAudioWaitForVideoKeyFrame(boolean audioWaitForVideoKeyFrame)
      Set to true if waiting for first video key frame to start recording audio
      Parameters:
      audioWaitForVideoKeyFrame - true if waiting for first video key frame to start recording audio
    • isDataWaitForVideoKeyFrame

      boolean isDataWaitForVideoKeyFrame()
    • setDataWaitForVideoKeyFrame

      void setDataWaitForVideoKeyFrame(boolean dataWaitForVideoKeyFrame)
    • isUseCalculatedAudioTimecodes

      boolean isUseCalculatedAudioTimecodes()
      Set to true to calculate timecodes based on samples per-frame (more accurate, default is true)
      Returns:
      true to calculate timecodes based on samples per-frame
    • setUseCalculatedAudioTimecodes

      void setUseCalculatedAudioTimecodes(boolean calulateTimecodes)
      Set to true to calculate timecodes based on samples per-frame (more accurate, default is true)
      Parameters:
      calulateTimecodes - true to calculate timecodes based on samples per-frame