blob: b257e4ee50ed6618ccd89e710abbe792c622588b [file] [log] [blame]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <android/bitmap.h>
#include <android/log.h>
#include <cpu-features.h>
#include <jni.h>
#include <cstdio>
#include <memory>
#include <new>
#include "avif/avif.h"
#define LOG_TAG "avif_jni"
#define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#define FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE Java_org_aomedia_avif_android_AvifDecoder_##NAME( \
JNIEnv* env, jobject thiz, ##__VA_ARGS__); \
} \
JNIEXPORT RETURN_TYPE Java_org_aomedia_avif_android_AvifDecoder_##NAME( \
JNIEnv* env, jobject thiz, ##__VA_ARGS__)
namespace {
// RAII wrapper class that properly frees the decoder related objects on
// destruction.
struct AvifDecoderWrapper {
public:
AvifDecoderWrapper() = default;
// Not copyable or movable.
AvifDecoderWrapper(const AvifDecoderWrapper&) = delete;
AvifDecoderWrapper& operator=(const AvifDecoderWrapper&) = delete;
~AvifDecoderWrapper() {
if (decoder != nullptr) {
avifDecoderDestroy(decoder);
}
}
avifDecoder* decoder = nullptr;
};
bool CreateDecoderAndParse(AvifDecoderWrapper* const decoder,
const uint8_t* const buffer, int length,
int threads) {
decoder->decoder = avifDecoderCreate();
if (decoder->decoder == nullptr) {
LOGE("Failed to create AVIF Decoder.");
return false;
}
decoder->decoder->maxThreads = threads;
decoder->decoder->ignoreXMP = AVIF_TRUE;
decoder->decoder->ignoreExif = AVIF_TRUE;
// Turn off 'clap' (clean aperture) property validation. The JNI wrapper
// ignores the 'clap' property.
decoder->decoder->strictFlags &= ~AVIF_STRICT_CLAP_VALID;
// Allow 'pixi' (pixel information) property to be missing. Older versions of
// libheif did not add the 'pixi' item property to AV1 image items (See
// crbug.com/1198455).
decoder->decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
avifResult res = avifDecoderSetIOMemory(decoder->decoder, buffer, length);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to set AVIF IO to a memory reader.");
return false;
}
res = avifDecoderParse(decoder->decoder);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to parse AVIF image: %s.", avifResultToString(res));
return false;
}
return true;
}
avifResult AvifImageToBitmap(JNIEnv* const env,
AvifDecoderWrapper* const decoder,
jobject bitmap) {
AndroidBitmapInfo bitmap_info;
if (AndroidBitmap_getInfo(env, bitmap, &bitmap_info) < 0) {
LOGE("AndroidBitmap_getInfo failed.");
return AVIF_RESULT_UNKNOWN_ERROR;
}
// Ensure that the bitmap is large enough to store the decoded image.
if (bitmap_info.width < decoder->decoder->image->width ||
bitmap_info.height < decoder->decoder->image->height) {
LOGE(
"Bitmap is not large enough to fit the image. Bitmap %dx%d Image "
"%dx%d.",
bitmap_info.width, bitmap_info.height, decoder->decoder->image->width,
decoder->decoder->image->height);
return AVIF_RESULT_UNKNOWN_ERROR;
}
// Ensure that the bitmap format is RGBA_8888, RGB_565 or RGBA_F16.
if (bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 &&
bitmap_info.format != ANDROID_BITMAP_FORMAT_RGB_565 &&
bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_F16) {
LOGE("Bitmap format (%d) is not supported.", bitmap_info.format);
return AVIF_RESULT_NOT_IMPLEMENTED;
}
void* bitmap_pixels = nullptr;
if (AndroidBitmap_lockPixels(env, bitmap, &bitmap_pixels) !=
ANDROID_BITMAP_RESULT_SUCCESS) {
LOGE("Failed to lock Bitmap.");
return AVIF_RESULT_UNKNOWN_ERROR;
}
avifRGBImage rgb_image;
avifRGBImageSetDefaults(&rgb_image, decoder->decoder->image);
if (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_F16) {
rgb_image.depth = 16;
rgb_image.isFloat = AVIF_TRUE;
} else if (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGB_565) {
rgb_image.format = AVIF_RGB_FORMAT_RGB_565;
rgb_image.depth = 8;
} else {
rgb_image.depth = 8;
}
rgb_image.pixels = static_cast<uint8_t*>(bitmap_pixels);
rgb_image.rowBytes = bitmap_info.stride;
// Android always sees the Bitmaps as premultiplied with alpha when it renders
// them:
// https://developer.android.com/reference/android/graphics/Bitmap#setPremultiplied(boolean)
rgb_image.alphaPremultiplied = AVIF_TRUE;
const avifResult res = avifImageYUVToRGB(decoder->decoder->image, &rgb_image);
AndroidBitmap_unlockPixels(env, bitmap);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to convert YUV Pixels to RGB. Status: %d", res);
return res;
}
return AVIF_RESULT_OK;
}
avifResult DecodeNextImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
jobject bitmap) {
avifResult res = avifDecoderNextImage(decoder->decoder);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to decode AVIF image. Status: %d", res);
return res;
}
return AvifImageToBitmap(env, decoder, bitmap);
}
avifResult DecodeNthImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
uint32_t n, jobject bitmap) {
avifResult res = avifDecoderNthImage(decoder->decoder, n);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to decode AVIF image. Status: %d", res);
return res;
}
return AvifImageToBitmap(env, decoder, bitmap);
}
int getThreadCount(int threads) {
return (threads == 0) ? android_getCpuCount() : threads;
}
// Checks if there is a pending JNI exception that will be thrown when the
// control returns to the java layer. If there is none, it will return false. If
// there is one, then it will clear the pending exception and return true.
// Whenever this function returns true, the caller should treat it as a fatal
// error and return with a failure status at the earliest instance possible.
bool JniExceptionCheck(JNIEnv* env) {
if (!env->ExceptionCheck()) {
return false;
}
env->ExceptionClear();
return true;
}
} // namespace
jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
FUNC(jboolean, isAvifImage, jobject encoded, int length) {
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
const avifROData avif = {buffer, static_cast<size_t>(length)};
return avifPeekCompatibleFileType(&avif);
}
#define CHECK_EXCEPTION(ret) \
do { \
if (JniExceptionCheck(env)) return ret; \
} while (false)
#define FIND_CLASS(var, class_name, ret) \
const jclass var = env->FindClass(class_name); \
CHECK_EXCEPTION(ret); \
if (var == nullptr) return ret
#define GET_FIELD_ID(var, class_name, field_name, signature, ret) \
const jfieldID var = env->GetFieldID(class_name, field_name, signature); \
CHECK_EXCEPTION(ret); \
if (var == nullptr) return ret
FUNC(jboolean, getInfo, jobject encoded, int length, jobject info) {
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
AvifDecoderWrapper decoder;
if (!CreateDecoderAndParse(&decoder, buffer, length, /*threads=*/1)) {
return false;
}
FIND_CLASS(info_class, "org/aomedia/avif/android/AvifDecoder$Info", false);
GET_FIELD_ID(width, info_class, "width", "I", false);
GET_FIELD_ID(height, info_class, "height", "I", false);
GET_FIELD_ID(depth, info_class, "depth", "I", false);
GET_FIELD_ID(alpha_present, info_class, "alphaPresent", "Z", false);
env->SetIntField(info, width, decoder.decoder->image->width);
CHECK_EXCEPTION(false);
env->SetIntField(info, height, decoder.decoder->image->height);
CHECK_EXCEPTION(false);
env->SetIntField(info, depth, decoder.decoder->image->depth);
CHECK_EXCEPTION(false);
env->SetBooleanField(info, alpha_present, decoder.decoder->alphaPresent);
CHECK_EXCEPTION(false);
return true;
}
FUNC(jboolean, decode, jobject encoded, int length, jobject bitmap,
jint threads) {
if (threads < 0) {
LOGE("Invalid value for threads (%d).", threads);
return false;
}
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
AvifDecoderWrapper decoder;
if (!CreateDecoderAndParse(&decoder, buffer, length,
getThreadCount(threads))) {
return false;
}
return DecodeNextImage(env, &decoder, bitmap) == AVIF_RESULT_OK;
}
FUNC(jlong, createDecoder, jobject encoded, jint length, jint threads) {
if (threads < 0) {
LOGE("Invalid value for threads (%d).", threads);
return 0;
}
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
std::unique_ptr<AvifDecoderWrapper> decoder(new (std::nothrow)
AvifDecoderWrapper());
if (decoder == nullptr) {
return 0;
}
if (!CreateDecoderAndParse(decoder.get(), buffer, length,
getThreadCount(threads))) {
return 0;
}
FIND_CLASS(avif_decoder_class, "org/aomedia/avif/android/AvifDecoder", 0);
GET_FIELD_ID(width_id, avif_decoder_class, "width", "I", 0);
GET_FIELD_ID(height_id, avif_decoder_class, "height", "I", 0);
GET_FIELD_ID(depth_id, avif_decoder_class, "depth", "I", 0);
GET_FIELD_ID(alpha_present_id, avif_decoder_class, "alphaPresent", "Z", 0);
GET_FIELD_ID(frame_count_id, avif_decoder_class, "frameCount", "I", 0);
GET_FIELD_ID(repetition_count_id, avif_decoder_class, "repetitionCount", "I",
0);
GET_FIELD_ID(frame_durations_id, avif_decoder_class, "frameDurations", "[D",
0);
env->SetIntField(thiz, width_id, decoder->decoder->image->width);
CHECK_EXCEPTION(0);
env->SetIntField(thiz, height_id, decoder->decoder->image->height);
CHECK_EXCEPTION(0);
env->SetIntField(thiz, depth_id, decoder->decoder->image->depth);
CHECK_EXCEPTION(0);
env->SetBooleanField(thiz, alpha_present_id, decoder->decoder->alphaPresent);
CHECK_EXCEPTION(0);
env->SetIntField(thiz, repetition_count_id,
decoder->decoder->repetitionCount);
CHECK_EXCEPTION(0);
const int frameCount = decoder->decoder->imageCount;
env->SetIntField(thiz, frame_count_id, frameCount);
CHECK_EXCEPTION(0);
// 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());
CHECK_EXCEPTION(0);
env->SetObjectField(thiz, frame_durations_id, durations);
CHECK_EXCEPTION(0);
return reinterpret_cast<jlong>(decoder.release());
}
#undef GET_FIELD_ID
#undef FIND_CLASS
#undef CHECK_EXCEPTION
FUNC(jint, nextFrame, jlong jdecoder, jobject bitmap) {
AvifDecoderWrapper* const decoder =
reinterpret_cast<AvifDecoderWrapper*>(jdecoder);
return DecodeNextImage(env, decoder, bitmap);
}
FUNC(jint, nextFrameIndex, jlong jdecoder) {
AvifDecoderWrapper* const decoder =
reinterpret_cast<AvifDecoderWrapper*>(jdecoder);
return decoder->decoder->imageIndex + 1;
}
FUNC(jint, nthFrame, jlong jdecoder, jint n, jobject bitmap) {
AvifDecoderWrapper* const decoder =
reinterpret_cast<AvifDecoderWrapper*>(jdecoder);
return DecodeNthImage(env, decoder, n, bitmap);
}
FUNC(jstring, resultToString, jint result) {
return env->NewStringUTF(avifResultToString(static_cast<avifResult>(result)));
}
FUNC(jstring, versionString) {
char codec_versions[256];
avifCodecVersions(codec_versions);
char version_string[512];
snprintf(version_string, sizeof(version_string),
"libavif: %s. Codecs: %s. libyuv: %d.", avifVersion(),
codec_versions, avifLibYUVVersion());
return env->NewStringUTF(version_string);
}
FUNC(void, destroyDecoder, jlong jdecoder) {
AvifDecoderWrapper* const decoder =
reinterpret_cast<AvifDecoderWrapper*>(jdecoder);
delete decoder;
}