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.