Skip to content

Commit e217740

Browse files
aryanpinglesurmajakearchibald
authored
Update libavif (#1381)
* Update libavif (v1.0.0-main) * Update libavif for improved compression and speed * v1.0.0 deprecates usage of min and max-quantizers; we use `quality` and `qualityAlpha` instead * Renamed `maxSpeed` to `MAX_EFFORT` for better readability * Update libavif (v1.0.1-main) * Refactor variable names for clarity * Update libaom (v3.7.0) * Add checks for API return values * Rename variables for readability Changes `cqlevel` to `quality`, and `cqAlphaLevel` to `qualityAlpha` * Minor patches in logic * Minor patches in lossless calculation * Add chroma subsampling options to AVIF * Add skeleton for sharp downsampling param * Try to use libsharpyuv * Encoder working, decoder isnt * Make sure sharpyuv is disabled for decoder * Add AVIF_LOCAL flags for sharp yuv * Get AVIF sharp YUV working * Clean up AVIF makefiles * AVIF: Make sharpyuv conditional on subsample * AVIF: Flags to speed up sharpyuv build * AVIF: Minor refactoring in enc.cpp * AVIF: Minor refactoring & renaming * AVIF: Use smart pointers to prevent memory leaks * AVIF: Minor refactoring * AVIF: Revert defaultoptions logic change --------- Co-authored-by: Surma <[email protected]> Co-authored-by: Jake Archibald <[email protected]>
1 parent d87eff7 commit e217740

19 files changed

+197
-101
lines changed

codecs/avif/Makefile

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
# libavif and libaom versions are from
2-
# google3/third_party/libavif/METADATA
3-
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/647c3c208cf152395d777c1bf7240d2ecf7df5a9.tar.gz
4-
CODEC_PACKAGE = node_modules/libavif.tar.gz
1+
# using libavif from https://github.com/AOMediaCodec/libavif
2+
LIBAVIF_URL = https://github.com/AOMediaCodec/libavif/archive/refs/tags/v1.0.1.tar.gz
3+
LIBAVIF_PACKAGE = node_modules/libavif.tar.gz
54

6-
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.6.0.tar.gz
5+
# using libaom from https://aomedia.googlesource.com/aom
6+
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.7.0.tar.gz
77
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
88

99
export CODEC_DIR = node_modules/libavif
1010
export BUILD_DIR = node_modules/build
1111
export LIBAOM_DIR = node_modules/libaom
1212

1313
override CFLAGS += "-Wno-unused-macros"
14-
export
14+
15+
# We must build libsharpyuv from a specific libwebp commit
16+
# See libavif/ext/libsharpyuv.cmd for more detail
17+
LIBWEBP_URL_WITH_SHARPYUV = https://chromium.googlesource.com/webm/libwebp/+archive/e2c85878f6a33f29948b43d3492d9cdaf801aa54.tar.gz
18+
LIBWEBP_DIR := $(CODEC_DIR)/ext/libwebp
19+
export LIBSHARPYUV := $(LIBWEBP_DIR)/build/libsharpyuv.a
1520

1621
OUT_ENC_JS = enc/avif_enc.js
1722
OUT_NODE_ENC_JS = enc/avif_node_enc.js
@@ -28,10 +33,10 @@ HELPER_MAKEFLAGS := -f helper.Makefile
2833

2934
.PHONY: all clean
3035

31-
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS) $(OUT_NODE_DEC_JS)
36+
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS)
3237

33-
$(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS): ENVIRONMENT=node
34-
$(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
38+
# ST-Encoding
39+
$(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt $(LIBSHARPYUV)
3540
$(MAKE) \
3641
$(HELPER_MAKEFLAGS) \
3742
OUT_JS=$@ \
@@ -42,9 +47,10 @@ $(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(L
4247
-DCONFIG_AV1_HIGHBITDEPTH=0 \
4348
" \
4449
ENVIRONMENT=$(ENVIRONMENT) \
45-
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0"
50+
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_LOCAL_LIBSHARPYUV=ON"
4651

47-
$(OUT_ENC_MT_JS) $(OUT_NODE_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
52+
# MT-Encoding
53+
$(OUT_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt $(LIBSHARPYUV)
4854
$(MAKE) \
4955
$(HELPER_MAKEFLAGS) \
5056
OUT_JS=$@ \
@@ -54,11 +60,11 @@ $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.t
5460
-DCONFIG_AV1_HIGHBITDEPTH=0 \
5561
" \
5662
ENVIRONMENT=$(ENVIRONMENT) \
57-
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0" \
63+
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_LOCAL_LIBSHARPYUV=ON" \
5864
OUT_FLAGS="-pthread"
5965

60-
$(OUT_NODE_DEC_JS): ENVIRONMENT=node
61-
$(OUT_NODE_DEC_JS) $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
66+
# Decoding
67+
$(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
6268
$(MAKE) \
6369
$(HELPER_MAKEFLAGS) \
6470
OUT_JS=$@ \
@@ -70,22 +76,56 @@ $(OUT_NODE_DEC_JS) $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(L
7076
ENVIRONMENT=$(ENVIRONMENT) \
7177
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0"
7278

73-
$(CODEC_PACKAGE):
74-
mkdir -p $(@D)
75-
curl -sL $(CODEC_URL) -o $@
79+
# LIBAOM EXTRACTION SECTION
7680

81+
# Download the libaom tarball
7782
$(LIBAOM_PACKAGE):
7883
mkdir -p $(@D)
7984
curl -sL $(LIBAOM_URL) -o $@
8085

81-
$(CODEC_DIR)/CMakeLists.txt: $(CODEC_PACKAGE)
82-
mkdir -p $(@D)
83-
tar xzm --strip 1 -C $(@D) -f $(CODEC_PACKAGE)
84-
86+
# Extract libaom from the tarball
8587
$(LIBAOM_DIR)/CMakeLists.txt: $(LIBAOM_PACKAGE)
8688
mkdir -p $(@D)
8789
tar xzm -C $(@D) -f $(LIBAOM_PACKAGE)
8890

91+
# LIBAVIF EXTRACTION SECTION
92+
93+
# Download the libavif tarball
94+
$(LIBAVIF_PACKAGE):
95+
mkdir -p $(@D)
96+
curl -sL $(LIBAVIF_URL) -o $@
97+
98+
# Extract libavif from the tarball
99+
$(CODEC_DIR)/CMakeLists.txt: $(LIBAVIF_PACKAGE)
100+
mkdir -p $(@D)
101+
tar xzm --strip 1 -C $(@D) -f $(LIBAVIF_PACKAGE)
102+
103+
# Create libavif/ext/libwebp
104+
$(LIBWEBP_DIR)/CMakeLists.txt: $(CODEC_DIR)/CMakeLists.txt
105+
mkdir -p $(LIBWEBP_DIR)
106+
curl -sL $(LIBWEBP_URL_WITH_SHARPYUV) \
107+
| tar xzm -C $(LIBWEBP_DIR)
108+
109+
# Make libsharpyuv.a
110+
$(LIBSHARPYUV): $(LIBWEBP_DIR)/CMakeLists.txt
111+
mkdir -p $(@D)
112+
emcmake cmake \
113+
-DWEBP_BUILD_ANIM_UTILS=OFF \
114+
-DWEBP_BUILD_CWEBP=OFF \
115+
-DWEBP_BUILD_DWEBP=OFF \
116+
-DWEBP_BUILD_GIF2WEBP=OFF \
117+
-DWEBP_BUILD_IMG2WEBP=OFF \
118+
-DWEBP_BUILD_VWEBP=OFF \
119+
-DWEBP_BUILD_WEBPINFO=OFF \
120+
-DWEBP_BUILD_LIBWEBPMUX=OFF \
121+
-DWEBP_BUILD_WEBPMUX=OFF \
122+
-DWEBP_BUILD_EXTRAS=OFF \
123+
-DBUILD_SHARED_LIBS=OFF \
124+
-DCMAKE_BUILD_TYPE=Release \
125+
-S $(LIBWEBP_DIR) \
126+
-B $(@D)
127+
$(MAKE) -C $(@D) sharpyuv
128+
89129
clean:
90130
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_JS) clean
91131
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_MT_JS) clean

codecs/avif/dec/avif_dec.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codecs/avif/dec/avif_dec.wasm

4.02 KB
Binary file not shown.

codecs/avif/dec/avif_node_dec.wasm

7 Bytes
Binary file not shown.

codecs/avif/enc/avif_enc.cpp

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,27 @@
33
#include <emscripten/val.h>
44
#include "avif/avif.h"
55

6+
#include <memory>
7+
#include <string>
8+
9+
#define RETURN_NULL_IF(expression) \
10+
do { \
11+
if (expression) \
12+
return val::null(); \
13+
} while (false)
14+
615
using namespace emscripten;
716

17+
using AvifImagePtr = std::unique_ptr<avifImage, decltype(&avifImageDestroy)>;
18+
using AvifEncoderPtr = std::unique_ptr<avifEncoder, decltype(&avifEncoderDestroy)>;
19+
820
struct AvifOptions {
9-
// [0 - 63]
10-
// 0 = lossless
11-
// 63 = worst quality
12-
int cqLevel;
13-
// As above, but -1 means 'use cqLevel'
14-
int cqAlphaLevel;
21+
// [0 - 100]
22+
// 0 = worst quality
23+
// 100 = lossless
24+
int quality;
25+
// As above, but -1 means 'use quality'
26+
int qualityAlpha;
1527
// [0 - 6]
1628
// Creates 2^n tiles in that dimension
1729
int tileRowsLog2;
@@ -35,12 +47,15 @@ struct AvifOptions {
3547
int tune;
3648
// 0-50
3749
int denoiseLevel;
50+
// toggles AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV
51+
bool enableSharpYUV;
3852
};
3953

4054
thread_local const val Uint8Array = val::global("Uint8Array");
4155

4256
val encode(std::string buffer, int width, int height, AvifOptions options) {
43-
avifRWData output = AVIF_DATA_EMPTY;
57+
avifResult status; // To check the return status for avif API's
58+
4459
int depth = 8;
4560
avifPixelFormat format;
4661
switch (options.subsample) {
@@ -58,11 +73,13 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
5873
break;
5974
}
6075

61-
bool lossless = options.cqLevel == AVIF_QUANTIZER_LOSSLESS &&
62-
options.cqAlphaLevel <= AVIF_QUANTIZER_LOSSLESS &&
76+
bool lossless = options.quality == AVIF_QUALITY_LOSSLESS &&
77+
(options.qualityAlpha == -1 || options.qualityAlpha == AVIF_QUALITY_LOSSLESS) &&
6378
format == AVIF_PIXEL_FORMAT_YUV444;
6479

65-
avifImage* image = avifImageCreate(width, height, depth, format);
80+
// Smart pointer for the input image in YUV format
81+
AvifImagePtr image(avifImageCreate(width, height, depth, format), avifImageDestroy);
82+
RETURN_NULL_IF(image == nullptr);
6683

6784
if (lossless) {
6885
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
@@ -73,74 +90,80 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
7390
uint8_t* rgba = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer.data()));
7491

7592
avifRGBImage srcRGB;
76-
avifRGBImageSetDefaults(&srcRGB, image);
93+
avifRGBImageSetDefaults(&srcRGB, image.get());
7794
srcRGB.pixels = rgba;
7895
srcRGB.rowBytes = width * 4;
79-
avifImageRGBToYUV(image, &srcRGB);
96+
if (options.enableSharpYUV) {
97+
srcRGB.chromaDownsampling = AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV;
98+
}
99+
status = avifImageRGBToYUV(image.get(), &srcRGB);
100+
RETURN_NULL_IF(status != AVIF_RESULT_OK);
80101

81-
avifEncoder* encoder = avifEncoderCreate();
102+
// Create a smart pointer for the encoder
103+
AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
104+
RETURN_NULL_IF(encoder == nullptr);
82105

83106
if (lossless) {
84-
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
85-
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
86-
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
87-
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
107+
encoder->quality = AVIF_QUALITY_LOSSLESS;
108+
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
88109
} else {
89-
encoder->minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
90-
encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
91-
encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY;
92-
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY;
93-
avifEncoderSetCodecSpecificOption(encoder, "end-usage", "q");
94-
avifEncoderSetCodecSpecificOption(encoder, "cq-level", std::to_string(options.cqLevel).c_str());
95-
avifEncoderSetCodecSpecificOption(encoder, "sharpness",
96-
std::to_string(options.sharpness).c_str());
97-
98-
if (options.cqAlphaLevel != -1) {
99-
avifEncoderSetCodecSpecificOption(encoder, "alpha:cq-level",
100-
std::to_string(options.cqAlphaLevel).c_str());
110+
status = avifEncoderSetCodecSpecificOption(encoder.get(), "sharpness",
111+
std::to_string(options.sharpness).c_str());
112+
RETURN_NULL_IF(status != AVIF_RESULT_OK);
113+
114+
// Set base quality
115+
encoder->quality = options.quality;
116+
// Conditionally set alpha quality
117+
if (options.qualityAlpha == -1) {
118+
encoder->qualityAlpha = options.quality;
119+
} else {
120+
encoder->qualityAlpha = options.qualityAlpha;
101121
}
102122

103-
if (options.tune == 2 || (options.tune == 0 && options.cqLevel <= 32)) {
104-
avifEncoderSetCodecSpecificOption(encoder, "tune", "ssim");
123+
if (options.tune == 2 || (options.tune == 0 && options.quality >= 50)) {
124+
status = avifEncoderSetCodecSpecificOption(encoder.get(), "tune", "ssim");
125+
RETURN_NULL_IF(status != AVIF_RESULT_OK);
105126
}
106127

107128
if (options.chromaDeltaQ) {
108-
avifEncoderSetCodecSpecificOption(encoder, "enable-chroma-deltaq", "1");
129+
status = avifEncoderSetCodecSpecificOption(encoder.get(), "color:enable-chroma-deltaq", "1");
130+
RETURN_NULL_IF(status != AVIF_RESULT_OK);
109131
}
110132

111-
avifEncoderSetCodecSpecificOption(encoder, "color:denoise-noise-level",
112-
std::to_string(options.denoiseLevel).c_str());
133+
status = avifEncoderSetCodecSpecificOption(encoder.get(), "color:denoise-noise-level",
134+
std::to_string(options.denoiseLevel).c_str());
135+
RETURN_NULL_IF(status != AVIF_RESULT_OK);
113136
}
114137

115138
encoder->maxThreads = emscripten_num_logical_cores();
116139
encoder->tileRowsLog2 = options.tileRowsLog2;
117140
encoder->tileColsLog2 = options.tileColsLog2;
118141
encoder->speed = options.speed;
119142

120-
avifResult encodeResult = avifEncoderWrite(encoder, image, &output);
143+
avifRWData output = AVIF_DATA_EMPTY;
144+
avifResult encodeResult = avifEncoderWrite(encoder.get(), image.get(), &output);
121145
auto js_result = val::null();
122146
if (encodeResult == AVIF_RESULT_OK) {
123147
js_result = Uint8Array.new_(typed_memory_view(output.size, output.data));
124148
}
125149

126-
avifImageDestroy(image);
127-
avifEncoderDestroy(encoder);
128150
avifRWDataFree(&output);
129151
return js_result;
130152
}
131153

132154
EMSCRIPTEN_BINDINGS(my_module) {
133155
value_object<AvifOptions>("AvifOptions")
134-
.field("cqLevel", &AvifOptions::cqLevel)
135-
.field("cqAlphaLevel", &AvifOptions::cqAlphaLevel)
156+
.field("quality", &AvifOptions::quality)
157+
.field("qualityAlpha", &AvifOptions::qualityAlpha)
136158
.field("tileRowsLog2", &AvifOptions::tileRowsLog2)
137159
.field("tileColsLog2", &AvifOptions::tileColsLog2)
138160
.field("speed", &AvifOptions::speed)
139161
.field("chromaDeltaQ", &AvifOptions::chromaDeltaQ)
140162
.field("sharpness", &AvifOptions::sharpness)
141163
.field("tune", &AvifOptions::tune)
142164
.field("denoiseLevel", &AvifOptions::denoiseLevel)
143-
.field("subsample", &AvifOptions::subsample);
165+
.field("subsample", &AvifOptions::subsample)
166+
.field("enableSharpYUV", &AvifOptions::enableSharpYUV);
144167

145168
function("encode", &encode);
146169
}

codecs/avif/enc/avif_enc.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ export const enum AVIFTune {
55
}
66

77
export interface EncodeOptions {
8-
cqLevel: number;
8+
quality: number;
9+
qualityAlpha: number;
910
denoiseLevel: number;
10-
cqAlphaLevel: number;
1111
tileRowsLog2: number;
1212
tileColsLog2: number;
1313
speed: number;
1414
subsample: number;
1515
chromaDeltaQ: boolean;
1616
sharpness: number;
17+
enableSharpYUV: boolean;
1718
tune: AVIFTune;
1819
}
1920

codecs/avif/enc/avif_enc.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codecs/avif/enc/avif_enc.wasm

38.6 KB
Binary file not shown.

codecs/avif/enc/avif_enc_mt.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codecs/avif/enc/avif_enc_mt.wasm

13.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)