awslabs / amazon-kinesis-video-streams-producer-sdk-java

Allows developers to install and customize their connected camera and other devices to securely stream video, audio, and time-encoded data to Kinesis Video Streams
Apache License 2.0
78 stars 76 forks source link

Add PutEventMetadata support to Java and JNI #193

Open stefankiesz opened 6 months ago

stefankiesz commented 6 months ago

Brings PutEventMetadata support for the Java Producer SDK through the following changes:


Testing

The following testing and validation was conducted for various STREAM_EVENT_TYPE (StreamEventType in Java end) values using a KVS stream configured for image generation and notifications.

A. IMAGE_GENERATION - Valid Inputs

1. 0 StreamEventMetadata Name/Value Pairs

StreamEventMetadata(null, (byte)0, new String[]{}, new String[]{})

The first S3 object received contains an image of the footage: image

✅ This image matches the first frame contained in the set of test .h264 files: image Due to the specific format or packaging of our .h264 test files, it was not readily possible to use an ffmpeg command to display the first frame as an image. An alternative technique was used to view the above first test frame which involved hardcoding the Java sample's image file index to remain at 0, so the first frame was constantly uploaded to KVS. Playing back the KVS stream at any recent point would then show that first frame.

Note: Images will no longer be shown for the remaining test cases below due to redundancy.

✅ The S3 object contains no additional metadata as expected: image


2. 1 StreamEventMetadata Name/Value Pair

StreamEventMetadata(null, (byte)1, new String[]{"aTestName"}, new String[]{"aTestValue"})

✅ S3 saved image is valid. ✅ S3 metadata received as expected: image

✅ The above successful tests were the case for pair counts from 1-9, but 10 failed as seen below.


3. 10 StreamEventMetadata Name/Value Pairs

String[] testNames = new String[]{"name-one", "name-two", "name-three", "name-four", "name-five", "name-six", "name-seven", "name-eight", "name-nine", "name-ten"};
String[] testValues = new String[]{"value-one", "value-two", "value-three", "value-four", "value-five", "value-six", "value-seven", "value-eight", "value-nine", "value-ten"};
testMetadata = new StreamEventMetadata(null, (byte)10, testNames, testValues);

❌ No image object in S3. ❌ No metadata. PutEventMetadata fails with 0x52000074 STATUS_MAX_FRAGMENT_METADATA_COUNT. NOTE: This uncovered that the true limit to name/value pairs in PIC is 9, or 8 if using an S3 imagePrefix.


4. Long-Running Test: 1 StreamEventMetadata Name/Value Pair

The following test was run for 11 hours:

StreamEventMetadata eventMetadata;
String[] eventMetadataNames = new String[]{"eventMetadata-name-1"};
String[] eventMetadataValues = new String[]{"eventMetadata-value-1"};
eventMetadata = new StreamEventMetadata(null, (byte)eventMetadataNames.length, eventMetadataNames, eventMetadataValues);
mkvDataAvailableCallback.onEventMetadataAvailable(StreamEventType.STREAM_EVENT_TYPE_IMAGE_GENERATION.getIntType(), eventMetadata);

✅ Images and tags were received in the S3 bucket after 11 hours, with no errors.


5. Null StreamEventMetadata

mkvDataAvailableCallback.onEventMetadataAvailable(StreamEventType.STREAM_EVENT_TYPE_IMAGE_GENERATION.getIntType(), null);

✅ S3 saved image is valid. ✅ The S3 object contains no additional metadata as expected.


B. IMAGE_GENERATION - Invalid Inputs

1. Invalid Name: "AWS"

String[] testNames = new String[]{"AWS"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

✅ Received the expected error status code: 0x52000077 | STATUS_INVALID_METADATA_NAME


2. Name Too Long

Limited by MKV_MAX_TAG_NAME_LEN which is 128.

Testing using 128 characters:

String[] testNames = new String[]{"this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

✅ No error. The name/value pair is present in the S3 object: image

Testing using 129 characters:

String[] testNames = new String[]{"this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

✅ Received the expected error status code: 0x5200008e | STATUS_INVALID_IMAGE_METADATA_KEY_LENGTH


3. Value Too Long

Limited by MKV_MAX_TAG_VALUE_LEN which is 256.

Testing using 256 characters:

String[] testNames = new String[]{"name-one"};
String[] testValues = new String[]{"this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-t"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

✅ No error. The name/value pair is present in the S3 object: image

Testing using 257 characters:

String[] testNames = new String[]{"name-one"};
String[] testValues = new String[]{"this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-this-is-a-test-th"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

✅ Received the expected error status code: 0x5200008f | STATUS_INVALID_IMAGE_METADATA_VALUE_LENGTH


4. Names is Empty, but numberOfPairs is > 0

String[] testNames = new String[]{};
String[] testValues = new String[]{};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

✅ Received the expected error status code: 0x00000001 | STATUS_NULL_ARG


5. Names is Not Empty, but numberOfPairs is < Names.length

Using previously used arrays of 10 name/value pairs, but with numberOfPairs set to 1:

String[] testNames = new String[]{"name-one", "name-two", "name-three", "name-four", "name-five", "name-six", "name-seven", "name-eight", "name-nine", "name-ten"};
String[] testValues = new String[]{"value-one", "value-two", "value-three", "value-four", "value-five", "value-six", "value-seven", "value-eight", "value-nine", "value-ten"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);

⚠️ We see only one name/value pair is saved in the object, so specifying the correct number of numberOfPairs is NOT optional: image


C. NONE

String[] testNames = new String[]{"name-one"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);
mkvDataAvailableCallback.onEventMetadataAvailable(StreamEventType.STREAM_EVENT_TYPE_NONE.getIntType(), testMetadata);

✅ Received the expected error status code: 0x00000002 | STATUS_INVALID_ARG


D. NOTIFICATION

Notification functionality was tested using an SNS topic with an email subscription. ✅ Received the expected notification via email with the expected SNS payload:

{"StreamArn":"arn:aws:kinesisvideo:us-west-2:XXXXX:stream/XXXX/XX","FragmentNumber":"XXXX","FragmentStartProducerTimestamp":1715386095807,"FragmentStartServerTimestamp":1715386097133,"NotificationType":"PERSISTED","NotificationPayload":{"name-one":"value-one"}}


E. LAST

String[] testNames = new String[]{"name-one"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);
mkvDataAvailableCallback.onEventMetadataAvailable(StreamEventType.STREAM_EVENT_TYPE_LAST.getIntType(), testMetadata);

✅ Received the expected error status code: 0x00000002 | STATUS_INVALID_ARG


F. Multiple StreamEventType

Using bitwise OR, IMAGE_GENERATION (1 = 001) was combined with NOTIFICATION (2 = 010) to equal 3 (011):

String[] testNames = new String[]{"name-one"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);
mkvDataAvailableCallback.onEventMetadataAvailable(3, testMetadata);

✅ Both, the S3 object containing an image with tags, and the SNS email notification were received: image


G. Negative StreamEventType

Care must be take here as the Java int passed to the JNI can be negative, while the StreamEventType parameter is an unsigned integer in PIC. Checks were placed in both the JNI and Java layers to error out if StreamEventType is negative.

String[] testNames = new String[]{"name-one"};
String[] testValues = new String[]{"value-one"};
testMetadata = new StreamEventMetadata(null, (byte)1, testNames, testValues);
mkvDataAvailableCallback.onEventMetadataAvailable(-1, testMetadata);

✅ Received the expected Java error due to Precondition check: java.lang.IllegalArgumentException

After removing the Java Precondition check to validate the JNI check: ✅ Received the expected native exception: 2024-05-10 17:29:48,969 [pool-3-thread-1] ERROR c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoClientWrapper - putKinesisVideoEventMetadata(): STREAM_EVENT_TYPE cannot be negative.




By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.