1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
// 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;
}
|