Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions android_jni/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ $ ./dav1d_android.sh "${ANDROID_NDK_HOME}"
$ cd ..
```

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.

If you want to use libgav1 instead:

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.ColorSpace;
import android.hardware.HardwareBuffer;
import android.os.Build;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -256,6 +259,74 @@ public void testDecodeRegularClass() throws IOException {
decoder.release();
}

@Test
public void testDecodeToHardwareBuffer() throws IOException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return;
}
if (image.isAnimated || config != Config.ARGB_8888) {
return;
}
ByteBuffer buffer = image.getBuffer();
assertThat(buffer).isNotNull();
Info info = new Info();
assertThat(AvifDecoder.getInfo(buffer, buffer.remaining(), info)).isTrue();

HardwareBuffer hardwareBuffer =
AvifDecoder.decodeToHardwareBuffer(buffer, buffer.remaining(), image.threads);
assertThat(hardwareBuffer).isNotNull();
assertThat(hardwareBuffer.getWidth()).isEqualTo(info.width);
assertThat(hardwareBuffer.getHeight()).isEqualTo(info.height);
assertThat(hardwareBuffer.getFormat()).isEqualTo(HardwareBuffer.RGBA_8888);
hardwareBuffer.close();

for (float scaleFactor : SCALE_FACTORS) {
int targetWidth = (int) (info.width * scaleFactor);
int targetHeight = (int) (info.height * scaleFactor);
hardwareBuffer =
AvifDecoder.decodeToHardwareBuffer(
buffer, buffer.remaining(), targetWidth, targetHeight, image.threads);
assertThat(hardwareBuffer).isNotNull();
assertThat(hardwareBuffer.getWidth()).isEqualTo(targetWidth);
assertThat(hardwareBuffer.getHeight()).isEqualTo(targetHeight);
Bitmap hardwareBitmap =
Bitmap.wrapHardwareBuffer(hardwareBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
assertThat(hardwareBitmap).isNotNull();
assertThat(hardwareBitmap.getWidth()).isEqualTo(targetWidth);
assertThat(hardwareBitmap.getHeight()).isEqualTo(targetHeight);
hardwareBitmap.recycle();
hardwareBuffer.close();
}
}

@Test
public void testDecodeToHardwareBufferRegularClass() throws IOException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return;
}
if (config != Config.ARGB_8888) {
return;
}
ByteBuffer buffer = image.getBuffer();
assertThat(buffer).isNotNull();
AvifDecoder decoder = AvifDecoder.create(buffer, image.threads);
assertThat(decoder).isNotNull();
for (int i = 0; i < image.frameCount; ++i) {
assertThat(decoder.nextFrameIndex()).isEqualTo(i);
HardwareBuffer hardwareBuffer = decoder.nextFrameHardwareBuffer();
assertThat(hardwareBuffer).isNotNull();
assertThat(hardwareBuffer.getWidth()).isEqualTo(image.width);
assertThat(hardwareBuffer.getHeight()).isEqualTo(image.height);
hardwareBuffer.close();
}
if (image.isAnimated) {
HardwareBuffer hardwareBuffer = decoder.nthFrameHardwareBuffer(0);
assertThat(hardwareBuffer).isNotNull();
hardwareBuffer.close();
}
decoder.release();
}

@Test
public void testUtilityFunctions() throws IOException {
// Test the avifResult value whose value and string representations are least likely to change.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package org.aomedia.avif.android;

import android.graphics.Bitmap;
import android.hardware.HardwareBuffer;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.nio.ByteBuffer;

/**
Expand All @@ -16,8 +18,9 @@
*
* <p>This class can be accessed statically without instantiating an object. This is useful to
* simply sniff and decode still AVIF images without having to maintain any decoder state. The
* following are the methods that can be accessed this way: {@link isAvifImage}, {@link getInfo} and
* {@link decode}. The {@link Info} inner class is used only in this case.
* following are the methods that can be accessed this way: {@link isAvifImage}, {@link getInfo},
* {@link decode} and {@link decodeToHardwareBuffer}. The {@link Info} inner class is used only in
* this case.
*
* <p>2) As an instantiated regular class.
*
Expand Down Expand Up @@ -118,6 +121,44 @@ public static boolean decode(ByteBuffer encoded, int length, Bitmap bitmap) {
*/
public static native boolean decode(ByteBuffer encoded, int length, Bitmap bitmap, int threads);

/**
* Decodes the AVIF image into an {@link HardwareBuffer} with RGBA_8888 pixels.
*
* <p>The returned buffer can be wrapped as a hardware {@link Bitmap} via {@link
* Bitmap#wrapHardwareBuffer(HardwareBuffer, android.graphics.ColorSpace)} on API 29+. Callers on
* older API levels must not use this method.
*
* <p>If {@code targetWidth} and {@code targetHeight} are both positive, the decoded image is
* scaled to those dimensions before RGB conversion. Otherwise the cropped image dimensions are
* used.
*
* @param encoded The encoded AVIF image. encoded.position() must be 0.
* @param length Length of the encoded buffer.
* @param targetWidth Desired output width, or 0 to use the image width.
* @param targetHeight Desired output height, or 0 to use the image height.
* @param threads Number of threads to be used for the AVIF decode.
* @return a HardwareBuffer on success, or null on failure.
*/
@RequiresApi(29)
public static HardwareBuffer decodeToHardwareBuffer(
ByteBuffer encoded, int length, int targetWidth, int targetHeight, int threads) {
return decodeToHardwareBufferNative(encoded, length, targetWidth, targetHeight, threads);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

HardwareBuffer クラスは Android API 26 (Android 8.0) で導入されました。

AvifDecoder のメソッドシグネチャ(decodeToHardwareBuffernextFrameHardwareBuffer など)に HardwareBuffer を含めると、Android API < 26 のデバイスでクラスの検証(verification)に失敗し、VerifyError または NoClassDefFoundError が発生します。これにより、古いデバイスでは通常の Bitmap デコード機能を含め、AvifDecoder クラス自体のロードが完全に不可能になります。

この問題を回避するために、すべての HardwareBuffer 関連のメソッドを、API >= 29 でのみロードされる別のヘルパークラス(例: AvifHardwareDecoder または遅延ロードされるインナークラス)に移動することを推奨します。これにより、古いデバイスでも AvifDecoder を安全にロードして使用できるようになります。


/**
* Decodes the AVIF image into an {@link HardwareBuffer} at the cropped image dimensions.
*
* @see #decodeToHardwareBuffer(ByteBuffer, int, int, int, int)
*/
@RequiresApi(29)
public static HardwareBuffer decodeToHardwareBuffer(
ByteBuffer encoded, int length, int threads) {
return decodeToHardwareBuffer(encoded, length, 0, 0, threads);
}

private static native HardwareBuffer decodeToHardwareBufferNative(
ByteBuffer encoded, int length, int targetWidth, int targetHeight, int threads);

/** Get the width of the image. */
public int getWidth() {
return width;
Expand Down Expand Up @@ -208,6 +249,33 @@ public int nextFrame(Bitmap bitmap) {

private native int nextFrame(long decoder, Bitmap bitmap);

/**
* Decodes the next frame of the animated AVIF into an {@link HardwareBuffer}.
*
* @param targetWidth Desired output width, or 0 to use the image width.
* @param targetHeight Desired output height, or 0 to use the image height.
* @return a HardwareBuffer on success, or null on failure.
* @see #decodeToHardwareBuffer(ByteBuffer, int, int, int, int)
*/
@RequiresApi(29)
@Nullable
public HardwareBuffer nextFrameHardwareBuffer(int targetWidth, int targetHeight) {
return nextFrameHardwareBuffer(decoder, targetWidth, targetHeight);
}

/**
* Decodes the next frame of the animated AVIF into an {@link HardwareBuffer} at the cropped
* image dimensions.
*/
@RequiresApi(29)
@Nullable
public HardwareBuffer nextFrameHardwareBuffer() {
return nextFrameHardwareBuffer(decoder, 0, 0);
}

private native HardwareBuffer nextFrameHardwareBuffer(
long decoder, int targetWidth, int targetHeight);

/**
* Get the 0-based index of the frame that will be returned by the next call to {@link nextFrame}.
* If the returned value is same as {@link getFrameCount}, then the next call to {@link nextFrame}
Expand Down Expand Up @@ -238,6 +306,34 @@ public int nthFrame(int n, Bitmap bitmap) {

private native int nthFrame(long decoder, int n, Bitmap bitmap);

/**
* Decodes the nth frame of the animated AVIF into an {@link HardwareBuffer}.
*
* @param n The zero-based index of the frame to be decoded.
* @param targetWidth Desired output width, or 0 to use the image width.
* @param targetHeight Desired output height, or 0 to use the image height.
* @return a HardwareBuffer on success, or null on failure.
* @see #decodeToHardwareBuffer(ByteBuffer, int, int, int, int)
*/
@RequiresApi(29)
@Nullable
public HardwareBuffer nthFrameHardwareBuffer(int n, int targetWidth, int targetHeight) {
return nthFrameHardwareBuffer(decoder, n, targetWidth, targetHeight);
}

/**
* Decodes the nth frame of the animated AVIF into an {@link HardwareBuffer} at the cropped
* image dimensions.
*/
@RequiresApi(29)
@Nullable
public HardwareBuffer nthFrameHardwareBuffer(int n) {
return nthFrameHardwareBuffer(decoder, n, 0, 0);
}

private native HardwareBuffer nthFrameHardwareBuffer(
long decoder, int n, int targetWidth, int targetHeight);

/**
* Returns a String describing an avifResult enum value.
*
Expand Down
2 changes: 1 addition & 1 deletion android_jni/avifandroidjni/src/main/jni/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ include_directories(${CPU_FEATURES_DIR})
add_library(cpufeatures STATIC "${CPU_FEATURES_DIR}/cpu-features.c")

target_link_options(avif_android PRIVATE "-Wl,-z,max-page-size=16384")
target_link_libraries(avif_android jnigraphics avif log cpufeatures)
target_link_libraries(avif_android jnigraphics nativewindow avif log cpufeatures)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

target_link_librariesnativewindow を直接リンクすると、libavif_android.solibnativewindow.so へのハード依存関係が導入されます。

libnativewindow.so は Android API 26 (Android 8.0) で導入されたため、Android API < 26 のデバイスで libavif_android.so をロードしようとすると、ハードウェアバッファデコード機能を使用していない場合でも、即座に UnsatisfiedLinkError(例: library "libnativewindow.so" not found)が発生してアプリがクラッシュします。

古い Android バージョンとの後方互換性を維持するために、target_link_libraries から nativewindow を削除し、API レベル 29 以上で実行されている場合にのみ、dlopen を使用して libnativewindow.so を動的にロードし、dlsym を使用して必要な AHardwareBuffer_* シンボルを解決するようにしてください。

target_link_libraries(avif_android jnigraphics avif log cpufeatures)

Loading
Loading