Skip to content

Commit

Permalink
Support for progressive AVIFs and operating point selection
Browse files Browse the repository at this point in the history
* Support for parsing boxes: a1op, a1lx, lsel
* Added operating point and "skip" support to avifCodec impls for layer selection support
* Reworked avifdec decoding to support nth image decoding and minor optimization
* New avifProgressiveState enum to expose info about progressive AVIFs to users of libavif
* New avifdec args: --progressive, --index
  • Loading branch information
Joe Drago authored and joedrago committed Jun 23, 2021
1 parent 791ab3f commit bffba3b
Show file tree
Hide file tree
Showing 11 changed files with 547 additions and 137 deletions.
130 changes: 88 additions & 42 deletions apps/avifdec.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ static void syntax(void)
printf(" -u,--upsampling U : Chroma upsampling (for 420/422). automatic (default), fastest, best, nearest, or bilinear\n");
printf(" -r,--raw-color : Output raw RGB values instead of multiplying by alpha when saving to opaque formats\n");
printf(" (JPEG only; not applicable to y4m)\n");
printf(" --index : When decoding an image sequence or progressive image, specify which frame index to decode (Default: 0)\n");
printf(" --progressive : Enable progressive AVIF processing. If a progressive image is encountered, avifdec will use\n");
printf(" --index to choose which variant to decode (in progressive order).\n");
printf(" --no-strict : Disable strict decoding, which disables strict validation checks and errors\n");
printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n");
printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n");
Expand All @@ -55,7 +58,9 @@ int main(int argc, char * argv[])
avifChromaUpsampling chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC;
avifBool ignoreICC = AVIF_FALSE;
avifBool rawColor = AVIF_FALSE;
avifBool allowProgressive = AVIF_FALSE;
avifStrictFlags strictFlags = AVIF_STRICT_ENABLED;
uint32_t frameIndex = 0;

if (argc < 2) {
syntax();
Expand Down Expand Up @@ -128,6 +133,11 @@ int main(int argc, char * argv[])
}
} else if (!strcmp(arg, "-r") || !strcmp(arg, "--raw-color")) {
rawColor = AVIF_TRUE;
} else if (!strcmp(arg, "--progressive")) {
allowProgressive = AVIF_TRUE;
} else if (!strcmp(arg, "--index")) {
NEXTARG();
frameIndex = (uint32_t)atoi(arg);
} else if (!strcmp(arg, "--no-strict")) {
strictFlags = AVIF_STRICT_DISABLED;
} else if (!strcmp(arg, "-i") || !strcmp(arg, "--info")) {
Expand Down Expand Up @@ -165,6 +175,7 @@ int main(int argc, char * argv[])
decoder->maxThreads = jobs;
decoder->codecChoice = codecChoice;
decoder->strictFlags = strictFlags;
decoder->allowProgressive = allowProgressive;
avifResult result = avifDecoderSetIOFile(decoder, inputFilename);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "Cannot open file for read: %s\n", inputFilename);
Expand All @@ -182,17 +193,31 @@ int main(int argc, char * argv[])
decoder->durationInTimescales,
decoder->imageCount,
(decoder->imageCount == 1) ? "" : "s");
printf(" * Frames:\n");
if (decoder->imageCount > 1) {
printf(" * %s Frames: (%u expected frames)\n",
(decoder->progressiveState != AVIF_PROGRESSIVE_STATE_UNAVAILABLE) ? "Progressive Image" : "Image Sequence",
decoder->imageCount);
} else {
printf(" * Frame:\n");
}

int frameIndex = 0;
while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) {
printf(" * Decoded frame [%d] [pts %2.2f (%" PRIu64 " timescales)] [duration %2.2f (%" PRIu64 " timescales)]\n",
frameIndex,
int currIndex = 0;
avifResult nextImageResult = avifDecoderNextImage(decoder);
while (nextImageResult == AVIF_RESULT_OK) {
printf(" * Decoded frame [%d] [pts %2.2f (%" PRIu64 " timescales)] [duration %2.2f (%" PRIu64 " timescales)] [%ux%u]\n",
currIndex,
decoder->imageTiming.pts,
decoder->imageTiming.ptsInTimescales,
decoder->imageTiming.duration,
decoder->imageTiming.durationInTimescales);
++frameIndex;
decoder->imageTiming.durationInTimescales,
decoder->image->width,
decoder->image->height);
++currIndex;
nextImageResult = avifDecoderNextImage(decoder);
}
if (nextImageResult != AVIF_RESULT_NO_IMAGES_REMAINING) {
printf("ERROR: Failed to decode frame: %s\n", avifResultToString(nextImageResult));
avifDumpDiagnostics(&decoder->diag);
}
} else {
printf("ERROR: Failed to decode image: %s\n", avifResultToString(result));
Expand All @@ -214,52 +239,73 @@ int main(int argc, char * argv[])
(jobs == 1) ? "" : "s");

int returnCode = 0;
avifImage * avif = avifImageCreateEmpty();
avifDecoder * decoder = avifDecoderCreate();
decoder->maxThreads = jobs;
decoder->codecChoice = codecChoice;
decoder->strictFlags = strictFlags;
avifResult decodeResult = avifDecoderReadFile(decoder, avif, inputFilename);
if (decodeResult == AVIF_RESULT_OK) {
printf("Image decoded: %s\n", inputFilename);
printf("Image details:\n");
avifImageDump(avif, 0, 0);
decoder->allowProgressive = allowProgressive;

if (ignoreICC && (avif->icc.size > 0)) {
printf("[--ignore-icc] Discarding ICC profile.\n");
avifImageSetProfileICC(avif, NULL, 0);
}
avifResult result = avifDecoderSetIOFile(decoder, inputFilename);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "Cannot open file for read: %s\n", inputFilename);
returnCode = 1;
goto cleanup;
}

result = avifDecoderParse(decoder);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to parse image: %s\n", avifResultToString(result));
returnCode = 1;
goto cleanup;
}

result = avifDecoderNthImage(decoder, frameIndex);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to decode image: %s\n", avifResultToString(result));
returnCode = 1;
goto cleanup;
}

printf("Image decoded: %s\n", inputFilename);
printf("Image details:\n");
avifImageDump(decoder->image, 0, 0, decoder->progressiveState);

if (ignoreICC && (decoder->image->icc.size > 0)) {
printf("[--ignore-icc] Discarding ICC profile.\n");
avifImageSetProfileICC(decoder->image, NULL, 0);
}

avifAppFileFormat outputFormat = avifGuessFileFormat(outputFilename);
if (outputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
fprintf(stderr, "Cannot determine output file extension: %s\n", outputFilename);
avifAppFileFormat outputFormat = avifGuessFileFormat(outputFilename);
if (outputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
fprintf(stderr, "Cannot determine output file extension: %s\n", outputFilename);
returnCode = 1;
} else if (outputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
if (!y4mWrite(outputFilename, decoder->image)) {
returnCode = 1;
} else if (outputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
if (!y4mWrite(outputFilename, avif)) {
returnCode = 1;
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
// Bypass alpha multiply step during conversion
if (rawColor) {
avif->alphaPremultiplied = AVIF_TRUE;
}
if (!avifJPEGWrite(outputFilename, avif, jpegQuality, chromaUpsampling)) {
returnCode = 1;
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_PNG) {
if (!avifPNGWrite(outputFilename, avif, requestedDepth, chromaUpsampling)) {
returnCode = 1;
}
} else {
fprintf(stderr, "Unrecognized file extension: %s\n", outputFilename);
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
// Bypass alpha multiply step during conversion
if (rawColor) {
decoder->image->alphaPremultiplied = AVIF_TRUE;
}
if (!avifJPEGWrite(outputFilename, decoder->image, jpegQuality, chromaUpsampling)) {
returnCode = 1;
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_PNG) {
if (!avifPNGWrite(outputFilename, decoder->image, requestedDepth, chromaUpsampling)) {
returnCode = 1;
}
} else {
printf("ERROR: Failed to decode image: %s\n", avifResultToString(decodeResult));
avifDumpDiagnostics(&decoder->diag);
fprintf(stderr, "Unrecognized file extension: %s\n", outputFilename);
returnCode = 1;
}
avifDecoderDestroy(decoder);
avifImageDestroy(avif);

cleanup:
if (decoder) {
if (returnCode != 0) {
avifDumpDiagnostics(&decoder->diag);
}
avifDecoderDestroy(decoder);
}
return returnCode;
}
2 changes: 1 addition & 1 deletion apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ int main(int argc, char * argv[])
lossyHint = " (Lossless)";
}
printf("AVIF to be written:%s\n", lossyHint);
avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1]);
avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1], AVIF_PROGRESSIVE_STATE_UNAVAILABLE);

printf("Encoding with AV1 codec '%s' speed [%d], color QP [%d (%s) <-> %d (%s)], alpha QP [%d (%s) <-> %d (%s)], tileRowsLog2 [%d], tileColsLog2 [%d], %d worker thread(s), please wait...\n",
avifCodecName(codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE),
Expand Down
10 changes: 6 additions & 4 deletions apps/shared/avifutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ static void printClapFraction(const char * name, int32_t n, int32_t d)
printf(", ");
}

static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifBool alphaPresent)
static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifBool alphaPresent, avifProgressiveState progressiveState)
{
uint32_t width = avif->width;
uint32_t height = avif->height;
Expand Down Expand Up @@ -105,17 +105,19 @@ static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uin
printf(" * imir (Mirror) : Mode %u (%s)\n", avif->imir.mode, (avif->imir.mode == 0) ? "top-to-bottom" : "left-to-right");
}
}
printf(" * Alpha : %s\n", alphaPresent ? (avif->alphaPremultiplied ? "Premultiplied" : "Not premultiplied") : "Absent");
printf(" * Progressive : %s\n", avifProgressiveStateToString(progressiveState));
}

void avifImageDump(avifImage * avif, uint32_t gridCols, uint32_t gridRows)
void avifImageDump(avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifProgressiveState progressiveState)
{
const avifBool alphaPresent = avif->alphaPlane && (avif->alphaRowBytes > 0);
avifImageDumpInternal(avif, gridCols, gridRows, alphaPresent);
avifImageDumpInternal(avif, gridCols, gridRows, alphaPresent, progressiveState);
}

void avifContainerDump(avifDecoder * decoder)
{
avifImageDumpInternal(decoder->image, 0, 0, decoder->alphaPresent);
avifImageDumpInternal(decoder->image, 0, 0, decoder->alphaPresent, decoder->progressiveState);
}

void avifPrintVersions(void)
Expand Down
2 changes: 1 addition & 1 deletion apps/shared/avifutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#define AVIF_FMT_ZU "%zu"
#endif

void avifImageDump(avifImage * avif, uint32_t gridCols, uint32_t gridRows);
void avifImageDump(avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifProgressiveState progressiveState);
void avifContainerDump(avifDecoder * decoder);
void avifPrintVersions(void);
void avifDumpDiagnostics(const avifDiagnostics * diag);
Expand Down
49 changes: 43 additions & 6 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,31 @@ typedef struct avifImageTiming
uint64_t durationInTimescales; // duration in "timescales"
} avifImageTiming;

typedef enum avifProgressiveState
{
// The current AVIF/Source does not offer a progressive image. This will always be the state
// for an image sequence.
AVIF_PROGRESSIVE_STATE_UNAVAILABLE = 0,

// The current AVIF/Source offers a progressive image, but avifDecoder.allowProgressive is not
// enabled, so it will behave as if the image was not progressive and will simply decode the
// best version of this item.
AVIF_PROGRESSIVE_STATE_AVAILABLE,

// The current AVIF/Source offers a progressive image, and avifDecoder.allowProgressive is true.
// In this state, avifDecoder.imageCount will be the count of all of the available progressive
// layers, and any specific layer can be decoded using avifDecoderNthImage() as if it was an
// image sequence, or simply using repeated calls to avifDecoderNextImage() to decode better and
// better versions of this image.
AVIF_PROGRESSIVE_STATE_ACTIVE
} avifProgressiveState;
AVIF_API const char * avifProgressiveStateToString(avifProgressiveState progressiveState);

typedef struct avifDecoder
{
// --------------------------------------------------------------------------------------------
// Inputs

// Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c)
avifCodecChoice codecChoice;

Expand All @@ -772,6 +795,16 @@ typedef struct avifDecoder
// Set this via avifDecoderSetSource().
avifDecoderSource requestedSource;

// If this is true and a progressive AVIF is decoded, avifDecoder will behave as if the AVIF
// is an image sequence, in that it will set imageCount to the available number of progressive
// frames available, and avifDecoderNextImage()/avifDecoderNthImage() will all for specific
// variants of a progressive image to be decoded. To distinguish between a progressive AVIF
// and an AVIF image sequence, inspect avifDecoder.progressiveState.
avifBool allowProgressive;

// --------------------------------------------------------------------------------------------
// Outputs

// All decoded image data; owned by the decoder. All information in this image is incrementally
// added and updated as avifDecoder*() functions are called. After a successful call to
// avifDecoderParse(), all values in decoder->image (other than the planes/rowBytes themselves)
Expand All @@ -788,12 +821,13 @@ typedef struct avifDecoder
avifImage * image;

// Counts and timing for the current image in an image sequence. Uninteresting for single image files.
int imageIndex; // 0-based
int imageCount; // Always 1 for non-sequences
avifImageTiming imageTiming; //
uint64_t timescale; // timescale of the media (Hz)
double duration; // in seconds (durationInTimescales / timescale)
uint64_t durationInTimescales; // duration in "timescales"
int imageIndex; // 0-based
int imageCount; // Always 1 for non-progressive, non-sequence AVIFs.
avifProgressiveState progressiveState; // See avifProgressiveState declaration
avifImageTiming imageTiming; //
uint64_t timescale; // timescale of the media (Hz)
double duration; // in seconds (durationInTimescales / timescale)
uint64_t durationInTimescales; // duration in "timescales"

// This is true when avifDecoderParse() detects an alpha plane. Use this to find out if alpha is
// present after a successful call to avifDecoderParse(), but prior to any call to
Expand All @@ -820,6 +854,9 @@ typedef struct avifDecoder
// stats from the most recent read, possibly 0s if reading an image sequence
avifIOStats ioStats;

// --------------------------------------------------------------------------------------------
// Internals

// Use one of the avifDecoderSetIO*() functions to set this
avifIO * io;

Expand Down
15 changes: 11 additions & 4 deletions include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,20 @@ typedef struct avifDecodeSample
avifBool partialData; // if true, data exists but doesn't have all of the sample in it

uint32_t itemID; // if non-zero, data comes from a mergedExtents buffer in an avifDecoderItem, not a file offset
uint64_t offset; // used only when itemID is zero, ignored and set to 0 when itemID is non-zero
size_t size;
avifBool sync; // is sync sample (keyframe)
uint64_t offset; // additional offset into data. Can be used to offset into an itemID's payload as well.
size_t size; //
uint8_t skip; // After feeding this sample, this is how many frames must be skipped before returning a frame
// This is used in layer selection, as layer selection requires that decoders decode all
// layers sequentially, but only a specific layer index is actually wanted.
avifBool sync; // is sync sample (keyframe)
} avifDecodeSample;
AVIF_ARRAY_DECLARE(avifDecodeSampleArray, avifDecodeSample, sample);

typedef struct avifCodecDecodeInput
{
avifDecodeSampleArray samples;
avifBool alpha; // if true, this is decoding an alpha plane
avifBool allLayers; // if true, the underlying codec must decode all layers, not just the best layer
avifBool alpha; // if true, this is decoding an alpha plane
} avifCodecDecodeInput;

avifCodecDecodeInput * avifCodecDecodeInputCreate(void);
Expand Down Expand Up @@ -244,6 +248,9 @@ typedef struct avifCodec
struct avifCodecInternal * internal; // up to each codec to use how it wants
//
avifDiagnostics * diag; // Shallow copy; owned by avifEncoder or avifDecoder
//
uint8_t operatingPointIndex; // Operating point Index, defaults to 0.
avifBool allLayers; // if true, the underlying codec must decode all layers, not just the best layer

avifCodecGetNextImageFunc getNextImage;
avifCodecEncodeImageFunc encodeImage;
Expand Down
14 changes: 14 additions & 0 deletions src/avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ const char * avifResultToString(avifResult result)
return "Unknown Error";
}

const char * avifProgressiveStateToString(avifProgressiveState progressiveState)
{
// clang-format off
switch (progressiveState) {
case AVIF_PROGRESSIVE_STATE_UNAVAILABLE: return "Unavailable";
case AVIF_PROGRESSIVE_STATE_AVAILABLE: return "Available";
case AVIF_PROGRESSIVE_STATE_ACTIVE: return "Active";
default:
break;
}
// clang-format on
return "Unknown";
}

// This function assumes nothing in this struct needs to be freed; use avifImageClear() externally
static void avifImageSetDefaults(avifImage * image)
{
Expand Down
Loading

0 comments on commit bffba3b

Please sign in to comment.