blob: ce50a7f235e2d207b26741f8e1e64b853d27079a [file] [log] [blame]
// Copyright 2020 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/avif.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
// This example intends to show how a custom avifIO implementation can be used to decode
// partially-downloaded AVIFs. Read either the avif_example_decode_file or
// avif_example_decode_memory examples for something more basic.
//
// This test will emit something like this:
//
// File: [file.avif @ 4823 / 125867 bytes, Metadata] parse returned: OK
// File: [file.avif @ 125867 / 125867 bytes, Metadata] nextImage returned: OK
// File: [file.avif @ 390 / 125867 bytes, IgnoreMetadata] parse returned: OK
// File: [file.avif @ 125867 / 125867 bytes, IgnoreMetadata] nextImage returned: OK
//
// In the above output, file.avif is 125867 bytes. If parsing Exif/XMP metadata is enabled,
// avifDecoderParse() finally returns AVIF_RESULT_OK once 4823 bytes are "downloaded".
// and requires the entire file to decode the first image (this example is a single image AVIF).
// If Exif/XMP metadata is ignored, avifDecoderParse() only needs the first 390 bytes to return OK.
//
// How much of an AVIF is required to be downloaded in order to return OK from avifDecoderParse()
// or avifDecoderNextImage() varies wildly due to the packing of the file. Ideally, the end of the
// AVIF is simply a large mdat or moov box full of AV1 payloads, and all metadata (meta boxes,
// Exif/XMP payloads, etc) are as close to the front as possible. Any trailing MP4 boxes (free, etc)
// will cause avifDecoderParse() to have to wait to download those, as it can't ensure a successful
// parse without knowing what boxes are remaining.
typedef struct avifIOStreamingReader
{
avifIO io; // This must be first if you plan to cast this struct to an (avifIO *).
avifROData rodata; // The actual data.
size_t downloadedBytes; // How many bytes have been "downloaded" so far. This is what will
// dictate when we return AVIF_RESULT_WAITING_ON_IO in this example.
// The example will slowly increment this value (from 0) until
// avifDecoderParse() returns something other than AVIF_RESULT_WAITING_ON_IO,
// and then it will continue to incremement it until avifDecoderNextImage()
// returns something other than AVIF_RESULT_WAITING_ON_IO.
} avifIOStreamingReader;
// This example has interleaved the documentation above avifIOReadFunc in avif/avif.h to help
// explain why these checks are here.
static avifResult avifIOStreamingReaderRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
{
avifIOStreamingReader * reader = (avifIOStreamingReader *)io;
if (readFlags != 0) {
// Unsupported readFlags
return AVIF_RESULT_IO_ERROR;
}
// * If offset exceeds the size of the content (past EOF), return AVIF_RESULT_IO_ERROR.
if (offset > reader->rodata.size) {
return AVIF_RESULT_IO_ERROR;
}
// * If offset is *exactly* at EOF, provide any 0-byte buffer and return AVIF_RESULT_OK.
if (offset == reader->rodata.size) {
out->data = reader->rodata.data;
out->size = 0;
return AVIF_RESULT_OK;
}
// * If (offset+size) exceeds the contents' size, it must provide a truncated buffer that provides
// all bytes from the offset to EOF, and return AVIF_RESULT_OK.
uint64_t availableSize = reader->rodata.size - offset;
if (size > availableSize) {
size = (size_t)availableSize;
}
// * If (offset+size) does not exceed the contents' size but the *entire range* is unavailable yet
// (due to network conditions or any other reason), return AVIF_RESULT_WAITING_ON_IO.
if (offset > reader->downloadedBytes) {
return AVIF_RESULT_WAITING_ON_IO;
}
if (size > (reader->downloadedBytes - offset)) {
return AVIF_RESULT_WAITING_ON_IO;
}
// * If (offset+size) does not exceed the contents' size, it must provide the *entire range* and
// return AVIF_RESULT_OK.
out->data = reader->rodata.data + offset;
out->size = size;
return AVIF_RESULT_OK;
}
static void avifIOStreamingReaderDestroy(struct avifIO * io)
{
free(io);
}
// Returns null in case of memory allocation failure.
static avifIOStreamingReader * avifIOCreateStreamingReader(const uint8_t * data, size_t size)
{
avifIOStreamingReader * reader = calloc(1, sizeof(avifIOStreamingReader));
if (!reader)
return NULL;
// It is legal for io.destroy to be NULL, in which you are responsible for cleaning up
// your own reader. This allows for a pre-existing, on-the-stack, or member variable to be
// used as an avifIO*.
reader->io.destroy = avifIOStreamingReaderDestroy;
// The heart of the reader is this function. See the implementation and comments above.
reader->io.read = avifIOStreamingReaderRead;
// See the documentation for sizeHint in avif/avif.h. It is not required to be set, but it is recommended.
reader->io.sizeHint = size;
// See the documentation for persistent in avif/avif.h. Enabling this adds heavy restrictions to
// the lifetime of the buffers you return from io.read, but cuts down on memory overhead and memcpys.
reader->io.persistent = AVIF_TRUE;
reader->rodata.data = data;
reader->rodata.size = size;
return reader;
}
int main(int argc, char * argv[])
{
if (argc != 2) {
fprintf(stderr, "avif_example_decode_streaming [filename.avif]\n");
return 1;
}
const char * inputFilename = argv[1];
int returnCode = 1;
avifDecoder * decoder = NULL;
// Read entire file into fileBuffer
FILE * f = NULL;
uint8_t * fileBuffer = NULL;
f = fopen(inputFilename, "rb");
if (!f) {
fprintf(stderr, "Cannot open file for read: %s\n", inputFilename);
goto cleanup;
}
fseek(f, 0, SEEK_END);
long fileSize = ftell(f);
if (fileSize < 0) {
fprintf(stderr, "Truncated file: %s\n", inputFilename);
goto cleanup;
}
fseek(f, 0, SEEK_SET);
fileBuffer = malloc(fileSize);
long bytesRead = (long)fread(fileBuffer, 1, fileSize, f);
if (bytesRead != fileSize) {
fprintf(stderr, "Cannot read file: %s\n", inputFilename);
goto cleanup;
}
decoder = avifDecoderCreate();
if (!decoder) {
fprintf(stderr, "Memory allocation failure\n");
goto cleanup;
}
// Override decoder defaults here (codecChoice, requestedSource, ignoreExif, ignoreXMP, etc)
avifIOStreamingReader * io = avifIOCreateStreamingReader(fileBuffer, fileSize);
if (!io) {
fprintf(stderr, "Memory allocation failure\n");
goto cleanup;
}
avifDecoderSetIO(decoder, (avifIO *)io);
for (int pass = 0; pass < 2; ++pass) {
// This shows the difference in how much data avifDecoderParse() needs from the file
// depending on whether or not Exif/XMP metadata is necessary. If the caller plans to
// interpret this metadata, avifDecoderParse() will continue to return
// AVIF_RESULT_WAITING_ON_IO until it has those payloads in their entirety (if they exist).
decoder->ignoreExif = decoder->ignoreXMP = (pass > 0);
// Slowly pretend to have streamed-in / downloaded more and more bytes by incrementing io->downloadedBytes
avifResult parseResult = AVIF_RESULT_UNKNOWN_ERROR;
for (io->downloadedBytes = 0; io->downloadedBytes <= io->io.sizeHint; ++io->downloadedBytes) {
parseResult = avifDecoderParse(decoder);
if (parseResult == AVIF_RESULT_WAITING_ON_IO) {
continue;
}
if (parseResult != AVIF_RESULT_OK) {
returnCode = 1;
}
// See other examples on how to access the parsed information.
printf("File: [%s @ %zu / %" PRIu64 " bytes, %s] parse returned: %s\n",
inputFilename,
io->downloadedBytes,
io->io.sizeHint,
decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
avifResultToString(parseResult));
break;
}
if (parseResult == AVIF_RESULT_OK) {
for (; io->downloadedBytes <= io->io.sizeHint; ++io->downloadedBytes) {
avifResult nextImageResult = avifDecoderNextImage(decoder);
if (nextImageResult == AVIF_RESULT_WAITING_ON_IO) {
continue;
}
if (nextImageResult != AVIF_RESULT_OK) {
returnCode = 1;
}
// See other examples on how to access the pixel content of the images.
printf("File: [%s @ %zu / %" PRIu64 " bytes, %s] nextImage returned: %s\n",
inputFilename,
io->downloadedBytes,
io->io.sizeHint,
decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
avifResultToString(nextImageResult));
break;
}
}
}
returnCode = 0;
cleanup:
if (decoder) {
avifDecoderDestroy(decoder); // this calls avifIOStreamingReaderDestroy for us
}
if (f) {
fclose(f);
}
free(fileBuffer);
return returnCode;
}