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>