Custom module to create single frame snapshots of live and VOD stream

Below is a sample Wowza Pro custom module to create a single key frame .flv file (snapshot) of either a live or video on demand stream. To use this code, download and install the Wowza Pro IDE and follow the instructions in the included User’s Guide.

package com.wowza.wms.plugin.test.module;

import java.io.*;

import com.wowza.util.*;
import com.wowza.wms.module.*;
import com.wowza.wms.amf.*;
import com.wowza.wms.application.*;
import com.wowza.wms.client.IClient;
import com.wowza.wms.stream.*;
import com.wowza.wms.vhost.*;
import com.wowza.wms.request.*;

public class CreateSnapshot extends ModuleBase
{
	Object lock = new Object();
	
	public void createSnapshotLive(IClient client, RequestFunction function, AMFDataList params) 
	{
		String streamName = params.getString(PARAM1);
		
		String fileName = "";
		IApplicationInstance appInstance = client.getAppInstance();
		MediaStreamMap streams = appInstance.getStreams();
		IMediaStream stream = streams.getStream(streamName);
		if (stream != null)
		{
			AMFPacket packet = stream.getLastKeyFrame();
			if (packet != null)
			{
				fileName = streamName + "_" + packet.getAbsTimecode() + ".flv";
				File newFile = stream.getStreamFileForWrite(streamName, null, null);
				
				String filePath = newFile.getPath().substring(0, newFile.getPath().length()-4) + "_" + packet.getAbsTimecode() + ".flv";
								
				try
				{
					synchronized(lock)
					{
						BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filePath), false));
						FLVUtils.writeHeader(out, 0, null);
						
						AMFPacket codecConfig = stream.getVideoCodecConfigPacket(packet.getAbsTimecode());
						if (codecConfig != null)
							FLVUtils.writeChunk(out, codecConfig.getDataBuffer(), codecConfig.getSize(), 0, (byte)codecConfig.getType());
						
						FLVUtils.writeChunk(out, packet.getDataBuffer(), packet.getSize(), 0, (byte)packet.getType());
						out.close();
					}
					
					getLogger().info("snapshot created: "+filePath);
				}
				catch (Exception e)
				{
					getLogger().error("createSnapshot: "+e.toString());
				}
			}
		}
		
		sendResult(client, params, fileName);
	}
	
	public void createSnapshotVOD(IClient client, RequestFunction function, AMFDataList params) 
	{
		String streamName = params.getString(PARAM1);
		int timecode = params.getInt(PARAM2);
		
		String fileName = "";
		IApplicationInstance appInstance = client.getAppInstance();
		
		String flvFilePath = appInstance.getStreamStoragePath() + "/" + streamName + ".flv";
		File flvFile = new File(flvFilePath);
		
		if (flvFile.exists())
		{
			AMFPacket lastVideoKeyFrame = null;
			try
			{
				BufferedInputStream is = new BufferedInputStream(new FileInputStream(flvFile));
				FLVUtils.readHeader(is);
				AMFPacket amfPacket;
				while ((amfPacket = FLVUtils.readChunk(is)) != null)
				{
					if (lastVideoKeyFrame != null && amfPacket.getTimecode() > timecode)
						break;
					if (amfPacket.getType() != IVHost.CONTENTTYPE_VIDEO)
						continue;
					if (FLVUtils.isVideoKeyFrame(amfPacket)) //if (FLVUtils.getFrameType(amfPacket.getFirstByte()) == FLVUtils.FLV_KFRAME)
						lastVideoKeyFrame = amfPacket;
				}
				is.close();
			}
			catch (Exception e)
			{
				getLogger().error("Error: createSnapshotVOD: reading flv: "+e.toString());
			}
			
			if (lastVideoKeyFrame != null)
			{
				try
				{
					fileName = streamName + "_" + timecode;
					String filePath = appInstance.getStreamStoragePath() + "/" + fileName + ".flv";
					BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filePath), false));
					FLVUtils.writeHeader(out, 0, null);
					FLVUtils.writeChunk(out, lastVideoKeyFrame.getDataBuffer(), lastVideoKeyFrame.getSize(), 0, (byte)lastVideoKeyFrame.getType());
					out.close();
					
					getLogger().info("snapshot created: "+filePath);
				}
				catch (Exception e)
				{
					getLogger().error("Error: createSnapshotVOD: writing flv: "+e.toString());
				}
			}
		}
		
		sendResult(client, params, fileName);
	}
}

You will need to add this module definition to the Application.xml file of any applications you wish to have use this module:

<Module>
	<Name>snapshot</Name>
	<Description>Create Snapshot</Description>
	<Class>com.wowza.wms.plugin.test.module.CreateSnapshot</Class>
</Module>

Here is the code to call it from the client side:

// live
var resultObj:Object = new Object();
resultObj.onResult = function(fileName:String)
{
	trace("result: "+fileName);
}
nc.call("createSnapshotLive", resultObj, "test");

// video on demand
var resultObj:Object = new Object();
resultObj.onResult = function(fileName:String)
{
	trace("result: "+fileName);
}
nc.call("createSnapshotVOD", resultObj, "merry_melodies_falling_hare", 30000); // 30 seconds into the video

A compiled version of this module is included in a collection of utility modules that you can download here:

http://community.wowza.com/t/-/237

FFMPEG can be used to turn the .flv thumbnail into a PNG thumbnail. Here is an FFMPEG command line that works for me.

ffmpeg -i thumbnail.flv -vcodec png -vframes 1 -an -f rawvideo -s 320x240 thumbnail.png

Note: To learn how to compile and link this code into a customer server module, download and install the Wowza IDE and follow the instructions in the included User’s Guide.

Charlie

At this time you cannot take snapshots of H.264 streams. The file it generates will not be recoginzed by any of the usual tools.

Charlie

It is not super simple. Flash supports basically 3 different video formats; Sorenson Spark (variant of H.263), On2 VP6 and H.264. Decoding these formats is not super simple. It requires a bunch of math transformations and pre-defined matrixes. You can look at source code from project like ffmpeg, x264 to see how it is done. It is pretty difficult to extract out just the subset needed to decode a key frame.

Charlie

I just tried it with H.264 and it worked for me. I think your ffmpeg command line is wrong. I used:

ffmpeg -i myStream_45135.flv -vcodec png -vframes 1 -an -f rawvideo -s 320x240 myStream_45135.png

Worked great!!!

Charlie

I downloaded the latest version for Windows from here:

http://ffmpeg.arrozcru.org/builds/

Charlie

Excellent, Charlie

Or take a look at this post:

Web service API:

http://community.wowza.com/t/-/52

Charlie

Thanks, Charlie. Would you consider modifying this to create a jpg file from one or more keyframes? I realize that another tool (ffmpeg?) may need to be installed, and I know there’s some code on another thread that addresses this, but I still don’t quite see how to do this.

We’re also have a hard time installing the second custom module using the Eclipse IDE. The first one (Hello World) works, but the second one didn’t even trying on two different machines and OS’s. We’re java novices, but experienced coders.

Thx,

Tac

I search a way to extract a snapshot not at the beginning or a fixed time but in the middle of a flv. So i made a little try :

//Find duration of the file
try{
BufferedInputStream is = new BufferedInputStream(new FileInputStream(f.getAbsoluteFile()));
   FLVUtils.readHeader(is);
   int a = FLVUtils.getFrameType(FLVUtils.FLV_CHUNKHEADER_ITIMECODE);
   is.close();
   System.out.println("duration : "+a);
}catch(Exception e){
   e.printStackTrace();
}

//extract the snapshot
String lCommand = "ffmpeg.exe -i \""+flvPath+"\" -vcodec mjpeg -vframes 1 -an -f rawvideo -y \""+jpgPath+"\"";
try{
   Runtime.getRuntime().exec(lCommand);
}catch(Exception e){
   System.err.println(e.toString());
}

The method to extract snapshot with ffmpeg works fine, I just need to put ‘ss’ argument to specify which image extract. But I don’t arrive to get the duration of a flv file.

With stream object, it’s possible to use stream.getMaxTimecode() but i don’t find any equivalent with File, FLVUtils, AMFPacket & co.

P.S. In FLVUtils, writeDuration(file, duration) allow to custom this value but there is no getter to easy get this value.

Can this method be used to convert flv to various other video formats with ffmpeg using a custom module? Can it be called as soon as an flv is published?

Greetings to Everyone!

I am trying to use the snapshot code posted in this forum, for capturing a snapshot from a web camera. I have made the appropriate changes to create server side module as well as changes to Application.xml and the flash side code.

I am invoking snapshot live when a button is clicked. However there is no desired action on clicking the button. No file is created with the snapshot. It seems as though nothing happened.

It would be a great help if someone could comment on this towards resolution of the problem.

Thanks & Regards,

Kishore

What happens when you try the second module? Are you getting error messages? The second module is truely a second module. So make sure you add an additional reference for it in Application.xml. So you need both of these module definitions to make it work:

<Module>
	<Name>MyFirstModule</Name>
	<Description>MyCompany MyFirstModule</Description>
	<Class>com.mycompany.wms.module.MyFirstModule</Class>
</Module>
<Module>
	<Name>ModuleServerSide</Name>
	<Description>MyCompany ModuleServerSide</Description>
	<Class>com.mycompany.wms.module.ModuleServerSide</Class>
</Module>

BTW, I will think about the ffmpeg thing but it seems beyond the scope of this site.

Charlie

I don’t have a pure Java way to do this. In the past I have used ffmeg to generate thumbnails from a .flv file. It seems like the more recent versions of ffmpeg have a different command line interface then I am used to. So I am not sure exactly how to do it.

Does anyone else have experience with using ffmpeg to generate JPG or PNG from .flv files?

Charlie

FLVUtils.getLastTC(File file)

Charlie

If you are trying to do this with H.264 video it will not work. We do not have a similar system for H.264.

Charlie

I modified the Java code in the first post. See if that does the trick. I did not have time to test it.

Charlie

Your code does not included the modification for H.264. It is this section:

BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filePath), false));
FLVUtils.writeHeader(out, 0, null);
AMFPacket codecConfig = stream.getVideoCodecConfigPacket(packet.getAbsTimecode());
if (codecConfig != null)
	FLVUtils.writeChunk(out, codecConfig.getDataBuffer(), codecConfig.getSize(), 0, (byte)codecConfig.getType());
FLVUtils.writeChunk(out, packet.getDataBuffer(), packet.getSize(), 0, (byte)packet.getType());
out.close();

It is where it is dealing with the codecConfig packet that is needed for H.264.

Charlie

Now I am lost. What exactly are you trying to do? If you are just using the code in this first post of this thread just update your code to the most recent version of this code and it should support H.264. If you are doing something else please describe in detail.

Charlie

If the file is already saved you should be able to grab a key frame without re-creating your own mini .flv file by using ffmpeg on the longer video. Just be sure you grab the most recent ffmpeg version you can find. The version I use is here: http://ffmpeg.arrozcru.org/builds. It will support H.264 in FLV. You should be able to use a command line similar to the one in the first post of this thread.

Charlie

I don’t understand the question. FLVUtils does not offer this feature.

Charlie