This article describes how to use the Wowza Streaming Engine™ Java API to plug into the live stream transcoder pipeline to control when keyframes are inserted into a transcoded stream. Through this API, you can both force additional keyframes and stop keyframes from being inserted.
Note: Wowza Streaming Engine media server software version 4.5.0.03 or later is required.
The following is module example code that shows this API in action:
import java.util.*;
import com.wowza.util.*;
import com.wowza.wms.application.*;
import com.wowza.wms.module.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.stream.livetranscoder.*;
import com.wowza.wms.transcoder.model.*;
public class ModuleTranscoderVideoKeyFrameControl extends ModuleBase
{
private static final Class<ModuleTranscoderVideoKeyFrameControl> CLASS = ModuleTranscoderVideoKeyFrameControl.class;
private static final String CLASSNAME = "ModuleTranscoderVideoKeyFrameControl";
private IApplicationInstance appInstance = null;
class MyTranscoderCreateNotifier extends LiveStreamTranscoderNotifyBase
{
private IApplicationInstance appInstance = null;
public MyTranscoderCreateNotifier(IApplicationInstance appInstance)
{
this.appInstance = appInstance;
}
public void onLiveStreamTranscoderCreate(ILiveStreamTranscoder liveStreamTranscoder, IMediaStream stream)
{
getLogger().info(CLASSNAME+"#MyTranscoderCreateNotifier.onLiveStreamTranscoderCreate["+appInstance.getContextStr()+"]: "+stream.getName());
// Add a live stream transcoder action listener so we can plug into the transcoding pipeline
((LiveStreamTranscoder)liveStreamTranscoder).addActionListener(new MyTranscoderActionNotifier(appInstance, liveStreamTranscoder, stream));
}
}
class MyTranscoderActionNotifier extends LiveStreamTranscoderActionNotifyBase
{
private IApplicationInstance appInstance = null;
private ILiveStreamTranscoder liveStreamTranscoder = null;
private IMediaStream stream = null;
public MyTranscoderActionNotifier(IApplicationInstance appInstance, ILiveStreamTranscoder liveStreamTranscoder, IMediaStream stream)
{
this.appInstance = appInstance;
this.liveStreamTranscoder = liveStreamTranscoder;
this.stream = stream;
}
// Called when the transcoding session is setup and ready to start
public void onInitStop(LiveStreamTranscoder liveStreamTranscoder)
{
getLogger().info(CLASSNAME+"#MyTranscoderActionNotifier.onInitStop["+appInstance.getContextStr()+"/"+liveStreamTranscoder.getStreamName()+"]");
try
{
while(true)
{
TranscoderStream transcoderStream = liveStreamTranscoder.getTranscodingStream();
if (transcoderStream == null)
break;
TranscoderSession transcoderSession = liveStreamTranscoder.getTranscodingSession();
TranscoderSessionVideo transcoderVideoSession = transcoderSession.getSessionVideo();
List<TranscoderSessionVideoEncode> videoEncodes = transcoderVideoSession.getEncodes();
for(TranscoderSessionVideoEncode videoEncode : videoEncodes)
{
getLogger().info(CLASSNAME+"#MyTranscoderActionNotifier.onInitStop["+appInstance.getContextStr()+"/"+liveStreamTranscoder.getStreamName()+"] Add frame listener: rendition:"+videoEncode.getName());
// add a transcoder video frame listener
videoEncode.addFrameListener(new MyTranscoderVideoEncoderNotifier(appInstance, liveStreamTranscoder, stream));
}
break;
}
}
catch(Exception e)
{
getLogger().error(CLASSNAME+"#MyTranscoderActionNotifier.onInitStop["+appInstance.getContextStr()+"/"+liveStreamTranscoder.getStreamName()+"] ", e);
}
}
}
class MyTranscoderVideoEncoderNotifier extends TranscoderVideoEncoderNotifyBase2
{
private IApplicationInstance appInstance = null;
private LiveStreamTranscoder liveStreamTranscoder = null;
private IMediaStream stream = null;
private long extraKeyFrames = 0;
public MyTranscoderVideoEncoderNotifier(IApplicationInstance appInstance, LiveStreamTranscoder liveStreamTranscoder, IMediaStream stream)
{
this.appInstance = appInstance;
this.liveStreamTranscoder = liveStreamTranscoder;
this.stream = stream;
}
public void onBeforeEncodeFrame(TranscoderSessionVideoEncode sessionVideoEncode, TranscoderStreamDestinationVideo destinationVideo, TranscoderVideoEncodeFrameContext frameContext)
{
TranscoderDecodedFrame decodedFrame = frameContext.getFrameHolder().getEncoderNextFrame();
if (decodedFrame != null)
{
int frameType = FLVUtils.FLV_PFRAME;
// every 10 frames we are going to force a keyframe
if ((extraKeyFrames%10) == 0)
{
frameType = FLVUtils.FLV_KFRAME;
getLogger().info(CLASSNAME+"#MyTranscoderVideoEncoderNotifier.onBeforeEncodeFrame["+appInstance.getContextStr()+"/"+liveStreamTranscoder.getStreamName()+"("+destinationVideo.getDestination().getName()+")"+"] Insert key frame: frameType[source]:"+FLVUtils.frameTypeToString((int)decodedFrame.getFrameType())+" timecode:"+decodedFrame.getTimecode());
}
frameContext.setFrameType(frameType);
extraKeyFrames++;
}
}
public void onAfterEncodeFrame(TranscoderSessionVideoEncode sessionVideoEncode, TranscoderStreamDestinationVideo destinationVideo, TranscoderVideoEncodeFrameContext frameContext)
{
}
}
public void onAppStart(IApplicationInstance appInstance)
{
getLogger().info(CLASSNAME+".onAppStart["+appInstance.getContextStr()+"]");
this.appInstance = appInstance;
// Add a live stream transcoder listener (called each time a transcoder session is started)
appInstance.addLiveStreamTranscoderListener(new MyTranscoderCreateNotifier(appInstance));
}
}
Where:
- onAppStart - Inserts a live stream transcoder listener that listens for new live stream transcoder sessions.
- MyTranscoderCreateNotifier.onLiveStreamTranscoderCreate - Is called when a transcoder session is created and inserts an action listener to monitor for transcoder session events.
- MyTranscoderActionNotifier.onInitStop - Is called when the session setup is completed and inserts a video frame listener for each encoding rendition that's being generated.
- MyTranscoderVideoEncoderNotifier.onBeforeEncodeFrame - Is called before each frame is sent to the encoder, which provides a chance to control the keyframe insertion.
If <KeyFrameInterval>/<FollowSource> is set to false, then the frameContext.getFrameType() is set to either FLVUtils.FLV_PFRAME (progressive) or FLVUtils.FLV_KFRAME (key) based on the KeyFrameInterval/Interval (keyframe interval) setting.
In either case, you can force the frame to be either a key or a progressive frame by calling frameContext.setFrameType(frameType) with the appropriate frame type.
To debug the keyframe insertion, add the following custom property:
- In Wowza Streaming Engine Manager, click the Applications tab, and then click the name of your live application in the contents panel.
- On the live application page Properties tab, click Custom in the Quick Links bar.
Note: Access to the Properties tab is limited to administrators with advanced permissions. For more information, see Manage credentials.
- In the Custom area, click Edit.
- Click Add Custom Property, specify the following settings in the Add Custom Property dialog box, and then click Add:
- Path - Select /Root/Application/Streams.
- Name - Enter debugKeyFrameTimecodes.
- Type - Select Boolean.
- Value - Enter true.
- Path - Select /Root/Application/Streams.
- Click Save, and then restart the live application to apply the changes.




