diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc index cec1c880e5..02d594b61d 100644 --- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc +++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc @@ -14,40 +14,40 @@ #include "avif/avif.h" #define LOG_TAG "avif_jni" -#define LOGE(...) \ - ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#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__) +#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__) #define IGNORE_UNUSED_JNI_PARAMETERS \ - (void) env; \ - (void) thiz + (void)env; \ + (void)thiz -namespace { +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); +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; - avifCropRect crop; + avifDecoder * decoder = nullptr; + avifCropRect crop; }; // Returns true when `encoded` is a direct ByteBuffer of at least `length` @@ -55,210 +55,201 @@ struct AvifDecoderWrapper { // direct buffer address and `*out_size` is set to `(size_t)length`. On // failure, logs via LOGE() and returns false; callers should propagate a // clean failure (false / 0 / nullptr) to the Java layer. -bool ValidateDirectBuffer(JNIEnv* env, jobject encoded, jint length, - const uint8_t** out_buffer, size_t* out_size) { - if (length < 0) { - LOGE("AVIF JNI: negative length (%d) rejected.", length); - return false; - } - const jlong capacity = env->GetDirectBufferCapacity(encoded); - if (capacity < 0) { - LOGE("AVIF JNI: encoded is not a direct ByteBuffer."); - return false; - } - if (static_cast(length) > capacity) { - LOGE("AVIF JNI: length (%d) exceeds direct buffer capacity (%lld).", - length, static_cast(capacity)); - return false; - } - const void* const address = env->GetDirectBufferAddress(encoded); - if (address == nullptr) { - LOGE("AVIF JNI: GetDirectBufferAddress returned null."); - return false; - } - *out_buffer = static_cast(address); - *out_size = static_cast(length); - return true; +bool ValidateDirectBuffer(JNIEnv * env, jobject encoded, jint length, const uint8_t ** out_buffer, size_t * out_size) +{ + if (length < 0) { + LOGE("AVIF JNI: negative length (%d) rejected.", length); + return false; + } + const jlong capacity = env->GetDirectBufferCapacity(encoded); + if (capacity < 0) { + LOGE("AVIF JNI: encoded is not a direct ByteBuffer."); + return false; + } + if (static_cast(length) > capacity) { + LOGE("AVIF JNI: length (%d) exceeds direct buffer capacity (%lld).", length, static_cast(capacity)); + return false; + } + const void * const address = env->GetDirectBufferAddress(encoded); + if (address == nullptr) { + LOGE("AVIF JNI: GetDirectBufferAddress returned null."); + return false; + } + *out_buffer = static_cast(address); + *out_size = static_cast(length); + return true; } -bool CreateDecoderAndParse(AvifDecoderWrapper* const decoder, - const uint8_t* const buffer, size_t 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 libavif's 'clap' (clean aperture) property validation. This allows - // us to detect and ignore streams that have an invalid 'clap' property - // instead failing. - 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; - } - - avifDiagnostics diag; - // If the image does not have a valid 'clap' property, then we simply display - // the whole image. - // TODO(vigneshv): Handle the case of avifCropRectRequiresUpsampling() - // returning true. - if (!(decoder->decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) || - !avifCropRectFromCleanApertureBox( - &decoder->crop, &decoder->decoder->image->clap, - decoder->decoder->image->width, decoder->decoder->image->height, - &diag) || - avifCropRectRequiresUpsampling(&decoder->crop, - decoder->decoder->image->yuvFormat)) { - decoder->crop.width = decoder->decoder->image->width; - decoder->crop.height = decoder->decoder->image->height; - decoder->crop.x = 0; - decoder->crop.y = 0; - } - return true; +bool CreateDecoderAndParse(AvifDecoderWrapper * const decoder, const uint8_t * const buffer, size_t 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 libavif's 'clap' (clean aperture) property validation. This allows + // us to detect and ignore streams that have an invalid 'clap' property + // instead failing. + 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; + } + + avifDiagnostics diag; + // If the image does not have a valid 'clap' property, then we simply display + // the whole image. + // TODO(vigneshv): Handle the case of avifCropRectRequiresUpsampling() + // returning true. + if (!(decoder->decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) || + !avifCropRectFromCleanApertureBox(&decoder->crop, + &decoder->decoder->image->clap, + decoder->decoder->image->width, + decoder->decoder->image->height, + &diag) || + avifCropRectRequiresUpsampling(&decoder->crop, decoder->decoder->image->yuvFormat)) { + decoder->crop.width = decoder->decoder->image->width; + decoder->crop.height = decoder->decoder->image->height; + decoder->crop.x = 0; + decoder->crop.y = 0; + } + 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 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; - } - avifImage* image; - std::unique_ptr cropped_image( - nullptr, avifImageDestroy); - avifResult res; - if (decoder->decoder->image->width == decoder->crop.width && - decoder->decoder->image->height == decoder->crop.height && - decoder->crop.x == 0 && decoder->crop.y == 0) { - image = decoder->decoder->image; - } else { - cropped_image.reset(avifImageCreateEmpty()); - if (cropped_image == nullptr) { - LOGE("Failed to allocate cropped image."); - return AVIF_RESULT_OUT_OF_MEMORY; - } - res = avifImageSetViewRect(cropped_image.get(), decoder->decoder->image, - &decoder->crop); +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 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; + } + avifImage * image; + std::unique_ptr cropped_image(nullptr, avifImageDestroy); + avifResult res; + if (decoder->decoder->image->width == decoder->crop.width && decoder->decoder->image->height == decoder->crop.height && + decoder->crop.x == 0 && decoder->crop.y == 0) { + image = decoder->decoder->image; + } else { + cropped_image.reset(avifImageCreateEmpty()); + if (cropped_image == nullptr) { + LOGE("Failed to allocate cropped image."); + return AVIF_RESULT_OUT_OF_MEMORY; + } + res = avifImageSetViewRect(cropped_image.get(), decoder->decoder->image, &decoder->crop); + if (res != AVIF_RESULT_OK) { + LOGE("Failed to set crop rectangle. Status: %d", res); + return res; + } + image = cropped_image.get(); + } + std::unique_ptr image_copy(nullptr, avifImageDestroy); + if (image->width != bitmap_info.width || image->height != bitmap_info.height) { + // If the avifImage does not own the planes, then create a copy for safe + // scaling. + if (!image->imageOwnsYUVPlanes || !image->imageOwnsAlphaPlane) { + image_copy.reset(avifImageCreateEmpty()); + if (image_copy == nullptr) { + LOGE("Failed to allocate image for scaling."); + return AVIF_RESULT_OUT_OF_MEMORY; + } + res = avifImageCopy(image_copy.get(), image, AVIF_PLANES_ALL); + if (res != AVIF_RESULT_OK) { + LOGE("Failed to make a copy of the image for scaling. Status: %d", res); + return res; + } + image = image_copy.get(); + } + avifDiagnostics diag; + res = avifImageScale(image, bitmap_info.width, bitmap_info.height, &diag); + if (res != AVIF_RESULT_OK) { + LOGE("Failed to scale image. Status: %d", res); + return res; + } + } + + avifRGBImage rgb_image; + avifRGBImageSetDefaults(&rgb_image, 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(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; + res = avifImageYUVToRGB(image, &rgb_image); + AndroidBitmap_unlockPixels(env, bitmap); if (res != AVIF_RESULT_OK) { - LOGE("Failed to set crop rectangle. Status: %d", res); - return res; - } - image = cropped_image.get(); - } - std::unique_ptr image_copy( - nullptr, avifImageDestroy); - if (image->width != bitmap_info.width || - image->height != bitmap_info.height) { - // If the avifImage does not own the planes, then create a copy for safe - // scaling. - if (!image->imageOwnsYUVPlanes || !image->imageOwnsAlphaPlane) { - image_copy.reset(avifImageCreateEmpty()); - if (image_copy == nullptr) { - LOGE("Failed to allocate image for scaling."); - return AVIF_RESULT_OUT_OF_MEMORY; - } - res = avifImageCopy(image_copy.get(), image, AVIF_PLANES_ALL); - if (res != AVIF_RESULT_OK) { - LOGE("Failed to make a copy of the image for scaling. Status: %d", res); + LOGE("Failed to convert YUV Pixels to RGB. Status: %d", res); return res; - } - image = image_copy.get(); } - avifDiagnostics diag; - res = avifImageScale(image, bitmap_info.width, bitmap_info.height, &diag); - if (res != AVIF_RESULT_OK) { - LOGE("Failed to scale image. Status: %d", res); - return res; - } - } - - avifRGBImage rgb_image; - avifRGBImageSetDefaults(&rgb_image, 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(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; - res = avifImageYUVToRGB(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; + 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 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); +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) { - if (threads < 0) { - return android_getCpuCount(); - } - if (threads == 0) { - // Empirically, on Android devices with more than 1 core, decoding with 2 - // threads is almost always better than using as many threads as CPU cores. - return std::min(android_getCpuCount(), 2); - } - return threads; +int getThreadCount(int threads) +{ + if (threads < 0) { + return android_getCpuCount(); + } + if (threads == 0) { + // Empirically, on Android devices with more than 1 core, decoding with 2 + // threads is almost always better than using as many threads as CPU cores. + return std::min(android_getCpuCount(), 2); + } + return threads; } // Checks if there is a pending JNI exception that will be thrown when the @@ -266,209 +257,208 @@ int getThreadCount(int threads) { // 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 as early as possible. -bool JniExceptionCheck(JNIEnv* env) { - if (!env->ExceptionCheck()) { - return false; - } - env->ExceptionClear(); - return true; +bool JniExceptionCheck(JNIEnv * env) +{ + if (!env->ExceptionCheck()) { + return false; + } + env->ExceptionClear(); + return true; } -} // namespace +} // namespace -jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - return -1; - } - return JNI_VERSION_1_6; +jint JNI_OnLoad(JavaVM * vm, void * /*reserved*/) +{ + JNIEnv * env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + return JNI_VERSION_1_6; } -FUNC(jboolean, isAvifImage, jobject encoded, int length) { - IGNORE_UNUSED_JNI_PARAMETERS; - const uint8_t* buffer = nullptr; - size_t size = 0; - if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { - return false; - } - const avifROData avif = {buffer, size}; - return avifPeekCompatibleFileType(&avif); +FUNC(jboolean, isAvifImage, jobject encoded, int length) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + const uint8_t * buffer = nullptr; + size_t size = 0; + if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { + return false; + } + const avifROData avif = { buffer, size }; + 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) { - IGNORE_UNUSED_JNI_PARAMETERS; - const uint8_t* buffer = nullptr; - size_t size = 0; - if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { - return false; - } - AvifDecoderWrapper decoder; - if (!CreateDecoderAndParse(&decoder, buffer, size, /*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.crop.width); - CHECK_EXCEPTION(false); - env->SetIntField(info, height, decoder.crop.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; +#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) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + const uint8_t * buffer = nullptr; + size_t size = 0; + if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { + return false; + } + AvifDecoderWrapper decoder; + if (!CreateDecoderAndParse(&decoder, buffer, size, /*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.crop.width); + CHECK_EXCEPTION(false); + env->SetIntField(info, height, decoder.crop.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) { - IGNORE_UNUSED_JNI_PARAMETERS; - const uint8_t* buffer = nullptr; - size_t size = 0; - if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { - return false; - } - AvifDecoderWrapper decoder; - if (!CreateDecoderAndParse(&decoder, buffer, size, - getThreadCount(threads))) { - return false; - } - return DecodeNextImage(env, &decoder, bitmap) == AVIF_RESULT_OK; +FUNC(jboolean, decode, jobject encoded, int length, jobject bitmap, jint threads) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + const uint8_t * buffer = nullptr; + size_t size = 0; + if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { + return false; + } + AvifDecoderWrapper decoder; + if (!CreateDecoderAndParse(&decoder, buffer, size, getThreadCount(threads))) { + return false; + } + return DecodeNextImage(env, &decoder, bitmap) == AVIF_RESULT_OK; } -FUNC(jlong, createDecoder, jobject encoded, jint length, jint threads) { - const uint8_t* buffer = nullptr; - size_t size = 0; - if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { - return 0; - } - std::unique_ptr decoder(new (std::nothrow) - AvifDecoderWrapper()); - if (decoder == nullptr) { - return 0; - } - if (!CreateDecoderAndParse(decoder.get(), buffer, size, - 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->crop.width); - CHECK_EXCEPTION(0); - env->SetIntField(thiz, height_id, decoder->crop.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 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(decoder.release()); +FUNC(jlong, createDecoder, jobject encoded, jint length, jint threads) +{ + const uint8_t * buffer = nullptr; + size_t size = 0; + if (!ValidateDirectBuffer(env, encoded, length, &buffer, &size)) { + return 0; + } + std::unique_ptr decoder(new (std::nothrow) AvifDecoderWrapper()); + if (decoder == nullptr) { + return 0; + } + if (!CreateDecoderAndParse(decoder.get(), buffer, size, 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->crop.width); + CHECK_EXCEPTION(0); + env->SetIntField(thiz, height_id, decoder->crop.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 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(decoder.release()); } #undef GET_FIELD_ID #undef FIND_CLASS #undef CHECK_EXCEPTION -FUNC(jint, nextFrame, jlong jdecoder, jobject bitmap) { - IGNORE_UNUSED_JNI_PARAMETERS; - AvifDecoderWrapper* const decoder = - reinterpret_cast(jdecoder); - return DecodeNextImage(env, decoder, bitmap); +FUNC(jint, nextFrame, jlong jdecoder, jobject bitmap) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + AvifDecoderWrapper * const decoder = reinterpret_cast(jdecoder); + return DecodeNextImage(env, decoder, bitmap); } -FUNC(jint, nextFrameIndex, jlong jdecoder) { - IGNORE_UNUSED_JNI_PARAMETERS; - AvifDecoderWrapper* const decoder = - reinterpret_cast(jdecoder); - return decoder->decoder->imageIndex + 1; +FUNC(jint, nextFrameIndex, jlong jdecoder) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + AvifDecoderWrapper * const decoder = reinterpret_cast(jdecoder); + return decoder->decoder->imageIndex + 1; } -FUNC(jint, nthFrame, jlong jdecoder, jint n, jobject bitmap) { - IGNORE_UNUSED_JNI_PARAMETERS; - AvifDecoderWrapper* const decoder = - reinterpret_cast(jdecoder); - return DecodeNthImage(env, decoder, n, bitmap); +FUNC(jint, nthFrame, jlong jdecoder, jint n, jobject bitmap) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + AvifDecoderWrapper * const decoder = reinterpret_cast(jdecoder); + return DecodeNthImage(env, decoder, n, bitmap); } -FUNC(jstring, resultToString, jint result) { - IGNORE_UNUSED_JNI_PARAMETERS; - return env->NewStringUTF(avifResultToString(static_cast(result))); +FUNC(jstring, resultToString, jint result) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + return env->NewStringUTF(avifResultToString(static_cast(result))); } -FUNC(jstring, versionString) { - IGNORE_UNUSED_JNI_PARAMETERS; - char codec_versions[256]; - avifCodecVersions(codec_versions); - char libyuv_version[64]; - if (avifLibYUVVersion() > 0) { - snprintf(libyuv_version, sizeof(libyuv_version), " libyuv: %u.", - avifLibYUVVersion()); - } else { - libyuv_version[0] = '\0'; - } - char version_string[512]; - snprintf(version_string, sizeof(version_string), "libavif: %s. Codecs: %s.%s", - avifVersion(), codec_versions, libyuv_version); - return env->NewStringUTF(version_string); +FUNC(jstring, versionString) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + char codec_versions[256]; + avifCodecVersions(codec_versions); + char libyuv_version[64]; + if (avifLibYUVVersion() > 0) { + snprintf(libyuv_version, sizeof(libyuv_version), " libyuv: %u.", avifLibYUVVersion()); + } else { + libyuv_version[0] = '\0'; + } + char version_string[512]; + snprintf(version_string, sizeof(version_string), "libavif: %s. Codecs: %s.%s", avifVersion(), codec_versions, libyuv_version); + return env->NewStringUTF(version_string); } -FUNC(void, destroyDecoder, jlong jdecoder) { - IGNORE_UNUSED_JNI_PARAMETERS; - AvifDecoderWrapper* const decoder = - reinterpret_cast(jdecoder); - delete decoder; +FUNC(void, destroyDecoder, jlong jdecoder) +{ + IGNORE_UNUSED_JNI_PARAMETERS; + AvifDecoderWrapper * const decoder = reinterpret_cast(jdecoder); + delete decoder; } diff --git a/include/avif/avif.h b/include/avif/avif.h index 204827b403..9ce3f57c6b 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -100,6 +100,18 @@ typedef int avifBool; // a 12 hour AVIF image sequence, running at 60 fps (a basic sanity check as this is quite ridiculous) #define AVIF_DEFAULT_IMAGE_COUNT_LIMIT (12 * 3600 * 60) +// A reasonable default for maximum item count in a meta box. +#define AVIF_DEFAULT_ITEM_COUNT_LIMIT 4096 + +// A reasonable default for maximum property count in a meta box. +#define AVIF_DEFAULT_PROPERTY_COUNT_LIMIT 4096 + +// A reasonable default for maximum extent count for an item. +#define AVIF_DEFAULT_EXTENT_COUNT_LIMIT 64 + +// A reasonable default for maximum group count in a grpl box. +#define AVIF_DEFAULT_GROUP_COUNT_LIMIT 1024 + #define AVIF_QUALITY_DEFAULT -1 #define AVIF_QUALITY_WORST 0 #define AVIF_QUALITY_BEST 100 @@ -1383,6 +1395,23 @@ typedef struct avifDecoder avifImageContentTypeFlags imageContentToDecode; // Changeable decoder setting. // Version 1.2.0 ends here. Add any new members after this line. + + // This provides an upper bound on how many items can be created in a meta box. + // The default is AVIF_DEFAULT_ITEM_COUNT_LIMIT, and setting this to 0 disables the limit. + uint32_t itemCountLimit; + + // This provides an upper bound on how many properties can be created in a meta box. + // The default is AVIF_DEFAULT_PROPERTY_COUNT_LIMIT, and setting this to 0 disables the limit. + uint32_t propertyCountLimit; + + // This provides an upper bound on how many extents an item can have. + // The default is AVIF_DEFAULT_EXTENT_COUNT_LIMIT, and setting this to 0 disables the limit. + uint32_t extentCountLimit; + + // This provides an upper bound on how many groups can be created in a grpl box. + // The default is AVIF_DEFAULT_GROUP_COUNT_LIMIT, and setting this to 0 disables the limit. + uint32_t groupCountLimit; + // -------------------------------------------------------------------------------------------- } avifDecoder; diff --git a/poc.avif b/poc.avif new file mode 100644 index 0000000000..cb7c56c346 Binary files /dev/null and b/poc.avif differ diff --git a/poc_v2.avif b/poc_v2.avif new file mode 100644 index 0000000000..42f30ff95b Binary files /dev/null and b/poc_v2.avif differ diff --git a/src/read.c b/src/read.c index a6a33d8102..3ce077886a 100644 --- a/src/read.c +++ b/src/read.c @@ -797,6 +797,7 @@ AVIF_ARRAY_DECLARE(avifTileArray, avifTile, tile); // of that box are implicitly associated with that track. typedef struct avifMeta { + struct avifDecoder * decoder; // Unowned; A back-pointer for limits and diagnostics // Items (from HEIF) are the generic storage for any data that does not require timed processing // (single image color planes, alpha planes, EXIF, XMP, etc). Each item has a unique integer ID >1, // and is defined by a series of child boxes in a meta box: @@ -849,13 +850,14 @@ typedef struct avifMeta static void avifMetaDestroy(avifMeta * meta); -static avifMeta * avifMetaCreate(void) +static avifMeta * avifMetaCreate(struct avifDecoder * decoder) { avifMeta * meta = (avifMeta *)avifAlloc(sizeof(avifMeta)); if (meta == NULL) { return NULL; } memset(meta, 0, sizeof(avifMeta)); + meta->decoder = decoder; if (!avifArrayCreate(&meta->items, sizeof(avifDecoderItem *), 8) || !avifArrayCreate(&meta->properties, sizeof(avifProperty), 16) || !avifArrayCreate(&meta->entityToGroups, sizeof(avifEntityToGroup), 1)) { avifMetaDestroy(meta); @@ -976,6 +978,7 @@ typedef struct avifDecoderData uint8_t majorBrand[4]; // From the file's ftyp, used by AVIF_DECODER_SOURCE_AUTO avifBrandArray compatibleBrands; // From the file's ftyp avifDiagnostics * diag; // Shallow copy; owned by avifDecoder + avifDecoder * decoder; // Shallow copy; owned by the user const avifSampleTable * sourceSampleTable; // NULL unless (source == AVIF_DECODER_SOURCE_TRACKS), owned by an avifTrack avifBool cicpSet; // True if avifDecoder's image has had its CICP set correctly yet. // This allows nclx colr boxes to override AV1 CICP, as specified in the MIAF @@ -992,14 +995,16 @@ typedef struct avifDecoderData static void avifDecoderDataDestroy(avifDecoderData * data); -static avifDecoderData * avifDecoderDataCreate(void) +static avifDecoderData * avifDecoderDataCreate(avifDecoder * decoder) { avifDecoderData * data = (avifDecoderData *)avifAlloc(sizeof(avifDecoderData)); if (data == NULL) { return NULL; } memset(data, 0, sizeof(avifDecoderData)); - data->meta = avifMetaCreate(); + data->decoder = decoder; + data->diag = &decoder->diag; + data->meta = avifMetaCreate(decoder); if (data->meta == NULL || !avifArrayCreate(&data->tracks, sizeof(avifTrack), 2) || !avifArrayCreate(&data->tiles, sizeof(avifTile), 8)) { avifDecoderDataDestroy(data); @@ -1073,7 +1078,7 @@ static avifTrack * avifDecoderDataCreateTrack(avifDecoderData * data) if (track == NULL) { return NULL; } - track->meta = avifMetaCreate(); + track->meta = avifMetaCreate(data->decoder); if (track->meta == NULL) { avifArrayPop(&data->tracks); return NULL; @@ -2015,6 +2020,10 @@ static avifResult avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, } else { AVIF_CHECKERR(avifROStreamReadU32(&s, &itemCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) item_count; } + if (meta->decoder->itemCountLimit > 0 && itemCount > meta->decoder->itemCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded itemCountLimit (%u)", meta->decoder->itemCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (uint32_t i = 0; i < itemCount; ++i) { uint32_t itemID; if (version < 2) { @@ -2058,6 +2067,10 @@ static avifResult avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, AVIF_CHECKERR(avifROStreamReadUX8(&s, &baseOffset, baseOffsetSize), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(base_offset_size*8) base_offset; uint16_t extentCount; AVIF_CHECKERR(avifROStreamReadU16(&s, &extentCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) extent_count; + if (meta->decoder->extentCountLimit > 0 && extentCount > meta->decoder->extentCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded extentCountLimit (%u)", meta->decoder->extentCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (int extentIter = 0; extentIter < extentCount; ++extentIter) { if ((version == 1 || version == 2) && indexSize > 0) { // Section 8.11.3.1 of ISO/IEC 14496-12: @@ -2913,7 +2926,8 @@ static avifBool avifParseAV1LayeredImageIndexingProperty(avifProperty * prop, co return AVIF_TRUE; } -static avifResult avifParseItemPropertyContainerBox(avifPropertyArray * properties, +static avifResult avifParseItemPropertyContainerBox(avifDecoder * decoder, + avifPropertyArray * properties, uint64_t rawOffset, const uint8_t * raw, size_t rawLen, @@ -2926,6 +2940,11 @@ static avifResult avifParseItemPropertyContainerBox(avifPropertyArray * properti avifBoxHeader header; AVIF_CHECKERR(avifROStreamReadBoxHeader(&s, &header), AVIF_RESULT_BMFF_PARSE_FAILED); + if (decoder->propertyCountLimit > 0 && properties->count >= decoder->propertyCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded propertyCountLimit (%u)", decoder->propertyCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } + avifProperty * prop = (avifProperty *)avifArrayPush(properties); AVIF_CHECKERR(prop != NULL, AVIF_RESULT_OUT_OF_MEMORY); memcpy(prop->type, header.type, 4); @@ -2995,6 +3014,10 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ uint32_t entryCount; AVIF_CHECKERR(avifROStreamReadU32(&s, &entryCount), AVIF_RESULT_BMFF_PARSE_FAILED); + if (meta->decoder->itemCountLimit > 0 && entryCount > meta->decoder->itemCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded itemCountLimit (%u)", meta->decoder->itemCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } unsigned int prevItemID = 0; for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) { // ISO/IEC 14496-12, Seventh edition, 2022-01, Section 8.11.14.1: @@ -3201,7 +3224,8 @@ static avifResult avifParseItemPropertiesBox(avifMeta * meta, uint64_t rawOffset } // Read all item properties inside of ItemPropertyContainerBox - AVIF_CHECKRES(avifParseItemPropertyContainerBox(&meta->properties, + AVIF_CHECKRES(avifParseItemPropertyContainerBox(meta->decoder, + &meta->properties, rawOffset + avifROStreamOffset(&s), avifROStreamCurrent(&s), ipcoHeader.size, @@ -3315,6 +3339,11 @@ static avifResult avifParseItemInfoBox(avifMeta * meta, const uint8_t * raw, siz return AVIF_RESULT_BMFF_PARSE_FAILED; } + if (meta->decoder->itemCountLimit > 0 && entryCount > meta->decoder->itemCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded itemCountLimit (%u)", meta->decoder->itemCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } + for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) { avifBoxHeader infeHeader; AVIF_CHECKERR(avifROStreamReadBoxHeader(&s, &infeHeader), AVIF_RESULT_BMFF_PARSE_FAILED); @@ -3374,6 +3403,10 @@ static avifResult avifParseItemReferenceBox(avifMeta * meta, const uint8_t * raw uint16_t referenceCount = 0; AVIF_CHECKERR(avifROStreamReadU16(&s, &referenceCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) reference_count; + if (meta->decoder->itemCountLimit > 0 && referenceCount > meta->decoder->itemCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded itemCountLimit (%u)", meta->decoder->itemCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (uint16_t refIndex = 0; refIndex < referenceCount; ++refIndex) { uint32_t toID = 0; @@ -3421,6 +3454,10 @@ static avifResult avifParseGroupsListBox(avifMeta * meta, const uint8_t * raw, s BEGIN_STREAM(s, raw, rawLen, diag, "Box[grpl]"); while (avifROStreamHasBytesLeft(&s, 1)) { + if (meta->decoder->groupCountLimit > 0 && meta->entityToGroups.count >= meta->decoder->groupCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded groupCountLimit (%u)", meta->decoder->groupCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } avifBoxHeader groupHeader; AVIF_CHECKERR(avifROStreamReadBoxHeader(&s, &groupHeader), AVIF_RESULT_BMFF_PARSE_FAILED); // We don't check the flag or version as they depend on the grouping type (and for simplicity). @@ -3438,6 +3475,10 @@ static avifResult avifParseGroupsListBox(avifMeta * meta, const uint8_t * raw, s AVIF_CHECKERR(avifROStreamReadU32(&s, &group->groupID), AVIF_RESULT_BMFF_PARSE_FAILED); uint32_t numEntitiesInGroup; AVIF_CHECKERR(avifROStreamReadU32(&s, &numEntitiesInGroup), AVIF_RESULT_BMFF_PARSE_FAILED); + if (meta->decoder->itemCountLimit > 0 && numEntitiesInGroup > meta->decoder->itemCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded itemCountLimit (%u)", meta->decoder->itemCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (uint32_t i = 0; i < numEntitiesInGroup; ++i) { uint32_t * entityId = avifArrayPush(&group->entityIDs); AVIF_CHECKERR(entityId != NULL, AVIF_RESULT_OUT_OF_MEMORY); @@ -3597,7 +3638,12 @@ static avifBool avifParseMediaHeaderBox(avifTrack * track, const uint8_t * raw, return AVIF_TRUE; } -static avifResult avifParseChunkOffsetBox(avifSampleTable * sampleTable, avifBool largeOffsets, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) +static avifResult avifParseChunkOffsetBox(const avifDecoder * decoder, + avifSampleTable * sampleTable, + avifBool largeOffsets, + const uint8_t * raw, + size_t rawLen, + avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, largeOffsets ? "Box[co64]" : "Box[stco]"); @@ -3605,6 +3651,10 @@ static avifResult avifParseChunkOffsetBox(avifSampleTable * sampleTable, avifBoo uint32_t entryCount; AVIF_CHECKERR(avifROStreamReadU32(&s, &entryCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) entry_count; + if (decoder->imageCountLimit > 0 && entryCount > decoder->imageCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded imageCountLimit (%u)", decoder->imageCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (uint32_t i = 0; i < entryCount; ++i) { uint64_t offset; if (largeOffsets) { @@ -3622,7 +3672,11 @@ static avifResult avifParseChunkOffsetBox(avifSampleTable * sampleTable, avifBoo return AVIF_RESULT_OK; } -static avifResult avifParseSampleToChunkBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) +static avifResult avifParseSampleToChunkBox(const avifDecoder * decoder, + avifSampleTable * sampleTable, + const uint8_t * raw, + size_t rawLen, + avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, "Box[stsc]"); @@ -3630,6 +3684,10 @@ static avifResult avifParseSampleToChunkBox(avifSampleTable * sampleTable, const uint32_t entryCount; AVIF_CHECKERR(avifROStreamReadU32(&s, &entryCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) entry_count; + if (decoder->imageCountLimit > 0 && entryCount > decoder->imageCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded imageCountLimit (%u)", decoder->imageCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } uint32_t prevFirstChunk = 0; for (uint32_t i = 0; i < entryCount; ++i) { avifSampleTableSampleToChunk * sampleToChunk = (avifSampleTableSampleToChunk *)avifArrayPush(&sampleTable->sampleToChunks); @@ -3655,7 +3713,11 @@ static avifResult avifParseSampleToChunkBox(avifSampleTable * sampleTable, const return AVIF_RESULT_OK; } -static avifResult avifParseSampleSizeBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) +static avifResult avifParseSampleSizeBox(const avifDecoder * decoder, + avifSampleTable * sampleTable, + const uint8_t * raw, + size_t rawLen, + avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, "Box[stsz]"); @@ -3664,6 +3726,10 @@ static avifResult avifParseSampleSizeBox(avifSampleTable * sampleTable, const ui uint32_t allSamplesSize, sampleCount; AVIF_CHECKERR(avifROStreamReadU32(&s, &allSamplesSize), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) sample_size; AVIF_CHECKERR(avifROStreamReadU32(&s, &sampleCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) sample_count; + if (decoder->imageCountLimit > 0 && sampleCount > decoder->imageCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded imageCountLimit (%u)", decoder->imageCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } if (allSamplesSize > 0) { sampleTable->allSamplesSize = allSamplesSize; @@ -3677,7 +3743,11 @@ static avifResult avifParseSampleSizeBox(avifSampleTable * sampleTable, const ui return AVIF_RESULT_OK; } -static avifResult avifParseSyncSampleBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) +static avifResult avifParseSyncSampleBox(const avifDecoder * decoder, + avifSampleTable * sampleTable, + const uint8_t * raw, + size_t rawLen, + avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, "Box[stss]"); @@ -3685,6 +3755,10 @@ static avifResult avifParseSyncSampleBox(avifSampleTable * sampleTable, const ui uint32_t entryCount; AVIF_CHECKERR(avifROStreamReadU32(&s, &entryCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) entry_count; + if (decoder->imageCountLimit > 0 && entryCount > decoder->imageCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded imageCountLimit (%u)", decoder->imageCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (uint32_t i = 0; i < entryCount; ++i) { uint32_t sampleNumber = 0; @@ -3696,7 +3770,11 @@ static avifResult avifParseSyncSampleBox(avifSampleTable * sampleTable, const ui return AVIF_RESULT_OK; } -static avifResult avifParseTimeToSampleBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) +static avifResult avifParseTimeToSampleBox(const avifDecoder * decoder, + avifSampleTable * sampleTable, + const uint8_t * raw, + size_t rawLen, + avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, "Box[stts]"); @@ -3704,6 +3782,10 @@ static avifResult avifParseTimeToSampleBox(avifSampleTable * sampleTable, const uint32_t entryCount; AVIF_CHECKERR(avifROStreamReadU32(&s, &entryCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) entry_count; + if (decoder->imageCountLimit > 0 && entryCount > decoder->imageCountLimit) { + avifDiagnosticsPrintf(diag, "Exceeded imageCountLimit (%u)", decoder->imageCountLimit); + return AVIF_RESULT_BMFF_PARSE_FAILED; + } for (uint32_t i = 0; i < entryCount; ++i) { avifSampleTableTimeToSample * timeToSample = (avifSampleTableTimeToSample *)avifArrayPush(&sampleTable->timeToSamples); @@ -3714,7 +3796,8 @@ static avifResult avifParseTimeToSampleBox(avifSampleTable * sampleTable, const return AVIF_RESULT_OK; } -static avifResult avifParseSampleDescriptionBox(avifSampleTable * sampleTable, +static avifResult avifParseSampleDescriptionBox(avifDecoder * decoder, + avifSampleTable * sampleTable, uint64_t rawOffset, const uint8_t * raw, size_t rawLen, @@ -3752,7 +3835,8 @@ static avifResult avifParseSampleDescriptionBox(avifSampleTable * sampleTable, avifDiagnosticsPrintf(diag, "Not enough bytes to parse VisualSampleEntry"); return AVIF_RESULT_BMFF_PARSE_FAILED; } - AVIF_CHECKRES(avifParseItemPropertyContainerBox(&description->properties, + AVIF_CHECKRES(avifParseItemPropertyContainerBox(decoder, + &description->properties, rawOffset + avifROStreamOffset(&s) + VISUALSAMPLEENTRY_SIZE, avifROStreamCurrent(&s) + VISUALSAMPLEENTRY_SIZE, sampleEntryBytes - VISUALSAMPLEENTRY_SIZE, @@ -3773,8 +3857,7 @@ static avifResult avifParseSampleTableBox(avifTrack * track, uint64_t rawOffset, return AVIF_RESULT_BMFF_PARSE_FAILED; } track->sampleTable = avifSampleTableCreate(); - AVIF_CHECKERR(track->sampleTable != NULL, AVIF_RESULT_OUT_OF_MEMORY); - + AVIF_CHECKERR(track->sampleTable, AVIF_RESULT_OUT_OF_MEMORY); BEGIN_STREAM(s, raw, rawLen, diag, "Box[stbl]"); while (avifROStreamHasBytesLeft(&s, 1)) { @@ -3782,19 +3865,22 @@ static avifResult avifParseSampleTableBox(avifTrack * track, uint64_t rawOffset, AVIF_CHECKERR(avifROStreamReadBoxHeader(&s, &header), AVIF_RESULT_BMFF_PARSE_FAILED); if (!memcmp(header.type, "stco", 4)) { - AVIF_CHECKRES(avifParseChunkOffsetBox(track->sampleTable, AVIF_FALSE, avifROStreamCurrent(&s), header.size, diag)); + AVIF_CHECKRES( + avifParseChunkOffsetBox(track->meta->decoder, track->sampleTable, AVIF_FALSE, avifROStreamCurrent(&s), header.size, diag)); } else if (!memcmp(header.type, "co64", 4)) { - AVIF_CHECKRES(avifParseChunkOffsetBox(track->sampleTable, AVIF_TRUE, avifROStreamCurrent(&s), header.size, diag)); + AVIF_CHECKRES( + avifParseChunkOffsetBox(track->meta->decoder, track->sampleTable, AVIF_TRUE, avifROStreamCurrent(&s), header.size, diag)); } else if (!memcmp(header.type, "stsc", 4)) { - AVIF_CHECKRES(avifParseSampleToChunkBox(track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); + AVIF_CHECKRES(avifParseSampleToChunkBox(track->meta->decoder, track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); } else if (!memcmp(header.type, "stsz", 4)) { - AVIF_CHECKRES(avifParseSampleSizeBox(track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); + AVIF_CHECKRES(avifParseSampleSizeBox(track->meta->decoder, track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); } else if (!memcmp(header.type, "stss", 4)) { - AVIF_CHECKRES(avifParseSyncSampleBox(track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); + AVIF_CHECKRES(avifParseSyncSampleBox(track->meta->decoder, track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); } else if (!memcmp(header.type, "stts", 4)) { - AVIF_CHECKRES(avifParseTimeToSampleBox(track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); + AVIF_CHECKRES(avifParseTimeToSampleBox(track->meta->decoder, track->sampleTable, avifROStreamCurrent(&s), header.size, diag)); } else if (!memcmp(header.type, "stsd", 4)) { - AVIF_CHECKRES(avifParseSampleDescriptionBox(track->sampleTable, + AVIF_CHECKRES(avifParseSampleDescriptionBox(track->meta->decoder, + track->sampleTable, rawOffset + avifROStreamOffset(&s), avifROStreamCurrent(&s), header.size, @@ -5084,6 +5170,10 @@ avifDecoder * avifDecoderCreate(void) decoder->imageCountLimit = AVIF_DEFAULT_IMAGE_COUNT_LIMIT; decoder->strictFlags = AVIF_STRICT_ENABLED; decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_DECODE_DEFAULT; + decoder->itemCountLimit = AVIF_DEFAULT_ITEM_COUNT_LIMIT; + decoder->propertyCountLimit = AVIF_DEFAULT_PROPERTY_COUNT_LIMIT; + decoder->extentCountLimit = AVIF_DEFAULT_EXTENT_COUNT_LIMIT; + decoder->groupCountLimit = AVIF_DEFAULT_GROUP_COUNT_LIMIT; return decoder; } @@ -5310,9 +5400,8 @@ avifResult avifDecoderParse(avifDecoder * decoder) // ----------------------------------------------------------------------- // Parse BMFF boxes - decoder->data = avifDecoderDataCreate(); + decoder->data = avifDecoderDataCreate(decoder); AVIF_CHECKERR(decoder->data != NULL, AVIF_RESULT_OUT_OF_MEMORY); - decoder->data->diag = &decoder->diag; AVIF_CHECKRES(avifParse(decoder));