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.
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>
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>
| 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.
|
long getDuration()
boolean isAudioWaitForVideoKeyFrame()
boolean isDataWaitForVideoKeyFrame()
boolean isUseCalculatedAudioTimecodes()
boolean isVersionFile()
boolean isWaitForVideoKeyFrame()
void putMetaData(String name,
AMFData value)
name - field namevalue - metadata valuevoid setAudioWaitForVideoKeyFrame(boolean audioWaitForVideoKeyFrame)
audioWaitForVideoKeyFrame - true if waiting for first video key frame to start recording audiovoid setDataWaitForVideoKeyFrame(boolean dataWaitForVideoKeyFrame)
void setMediaWriterItem(com.wowza.wms.stream.MediaWriterItem mediaWriterItem)
mediaWriterItem - media write definitionvoid setParent(IMediaStream parent)
parent - void setUseCalculatedAudioTimecodes(boolean calulateTimecodes)
calulateTimecodes - true to calculate timecodes based on samples per-framevoid setVersionFile(boolean versionFile)
versionFile - void setWaitForVideoKeyFrame(boolean waitForVideoKeyFrame)
waitForVideoKeyFrame - wait for 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)
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