Wowza Community

onGrabFrame is never called

After having read this post and this article, I wrote and compiled the code below to grab a frame from a stream every x seconds. However, the GrabResult.onGrabFrame is never called. I have verified that the sourceVideo.grabFrame(new GrabResult()) is getting called every 4 secs.

Can anyone help me find why onGrabFrame isn’t called?

WSE 4.2 (build 15089); Win 10 (64-bit); Oracle JDK 1.8.0_45

PS: I know it would maybe better to use a TranscoderCreateNotifier and TranscoderActionNotifier, so that the Runnable isn’t started before the transcoder is actually processing video, but that’s not the cause of the problem - I tested that.

Disclaimer: I’m Authorized Wowza Consultant, doing Wowza configuration and development on a daily basis

package org.acme.wms.modules;
import com.wowza.wms.application.IApplicationInstance;
import com.wowza.wms.application.WMSProperties;
import com.wowza.wms.module.ModuleBase;
import com.wowza.wms.stream.IMediaStream;
import com.wowza.wms.stream.IMediaStreamActionNotify2;
import com.wowza.wms.transcoder.model.ITranscoderFrameGrabResult;
import com.wowza.wms.transcoder.model.LiveStreamTranscoder;
import com.wowza.wms.transcoder.model.TranscoderNativeVideoFrame;
import com.wowza.wms.transcoder.model.TranscoderStream;
import com.wowza.wms.transcoder.model.TranscoderStreamSourceVideo;
public class ModuleGrabFrame extends ModuleBase {
	
	@SuppressWarnings("unchecked")
	public void onStreamCreate(IMediaStream stream) {
		
		IMediaStreamActionNotify2 actionNotify = new StreamListener();
		WMSProperties props = stream.getProperties();
		
		synchronized (props) {
			props.put("streamActionNotifier", actionNotify);
		}
		
		stream.addClientListener(actionNotify);
	}
	
	public void onStreamDestroy(IMediaStream stream) {
		
		IMediaStreamActionNotify2 actionNotify = null;
		WMSProperties props = stream.getProperties();
		
		synchronized (props) {
			actionNotify = (IMediaStreamActionNotify2)stream.getProperties().get("streamActionNotifier");
		}
		
		if (actionNotify != null) {
			stream.removeClientListener(actionNotify);
		}
	}
	
	private final class StreamListener implements IMediaStreamActionNotify2 {
		
		// ...
		
		@Override
		public void onPublish(IMediaStream stream, String streamName, boolean isRecord, boolean isAppend) {
			new FrameGrabber(stream).run();
		}
		
		// ... rest of the IMediaStreamActionNotify2 implementation omitted 
	}
	
	private final class FrameGrabber implements Runnable {
		
		private IMediaStream _stream;
		private boolean _running;
		
		public FrameGrabber(IMediaStream stream) {
			_stream = stream;
		}
		@Override
		public void run() {
			while (_running) {
				LiveStreamTranscoder liveStreamTranscoder = (LiveStreamTranscoder) _stream.getLiveStreamTranscoder("transcoder");
				
				if (liveStreamTranscoder == null) {
					getLogger().warn("No LiveStreamTranscoder available (yet)");
					return;
				}
				
				TranscoderStream transcodingStream = liveStreamTranscoder.getTranscodingStream();
				
	            if (transcodingStream == null) {
	                getLogger().warn("No TranscoderStream available");
	                return;
	            }
	            
	            TranscoderStreamSourceVideo sourceVideo = transcodingStream.getSource().getVideo();
	            
	            if (sourceVideo == null) {
	            	getLogger().warn("No TranscoderStreamSourceVideo available");
	            	return;
	            }
	            
	            sourceVideo.grabFrame(new GrabResult());
	            
	            try {
					Thread.sleep(4000);
				} catch (InterruptedException e) {
					getLogger().error("Exception when trying to sleep", e);
					_running = false;
				}
			}
		}
	}
	
	public class GrabResult implements ITranscoderFrameGrabResult {
		@Override
		public void onGrabFrame(TranscoderNativeVideoFrame nativeFrame) {
			getLogger().info("Grabbing frame now ...");
		}
	}
}

The transcoder template is as follows

<?xml version="1.0" encoding="UTF-8"?>
<Root>
	<Transcode>
		<Description></Description>
		<Decode>
			<Video>
				<Implementation>default</Implementation>
				<Deinterlace>false</Deinterlace>
			</Video>
		</Decode>
		<Encodes>
			<Encode>
				<Name>Simple</Name>
				<Enable>true</Enable>
				<Description></Description>
				<StreamName>mp4:${SourceStreamName}_simple</StreamName>
				<Video>
					<Codec>H.264</Codec>
					<Implementation>QuickSync</Implementation>
					<GPUID>-1</GPUID>
					<Profile>baseline</Profile>
					<Bitrate>800000</Bitrate>
					<KeyFrameInterval>
						<FollowSource>true</FollowSource>
						<Interval>0</Interval>
					</KeyFrameInterval>
					<FrameSize>
						<FitMode>match-source</FitMode>
						<Width>0</Width>
						<Height>0</Height>
						<Crop>0,0,0,0</Crop>
						<SourceRectangle></SourceRectangle>
					</FrameSize>
				</Video>
				<Audio>
					<Codec>AAC</Codec>
					<Bitrate>64000</Bitrate>
				</Audio>
			</Encode>
		</Encodes>
	</Transcode>
</Root>

Hi Karel,

There are a few issues with your workflow:

		@Override
		public void onPublish(IMediaStream stream, String streamName, boolean isRecord, boolean isAppend) {
			new FrameGrabber(stream).run();
		}

I’m assuming you are wanting to spawn a new thread for your Runnable. All this is doing is creating a new FrameGrabber object and calling its run method in the current thread. If the encoder is waiting for a response from the server for the publish request (which it should be doing), before sending any stream data, it won’t receive this until your run method returns and the onPublish method can return.

You need to create a separate thread.

		@Override
		public void onPublish(IMediaStream stream, String streamName, boolean isRecord, boolean isAppend) {
			(new Thread(new FrameGrabber(stream))).start();
		}

The transcoder startup is triggered when the first stream data is received from the encoder. Because you are doing this check in the same thread that onPublish is running, the encoder shouldn’t have sent any steam data at this point. When you do the check, no data has arrived to trigger the transcoder startup and then you are returning from your run method instead of looping.

I haven’t tested it but the following should work better.

		@Override
		public void run() {
			long sleepTime = 100;
			while (_running) {
				try {
					Thread.sleep(sleepTime);
				} catch (InterruptedException e) {
					getLogger().error("Exception when trying to sleep", e);
					_running = false;
					break;
				}
				LiveStreamTranscoder liveStreamTranscoder = (LiveStreamTranscoder) _stream.getLiveStreamTranscoder("transcoder");
				
				if (liveStreamTranscoder == null) {
					getLogger().warn("No LiveStreamTranscoder available (yet)");
					continue;
				}
				
				TranscoderStream transcodingStream = liveStreamTranscoder.getTranscodingStream();
				
				if (transcodingStream == null) {
					getLogger().warn("No TranscoderStream available");
					continue;
				}
	            
				TranscoderStreamSourceVideo sourceVideo = transcodingStream.getSource().getVideo();
	            
				if (sourceVideo == null) {
					getLogger().warn("No TranscoderStreamSourceVideo available");
					continue;
				}
	            		
				getLogger().info("ready to grab a frame");
				sourceVideo.grabFrame(new GrabResult());
				sleepTime = 4000;
			}
		}

Using the proper event listeners is the best way to go and then you will be sure that everything is ready when you want to start grabbing frames.

Roger.