Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci-android-emulator-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ jobs:
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
id: setup-ndk
with:
# r25c is the same as 25.2.9519653.
ndk-version: r25c
# r27c is the same as 27.2.12479018.
ndk-version: r27c
add-to-path: false
- uses: ./.github/actions/setup-linux
with:
Expand All @@ -62,6 +62,6 @@ jobs:
working-directory: android_jni
api-level: 31
force-avd-creation: false
ndk: 25.2.9519653
ndk: 27.2.12479018
arch: x86_64
script: ./gradlew cAT
4 changes: 2 additions & 2 deletions .github/workflows/ci-android-jni.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ jobs:
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
id: setup-ndk
with:
# r25c is the same as 25.2.9519653.
ndk-version: r25c
# r27c is the same as 27.2.12479018.
ndk-version: r27c
add-to-path: false
- uses: ./.github/actions/setup-linux
with:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ The changes are relative to the previous release, unless the baseline is specifi
* Add the ignoreICC option to avifDecoder
* avifenc: add --ignore-alpha flag to discard alpha channel on encode
* avifgainmaputil: add --ignore-alpha flag to discard alpha channel
* Android JNI: enable LTO/IPO for Release native builds (dav1d/libyuv Android
scripts and LocalDav1d on Android also build with LTO)
* ext/libyuv_android.sh: disable JPEG/MJPEG (`CMAKE_DISABLE_FIND_PACKAGE_JPEG`)

### Changed since 1.4.2
* Android JNI: use link-u/dav1d (`avif` branch) instead of videolan dav1d

* Update LocalAvm.cmake: v1.0.0
* Update libyuv.cmd/LocalLibyuv.cmake: 5d03bf9ba (1949)
Expand Down
18 changes: 15 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ option(AVIF_ENABLE_WERROR "Treat all compiler warnings as errors" OFF)
option(AVIF_ENABLE_EXPERIMENTAL_MINI "Enable experimental reduced header" OFF)
option(AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI "Enable experimental PixelInformationProperty syntax from HEIF 3rd Ed. Amd2" OFF)

# Decode-only: exclude encoder (src/write.c), colrconvert.c, sampletransform.c;
# stub encoder / gainmap Apply-Compute / RGB→YUV / sample-transform APIs; slim reformat paths.
option(AVIF_DECODE_ONLY "Build decode-only (no encoder / write.c)" OFF)

set(AVIF_PKG_CONFIG_EXTRA_LIBS_PRIVATE "")
set(AVIF_PKG_CONFIG_EXTRA_REQUIRES_PRIVATE "")

Expand Down Expand Up @@ -358,11 +362,14 @@ if(AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI)
add_compile_definitions(AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI)
endif()

if(AVIF_DECODE_ONLY)
add_compile_definitions(AVIF_DECODE_ONLY)
endif()

set(AVIF_SRCS
src/alpha.c
src/avif.c
src/colr.c
src/colrconvert.c
src/diag.c
src/exif.c
src/gainmap.c
Expand All @@ -375,13 +382,18 @@ set(AVIF_SRCS
src/reformat.c
src/reformat_libsharpyuv.c
src/reformat_libyuv.c
src/sampletransform.c
src/scale.c
src/stream.c
src/utils.c
src/write.c
)

if(AVIF_DECODE_ONLY)
list(APPEND AVIF_SRCS src/encode_stubs.c)
message(STATUS "libavif: AVIF_DECODE_ONLY enabled (encoder/write.c, colrconvert.c, sampletransform.c excluded)")
else()
list(APPEND AVIF_SRCS src/write.c src/colrconvert.c src/sampletransform.c)
endif()

if(AVIF_ENABLE_COMPLIANCE_WARDEN)
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ext/ComplianceWarden")
message(FATAL_ERROR "AVIF_ENABLE_COMPLIANCE_WARDEN: ext/ComplianceWarden is missing, bailing out")
Expand Down
92 changes: 91 additions & 1 deletion android_jni/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ $ git clone https://github.com/AOMediaCodec/libavif.git
$ cd libavif
```

Step 2 - Set the SDK and NDK paths in environment variables. (Recommended Android NDK revision: r25c)
Step 2 - Set the SDK and NDK paths in environment variables. (Recommended Android NDK revision: r27c / `27.2.12479018`)

```
$ export ANDROID_SDK_ROOT="/path/to/android/sdk"
$ export ANDROID_NDK_HOME="/path/to/android/ndk"
```

Use a **host-matching** NDK (Linux NDK on Linux/WSL, Darwin NDK on macOS). The Windows
NDK toolchain cannot be used from WSL/Linux.

Step 3 - Checkout and build dav1d (or libgav1)

```
Expand All @@ -39,6 +42,26 @@ $ ./dav1d_android.sh "${ANDROID_NDK_HOME}"
$ cd ..
```

`dav1d_android.sh` clones [link-u/dav1d](https://github.com/link-u/dav1d)
(`avif` branch). If `ext/dav1d` is absent, the Android JNI CMake LOCAL path
fetches the same repository/branch via `LocalDav1d.cmake`.

The script generates meson cross-files targeting **API 21** for all ABIs (required for
NDK r27+, which removed API levels below 21). Override with `ANDROID_API` if needed.

The Android dav1d build is configured with `-Dbitdepths=8` (8-bit AV1 only). Re-run the
script after changing this option so that all ABIs are rebuilt.

The Android JNI Release build enables LTO/IPO (`AVIF_ANDROID_ENABLE_LTO`, default ON).
`dav1d_android.sh` and `libyuv_android.sh` build with LTO so those static libraries
participate in the final `libavif_android.so` link. Re-run both scripts after updating
them if you previously built without LTO. Pass `-DAVIF_ANDROID_ENABLE_LTO=OFF` via
Gradle `android.extraCMakeFlags` to disable.

Instrumented tests assume this 8-bit-only dav1d build: 10/12-bit assets are still parsed via
`getInfo`, but decode APIs are expected to fail for those streams. If you rebuild dav1d with full
bitdepths (`-Dbitdepths=8,16`), update the tests accordingly.

If you want to use libgav1 instead:

```
Expand All @@ -59,6 +82,9 @@ $ ./libyuv_android.sh "${ANDROID_NDK_HOME}"
$ cd ..
```

`libyuv_android.sh` disables JPEG/MJPEG support (`CMAKE_DISABLE_FIND_PACKAGE_JPEG`)
since AVIF decode does not use it.

If you do not want to use libyuv, then update
[CMakeLists.txt](avifandroidjni/src/main/jni/CMakeLists.txt) as follows:
* Set `AVIF_LIBYUV` to `OFF`.
Expand All @@ -83,6 +109,10 @@ Step 1 - Build the library
Make sure to build the library by following the steps under
[Generate the AAR package](#generate-the-aar-package) section above.

These tests assume the default Android dav1d build (`-Dbitdepths=8`). Decode success
cases cover 8-bit images only; 10/12-bit assets verify `getInfo` and clean decode
failure.

Step 2 - Set up a device/emulator

Make sure that a device or an emulator has been set up and is available via
Expand All @@ -105,3 +135,63 @@ To build the project from within Android Studio, follow the all the steps from t

Maven hosted version of libavif can be found here:
https://repo1.maven.org/maven2/org/aomedia/avif/android/avif/

## Hardware Buffer Decoding

Use `AvifHardwareDecoder` on API 29+ to decode directly into `HardwareBuffer` objects suitable
for GPU sampling (for example via `Bitmap.wrapHardwareBuffer`).

By default, output uses `RGBA_8888`. Pass `allowGray565 = true` to opt in to **Gray565** packing
(`HardwareBuffer.RGB_565`) when **all** of the following hold:

* The decoded image is 8-bit monochrome (`YUV400`) with no alpha plane.
* `allowGray565` is `true`.

If any condition fails, or if `AHardwareBuffer_allocate` fails for `R5G6B5`, the decoder falls
back to `RGBA_8888`. (`R5G6B5` is a universally supported HardwareBuffer format from API 26; the
caller's API 29 gate for `wrapHardwareBuffer` is sufficient—there is no API 35 requirement.)

`Bitmap.Config.RGB_565` is **not** supported on the software Bitmap decode path; only Gray565 via
`HardwareBuffer` (above) uses the `RGB_565` container.

### Gray565 packing

`RGB_565` here is **not** a true color RGB565 image. It is an 8-bit grayscale value packed into
the RGB565 bit fields (R is the most-significant field):

* Encoding: `Y = 4 * G6 + (R5 & 3)`, `B5 = 0`
equivalently `pixel = ((Y & 3) << 11) | ((Y >> 2) << 5)`.
* Limited-range Y (16–235) is expanded to 0–255 in the same LUT that builds the packed value;
full-range Y is used as-is. Color matrix coefficients (BT.601/709) do not apply on this path.
* Scaling (when requested) is applied with `avifImageScale` on the YUV image **before** packing.

### Display responsibility (required)

Drawing a Gray565 buffer without a restore transform looks like greenish noise, not grayscale.
Callers **must** apply a restore `ColorMatrix` / `ColorMatrixColorFilter` that maps each of R, G,
B to:

`31/255 · r + 252/255 · g`

(where `r`/`g` are the 8-bit expanded R/G channels from the RGB565 sample).

### Example

```java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
HardwareBuffer buffer =
AvifHardwareDecoder.decodeToHardwareBuffer(
encoded, encoded.remaining(), 0, 0, threads, /* allowGray565= */ true);
if (buffer != null) {
if (buffer.getFormat() == HardwareBuffer.RGB_565) {
// Apply the Gray565 restore ColorMatrix before drawing.
}
buffer.close();
}
}
```

Range-specific monochrome test assets (`mono_8bpc_limited.avif`, `mono_8bpc_full.avif`,
`mono_8bpc_ramp_full.avif`) can be generated with
[`generate_mono_test_assets.sh`](avifandroidjni/src/androidTest/assets/generate_mono_test_assets.sh).

2 changes: 1 addition & 1 deletion android_jni/avifandroidjni/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
android {
namespace 'org.aomedia.avif.android'
compileSdk 31
ndkVersion "25.2.9519653"
ndkVersion "27.2.12479018"

defaultConfig {
minSdk 21
Expand Down
3 changes: 3 additions & 0 deletions android_jni/avifandroidjni/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
-keep class org.aomedia.avif.android.AvifDecoder$Info {
*;
}
-keep class org.aomedia.avif.android.AvifHardwareDecoder {
*;
}
14 changes: 12 additions & 2 deletions android_jni/avifandroidjni/src/androidTest/assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ Test files for still AVIF decoding.

### blue-and-magenta-crop.avif

Test file with cropping. The image has an encoded size of 320x280. If the Clean
Aperture box is honored, the size of the displayed image will be 180x100.
Test file that includes a Clean Aperture (`clap`) box. Encoded size is 320x280
(the JNI decoder ignores `clap` and always uses the encoded dimensions; clap
would otherwise display as 180x100).
[Source](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/images/resources/avif/blue-and-magenta-crop.avif;l=1;drc=3a13337543c1e7de6914f87cd6f02ab06751c572).

## Sub-Directory: animated_avif
Expand All @@ -22,3 +23,12 @@ Test files for animated AVIF decoding.

alpha_video.avif is patched at 0-indexed byte #259 replaced with 0x04 instead of
0x00 as [0x00 is an invalid item_id for the iref box](https://github.com/AOMediaCodec/av1-avif/issues/217).

### mono_8bpc_limited.avif, mono_8bpc_full.avif, mono_8bpc_ramp_full.avif

8-bit monochrome (`YUV400`) 256×16 ramps whose columns cover every code 0–255, with explicit
limited/full range for Gray565 LUT tests (`mono_8bpc_ramp_full.avif` is a copy of the full-range
file used by the restore ColorMatrix golden test).

Generate with [generate_mono_test_assets.sh](generate_mono_test_assets.sh) (uses
[gen_mono_ramp.c](gen_mono_ramp.c) against a libavif+aom build, or `avifenc` if available).
88 changes: 88 additions & 0 deletions android_jni/avifandroidjni/src/androidTest/assets/gen_mono_ramp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: BSD-2-Clause
// Helper: encode a 256x16 YUV400 ramp AVIF (limited or full range).
#include "avif/avif.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <limited|full> <out.avif>\n", argv[0]);
return 1;
}
const int limited = strcmp(argv[1], "limited") == 0;
if (!limited && strcmp(argv[1], "full") != 0) {
fprintf(stderr, "range must be limited or full\n");
return 1;
}

const uint32_t width = 256;
const uint32_t height = 16;
avifImage* image = avifImageCreate(width, height, 8, AVIF_PIXEL_FORMAT_YUV400);
if (!image) {
fprintf(stderr, "avifImageCreate failed\n");
return 1;
}
image->yuvRange = limited ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL;
image->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;

if (avifImageAllocatePlanes(image, AVIF_PLANES_YUV) != AVIF_RESULT_OK) {
fprintf(stderr, "avifImageAllocatePlanes failed\n");
avifImageDestroy(image);
return 1;
}

for (uint32_t y = 0; y < height; ++y) {
uint8_t* row = image->yuvPlanes[AVIF_CHAN_Y] + y * image->yuvRowBytes[AVIF_CHAN_Y];
for (uint32_t x = 0; x < width; ++x) {
row[x] = (uint8_t)x;
}
}

avifRWData raw = AVIF_DATA_EMPTY;
avifEncoder* encoder = avifEncoderCreate();
if (!encoder) {
fprintf(stderr, "avifEncoderCreate failed\n");
avifImageDestroy(image);
return 1;
}
// Near-lossless so Gray565 golden tests see every code.
encoder->quality = 100;
encoder->qualityAlpha = 100;
encoder->speed = 6;

avifResult res = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
if (res != AVIF_RESULT_OK) {
fprintf(stderr, "avifEncoderAddImage: %s\n", avifResultToString(res));
avifEncoderDestroy(encoder);
avifImageDestroy(image);
return 1;
}
res = avifEncoderFinish(encoder, &raw);
if (res != AVIF_RESULT_OK) {
fprintf(stderr, "avifEncoderFinish: %s\n", avifResultToString(res));
avifEncoderDestroy(encoder);
avifImageDestroy(image);
return 1;
}

FILE* f = fopen(argv[2], "wb");
if (!f) {
perror("fopen");
avifRWDataFree(&raw);
avifEncoderDestroy(encoder);
avifImageDestroy(image);
return 1;
}
fwrite(raw.data, 1, raw.size, f);
fclose(f);
printf("Wrote %s (%zu bytes)\n", argv[2], raw.size);

avifRWDataFree(&raw);
avifEncoderDestroy(encoder);
avifImageDestroy(image);
return 0;
}
Loading
Loading