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

      All Methods Instance Methods Abstract Methods 
      Modifier and Type Method Description
      long getDuration()
      Get the recorded duration of the file in seconds
      boolean isAudioWaitForVideoKeyFrame()
      Set to true if waiting for first video key frame to start recording audio
      boolean isDataWaitForVideoKeyFrame()  
      boolean isUseCalculatedAudioTimecodes()
      Set to true to calculate timecodes based on samples per-frame (more accurate, default is true)
      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 setAudioWaitForVideoKeyFrame​(boolean audioWaitForVideoKeyFrame)
      Set to true if waiting for first video key frame to start recording audio
      void setDataWaitForVideoKeyFrame​(boolean dataWaitForVideoKeyFrame)  
      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 setUseCalculatedAudioTimecodes​(boolean calulateTimecodes)
      Set to true to calculate timecodes based on samples per-frame (more accurate, default is true)
      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

      • 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
      • 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