android_jni: Add API for frame durations
Expose frame durations for animated images in the JNI API.
GOOGLE_INTERNAL_CL: 524968419
diff --git a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
index 2ad879d..d322c70 100644
--- a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
+++ b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
@@ -30,22 +30,31 @@
public final int depth;
public final int frameCount;
public final int repetitionCount;
+ public final double frameDuration;
public Image(
- String filename, int width, int height, int depth, int frameCount, int repetitionCount) {
+ String filename,
+ int width,
+ int height,
+ int depth,
+ int frameCount,
+ int repetitionCount,
+ double frameDuration) {
this.filename = filename;
this.width = width;
this.height = height;
this.depth = depth;
this.frameCount = frameCount;
this.repetitionCount = repetitionCount;
+ this.frameDuration = frameDuration;
}
}
private static final Image[] IMAGES = {
- // Parameter ordering: filename, width, height, depth, frameCount, repetitionCount.
- new Image("alpha_video.avif", 640, 480, 8, 48, -2),
- new Image("Chimera-AV1-10bit-480x270.avif", 480, 270, 10, 95, -2),
+ // Parameter ordering: filename, width, height, depth, frameCount, repetitionCount,
+ // frameDuration.
+ new Image("alpha_video.avif", 640, 480, 8, 48, -2, 0.04),
+ new Image("Chimera-AV1-10bit-480x270.avif", 480, 270, 10, 95, -2, 0.04),
};
private static final String ASSET_DIRECTORY = "animated_avif";
@@ -82,10 +91,14 @@
assertThat(decoder.getDepth()).isEqualTo(image.depth);
assertThat(decoder.getFrameCount()).isEqualTo(image.frameCount);
assertThat(decoder.getRepetitionCount()).isEqualTo(image.repetitionCount);
+ double[] frameDurations = decoder.getFrameDurations();
+ assertThat(frameDurations).isNotNull();
+ assertThat(frameDurations).hasLength(image.frameCount);
Bitmap bitmap = Bitmap.createBitmap(image.width, image.height, config);
assertThat(bitmap).isNotNull();
for (int i = 0; i < image.frameCount; i++) {
assertThat(decoder.nextFrame(bitmap)).isTrue();
+ assertThat(frameDurations[i]).isWithin(1.0e-2).of(image.frameDuration);
}
decoder.release();
}
diff --git a/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java b/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
index 39285d8..2bb4450 100644
--- a/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
+++ b/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
@@ -50,6 +50,7 @@
private int depth;
private int frameCount;
private int repetitionCount;
+ private double[] frameDurations;
private AvifDecoder(ByteBuffer encoded) {
decoder = createDecoder(encoded, encoded.remaining());
@@ -142,6 +143,11 @@
return repetitionCount;
}
+ /** Get the duration for each frame in the image. */
+ public double[] getFrameDurations() {
+ return frameDurations;
+ }
+
/** Releases the underlying decoder object. */
public void release() {
if (decoder != 0) {
diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
index 31d6f6f..ef9d69d 100644
--- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
+++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
@@ -6,6 +6,7 @@
#include <cpu-features.h>
#include <jni.h>
+#include <memory>
#include <new>
#include "avif/avif.h"
@@ -33,6 +34,7 @@
jfieldID global_depth;
jfieldID global_frame_count;
jfieldID global_repetition_count;
+jfieldID global_frame_durations;
// RAII wrapper class that properly frees the decoder related objects on
// destruction.
@@ -167,6 +169,8 @@
global_frame_count = env->GetFieldID(avif_decoder_class, "frameCount", "I");
global_repetition_count =
env->GetFieldID(avif_decoder_class, "repetitionCount", "I");
+ global_frame_durations =
+ env->GetFieldID(avif_decoder_class, "frameDurations", "[D");
return JNI_VERSION_1_6;
}
@@ -187,7 +191,8 @@
env->SetIntField(info, global_info_width, decoder.decoder->image->width);
env->SetIntField(info, global_info_height, decoder.decoder->image->height);
env->SetIntField(info, global_info_depth, decoder.decoder->image->depth);
- env->SetBooleanField(info, global_info_alpha_present, decoder.decoder->alphaPresent);
+ env->SetBooleanField(info, global_info_alpha_present,
+ decoder.decoder->alphaPresent);
return true;
}
@@ -211,21 +216,45 @@
FUNC(jlong, createDecoder, jobject encoded, int length) {
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
- AvifDecoderWrapper* decoder = new (std::nothrow) AvifDecoderWrapper();
+ std::unique_ptr<AvifDecoderWrapper> decoder(new (std::nothrow)
+ AvifDecoderWrapper());
if (decoder == nullptr) {
return 0;
}
// TODO(b/272577342): Make threads configurable.
- if (!CreateDecoderAndParse(decoder, buffer, length, /*threads=*/1)) {
+ if (!CreateDecoderAndParse(decoder.get(), buffer, length, /*threads=*/1)) {
return 0;
}
env->SetIntField(thiz, global_width, decoder->decoder->image->width);
env->SetIntField(thiz, global_height, decoder->decoder->image->height);
env->SetIntField(thiz, global_depth, decoder->decoder->image->depth);
- env->SetIntField(thiz, global_frame_count, decoder->decoder->imageCount);
env->SetIntField(thiz, global_repetition_count,
decoder->decoder->repetitionCount);
- return reinterpret_cast<jlong>(decoder);
+ const int frameCount = decoder->decoder->imageCount;
+ env->SetIntField(thiz, global_frame_count, frameCount);
+ // This native array is needed because setting one element at a time to a Java
+ // array from the JNI layer is inefficient.
+ std::unique_ptr<double[]> native_durations(
+ new (std::nothrow) double[frameCount]);
+ if (native_durations == nullptr) {
+ return 0;
+ }
+ for (int i = 0; i < frameCount; ++i) {
+ avifImageTiming timing;
+ if (avifDecoderNthImageTiming(decoder->decoder, i, &timing) !=
+ AVIF_RESULT_OK) {
+ return 0;
+ }
+ native_durations[i] = timing.duration;
+ }
+ jdoubleArray durations = env->NewDoubleArray(frameCount);
+ if (durations == nullptr) {
+ return 0;
+ }
+ env->SetDoubleArrayRegion(durations, /*start=*/0, frameCount,
+ native_durations.get());
+ env->SetObjectField(thiz, global_frame_durations, durations);
+ return reinterpret_cast<jlong>(decoder.release());
}
FUNC(jboolean, nextFrame, jlong jdecoder, jobject bitmap) {