Package 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
All Methods Instance Methods Abstract Methods Modifier and Type Method Description long
getDuration()
Get the recorded duration of the file in secondsboolean
isAudioWaitForVideoKeyFrame()
Set to true if waiting for first video key frame to start recording audioboolean
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 versionedboolean
isWaitForVideoKeyFrame()
get wait for key framevoid
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 audiovoid
setDataWaitForVideoKeyFrame(boolean dataWaitForVideoKeyFrame)
void
setMediaWriterItem(com.wowza.wms.stream.MediaWriterItem mediaWriterItem)
Set the media write definitionvoid
setParent(IMediaStream parent)
Set the parent stream for this media write objectvoid
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 versionedvoid
setWaitForVideoKeyFrame(boolean waitForVideoKeyFrame)
Set to true if you want the recorder to skip opening frames until it hits a key framevoid
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 packetsvideoPackets
- List of video packetsdataPackets
- List of data packetsaudioTCs
- List of audio timecodesvideoTCs
- List of video timecodesdataTCs
- List of data timecodesdataTypes
- list of integer packets types (IVHost.CONTENTTYPE_DATA0, IVHost.CONTENTTYPE_DATA3) - if null assumed to be IVHost.CONTENTTYPE_DATA0isFirst
- true if first packet to be writtenisLast
- 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 namevalue
- 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
-
-