rav1e codec support (encode-only), all codecs can coexist peacefully now, and can be queried for availability or specifically chosen at encode/decode time
diff --git a/.gitignore b/.gitignore
index 2ef5f4f..e7bdc00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
/build*
/ext/aom
+/ext/rav1e
+/ext/dav1d
diff --git a/.travis.yml b/.travis.yml
index c038653..576eef6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@
- cd ..
- mkdir build
- cd build
- - cmake -DCMAKE_BUILD_TYPE=Debug -DAVIF_BUILD_AOM=1 ..
+ - cmake -DCMAKE_BUILD_TYPE=Debug -DAVIF_CODEC_AOM=1 -DAVIF_LOCAL_AOM=1 ..
script:
- make
@@ -32,7 +32,7 @@
- cd ..
- mkdir build
- cd build
- - cmake -DCMAKE_BUILD_TYPE=Release -DAVIF_BUILD_AOM=1 ..
+ - cmake -DCMAKE_BUILD_TYPE=Release -DAVIF_CODEC_AOM=1 -DAVIF_LOCAL_AOM=1 ..
script:
- make
@@ -48,7 +48,7 @@
- cd ..
- mkdir build
- cd build
- - cmake -DCMAKE_BUILD_TYPE=Debug -DAVIF_BUILD_AOM=1 ..
+ - cmake -DCMAKE_BUILD_TYPE=Debug -DAVIF_CODEC_AOM=1 -DAVIF_LOCAL_AOM=1 ..
script:
- make
@@ -64,6 +64,6 @@
- cd ..
- mkdir build
- cd build
- - cmake -DCMAKE_BUILD_TYPE=Release -DAVIF_BUILD_AOM=1 ..
+ - cmake -DCMAKE_BUILD_TYPE=Release -DAVIF_CODEC_AOM=1 -DAVIF_LOCAL_AOM=1 ..
script:
- make
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e5590a..f5e2c0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+### Added
+- rav1e codec support (encode-only)
+- `rav1e.cmd` and `dav1d.cmd` to ext
+
+### Changed
+- All codecs can coexist peacefully now, and can be queried for availability or specifically chosen at encode/decode time
+- Updated README to indicate changes to CMake which facilitate codec reorg
## [0.4.2] - 2019-10-17
### Changed
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1c90eb..32dc34f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,10 +8,13 @@
add_definitions(-std=c99) # Enforce C99 for gcc
endif()
-option(AVIF_BUILD_AOM "Build the AOM codec by providing your own copy of the repo in ext/aom" OFF)
-
-option(AVIF_CODEC_AOM "Use the AOM codec for encoding (and decoding if no other decoder is present)" ON)
+option(AVIF_CODEC_AOM "Use the AOM codec for encoding (and decoding if no other decoder is present)" OFF)
option(AVIF_CODEC_DAV1D "Use the dav1d codec for decoding (overrides AOM decoding if also enabled)" OFF)
+option(AVIF_CODEC_RAV1E "Use the rav1e codec for encoding (overrides AOM encoding if also enabled)" OFF)
+
+option(AVIF_LOCAL_AOM "Build the AOM codec by providing your own copy of the repo in ext/aom" OFF)
+option(AVIF_LOCAL_DAV1D "Build the dav1d codec by providing your own copy of the repo in ext/dav1d" OFF)
+option(AVIF_LOCAL_RAV1E "Build the rav1e codec by providing your own copy of the repo in ext/rav1e" OFF)
add_subdirectory(ext)
@@ -80,16 +83,98 @@
)
set(AVIF_CODEC_LIBARIES)
-if(AVIF_CODEC_AOM)
- message(STATUS "libavif: Encoding library: aom")
- if(NOT AVIF_CODEC_DAV1D)
- message(STATUS "libavif: Decoding library: aom")
+
+if(AVIF_CODEC_DAV1D)
+ message(STATUS "libavif: Codec enabled: dav1d (decode)")
+ add_definitions(-DAVIF_CODEC_DAV1D=1)
+ set(AVIF_SRCS ${AVIF_SRCS}
+ src/codec_dav1d.c
+ )
+
+ if(AVIF_LOCAL_DAV1D)
+ set(LIB_FILENAME "${CMAKE_CURRENT_SOURCE_DIR}/ext/dav1d/build/src/libdav1d.a")
+ if(NOT EXISTS "${LIB_FILENAME}")
+ message(FATAL_ERROR "libavif: ${LIB_FILENAME} is missing, bailing out")
+ endif()
+
+ include_directories(
+ "${CMAKE_CURRENT_SOURCE_DIR}/ext/dav1d/build"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ext/dav1d/build/include"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ext/dav1d/build/include/dav1d"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ext/dav1d/include"
+ )
+ set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} ${LIB_FILENAME})
+ else()
+ # Check to see if dav1d is independently being built by the outer CMake project
+ if(TARGET dav1d)
+ set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} dav1d)
+ else()
+ find_library(LIBDAV1D_LIBRARY_PATH NAMES dav1d)
+ if(NOT LIBDAV1D_LIBRARY_PATH)
+ message(FATAL_ERROR "libavif: dav1d library not found")
+ endif()
+ find_path (LIBDAV1D_INCLUDE_PATH dav1d/dav1d.h)
+ if(NOT LIBDAV1D_INCLUDE_PATH)
+ message(FATAL_ERROR "libavif: dav1d library includes not found")
+ endif()
+ message(STATUS "LIBDAV1D_INCLUDE_PATH: ${LIBDAV1D_INCLUDE_PATH}")
+ include_directories(${LIBDAV1D_INCLUDE_PATH})
+ endif()
endif()
+endif()
+
+
+if(AVIF_CODEC_RAV1E)
+ message(STATUS "libavif: Codec enabled: rav1e (encode)")
+ add_definitions(-DAVIF_CODEC_RAV1E=1)
+ set(AVIF_SRCS ${AVIF_SRCS}
+ src/codec_rav1e.c
+ )
+
+ if(AVIF_LOCAL_RAV1E)
+ set(LIB_FILENAME "${CMAKE_CURRENT_SOURCE_DIR}/ext/rav1e/target/release/rav1e.lib")
+ if(NOT EXISTS "${LIB_FILENAME}")
+ set(LIB_FILENAME "${CMAKE_CURRENT_SOURCE_DIR}/ext/rav1e/target/release/librav1e.a")
+ if(NOT EXISTS "${LIB_FILENAME}")
+ message(FATAL_ERROR "libavif: compiled rav1e library is missing (in ext/rav1e/target/release), bailing out")
+ endif()
+ endif()
+
+ include_directories(
+ "${CMAKE_CURRENT_SOURCE_DIR}/ext/rav1e/target/release"
+ )
+ set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} ${LIB_FILENAME})
+ else()
+ # Check to see if dav1d is independently being built by the outer CMake project
+ if(TARGET dav1d)
+ set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} dav1d)
+ else()
+ find_library(LIBDAV1D_LIBRARY_PATH NAMES dav1d)
+ if(NOT LIBDAV1D_LIBRARY_PATH)
+ message(FATAL_ERROR "libavif: dav1d library not found")
+ endif()
+ find_path (LIBDAV1D_INCLUDE_PATH dav1d/dav1d.h)
+ if(NOT LIBDAV1D_INCLUDE_PATH)
+ message(FATAL_ERROR "libavif: dav1d library includes not found")
+ endif()
+ message(STATUS "LIBDAV1D_INCLUDE_PATH: ${LIBDAV1D_INCLUDE_PATH}")
+ include_directories(${LIBDAV1D_INCLUDE_PATH})
+ endif()
+ endif()
+
+ if(WIN32)
+ # Unfortunately, rav1e requires a few more libraries
+ set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} ws2_32.lib userenv.lib)
+ endif()
+endif()
+
+if(AVIF_CODEC_AOM)
+ message(STATUS "libavif: Codec enabled: aom (encode/decode)")
add_definitions(-DAVIF_CODEC_AOM=1)
set(AVIF_SRCS ${AVIF_SRCS}
src/codec_aom.c
)
- if(AVIF_BUILD_AOM)
+ if(AVIF_LOCAL_AOM)
include_directories(
"${CMAKE_CURRENT_SOURCE_DIR}/ext/aom"
"${AOM_BINARY_DIR}"
@@ -111,32 +196,8 @@
endif()
endif()
set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} aom)
-else()
- message(STATUS "libavif: Encoding library: (none)")
endif()
-if(AVIF_CODEC_DAV1D)
- message(STATUS "libavif: Decoding library: dav1d")
- add_definitions(-DAVIF_CODEC_DAV1D=1)
- set(AVIF_SRCS ${AVIF_SRCS}
- src/codec_dav1d.c
- )
-
- # Check to see if dav1d is independently being built by the outer CMake project
- if(NOT TARGET dav1d)
- find_library(LIBDAV1D_LIBRARY_PATH NAMES dav1d)
- if(NOT LIBDAV1D_LIBRARY_PATH)
- message(FATAL_ERROR "libavif: dav1d library not found")
- endif()
- find_path (LIBDAV1D_INCLUDE_PATH dav1d/dav1d.h)
- if(NOT LIBDAV1D_INCLUDE_PATH)
- message(FATAL_ERROR "libavif: dav1d library includes not found")
- endif()
- message(STATUS "LIBDAV1D_INCLUDE_PATH: ${LIBDAV1D_INCLUDE_PATH}")
- include_directories(${LIBDAV1D_INCLUDE_PATH})
- endif()
- set(AVIF_CODEC_LIBARIES ${AVIF_CODEC_LIBARIES} dav1d)
-endif()
if(NOT AVIF_CODEC_AOM AND NOT AVIF_CODEC_DAV1D)
message(FATAL_ERROR "libavif: No decoding library is enabled, bailing out.")
endif()
diff --git a/README.md b/README.md
index 0192cd4..aa864b5 100644
--- a/README.md
+++ b/README.md
@@ -202,14 +202,16 @@
Make sure nasm is available and in your PATH on your machine (if building
libaom), then use CMake to do a basic build (Debug or Release).
-You can choose between using `libaom` or `libdav1d` by using CMake options
-`AVIF_CODEC_AOM` and `AVIF_CODEC_DAV1D`. `libaom` must be enabled in order for
-encoding to work, and `libdav1d` overrides `libaom` for decoding if both are
-enabled. Currently `libdav1d` must be externally available (discoverable via
-CMake's `FIND_LIBRARY`) to use it.
+No AV1 codecs are enabled by default. Enable them by enabling any of the
+following CMake options:
+* AVIF_CODEC_AOM
+* AVIF_CODEC_DAV1D
+* AVIF_CODEC_RAV1E
-You can build libaom alongside libavif if you enable `AVIF_BUILD_AOM`, and you
-have a copy of the aom repo in the ext/ subdir. See ext/aom.cmd for details.
+These libraries (in their C API form) must be externally available
+(discoverable via CMake's `FIND_LIBRARY`) to use them, unless you
+build them locally from the `ext/` subdirectory, and then enabling the
+equivalent `AVIF_LOCAL_*` CMake option. See `ext/*.cmd` files for details.
# Prebuilt Library (Windows)
diff --git a/appveyor.yml b/appveyor.yml
index 26c9f7f..07fd67e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -52,7 +52,7 @@
cmake --version
- cmake .. -G %generator% -DAVIF_BUILD_AOM=1 -DAVIF_BUILD_STATIC=1 -DAVIF_BUILD_APPS=1
+ cmake .. -G %generator% -DAVIF_CODEC_AOM=1 -DAVIF_LOCAL_AOM=1 -DAVIF_LOCAL_STATIC=1 -DAVIF_LOCAL_APPS=1
after_build:
- cmd: >-
copy %CONFIGURATION%\avifenc.exe .
diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt
index 09bb835..8edf9bb 100644
--- a/ext/CMakeLists.txt
+++ b/ext/CMakeLists.txt
@@ -1,9 +1,9 @@
# Copyright 2019 Joe Drago. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause
-if(AVIF_CODEC_AOM AND AVIF_BUILD_AOM)
+if(AVIF_CODEC_AOM AND AVIF_LOCAL_AOM)
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/aom)
- message(FATAL_ERROR "libavif: AVIF_BUILD_AOM is enabled, but ext/aom dir is missing. See ext/aom.sh for information.")
+ message(FATAL_ERROR "libavif: AVIF_LOCAL_AOM is enabled, but ext/aom dir is missing. See ext/aom.sh for information.")
endif()
set(ENABLE_DOCS 0)
diff --git a/ext/aom.cmd b/ext/aom.cmd
index 08a6b27..2a28bfa 100755
--- a/ext/aom.cmd
+++ b/ext/aom.cmd
@@ -1,4 +1,4 @@
-: # If you want libavif to build libaom for you, you must clone the aom repo in this directory first, then enable CMake's AVIF_BUILD_AOM option.
+: # If you want libavif to build libaom for you, you must clone the aom repo in this directory first, then enable CMake's AVIF_CODEC_AOM and AVIF_LOCAL_AOM options.
: # The git SHA below is known to work, and will occasionally be updated. Feel free to use a more recent commit.
: # The odd choice of comment style in this file is to try to share this script between *nix and win32.
diff --git a/ext/dav1d.cmd b/ext/dav1d.cmd
new file mode 100644
index 0000000..70289c8
--- /dev/null
+++ b/ext/dav1d.cmd
@@ -0,0 +1,19 @@
+: # If you want to use a local build of dav1d, you must clone the dav1d repo in this directory first, then enable CMake's AVIF_CODEC_DAV1D and AVIF_LOCAL_DAV1D options.
+: # The git SHA below is known to work, and will occasionally be updated. Feel free to use a more recent commit.
+
+: # The odd choice of comment style in this file is to try to share this script between *nix and win32.
+
+: # meson and ninja must be in your PATH.
+
+: # If you're running this on Windows, be sure you've already run this (from your VC2017 install dir):
+: # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvars64.bat"
+
+git clone -n https://code.videolan.org/videolan/dav1d.git && cd dav1d && git checkout 0.5.1 && cd ..
+
+cd dav1d
+mkdir build
+cd build
+
+: # macOS might required: -Dc_args=-fno-stack-check
+meson --default-library=static --buildtype release ..
+ninja
diff --git a/ext/rav1e.cmd b/ext/rav1e.cmd
new file mode 100644
index 0000000..08b3f57
--- /dev/null
+++ b/ext/rav1e.cmd
@@ -0,0 +1,21 @@
+: # If you want to use a local build of rav1e, you must clone the rav1e repo in this directory first, then enable CMake's AVIF_CODEC_RAV1E and AVIF_LOCAL_RAV1E options.
+: # The git SHA below is known to work, and will occasionally be updated. Feel free to use a more recent commit.
+
+: # The odd choice of comment style in this file is to try to share this script between *nix and win32.
+
+: # cargo must be in your PATH. (use rustup or brew to install)
+
+: # If you're running this on Windows targeting Rust's windows-msvc, be sure you've already run this (from your VC2017 install dir):
+: # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvars64.bat"
+: #
+: # Also, the error that "The target windows-msvc is not supported yet" can safely be ignored provided that rav1e/target/release
+: # contains rav1e.h and rav1e.lib.
+
+git clone -n https://github.com/xiph/rav1e.git && cd rav1e && git checkout e4683b1846c08412fe26d4487af358383b54c81a && cd ..
+
+cd rav1e
+cargo install cbindgen
+cbindgen -c cbindgen.toml -l C -o target/release/rav1e.h --crate rav1e .
+
+cargo install cargo-c
+cargo cbuild --release
diff --git a/fuzz.sh b/fuzz.sh
index f7d7f49..b54863e 100644
--- a/fuzz.sh
+++ b/fuzz.sh
@@ -5,6 +5,6 @@
mkdir build.fuzz
cd build.fuzz
-CC=afl-clang cmake -G Ninja .. -DAVIF_CODEC_AOM=0 -DAVIF_CODEC_DAV1D=1 -DAVIF_BUILD_TESTS=1 || exit 1
+CC=afl-clang cmake -G Ninja .. -DAVIF_CODEC_AOM=0 -DAVIF_BUILD_AOM=0 -DAVIF_CODEC_DAV1D=1 -DAVIF_LOCAL_DAV1D=1 -DAVIF_BUILD_TESTS=1 || exit 1
ninja || exit 1
AFL_EXIT_WHEN_DONE=1 afl-fuzz -t 200 -i ../tests/inputs -o output.$(date "+%Y.%m.%d-%H.%M.%S") ./aviffuzz @@
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 576587f..29cd4a3 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -335,6 +335,27 @@
avifBool avifPrepareReformatState(avifImage * image, avifReformatState * state);
// ---------------------------------------------------------------------------
+// Codec selection
+
+typedef enum avifCodecChoice
+{
+ AVIF_CODEC_CHOICE_AUTO = 0,
+ AVIF_CODEC_CHOICE_AOM,
+ AVIF_CODEC_CHOICE_DAV1D, // Decode only
+ AVIF_CODEC_CHOICE_RAV1E // Encode only
+} avifCodecChoice;
+
+typedef enum avifCodecFlags
+{
+ AVIF_CODEC_FLAG_CAN_DECODE = (1 << 0),
+ AVIF_CODEC_FLAG_CAN_ENCODE = (1 << 1)
+} avifCodecFlags;
+
+// If this returns NULL, the codec choice/flag combination is unavailable
+const char * avifCodecName(avifCodecChoice choice, uint32_t requiredFlags);
+avifCodecChoice avifCodecChoiceFromName(const char * name);
+
+// ---------------------------------------------------------------------------
// avifDecoder
// Useful stats related to a read/write
@@ -375,6 +396,9 @@
typedef struct avifDecoder
{
+ // Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c)
+ avifCodecChoice codecChoice;
+
// avifs can have multiple sets of images in them. This specifies which to decode.
// Set this via avifDecoderSetSource().
avifDecoderSource requestedSource;
@@ -463,6 +487,9 @@
// Tiling values range [0-6], where the value indicates a request for 2^n tiles in that dimension.
typedef struct avifEncoder
{
+ // Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c)
+ avifCodecChoice codecChoice;
+
// settings
int maxThreads;
int minQuantizer;
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 846a91d..a8de61c 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -145,12 +145,16 @@
avifCodecDestroyInternalFunc destroyInternal;
} avifCodec;
-const char * avifCodecVersionAOM(void); // requires AVIF_CODEC_AOM
-avifCodec * avifCodecCreateAOM(void); // requires AVIF_CODEC_AOM
-const char * avifCodecVersionDav1d(void); // requires AVIF_CODEC_DAV1D
-avifCodec * avifCodecCreateDav1d(void); // requires AVIF_CODEC_DAV1D
+avifCodec * avifCodecCreate(avifCodecChoice choice, uint32_t requiredFlags);
void avifCodecDestroy(avifCodec * codec);
+avifCodec * avifCodecCreateAOM(void); // requires AVIF_CODEC_AOM (codec_aom.c)
+const char * avifCodecVersionAOM(void); // requires AVIF_CODEC_AOM (codec_aom.c)
+avifCodec * avifCodecCreateDav1d(void); // requires AVIF_CODEC_DAV1D (codec_dav1d.c)
+const char * avifCodecVersionDav1d(void); // requires AVIF_CODEC_DAV1D (codec_dav1d.c)
+avifCodec * avifCodecCreateRav1e(void); // requires AVIF_CODEC_RAV1E (codec_rav1e.c)
+const char * avifCodecVersionRav1e(void); // requires AVIF_CODEC_RAV1E (codec_rav1e.c)
+
// ---------------------------------------------------------------------------
// avifStream
diff --git a/src/avif.c b/src/avif.c
index 7aa0255..8526445 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -14,53 +14,6 @@
return AVIF_VERSION_STRING;
}
-void avifCodecVersions(char outBuffer[256])
-{
- const char * encodeCodec = NULL;
- const char * encodeVersion = NULL;
- const char * decodeCodec = NULL;
- const char * decodeVersion = NULL;
-
-#if defined(AVIF_CODEC_AOM)
- encodeCodec = "aom";
- encodeVersion = avifCodecVersionAOM();
- decodeCodec = encodeCodec;
- decodeVersion = encodeVersion;
-#endif
-#if defined(AVIF_CODEC_DAV1D)
- decodeCodec = "dav1d";
- decodeVersion = avifCodecVersionDav1d();
-#endif
-
- if (decodeCodec == NULL) {
- strcpy(outBuffer, "encode/decode: none");
- } else if (encodeCodec == decodeCodec) {
- strcpy(outBuffer, "encode/decode: ");
- strcat(outBuffer, decodeCodec);
- strcat(outBuffer, " ");
- strcat(outBuffer, decodeVersion);
- } else {
- outBuffer[0] = 0;
- if (encodeCodec) {
- strcat(outBuffer, "encode: ");
- strcat(outBuffer, encodeCodec);
- strcat(outBuffer, " ");
- strcat(outBuffer, encodeVersion);
- } else {
- strcat(outBuffer, "encode: none");
- }
- strcat(outBuffer, ", ");
- if (decodeCodec) {
- strcat(outBuffer, "decode: ");
- strcat(outBuffer, decodeCodec);
- strcat(outBuffer, " ");
- strcat(outBuffer, decodeVersion);
- } else {
- strcat(outBuffer, "decode: none");
- }
- }
-}
-
const char * avifPixelFormatToString(avifPixelFormat format)
{
switch (format) {
@@ -386,3 +339,92 @@
}
avifFree(codec);
}
+
+// ---------------------------------------------------------------------------
+// Codec availability and versions
+
+typedef const char * (*versionFunc)(void);
+typedef avifCodec * (*avifCodecCreateFunc)(void);
+
+struct AvailableCodec
+{
+ avifCodecChoice choice;
+ const char * name;
+ versionFunc version;
+ avifCodecCreateFunc create;
+ uint32_t flags;
+};
+
+// This is the main codec table; it determines all usage/availability in libavif.
+
+static struct AvailableCodec availableCodecs[] = {
+// Ordered by preference (for AUTO)
+
+#if defined(AVIF_CODEC_DAV1D)
+ { AVIF_CODEC_CHOICE_DAV1D, "dav1d", avifCodecVersionDav1d, avifCodecCreateDav1d, AVIF_CODEC_FLAG_CAN_DECODE },
+#endif
+#if defined(AVIF_CODEC_RAV1E)
+ { AVIF_CODEC_CHOICE_RAV1E, "rav1e", avifCodecVersionRav1e, avifCodecCreateRav1e, AVIF_CODEC_FLAG_CAN_ENCODE },
+#endif
+#if defined(AVIF_CODEC_AOM)
+ { AVIF_CODEC_CHOICE_AOM, "aom", avifCodecVersionAOM, avifCodecCreateAOM, AVIF_CODEC_FLAG_CAN_DECODE | AVIF_CODEC_FLAG_CAN_ENCODE },
+#endif
+ { AVIF_CODEC_CHOICE_AUTO, NULL, NULL, NULL, 0 }
+};
+
+static const int availableCodecsCount = (sizeof(availableCodecs) / sizeof(availableCodecs[0])) - 1;
+
+static struct AvailableCodec * findAvailableCodec(avifCodecChoice choice, uint32_t requiredFlags)
+{
+ for (int i = 0; i < availableCodecsCount; ++i) {
+ if ((choice != AVIF_CODEC_CHOICE_AUTO) && (availableCodecs[i].choice != choice)) {
+ continue;
+ }
+ if (requiredFlags && ((availableCodecs[i].flags & requiredFlags) != requiredFlags)) {
+ continue;
+ }
+ return &availableCodecs[i];
+ }
+ return NULL;
+}
+
+const char * avifCodecName(avifCodecChoice choice, uint32_t requiredFlags)
+{
+ struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags);
+ if (availableCodec) {
+ return availableCodec->name;
+ }
+ return NULL;
+}
+
+avifCodecChoice avifCodecChoiceFromName(const char * name)
+{
+ for (int i = 0; i < availableCodecsCount; ++i) {
+ if (!strcmp(availableCodecs[i].name, name)) {
+ return availableCodecs[i].choice;
+ }
+ }
+ return AVIF_CODEC_CHOICE_AUTO;
+}
+
+avifCodec * avifCodecCreate(avifCodecChoice choice, uint32_t requiredFlags)
+{
+ struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags);
+ if (availableCodec) {
+ return availableCodec->create();
+ }
+ return NULL;
+}
+
+void avifCodecVersions(char outBuffer[256])
+{
+ outBuffer[0] = 0;
+ for (int i = 0; i < availableCodecsCount; ++i) {
+ if (i > 0) {
+ strcat(outBuffer, ", ");
+ }
+ strcat(outBuffer, availableCodecs[i].name);
+ strcat(outBuffer, ":");
+ strcat(outBuffer, availableCodecs[i].version());
+ }
+}
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 26075a1..ff9ff52 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -27,11 +27,6 @@
#include <string.h>
-const char * avifCodecVersionAOM(void)
-{
- return aom_codec_version_str();
-}
-
struct avifCodecInternal
{
avifBool decoderInitialized;
@@ -435,6 +430,11 @@
memcpy(outConfig, &codec->internal->config, sizeof(avifCodecConfigurationBox));
}
+const char * avifCodecVersionAOM(void)
+{
+ return aom_codec_version_str();
+}
+
avifCodec * avifCodecCreateAOM(void)
{
avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
diff --git a/src/codec_dav1d.c b/src/codec_dav1d.c
index 57fbdcf..e3b95bf 100644
--- a/src/codec_dav1d.c
+++ b/src/codec_dav1d.c
@@ -3,6 +3,9 @@
#include "avif/internal.h"
+#if defined(_MSC_VER)
+#pragma warning(disable : 4201) // nonstandard extension used: nameless struct/union
+#endif
#include "dav1d/dav1d.h"
#include <string.h>
@@ -12,11 +15,6 @@
#define DAV1D_ERR(e) (-(e))
#endif
-const char * avifCodecVersionDav1d(void)
-{
- return dav1d_version();
-}
-
struct avifCodecInternal
{
Dav1dSettings dav1dSettings;
@@ -188,6 +186,11 @@
return AVIF_TRUE;
}
+const char * avifCodecVersionDav1d(void)
+{
+ return dav1d_version();
+}
+
avifCodec * avifCodecCreateDav1d(void)
{
avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
new file mode 100644
index 0000000..c9b5db9
--- /dev/null
+++ b/src/codec_rav1e.c
@@ -0,0 +1,240 @@
+// Copyright 2019 Joe Drago. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/internal.h"
+
+#include "rav1e.h"
+
+#include <string.h>
+
+struct avifCodecInternal
+{
+ avifCodecConfigurationBox config;
+};
+
+static void rav1eCodecDestroyInternal(avifCodec * codec)
+{
+ avifFree(codec->internal);
+}
+
+static avifBool rav1eCodecOpen(struct avifCodec * codec, uint32_t firstSampleIndex)
+{
+ (void)firstSampleIndex; // Codec is encode-only, this isn't used
+ (void)codec;
+ return AVIF_TRUE;
+}
+
+static avifBool rav1eCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
+{
+ avifBool success = AVIF_FALSE;
+
+ RaConfig * rav1eConfig = NULL;
+ RaContext * rav1eContext = NULL;
+ RaFrame * rav1eFrame = NULL;
+
+ int yShift = 0;
+ RaChromaSampling chromaSampling;
+ RaPixelRange rav1eRange;
+ if (alpha) {
+ rav1eRange = RA_PIXEL_RANGE_FULL;
+ chromaSampling = RA_CHROMA_SAMPLING_CS400;
+ } else {
+ rav1eRange = (image->yuvRange == AVIF_RANGE_FULL) ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
+ switch (image->yuvFormat) {
+ case AVIF_PIXEL_FORMAT_YUV444:
+ chromaSampling = RA_CHROMA_SAMPLING_CS444;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV422:
+ chromaSampling = RA_CHROMA_SAMPLING_CS422;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV420:
+ chromaSampling = RA_CHROMA_SAMPLING_CS420;
+ yShift = 1;
+ break;
+ case AVIF_PIXEL_FORMAT_YV12:
+ return AVIF_FALSE;
+ default:
+ return AVIF_FALSE;
+ }
+ }
+
+ avifPixelFormatInfo formatInfo;
+ avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
+
+ rav1eConfig = rav1e_config_default();
+ if (rav1e_config_set_pixel_format(
+ rav1eConfig, (uint8_t)image->depth, chromaSampling, RA_CHROMA_SAMPLE_POSITION_UNKNOWN, rav1eRange) < 0) {
+ goto cleanup;
+ }
+
+ if (rav1e_config_parse_int(rav1eConfig, "width", image->width) == -1) {
+ goto cleanup;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "height", image->height) == -1) {
+ goto cleanup;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "threads", encoder->maxThreads) == -1) {
+ goto cleanup;
+ }
+
+ int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
+ int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
+ if (alpha) {
+ minQuantizer = AVIF_QUANTIZER_LOSSLESS;
+ maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "min_quantizer", minQuantizer) == -1) {
+ goto cleanup;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "quantizer", maxQuantizer) == -1) {
+ goto cleanup;
+ }
+
+ // Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only.
+ // Profile 1. 8-bit and 10-bit 4:4:4
+ // Profile 2. 8-bit and 10-bit 4:2:2
+ // 12-bit 4:0:0, 4:2:2 and 4:4:4
+ uint8_t seqProfile = 0;
+ if (image->depth == 12) {
+ // Only seqProfile 2 can handle 12 bit
+ seqProfile = 2;
+ } else {
+ // 8-bit or 10-bit
+
+ if (alpha) {
+ seqProfile = 0;
+ } else {
+ switch (image->yuvFormat) {
+ case AVIF_PIXEL_FORMAT_YUV444:
+ seqProfile = 1;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV422:
+ seqProfile = 2;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV420:
+ seqProfile = 0;
+ break;
+ case AVIF_PIXEL_FORMAT_YV12:
+ seqProfile = 0;
+ break;
+ case AVIF_PIXEL_FORMAT_NONE:
+ default:
+ break;
+ }
+ }
+ }
+
+ // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
+ uint8_t seqLevelIdx0 = 31;
+ if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) {
+ // Image is 5.1 compatible
+ seqLevelIdx0 = 13; // 5.1
+ }
+
+ memset(&codec->internal->config, 0, sizeof(avifCodecConfigurationBox));
+ codec->internal->config.seqProfile = seqProfile;
+ codec->internal->config.seqLevelIdx0 = seqLevelIdx0;
+ codec->internal->config.seqTier0 = 0;
+ codec->internal->config.highBitdepth = (image->depth > 8) ? 1 : 0;
+ codec->internal->config.twelveBit = (image->depth == 12) ? 1 : 0;
+ codec->internal->config.monochrome = alpha ? 1 : 0;
+ codec->internal->config.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX;
+ codec->internal->config.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY;
+
+ if (encoder->tileRowsLog2 != 0) {
+ int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
+ if (rav1e_config_parse_int(rav1eConfig, "tile_rows", 1 << tileRowsLog2) == -1) {
+ goto cleanup;
+ }
+ }
+ if (encoder->tileColsLog2 != 0) {
+ int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
+ if (rav1e_config_parse_int(rav1eConfig, "tile_cols", 1 << tileColsLog2) == -1) {
+ goto cleanup;
+ }
+ }
+
+ if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
+ rav1e_config_set_color_description(rav1eConfig,
+ (RaMatrixCoefficients)image->nclx.matrixCoefficients,
+ (RaColorPrimaries)image->nclx.colourPrimaries,
+ (RaTransferCharacteristics)image->nclx.transferCharacteristics);
+ }
+
+ rav1eContext = rav1e_context_new(rav1eConfig);
+ if (!rav1eContext) {
+ goto cleanup;
+ }
+ rav1eFrame = rav1e_frame_new(rav1eContext);
+
+ int byteWidth = (image->depth > 8) ? 2 : 1;
+ if (alpha) {
+ rav1e_frame_fill_plane(rav1eFrame, 0, image->alphaPlane, image->alphaRowBytes * image->height, image->alphaRowBytes, byteWidth);
+ } else {
+ uint32_t uvHeight = image->height >> yShift;
+ if (uvHeight < 1) {
+ uvHeight = 1;
+ }
+ rav1e_frame_fill_plane(rav1eFrame, 0, image->yuvPlanes[0], image->yuvRowBytes[0] * image->height, image->yuvRowBytes[0], byteWidth);
+ rav1e_frame_fill_plane(rav1eFrame, 1, image->yuvPlanes[1], image->yuvRowBytes[1] * uvHeight, image->yuvRowBytes[1], byteWidth);
+ rav1e_frame_fill_plane(rav1eFrame, 2, image->yuvPlanes[2], image->yuvRowBytes[2] * uvHeight, image->yuvRowBytes[2], byteWidth);
+ }
+
+ RaEncoderStatus encoderStatus = rav1e_send_frame(rav1eContext, rav1eFrame);
+ if (encoderStatus != 0) {
+ goto cleanup;
+ }
+ encoderStatus = rav1e_send_frame(rav1eContext, NULL); // flush
+ if (encoderStatus != 0) {
+ goto cleanup;
+ }
+
+ RaPacket * pkt = NULL;
+ encoderStatus = rav1e_receive_packet(rav1eContext, &pkt);
+ if (encoderStatus != 0) {
+ goto cleanup;
+ }
+
+ if (pkt && pkt->data && (pkt->len > 0)) {
+ avifRWDataSet(obu, pkt->data, pkt->len);
+ success = AVIF_TRUE;
+ }
+cleanup:
+ if (rav1eFrame) {
+ rav1e_frame_unref(rav1eFrame);
+ rav1eFrame = NULL;
+ }
+ if (rav1eContext) {
+ rav1e_context_unref(rav1eContext);
+ rav1eContext = NULL;
+ }
+ if (rav1eConfig) {
+ rav1e_config_unref(rav1eConfig);
+ rav1eConfig = NULL;
+ }
+ return success;
+}
+
+static void rav1eCodecGetConfigurationBox(avifCodec * codec, avifCodecConfigurationBox * outConfig)
+{
+ memcpy(outConfig, &codec->internal->config, sizeof(avifCodecConfigurationBox));
+}
+
+const char * avifCodecVersionRav1e(void)
+{
+ return "0"; // https://github.com/xiph/rav1e/issues/1801
+}
+
+avifCodec * avifCodecCreateRav1e(void)
+{
+ avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
+ memset(codec, 0, sizeof(struct avifCodec));
+ codec->open = rav1eCodecOpen;
+ codec->encodeImage = rav1eCodecEncodeImage;
+ codec->getConfigurationBox = rav1eCodecGetConfigurationBox;
+ codec->destroyInternal = rav1eCodecDestroyInternal;
+
+ codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
+ memset(codec->internal, 0, sizeof(struct avifCodecInternal));
+ return codec;
+}
diff --git a/src/read.c b/src/read.c
index 23531d6..632e823 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1296,11 +1296,6 @@
avifResult avifDecoderParse(avifDecoder * decoder, avifROData * rawInput)
{
-#if !defined(AVIF_CODEC_AOM) && !defined(AVIF_CODEC_DAV1D)
- // Just bail out early, we're not surviving this function without a decoder compiled in
- return AVIF_RESULT_NO_CODEC_AVAILABLE;
-#endif
-
// Cleanup anything lingering in the decoder
avifDecoderCleanup(decoder);
@@ -1350,16 +1345,9 @@
return avifDecoderReset(decoder);
}
-static avifCodec * avifCodecCreateForDecode(avifCodecDecodeInput * decodeInput)
+static avifCodec * avifCodecCreateInternal(avifCodecChoice choice, avifCodecDecodeInput * decodeInput)
{
- avifCodec * codec = NULL;
-#if defined(AVIF_CODEC_DAV1D)
- codec = avifCodecCreateDav1d();
-#elif defined(AVIF_CODEC_AOM)
- codec = avifCodecCreateAOM();
-#else
-#error No decoder available!
-#endif
+ avifCodec * codec = avifCodecCreate(choice, AVIF_CODEC_FLAG_CAN_DECODE);
if (codec) {
codec->decodeInput = decodeInput;
}
@@ -1370,13 +1358,19 @@
{
avifDataResetCodec(decoder->data);
- decoder->data->codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreateForDecode(decoder->data->colorInput);
+ decoder->data->codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreateInternal(decoder->codecChoice, decoder->data->colorInput);
+ if (!decoder->data->codec[AVIF_CODEC_PLANES_COLOR]) {
+ return AVIF_RESULT_NO_CODEC_AVAILABLE;
+ }
if (!decoder->data->codec[AVIF_CODEC_PLANES_COLOR]->open(decoder->data->codec[AVIF_CODEC_PLANES_COLOR], decoder->imageIndex + 1)) {
return AVIF_RESULT_DECODE_COLOR_FAILED;
}
if (decoder->data->alphaInput) {
- decoder->data->codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreateForDecode(decoder->data->alphaInput);
+ decoder->data->codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreateInternal(decoder->codecChoice, decoder->data->alphaInput);
+ if (!decoder->data->codec[AVIF_CODEC_PLANES_ALPHA]) {
+ return AVIF_RESULT_NO_CODEC_AVAILABLE;
+ }
if (!decoder->data->codec[AVIF_CODEC_PLANES_ALPHA]->open(decoder->data->codec[AVIF_CODEC_PLANES_ALPHA], decoder->imageIndex + 1)) {
return AVIF_RESULT_DECODE_ALPHA_FAILED;
}
diff --git a/src/write.c b/src/write.c
index 3fd41f0..23774a1 100644
--- a/src/write.c
+++ b/src/write.c
@@ -38,15 +38,6 @@
avifFree(encoder);
}
-static avifCodec * avifCodecCreateForEncode()
-{
-#ifdef AVIF_CODEC_AOM
- return avifCodecCreateAOM();
-#else
- return NULL;
-#endif
-}
-
avifResult avifEncoderWrite(avifEncoder * encoder, avifImage * image, avifRWData * output)
{
if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) {
@@ -58,7 +49,7 @@
avifRWData alphaOBU = AVIF_DATA_EMPTY;
avifCodec * codec[AVIF_CODEC_PLANES_COUNT];
- codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreateForEncode();
+ codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!codec[AVIF_CODEC_PLANES_COLOR]) {
// Just bail out early, we're not surviving this function without an encoder compiled in
return AVIF_RESULT_NO_CODEC_AVAILABLE;
@@ -68,7 +59,10 @@
if (imageIsOpaque) {
codec[AVIF_CODEC_PLANES_ALPHA] = NULL;
} else {
- codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreateForEncode();
+ codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+ if (!codec[AVIF_CODEC_PLANES_ALPHA]) {
+ return AVIF_RESULT_NO_CODEC_AVAILABLE;
+ }
}
avifRWStream s;