diff --git a/README.md b/README.md
index ead0b57..3bae18a 100644
--- a/README.md
+++ b/README.md
@@ -105,12 +105,17 @@ with the AWS SDK for the Kinesis Video. This example provides examples for
## Release Notes
+### Release 1.0.6 (Sep 2018)
+* Introduce handling for empty fragment metadata
+* Added a new SimpleFrame Visitor to handle video with no tags
+* Refactored H264FrameDecoder, so that base class method can be reused by child class
+
### Release 1.0.5 (May 2018)
-* Introduce `GetMediaResponseStreamConsumer` as an abstract class used to consume the output of a GetMedia* call
+* Introduce `GetMediaResponseStreamConsumer` as an abstract class used to consume the output of a GetMedia* call
to Kinesis Video in a streaming fashion. Child classes will use visitors to implement different consumers.
* The `MergedOutputPiper` extends `GetMediaResponseStreamConsumer` to merge consecutive mkv streams in the output of GetMedia
- and pipes the merged stream to the stdin of a child process.
-* Add the capability and example to pipe the output of GetMedia calls to GStreamer using `MergedOutputPiper`.
+ and pipes the merged stream to the stdin of a child process.
+* Add the capability and example to pipe the output of GetMedia calls to GStreamer using `MergedOutputPiper`.
### Release 1.0.4 (April 2018)
* Add example for KinesisVideo Streams integration with Rekognition and draw Bounding Boxes for every sampled frame.
diff --git a/pom.xml b/pom.xml
index ebe0ea0..9485a95 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
amazon-kinesis-video-streams-parser-library
jar
Amazon Kinesis Video Streams Parser Library
- 1.0.5-SNAPSHOT
+ 1.0.6
The Amazon Kinesis Video Streams Parser Library for Java enables Java developers to parse the streams
returned by GetMedia calls to Amazon Kinesis Video.
@@ -107,6 +107,13 @@
2.8.1
test
+
+ org.powermock
+ powermock-mockito-release-full
+ 1.6.3
+ pom
+ test
+
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadata.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadata.java
index 79c7d98..19a5823 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadata.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadata.java
@@ -94,11 +94,12 @@ static FragmentMetadata createFromtagNametoValueMap(final Map ta
return new FragmentMetadata(getValueForTag(tagNameToTagValueMap, FRAGMENT_NUMBER_KEY),
Double.parseDouble(getValueForTag(tagNameToTagValueMap, SERVER_SIDE_TIMESTAMP_KEY)),
Double.parseDouble(getValueForTag(tagNameToTagValueMap, PRODCUER_SIDE_TIMESTAMP_KEY)));
- } else {
+ } else if (tagNameToTagValueMap.containsKey(FRAGMENT_NUMBER_KEY)) {
return new FragmentMetadata(getValueForTag(tagNameToTagValueMap, FRAGMENT_NUMBER_KEY),
Long.parseLong(getValueForTag(tagNameToTagValueMap, ERROR_ID_KEY)),
getValueForTag(tagNameToTagValueMap, ERROR_CODE_KEY));
}
+ return null;
}
private static String getValueForTag(Map tagNameToTagValueMap, String tagName) {
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java
index 00f060e..49e409a 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java
@@ -207,7 +207,7 @@ private void setMillisBehindLatestAndContinuationToken() {
private void collectPreClusterInfo() {
final Map tagNameToTagValueMap = getTagNameToValueMap();
- currentFragmentMetadata = Optional.of(FragmentMetadata.createFromtagNametoValueMap(tagNameToTagValueMap));
+ currentFragmentMetadata = Optional.ofNullable(FragmentMetadata.createFromtagNametoValueMap(tagNameToTagValueMap));
final Map> trackEntryElementNumberToMkvElement = getTrackEntryMap();
trackEntryElementNumberToMkvElement.values().stream().forEach(this::createTrackMetadata);
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java
index f47dcd0..52ba05c 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java
@@ -74,4 +74,4 @@ public void visit(com.amazonaws.kinesisvideo.parser.mkv.MkvDataElement dataEleme
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameDecoder.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameDecoder.java
new file mode 100644
index 0000000..7e04e9f
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameDecoder.java
@@ -0,0 +1,90 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License").
+You may not use this file except in compliance with the License.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import com.amazonaws.kinesisvideo.parser.mkv.Frame;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.jcodec.codecs.h264.H264Decoder;
+import org.jcodec.codecs.h264.mp4.AvcCBox;
+import org.jcodec.common.model.ColorSpace;
+import org.jcodec.common.model.Picture;
+import org.jcodec.scale.AWTUtil;
+import org.jcodec.scale.Transform;
+import org.jcodec.scale.Yuv420jToRgb;
+
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Optional;
+
+import static org.jcodec.codecs.h264.H264Utils.splitMOVPacket;
+
+@Slf4j
+public class H264FrameDecoder implements FrameVisitor.FrameProcessor {
+
+ private final H264Decoder decoder = new H264Decoder();
+ private final Transform transform = new Yuv420jToRgb();
+
+ @Getter
+ private int frameCount;
+
+ private byte[] codecPrivateData;
+
+ @Override
+ public void process(Frame frame, MkvTrackMetadata trackMetadata, Optional fragmentMetadata) {
+ decodeH264Frame(frame, trackMetadata);
+ }
+
+ protected BufferedImage decodeH264Frame(Frame frame, MkvTrackMetadata trackMetadata) {
+ ByteBuffer frameBuffer = frame.getFrameData();
+ int pixelWidth = trackMetadata.getPixelWidth().get().intValue();
+ int pixelHeight = trackMetadata.getPixelHeight().get().intValue();
+ codecPrivateData = trackMetadata.getCodecPrivateData().array();
+ log.debug("Decoding frames ... ");
+ // Read the bytes that appear to comprise the header
+ // See: https://www.matroska.org/technical/specs/index.html#simpleblock_structure
+
+ Picture rgb = Picture.create(pixelWidth, pixelHeight, ColorSpace.RGB);
+ BufferedImage bufferedImage = new BufferedImage(pixelWidth, pixelHeight, BufferedImage.TYPE_3BYTE_BGR);
+ AvcCBox avcC = AvcCBox.parseAvcCBox(ByteBuffer.wrap(codecPrivateData));
+
+ decoder.addSps(avcC.getSpsList());
+ decoder.addPps(avcC.getPpsList());
+
+ Picture buf = Picture.create(pixelWidth, pixelHeight, ColorSpace.YUV420J);
+ List byteBuffers = splitMOVPacket(frameBuffer, avcC);
+ Picture pic = decoder.decodeFrameFromNals(byteBuffers, buf.getData());
+
+ if (pic != null) {
+ // Work around for color issues in JCodec
+ // https://github.com/jcodec/jcodec/issues/59
+ // https://github.com/jcodec/jcodec/issues/192
+ byte[][] dataTemp = new byte[3][pic.getData().length];
+ dataTemp[0] = pic.getPlaneData(0);
+ dataTemp[1] = pic.getPlaneData(2);
+ dataTemp[2] = pic.getPlaneData(1);
+
+ Picture tmpBuf = Picture.createPicture(pixelWidth, pixelHeight, dataTemp, ColorSpace.YUV420J);
+ transform.transform(tmpBuf, rgb);
+ AWTUtil.toBufferedImage(rgb, bufferedImage);
+ frameCount++;
+ }
+ return bufferedImage;
+ }
+
+ public ByteBuffer getCodecPrivateData() {
+ return ByteBuffer.wrap(codecPrivateData);
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java
index c8cec85..819da70 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java
@@ -33,18 +33,12 @@
import static org.jcodec.codecs.h264.H264Utils.splitMOVPacket;
@Slf4j
-public class H264FrameRenderer implements FrameVisitor.FrameProcessor {
+public class H264FrameRenderer extends H264FrameDecoder {
private final KinesisVideoFrameViewer kinesisVideoFrameViewer;
- private final H264Decoder decoder = new H264Decoder();
- private final Transform transform = new Yuv420jToRgb();
-
- @Getter
- private int frameCount;
-
- private byte[] codecPrivateData;
protected H264FrameRenderer(final KinesisVideoFrameViewer kinesisVideoFrameViewer) {
+ super();
this.kinesisVideoFrameViewer = kinesisVideoFrameViewer;
this.kinesisVideoFrameViewer.setVisible(true);
}
@@ -58,46 +52,4 @@ public void process(Frame frame, MkvTrackMetadata trackMetadata, Optional byteBuffers = splitMOVPacket(frameBuffer, avcC);
- Picture pic = decoder.decodeFrameFromNals(byteBuffers, buf.getData());
-
- if (pic != null) {
- // Work around for color issues in JCodec
- // https://github.com/jcodec/jcodec/issues/59
- // https://github.com/jcodec/jcodec/issues/192
- byte[][] dataTemp = new byte[3][pic.getData().length];
- dataTemp[0] = pic.getPlaneData(0);
- dataTemp[1] = pic.getPlaneData(2);
- dataTemp[2] = pic.getPlaneData(1);
-
- Picture tmpBuf = Picture.createPicture(pixelWidth, pixelHeight, dataTemp, ColorSpace.YUV420J);
- transform.transform(tmpBuf, rgb);
- AWTUtil.toBufferedImage(rgb, bufferedImage);
- frameCount++;
- }
- return bufferedImage;
- }
-
- public ByteBuffer getCodecPrivateData() {
- return ByteBuffer.wrap(codecPrivateData);
- }
-
}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/SimpleFrameVisitor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/SimpleFrameVisitor.java
new file mode 100644
index 0000000..90c96f0
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/SimpleFrameVisitor.java
@@ -0,0 +1,91 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License").
+You may not use this file except in compliance with the License.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import com.amazonaws.kinesisvideo.parser.ebml.MkvTypeInfos;
+import com.amazonaws.kinesisvideo.parser.mkv.Frame;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitException;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitor;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvValue;
+import com.amazonaws.kinesisvideo.parser.mkv.visitors.CompositeMkvElementVisitor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.lang3.Validate;
+
+import java.math.BigInteger;
+
+/**
+ * Fragment metdata tags will not be present as there is no tag when the data (mkv/webm) is not
+ * retrieved from Kinesis video.
+ * As a result, user cannot get the timestamp from fragment metadata tags.
+ * This class is used to provide a FrameVisitor to process frame as well as time elements for timestamp.
+ **/
+
+@Slf4j
+public class SimpleFrameVisitor extends CompositeMkvElementVisitor {
+ private final FrameVisitorInternal frameVisitorInternal;
+ private final FrameProcessor frameProcessor;
+
+ private SimpleFrameVisitor(FrameProcessor frameProcessor) {
+ this.frameProcessor = frameProcessor;
+ frameVisitorInternal = new FrameVisitorInternal();
+ childVisitors.add(frameVisitorInternal);
+
+ }
+ public static SimpleFrameVisitor create(FrameProcessor frameProcessor) {
+ return new SimpleFrameVisitor(frameProcessor);
+ }
+ public interface FrameProcessor {
+ default void process(Frame frame, long clusterTimeCode, long timeCodeScale) {
+ throw new NotImplementedException("Default FrameVisitor with No Fragement MetaData");
+ }
+ }
+
+ private class FrameVisitorInternal extends MkvElementVisitor {
+ private long clusterTimeCode = -1;
+ private long timeCodeScale = -1;
+ @Override
+ public void visit(com.amazonaws.kinesisvideo.parser.mkv.MkvStartMasterElement startMasterElement)
+ throws com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitException {
+ }
+
+ @Override
+ public void visit(com.amazonaws.kinesisvideo.parser.mkv.MkvEndMasterElement endMasterElement)
+ throws com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitException {
+ }
+
+ @Override
+ public void visit(com.amazonaws.kinesisvideo.parser.mkv.MkvDataElement dataElement)
+ throws com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitException {
+
+ if (MkvTypeInfos.TIMECODE.equals(dataElement.getElementMetaData().getTypeInfo())) {
+ clusterTimeCode = ((BigInteger) dataElement.getValueCopy().getVal()).longValue();
+ }
+
+ if (MkvTypeInfos.TIMECODESCALE.equals(dataElement.getElementMetaData().getTypeInfo())) {
+ timeCodeScale = ((BigInteger) dataElement.getValueCopy().getVal()).longValue();
+ }
+
+ if (MkvTypeInfos.SIMPLEBLOCK.equals(dataElement.getElementMetaData().getTypeInfo())) {
+ if (clusterTimeCode == -1 || timeCodeScale == -1) {
+ throw new MkvElementVisitException("No timeCodeScale or timeCode found", new RuntimeException());
+ }
+ final MkvValue frame = dataElement.getValueCopy();
+ Validate.notNull(frame);
+ frameProcessor.process(frame.getVal(), clusterTimeCode, timeCodeScale);
+ }
+ }
+
+ }
+}
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java
index ac05fe9..3c14e4e 100644
--- a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java
@@ -51,7 +51,7 @@ public void basicTest() throws IOException, MkvElementVisitException {
continuationTokens.add("91343852333181432397633822764885441725874549018");
continuationTokens.add("91343852333181432402585582922026963247510532162");
- FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
+ final FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
StreamingMkvReader mkvStreamReader =
StreamingMkvReader.createDefault(new InputStreamParserByteSource(in));
int segmentCount = 0;
@@ -102,7 +102,7 @@ private void assertTrackAndFragmentInfo(FragmentMetadataVisitor fragmentVisitor,
@Test
public void withOutputSegmentMergerTest() throws IOException, MkvElementVisitException {
- FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
+ final FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -133,7 +133,7 @@ public void withOutputSegmentMergerTest() throws IOException, MkvElementVisitExc
@Test
public void testFragmentNumbers_NoClusterData() throws IOException, MkvElementVisitException {
- FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
+ final FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
String testFile = "empty-mkv-with-tags.mkv";
Set expectedFragmentNumbers = new HashSet<>(
Arrays.asList(
@@ -159,6 +159,46 @@ public void testFragmentNumbers_NoClusterData() throws IOException, MkvElementVi
Assert.assertEquals(expectedFragmentNumbers, visitedFragmentNumbers);
}
+ @Test
+ public void testFragmentMetadata_NoFragementMetadata_withWebm() throws IOException, MkvElementVisitException {
+ final FragmentMetadataVisitor fragmentMetadataVisitor = FragmentMetadataVisitor.create();
+ final String testFile = "big-buck-bunny_trailer.webm";
+ int metadataCount = 0;
+ final StreamingMkvReader mkvStreamReader = StreamingMkvReader
+ .createDefault(new InputStreamParserByteSource(TestResourceUtil.getTestInputStream(testFile)));
+ while (mkvStreamReader.mightHaveNext()) {
+ Optional mkvElement = mkvStreamReader.nextIfAvailable();
+ if (mkvElement.isPresent()) {
+ mkvElement.get().accept(fragmentMetadataVisitor);
+ Optional fragmentMetadata = fragmentMetadataVisitor.getCurrentFragmentMetadata();
+ if(fragmentMetadata.isPresent()) {
+ metadataCount ++;
+ }
+ }
+ }
+ Assert.assertEquals(0, metadataCount);
+ }
+
+ @Test
+ public void testFragmentMetadata_NoFragementMetadata_withMkv() throws IOException, MkvElementVisitException {
+ final FragmentMetadataVisitor fragmentMetadataVisitor = FragmentMetadataVisitor.create();
+ final String testFile = "clusters.mkv";
+ int metadataCount = 0;
+ final StreamingMkvReader mkvStreamReader = StreamingMkvReader
+ .createDefault(new InputStreamParserByteSource(TestResourceUtil.getTestInputStream(testFile)));
+ while (mkvStreamReader.mightHaveNext()) {
+ Optional mkvElement = mkvStreamReader.nextIfAvailable();
+ if (mkvElement.isPresent()) {
+ mkvElement.get().accept(fragmentMetadataVisitor);
+ Optional fragmentMetadata = fragmentMetadataVisitor.getCurrentFragmentMetadata();
+ if(fragmentMetadata.isPresent()) {
+ metadataCount ++;
+ }
+ }
+ }
+ Assert.assertEquals(0, metadataCount);
+ }
+
private static class TestCompositeVisitor extends CompositeMkvElementVisitor {
public TestCompositeVisitor(FragmentMetadataVisitor fragmentMetadataVisitor, OutputSegmentMerger merger) {
super(fragmentMetadataVisitor, merger);
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitorTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitorTest.java
new file mode 100644
index 0000000..79aa8cf
--- /dev/null
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitorTest.java
@@ -0,0 +1,71 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License").
+You may not use this file except in compliance with the License.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import com.amazonaws.kinesisvideo.parser.TestResourceUtil;
+import com.amazonaws.kinesisvideo.parser.ebml.InputStreamParserByteSource;
+
+import com.amazonaws.kinesisvideo.parser.mkv.Frame;
+import com.amazonaws.kinesisvideo.parser.mkv.StreamingMkvReader;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+public class FrameVisitorTest {
+ private int frameProcessCount;
+ private int nullFrameCount;
+ private static int SIMPLE_BLOCKS_COUNT_MKV = 444;
+
+ private final class TestFrameProcessor implements FrameVisitor.FrameProcessor {
+ @Override
+ public void process(final Frame frame, final MkvTrackMetadata trackMetadata,
+ final Optional fragmentMetadata) {
+ frameProcessCount++;
+ if(frame == null) {
+ nullFrameCount++;
+ }
+ }
+
+ }
+ private FrameVisitor frameVisitor;
+ private StreamingMkvReader streamingMkvReader;
+
+ @Before
+ public void setUp() throws Exception {
+ frameVisitor = FrameVisitor.create(new TestFrameProcessor());
+ }
+
+ @Test
+ public void testWithMkvVideo() throws Exception {
+ streamingMkvReader = StreamingMkvReader.createDefault(getClustersByteSource("clusters.mkv"));
+ streamingMkvReader.apply(frameVisitor);
+ Assert.assertEquals(SIMPLE_BLOCKS_COUNT_MKV, frameProcessCount);
+ Assert.assertEquals(0, nullFrameCount);
+ }
+
+ private InputStreamParserByteSource getClustersByteSource(String name) throws IOException {
+ return getInputStreamParserByteSource(name);
+ }
+
+ private InputStreamParserByteSource getInputStreamParserByteSource(String fileName) throws IOException {
+ final InputStream in = TestResourceUtil.getTestInputStream(fileName);
+ return new InputStreamParserByteSource(in);
+ }
+
+}
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameDecoderTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameDecoderTest.java
new file mode 100644
index 0000000..bf4ed8c
--- /dev/null
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameDecoderTest.java
@@ -0,0 +1,59 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License").
+You may not use this file except in compliance with the License.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import com.amazonaws.kinesisvideo.parser.TestResourceUtil;
+import com.amazonaws.kinesisvideo.parser.ebml.InputStreamParserByteSource;
+import com.amazonaws.kinesisvideo.parser.ebml.MkvTypeInfos;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitException;
+import com.amazonaws.kinesisvideo.parser.mkv.StreamingMkvReader;
+import com.amazonaws.kinesisvideo.parser.mkv.visitors.CompositeMkvElementVisitor;
+import com.amazonaws.kinesisvideo.parser.mkv.visitors.CountVisitor;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class H264FrameDecoderTest {
+
+ @Test
+ public void frameDecodeCountTest() throws IOException, MkvElementVisitException {
+ final InputStream in = TestResourceUtil.getTestInputStream("kinesis_video_renderer_example_output.mkv");
+ byte[] codecPrivateData = new byte[]{
+ 0x01, 0x64, 0x00, 0x28, (byte) 0xff, (byte) 0xe1, 0x00,
+ 0x0e, 0x27, 0x64, 0x00, 0x28, (byte) 0xac, 0x2b, 0x40,
+ 0x50, 0x1e, (byte) 0xd0, 0x0f, 0x12, 0x26, (byte) 0xa0,
+ 0x01, 0x00, 0x04, 0x28, (byte) 0xee, 0x1f, 0x2c
+ };
+
+ H264FrameDecoder frameDecoder = new H264FrameDecoder();
+ StreamingMkvReader mkvStreamReader =
+ StreamingMkvReader.createDefault(new InputStreamParserByteSource(in));
+
+ CountVisitor countVisitor = CountVisitor.create(MkvTypeInfos.CLUSTER, MkvTypeInfos.SEGMENT,
+ MkvTypeInfos.SIMPLEBLOCK, MkvTypeInfos.TRACKS);
+
+ mkvStreamReader.apply(new CompositeMkvElementVisitor(countVisitor, FrameVisitor.create(frameDecoder)));
+
+ Assert.assertEquals(8, countVisitor.getCount(MkvTypeInfos.TRACKS));
+ Assert.assertEquals(8, countVisitor.getCount(MkvTypeInfos.CLUSTER));
+ Assert.assertEquals(444, countVisitor.getCount(MkvTypeInfos.SIMPLEBLOCK));
+
+ Assert.assertEquals(444, frameDecoder.getFrameCount());
+ ByteBuffer codecPrivateDataFromFrame = frameDecoder.getCodecPrivateData();
+ Assert.assertEquals(ByteBuffer.wrap(codecPrivateData), codecPrivateDataFromFrame);
+ }
+}
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRendererTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRendererTest.java
new file mode 100644
index 0000000..f3a348d
--- /dev/null
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRendererTest.java
@@ -0,0 +1,61 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License").
+You may not use this file except in compliance with the License.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import com.amazonaws.kinesisvideo.parser.TestResourceUtil;
+import com.amazonaws.kinesisvideo.parser.ebml.InputStreamParserByteSource;
+import com.amazonaws.kinesisvideo.parser.ebml.MkvTypeInfos;
+import com.amazonaws.kinesisvideo.parser.examples.KinesisVideoFrameViewer;
+import com.amazonaws.kinesisvideo.parser.mkv.*;
+import com.amazonaws.kinesisvideo.parser.mkv.visitors.CompositeMkvElementVisitor;
+import com.amazonaws.kinesisvideo.parser.mkv.visitors.CountVisitor;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mock;
+
+
+@RunWith(PowerMockRunner.class)
+public class H264FrameRendererTest {
+
+ @Test
+ public void frameRenderCountTest() throws IOException, MkvElementVisitException {
+ final InputStream in = TestResourceUtil.getTestInputStream("kinesis_video_renderer_example_output.mkv");
+
+ final KinesisVideoFrameViewer kinesisVideoFrameViewer = mock(KinesisVideoFrameViewer.class);
+ doNothing().when(kinesisVideoFrameViewer).update(any(BufferedImage.class));
+ H264FrameRenderer frameRenderer = H264FrameRenderer.create(kinesisVideoFrameViewer);
+ StreamingMkvReader mkvStreamReader =
+ StreamingMkvReader.createDefault(new InputStreamParserByteSource(in));
+
+ CountVisitor countVisitor =
+ CountVisitor.create(MkvTypeInfos.CLUSTER, MkvTypeInfos.SEGMENT, MkvTypeInfos.SIMPLEBLOCK);
+
+ mkvStreamReader.apply(new CompositeMkvElementVisitor(countVisitor, FrameVisitor.create(frameRenderer)));
+
+ Assert.assertEquals(444, frameRenderer.getFrameCount());
+
+ verify(kinesisVideoFrameViewer, times(444)).update(any(BufferedImage.class));
+ }
+}
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/SimpleFrameVisitorTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/SimpleFrameVisitorTest.java
new file mode 100644
index 0000000..efbdf06
--- /dev/null
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/SimpleFrameVisitorTest.java
@@ -0,0 +1,128 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License").
+You may not use this file except in compliance with the License.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import com.amazonaws.kinesisvideo.parser.TestResourceUtil;
+import com.amazonaws.kinesisvideo.parser.ebml.EBMLElementMetaData;
+import com.amazonaws.kinesisvideo.parser.ebml.InputStreamParserByteSource;
+import com.amazonaws.kinesisvideo.parser.ebml.MkvTypeInfos;
+import com.amazonaws.kinesisvideo.parser.mkv.Frame;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvDataElement;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitException;
+import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitor;
+import com.amazonaws.kinesisvideo.parser.mkv.StreamingMkvReader;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({SimpleFrameVisitor.class})
+public class SimpleFrameVisitorTest {
+ private int frameProcessCount;
+ private int nullFrameCount;
+ private static int SIMPLE_BLOCKS_COUNT_WEBM = 2308;
+ private static int SIMPLE_BLOCKS_COUNT_MKV = 444;
+
+ private final class TestFrameProcessor implements SimpleFrameVisitor.FrameProcessor {
+ @Override
+ public void process(final Frame frame, long clusterTimeCode, long timeCodeScale) {
+ frameProcessCount++;
+ if(frame == null) { nullFrameCount++; }
+ }
+ }
+
+ @Mock
+ private MkvDataElement mockDataElement;
+ @Mock
+ private EBMLElementMetaData mockElementMetaData;
+ private SimpleFrameVisitor frameVisitor;
+ private StreamingMkvReader streamingMkvReader;
+
+ @Before
+ public void setUp() throws Exception {
+ frameVisitor = SimpleFrameVisitor
+ .create(new TestFrameProcessor());
+ }
+
+ @Test
+ public void testWithWebmVideo() throws Exception {
+ streamingMkvReader = StreamingMkvReader
+ .createDefault(getClustersByteSource("big-buck-bunny_trailer.webm"));
+ streamingMkvReader.apply(frameVisitor);
+
+ Assert.assertEquals(SIMPLE_BLOCKS_COUNT_WEBM, frameProcessCount);
+ Assert.assertEquals(nullFrameCount, 0);
+ MkvElementVisitor internal = Whitebox.getInternalState(frameVisitor, "frameVisitorInternal");
+ long timeCode = Whitebox.getInternalState(internal, "clusterTimeCode");
+ long timeCodeScale = Whitebox.getInternalState(internal, "timeCodeScale");
+ Assert.assertNotEquals(-1, timeCode);
+ Assert.assertNotEquals(-1, timeCodeScale);
+
+ }
+
+ @Test
+ public void testWithMkvVideo() throws Exception {
+ streamingMkvReader = StreamingMkvReader.createDefault(getClustersByteSource("clusters.mkv"));
+ streamingMkvReader.apply(frameVisitor);
+
+ Assert.assertEquals(SIMPLE_BLOCKS_COUNT_MKV, frameProcessCount);
+ Assert.assertEquals(nullFrameCount, 0);
+ MkvElementVisitor internal = Whitebox.getInternalState(frameVisitor, "frameVisitorInternal");
+ long timeCode = Whitebox.getInternalState(internal, "clusterTimeCode");
+ long timeCodeScale = Whitebox.getInternalState(internal, "timeCodeScale");
+ Assert.assertNotEquals(-1, timeCode);
+ Assert.assertNotEquals(-1, timeCodeScale);
+ }
+
+ @Test(expected = MkvElementVisitException.class)
+ public void testWhenNoTimeCode() throws Exception {
+ MkvElementVisitor internal = Whitebox.getInternalState(frameVisitor, "frameVisitorInternal");
+ Whitebox.setInternalState(internal, "timeCodeScale", 1);
+ PowerMockito.when(mockDataElement.getElementMetaData()).thenReturn(mockElementMetaData);
+ PowerMockito.when(mockElementMetaData.getTypeInfo()).thenReturn(MkvTypeInfos.SIMPLEBLOCK);
+ internal.visit(mockDataElement);
+
+ }
+
+
+ @Test(expected = MkvElementVisitException.class)
+ public void testWhenNoTimeCodeScale() throws Exception {
+ MkvElementVisitor internal = Whitebox.getInternalState(frameVisitor, "frameVisitorInternal");
+ Whitebox.setInternalState(internal, "clusterTimeCode", 1);
+ PowerMockito.when(mockDataElement.getElementMetaData()).thenReturn(mockElementMetaData);
+ PowerMockito.when(mockElementMetaData.getTypeInfo()).thenReturn(MkvTypeInfos.SIMPLEBLOCK);
+ internal.visit(mockDataElement);
+
+ }
+
+ private InputStreamParserByteSource getClustersByteSource(String name) throws IOException {
+ return getInputStreamParserByteSource(name);
+ }
+
+ private InputStreamParserByteSource getInputStreamParserByteSource(String fileName) throws IOException {
+ final InputStream in = TestResourceUtil.getTestInputStream(fileName);
+ return new InputStreamParserByteSource(in);
+ }
+
+}