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;