forked from AOMediaCodec/libavif
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathavif_example_decode_streaming.c
218 lines (188 loc) · 8.99 KB
/
avif_example_decode_streaming.c
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
// 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>
#include <string.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 succesful
// 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)
{
avifFree(io);
}
static avifIOStreamingReader * avifIOCreateStreamingReader(const uint8_t * data, size_t size)
{
avifIOStreamingReader * reader = avifAlloc(sizeof(avifIOStreamingReader));
memset(reader, 0, sizeof(avifIOStreamingReader));
// 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 = avifDecoderCreate();
// Override decoder defaults here (codecChoice, requestedSource, ignoreExif, ignoreXMP, etc)
// 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);
}
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;
}
avifIOStreamingReader * io = avifIOCreateStreamingReader(fileBuffer, fileSize);
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:
avifDecoderDestroy(decoder); // this calls avifIOStreamingReaderDestroy for us
if (f) {
fclose(f);
}
free(fileBuffer);
return returnCode;
}