| // 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; |
| } |