Image Sequence Support
* BMFF parser now reads moov box and sample table (stbl), which hold avifs image sequences
* libavif now reads 'avis' brands
* Split avifDecoderRead() into components for image sequences:
* avifDecoderSetSource()
* avifDecoderParse()
* avifDecoderNextImage()
* avifImageCopy()
* avifDecoderReset()
* avifDecoderRead() still exists as a simple single-image path
* Added decoder and image timings for image sequences
* Refactored codec API to not require each codec to maintain per-plane decoder instances
* avifImage can now "not own" its planes and directly point at decoder planes to avoid copies
* aviffuzz attempts to decode all images in source material twice (using avifDecoderReset())
* Switch decoder->quality to explicit [minQuantizer, maxQuantizer], update assoc. constants
* Add examples to README
diff --git a/src/read.c b/src/read.c
index 022eeb5..0371277 100644
--- a/src/read.c
+++ b/src/read.c
@@ -92,7 +92,6 @@
typedef struct avifSampleTableChunk
{
uint64_t offset;
- uint64_t size;
} avifSampleTableChunk;
AVIF_ARRAY_DECLARE(avifSampleTableChunkArray, avifSampleTableChunk, chunk);
@@ -125,7 +124,7 @@
avifSampleTableTimeToSampleArray timeToSamples;
} avifSampleTable;
-avifSampleTable * avifSampleTableCreate()
+static avifSampleTable * avifSampleTableCreate()
{
avifSampleTable * sampleTable = (avifSampleTable *)avifAlloc(sizeof(avifSampleTable));
memset(sampleTable, 0, sizeof(avifSampleTable));
@@ -136,7 +135,7 @@
return sampleTable;
}
-void avifSampleTableDestroy(avifSampleTable * sampleTable)
+static void avifSampleTableDestroy(avifSampleTable * sampleTable)
{
avifArrayDestroy(&sampleTable->chunks);
avifArrayDestroy(&sampleTable->sampleToChunks);
@@ -145,6 +144,21 @@
avifFree(sampleTable);
}
+static uint32_t avifSampleTableGetImageDelta(avifSampleTable * sampleTable, int imageIndex)
+{
+ int maxSampleIndex = 0;
+ for (uint32_t i = 0; i < sampleTable->timeToSamples.count; ++i) {
+ avifSampleTableTimeToSample * timeToSample = &sampleTable->timeToSamples.timeToSample[i];
+ maxSampleIndex += timeToSample->sampleCount;
+ if ((imageIndex < maxSampleIndex) || (i == (sampleTable->timeToSamples.count - 1))) {
+ return timeToSample->sampleDelta;
+ }
+ }
+
+ // TODO: fail here?
+ return 1;
+}
+
// one video track ("trak" contents)
typedef struct avifTrack
{
@@ -157,6 +171,67 @@
AVIF_ARRAY_DECLARE(avifTrackArray, avifTrack, track);
// ---------------------------------------------------------------------------
+// avifCodecDecodeInput
+
+static avifCodecDecodeInput * avifCodecDecodeInputCreate()
+{
+ avifCodecDecodeInput * decodeInput = (avifCodecDecodeInput *)avifAlloc(sizeof(avifCodecDecodeInput));
+ memset(decodeInput, 0, sizeof(avifCodecDecodeInput));
+ avifArrayCreate(&decodeInput->samples, sizeof(avifRawData), 1);
+ return decodeInput;
+}
+
+static void avifCodecDecodeInputDestroy(avifCodecDecodeInput * decodeInput)
+{
+ avifArrayDestroy(&decodeInput->samples);
+ avifFree(decodeInput);
+}
+
+static avifBool avifCodecDecodeInputGetSamples(avifCodecDecodeInput * decodeInput, avifSampleTable * sampleTable, avifRawData * rawInput)
+{
+ uint32_t sampleSizeIndex = 0;
+ for (uint32_t chunkIndex = 0; chunkIndex < sampleTable->chunks.count; ++chunkIndex) {
+ avifSampleTableChunk * chunk = &sampleTable->chunks.chunk[chunkIndex];
+
+ // First, figure out how many samples are in this chunk
+ uint32_t sampleCount = 0;
+ for (int sampleToChunkIndex = sampleTable->sampleToChunks.count - 1; sampleToChunkIndex >= 0; --sampleToChunkIndex) {
+ avifSampleTableSampleToChunk * sampleToChunk = &sampleTable->sampleToChunks.sampleToChunk[sampleToChunkIndex];
+ if (sampleToChunk->firstChunk <= (chunkIndex + 1)) {
+ sampleCount = sampleToChunk->samplesPerChunk;
+ break;
+ }
+ }
+ if (sampleCount == 0) {
+ // chunks with 0 samples are invalid
+ return AVIF_FALSE;
+ }
+
+ uint64_t sampleOffset = chunk->offset;
+ for (uint32_t sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
+ if (sampleSizeIndex >= sampleTable->sampleSizes.count) {
+ // We've run out of samples to sum
+ return AVIF_FALSE;
+ }
+
+ avifSampleTableSampleSize * sampleSize = &sampleTable->sampleSizes.sampleSize[sampleSizeIndex];
+
+ avifRawData * rawSample = (avifRawData *)avifArrayPushPtr(&decodeInput->samples);
+ rawSample->data = rawInput->data + sampleOffset;
+ rawSample->size = sampleSize->size;
+
+ if (sampleOffset > (uint64_t)rawInput->size) {
+ return AVIF_FALSE;
+ }
+
+ sampleOffset += sampleSize->size;
+ ++sampleSizeIndex;
+ }
+ }
+ return AVIF_TRUE;
+}
+
+// ---------------------------------------------------------------------------
// avifData
typedef struct avifData
@@ -165,10 +240,15 @@
avifItemArray items;
avifPropertyArray properties;
avifTrackArray tracks;
- int propertyCount;
+ avifRawData rawInput;
+ avifCodecDecodeInput * colorInput;
+ avifCodecDecodeInput * alphaInput;
+ avifDecoderSource source;
+ avifSampleTable * sourceSampleTable; // NULL unless (source == AVIF_DECODER_SOURCE_TRACKS), owned by an avifTrack
+ struct avifCodec * codec[AVIF_CODEC_PLANES_COUNT];
} avifData;
-avifData * avifDataCreate()
+static avifData * avifDataCreate()
{
avifData * data = (avifData *)avifAlloc(sizeof(avifData));
memset(data, 0, sizeof(avifData));
@@ -178,8 +258,19 @@
return data;
}
-void avifDataDestroy(avifData * data)
+static void avifDataResetCodec(avifData * data)
{
+ for (int i = 0; i < AVIF_CODEC_PLANES_COUNT; ++i) {
+ if (data->codec[i]) {
+ avifCodecDestroy(data->codec[i]);
+ data->codec[i] = NULL;
+ }
+ }
+}
+
+static void avifDataDestroy(avifData * data)
+{
+ avifDataResetCodec(data);
avifArrayDestroy(&data->items);
avifArrayDestroy(&data->properties);
for (uint32_t i = 0; i < data->tracks.count; ++i) {
@@ -188,10 +279,16 @@
}
}
avifArrayDestroy(&data->tracks);
+ if (data->colorInput) {
+ avifCodecDecodeInputDestroy(data->colorInput);
+ }
+ if (data->alphaInput) {
+ avifCodecDecodeInputDestroy(data->alphaInput);
+ }
avifFree(data);
}
-avifItem * avifDataFindItem(avifData * data, uint32_t itemID)
+static avifItem * avifDataFindItem(avifData * data, uint32_t itemID)
{
if (itemID == 0) {
return NULL;
@@ -248,7 +345,7 @@
for (int i = 0; i < itemCount; ++i) {
uint16_t itemID; // unsigned int(16) item_ID;
CHECK(avifStreamReadU16(&s, &itemID)); //
- uint16_t dataReferenceIndex; // unsigned int(16) data_reference_index;
+ uint16_t dataReferenceIndex; // unsigned int(16) data_ref rence_index;
CHECK(avifStreamReadU16(&s, &dataReferenceIndex)); //
uint64_t baseOffset; // unsigned int(base_offset_size*8) base_offset;
CHECK(avifStreamReadUX8(&s, &baseOffset, baseOffsetSize)); //
@@ -261,6 +358,9 @@
CHECK(avifStreamReadUX8(&s, &extentLength, lengthSize));
avifItem * item = avifDataFindItem(data, itemID);
+ if (!item) {
+ return AVIF_FALSE;
+ }
item->id = itemID;
item->offset = (uint32_t)(baseOffset + extentOffset);
item->size = (uint32_t)extentLength;
@@ -323,8 +423,6 @@
{
BEGIN_STREAM(s, raw, rawLen);
- data->propertyCount = 0;
-
while (avifStreamHasBytesLeft(&s, 1)) {
avifBoxHeader header;
CHECK(avifStreamReadBoxHeader(&s, &header));
@@ -393,6 +491,9 @@
}
avifItem * item = avifDataFindItem(data, itemID);
+ if (!item) {
+ return AVIF_FALSE;
+ }
// Associate property with item
avifProperty * prop = &data->properties.prop[propertyIndex];
@@ -457,6 +558,10 @@
CHECK(avifStreamRead(&s, itemType, 4)); //
avifItem * item = avifDataFindItem(data, itemID);
+ if (!item) {
+ return AVIF_FALSE;
+ }
+
memcpy(item->type, itemType, sizeof(itemType));
return AVIF_TRUE;
}
@@ -473,7 +578,7 @@
CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) entry_count;
entryCount = tmp;
} else if (version == 1) {
- CHECK(avifStreamReadU32(&s, &entryCount)); // unsigned int(16) entry_count;
+ CHECK(avifStreamReadU32(&s, &entryCount)); // unsigned int(32) entry_count;
} else {
return AVIF_FALSE;
}
@@ -512,7 +617,7 @@
CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) from_item_ID;
fromID = tmp;
} else if (version == 1) {
- CHECK(avifStreamReadU32(&s, &fromID)); // unsigned int(32) from_item_ID;
+ CHECK(avifStreamReadU32(&s, &fromID)); // unsigned int(32) from_item_ID;
} else {
// unsupported iref version, skip it
break;
@@ -528,7 +633,7 @@
CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) to_item_ID;
toID = tmp;
} else if (version == 1) {
- CHECK(avifStreamReadU32(&s, &toID)); // unsigned int(32) to_item_ID;
+ CHECK(avifStreamReadU32(&s, &toID)); // unsigned int(32) to_item_ID;
} else {
// unsupported iref version, skip it
break;
@@ -537,6 +642,10 @@
// Read this reference as "{fromID} is a {irefType} for {toID}"
if (fromID && toID) {
avifItem * item = avifDataFindItem(data, fromID);
+ if (!item) {
+ return AVIF_FALSE;
+ }
+
if (!memcmp(irefHeader.type, "thmb", 4)) {
item->thumbnailForID = toID;
}
@@ -743,39 +852,6 @@
CHECK(avifStreamSkip(&s, header.size));
}
-
- // Now calculate chunk sizes from the read-in sample table
- uint32_t sampleSizeIndex = 0;
- for (uint32_t chunkIndex = 0; chunkIndex < track->sampleTable->chunks.count; ++chunkIndex) {
- avifSampleTableChunk * chunk = &track->sampleTable->chunks.chunk[chunkIndex];
-
- // First, figure out how many samples are in this chunk
- uint32_t sampleCount = 0;
- for (int sampleToChunkIndex = track->sampleTable->sampleToChunks.count - 1; sampleToChunkIndex >= 0; --sampleToChunkIndex) {
- avifSampleTableSampleToChunk * sampleToChunk = &track->sampleTable->sampleToChunks.sampleToChunk[sampleToChunkIndex];
- if (sampleToChunk->firstChunk <= (chunkIndex + 1)) {
- sampleCount = sampleToChunk->samplesPerChunk;
- break;
- }
- }
- if (sampleCount == 0) {
- // chunks with 0 samples are invalid
- return AVIF_FALSE;
- }
-
- // Then sum up the next sampleCount samples into this chunk
- for (uint32_t sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
- if (sampleSizeIndex >= track->sampleTable->sampleSizes.count) {
- // We've run out of samples to sum
- return AVIF_FALSE;
- }
-
- avifSampleTableSampleSize * sampleSize = &track->sampleTable->sampleSizes.sampleSize[sampleSizeIndex];
- chunk->size += sampleSize->size;
- ++sampleSizeIndex;
- }
- }
-
return AVIF_TRUE;
}
@@ -815,6 +891,26 @@
return AVIF_TRUE;
}
+static avifBool avifTrackReferenceBox(avifData * data, avifTrack * track, uint8_t * raw, size_t rawLen)
+{
+ BEGIN_STREAM(s, raw, rawLen);
+
+ while (avifStreamHasBytesLeft(&s, 1)) {
+ avifBoxHeader header;
+ CHECK(avifStreamReadBoxHeader(&s, &header));
+
+ if (!memcmp(header.type, "auxl", 4)) {
+ uint32_t toID;
+ CHECK(avifStreamReadU32(&s, &toID)); // unsigned int(32) track_IDs[]
+ CHECK(avifStreamSkip(&s, header.size - sizeof(uint32_t))); // just take the first one
+ track->auxForID = toID;
+ } else {
+ CHECK(avifStreamSkip(&s, header.size));
+ }
+ }
+ return AVIF_TRUE;
+}
+
static avifBool avifParseTrackBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
@@ -829,6 +925,8 @@
CHECK(avifParseTrackHeaderBox(data, track, avifStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "mdia", 4)) {
CHECK(avifParseMediaBox(data, track, avifStreamCurrent(&s), header.size));
+ } else if (!memcmp(header.type, "tref", 4)) {
+ CHECK(avifTrackReferenceBox(data, track, avifStreamCurrent(&s), header.size));
}
CHECK(avifStreamSkip(&s, header.size));
@@ -904,90 +1002,228 @@
return decoder;
}
+static void avifDecoderCleanup(avifDecoder * decoder)
+{
+ if (decoder->data) {
+ avifDataDestroy(decoder->data);
+ decoder->data = NULL;
+ }
+
+ if (decoder->image) {
+ avifImageDestroy(decoder->image);
+ decoder->image = NULL;
+ }
+}
+
void avifDecoderDestroy(avifDecoder * decoder)
{
+ avifDecoderCleanup(decoder);
avifFree(decoder);
}
-avifResult avifDecoderRead(avifDecoder * decoder, avifImage * image, avifRawData * input)
+avifResult avifDecoderSetSource(avifDecoder * decoder, avifDecoderSource source)
{
- avifCodec * codec = NULL;
- avifData * data = NULL;
- avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
+ decoder->requestedSource = source;
+ return avifDecoderReset(decoder);
+}
+avifResult avifDecoderParse(avifDecoder * decoder, avifRawData * rawInput)
+{
#if !defined(AVIF_CODEC_AOM) && !defined(AVIF_CODEC_DAV1D)
// Just bail out early, we're not surviving this function without a decoder compiled in
return AVIF_RESULT_NO_CODEC_AVAILABLE;
#endif
+ // Cleanup anything lingering in the decoder
+ avifDecoderCleanup(decoder);
+
// -----------------------------------------------------------------------
// Parse BMFF boxes
- data = avifDataCreate();
- if (!avifParse(data, input->data, input->size)) {
- result = AVIF_RESULT_BMFF_PARSE_FAILED;
- goto cleanup;
+ decoder->data = avifDataCreate();
+
+ // Shallow copy, on purpose
+ memcpy(&decoder->data->rawInput, rawInput, sizeof(avifRawData));
+
+ if (!avifParse(decoder->data, decoder->data->rawInput.data, decoder->data->rawInput.size)) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
}
- avifBool avifCompatible = (memcmp(data->ftyp.majorBrand, "avif", 4) == 0) ? AVIF_TRUE : AVIF_FALSE;
+ avifBool avifCompatible = (memcmp(decoder->data->ftyp.majorBrand, "avif", 4) == 0) ? AVIF_TRUE : AVIF_FALSE;
if (!avifCompatible) {
- for (int compatibleBrandIndex = 0; compatibleBrandIndex < data->ftyp.compatibleBrandsCount; ++compatibleBrandIndex) {
- uint8_t * compatibleBrand = &data->ftyp.compatibleBrands[4 * compatibleBrandIndex];
- if (!memcmp(compatibleBrand, "avif", 4)) {
- avifCompatible = AVIF_TRUE;
- break;
+ avifCompatible = (memcmp(decoder->data->ftyp.majorBrand, "avis", 4) == 0) ? AVIF_TRUE : AVIF_FALSE;
+ if (!avifCompatible) {
+ for (int compatibleBrandIndex = 0; compatibleBrandIndex < decoder->data->ftyp.compatibleBrandsCount; ++compatibleBrandIndex) {
+ uint8_t * compatibleBrand = &decoder->data->ftyp.compatibleBrands[4 * compatibleBrandIndex];
+ if (!memcmp(compatibleBrand, "avif", 4)) {
+ avifCompatible = AVIF_TRUE;
+ break;
+ }
+ if (!memcmp(compatibleBrand, "avis", 4)) {
+ avifCompatible = AVIF_TRUE;
+ break;
+ }
}
}
}
if (!avifCompatible) {
- result = AVIF_RESULT_INVALID_FTYP;
- goto cleanup;
+ return AVIF_RESULT_INVALID_FTYP;
+ }
+
+ // Sanity check items
+ for (uint32_t itemIndex = 0; itemIndex < decoder->data->items.count; ++itemIndex) {
+ avifItem * item = &decoder->data->items.item[itemIndex];
+ if (item->offset > decoder->data->rawInput.size) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ uint64_t offsetSize = (uint64_t)item->offset + (uint64_t)item->size;
+ if (offsetSize > (uint64_t)decoder->data->rawInput.size) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ }
+
+ // Sanity check tracks
+ for (uint32_t trackIndex = 0; trackIndex < decoder->data->tracks.count; ++trackIndex) {
+ avifTrack * track = &decoder->data->tracks.track[trackIndex];
+ if (!track->sampleTable) {
+ continue;
+ }
+
+ for (uint32_t chunkIndex = 0; chunkIndex < track->sampleTable->chunks.count; ++chunkIndex) {
+ avifSampleTableChunk * chunk = &track->sampleTable->chunks.chunk[chunkIndex];
+ if (chunk->offset > decoder->data->rawInput.size) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ }
+ }
+ return avifDecoderReset(decoder);
+}
+
+static avifCodec * avifCodecCreateForDecode(avifCodecDecodeInput * decodeInput)
+{
+ avifCodec * codec = NULL;
+#if defined(AVIF_CODEC_DAV1D)
+ codec = avifCodecCreateDav1d();
+#elif defined(AVIF_CODEC_AOM)
+ codec = avifCodecCreateAOM();
+#else
+#error No decoder available!
+#endif
+ if (codec) {
+ codec->decodeInput = decodeInput;
+ }
+ return codec;
+}
+
+avifResult avifDecoderReset(avifDecoder * decoder)
+{
+ avifData * data = decoder->data;
+ if (!data) {
+ // Nothing to reset.
+ return AVIF_RESULT_OK;
+ }
+
+ avifDataResetCodec(data);
+ if (!decoder->image) {
+ decoder->image = avifImageCreateEmpty();
}
// -----------------------------------------------------------------------
+ // Build decode input
- avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
- avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
- avifItem * colorOBUItem = NULL;
- avifItem * alphaOBUItem = NULL;
-
- // Sanity check items
- for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
- avifItem * item = &data->items.item[itemIndex];
- if (item->offset > input->size) {
- result = AVIF_RESULT_BMFF_PARSE_FAILED;
- goto cleanup;
+ data->sourceSampleTable = NULL; // Reset
+ if (decoder->requestedSource == AVIF_DECODER_SOURCE_AUTO) {
+ if (data->tracks.count > 0) {
+ data->source = AVIF_DECODER_SOURCE_TRACKS;
+ } else {
+ data->source = AVIF_DECODER_SOURCE_PRIMARY_ITEM;
}
- uint64_t offsetSize = (uint64_t)item->offset + (uint64_t)item->size;
- if (offsetSize > (uint64_t)input->size) {
- result = AVIF_RESULT_BMFF_PARSE_FAILED;
- goto cleanup;
- }
+ } else {
+ data->source = decoder->requestedSource;
}
- // Find the colorOBU item
- for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
- avifItem * item = &data->items.item[itemIndex];
- if (!item->id || !item->size) {
+ if (data->source == AVIF_DECODER_SOURCE_TRACKS) {
+ avifTrack * colorTrack = NULL;
+ avifTrack * alphaTrack = NULL;
+
+ // Find primary track - this probably needs some better detection
+ uint32_t colorTrackIndex = 0;
+ for (; colorTrackIndex < decoder->data->tracks.count; ++colorTrackIndex) {
+ avifTrack * track = &decoder->data->tracks.track[colorTrackIndex];
+ if (!track->sampleTable) {
+ continue;
+ }
+ if (!track->sampleTable->chunks.count) {
+ continue;
+ }
+ if (track->auxForID != 0) {
+ continue;
+ }
+
+ // Found one!
break;
}
- if (memcmp(item->type, "av01", 4)) {
- // probably exif or some other data
- continue;
+ if (colorTrackIndex == decoder->data->tracks.count) {
+ return AVIF_RESULT_NO_CONTENT;
}
- if (item->thumbnailForID != 0) {
- // It's a thumbnail, skip it
- continue;
+ colorTrack = &decoder->data->tracks.track[colorTrackIndex];
+
+ uint32_t alphaTrackIndex = 0;
+ for (; alphaTrackIndex < decoder->data->tracks.count; ++alphaTrackIndex) {
+ avifTrack * track = &decoder->data->tracks.track[alphaTrackIndex];
+ if (!track->sampleTable) {
+ continue;
+ }
+ if (!track->sampleTable->chunks.count) {
+ continue;
+ }
+ if (track->auxForID == colorTrack->id) {
+ // Found it!
+ break;
+ }
+ }
+ if (alphaTrackIndex != decoder->data->tracks.count) {
+ alphaTrack = &decoder->data->tracks.track[alphaTrackIndex];
}
- colorOBUItem = item;
- colorOBU.data = input->data + item->offset;
- colorOBU.size = item->size;
- break;
- }
+ // TODO: We must get color profile information from somewhere; likely the color OBU as a fallback
- // Find the alphaOBU item, if any
- if (colorOBUItem) {
+ data->colorInput = avifCodecDecodeInputCreate();
+ if (!avifCodecDecodeInputGetSamples(data->colorInput, colorTrack->sampleTable, &decoder->data->rawInput)) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+
+ if (alphaTrack) {
+ data->alphaInput = avifCodecDecodeInputCreate();
+ if (!avifCodecDecodeInputGetSamples(data->alphaInput, alphaTrack->sampleTable, &decoder->data->rawInput)) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ data->alphaInput->alpha = AVIF_TRUE;
+ }
+
+ // Stash off sample table for future timing information
+ data->sourceSampleTable = colorTrack->sampleTable;
+
+ // Image sequence timing
+ decoder->imageIndex = -1;
+ decoder->imageCount = data->colorInput->samples.count;
+ decoder->timescale = colorTrack->mediaTimescale;
+ decoder->durationInTimescales = colorTrack->mediaDuration;
+ if (colorTrack->mediaTimescale) {
+ decoder->duration = (double)decoder->durationInTimescales / (double)colorTrack->mediaTimescale;
+ } else {
+ decoder->duration = 0;
+ }
+ memset(&decoder->imageTiming, 0, sizeof(decoder->imageTiming)); // to be set in avifDecoderNextImage()
+ } else {
+ // Create from items
+
+ avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
+ avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
+ avifItem * colorOBUItem = NULL;
+ avifItem * alphaOBUItem = NULL;
+
+ // Find the colorOBU item
for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
avifItem * item = &data->items.item[itemIndex];
if (!item->id || !item->size) {
@@ -1002,105 +1238,160 @@
continue;
}
- if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) {
- alphaOBUItem = item;
- alphaOBU.data = input->data + item->offset;
- alphaOBU.size = item->size;
- break;
+ colorOBUItem = item;
+ colorOBU.data = data->rawInput.data + item->offset;
+ colorOBU.size = item->size;
+ break;
+ }
+
+ // Find the alphaOBU item, if any
+ if (colorOBUItem) {
+ for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
+ avifItem * item = &data->items.item[itemIndex];
+ if (!item->id || !item->size) {
+ break;
+ }
+ if (memcmp(item->type, "av01", 4)) {
+ // probably exif or some other data
+ continue;
+ }
+ if (item->thumbnailForID != 0) {
+ // It's a thumbnail, skip it
+ continue;
+ }
+
+ if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) {
+ alphaOBUItem = item;
+ alphaOBU.data = data->rawInput.data + item->offset;
+ alphaOBU.size = item->size;
+ break;
+ }
}
}
- }
- if (colorOBU.size == 0) {
- result = AVIF_RESULT_NO_AV1_ITEMS_FOUND;
- goto cleanup;
- }
- avifBool hasAlpha = (alphaOBU.size > 0) ? AVIF_TRUE : AVIF_FALSE;
-
-#if defined(AVIF_CODEC_DAV1D)
- codec = avifCodecCreateDav1d();
-#elif defined(AVIF_CODEC_AOM)
- codec = avifCodecCreateAOM();
-#else
- // #error No decoder available!
- return AVIF_RESULT_NO_CODEC_AVAILABLE;
-#endif
- if (!codec->decode(codec, AVIF_CODEC_PLANES_COLOR, &colorOBU)) {
- result = AVIF_RESULT_DECODE_COLOR_FAILED;
- goto cleanup;
- }
- avifCodecImageSize colorPlanesSize = codec->getImageSize(codec, AVIF_CODEC_PLANES_COLOR);
-
- avifCodecImageSize alphaPlanesSize;
- memset(&alphaPlanesSize, 0, sizeof(alphaPlanesSize));
- if (hasAlpha) {
- if (!codec->decode(codec, AVIF_CODEC_PLANES_ALPHA, &alphaOBU)) {
- result = AVIF_RESULT_DECODE_ALPHA_FAILED;
- goto cleanup;
+ if (colorOBU.size == 0) {
+ return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
}
- alphaPlanesSize = codec->getImageSize(codec, AVIF_CODEC_PLANES_ALPHA);
- if ((colorPlanesSize.width != alphaPlanesSize.width) || (colorPlanesSize.height != alphaPlanesSize.height)) {
- result = AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH;
- goto cleanup;
+ if (colorOBUItem->colrPresent) {
+ if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_ICC) {
+ avifImageSetProfileICC(decoder->image, colorOBUItem->colr.icc, colorOBUItem->colr.iccSize);
+ } else if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_NCLX) {
+ avifImageSetProfileNCLX(decoder->image, &colorOBUItem->colr.nclx);
+ }
+ }
+
+ data->colorInput = avifCodecDecodeInputCreate();
+ avifRawData * rawColorInput = (avifRawData *)avifArrayPushPtr(&data->colorInput->samples);
+ memcpy(rawColorInput, &colorOBU, sizeof(avifRawData));
+ if (alphaOBU.size > 0) {
+ data->alphaInput = avifCodecDecodeInputCreate();
+ avifRawData * rawAlphaInput = (avifRawData *)avifArrayPushPtr(&data->alphaInput->samples);
+ memcpy(rawAlphaInput, &alphaOBU, sizeof(avifRawData));
+ data->alphaInput->alpha = AVIF_TRUE;
+ }
+
+ // Set all counts and timing to safe-but-uninteresting values
+ decoder->imageIndex = -1;
+ decoder->imageCount = 1;
+ decoder->imageTiming.timescale = 1;
+ decoder->imageTiming.pts = 0;
+ decoder->imageTiming.ptsInTimescales = 0;
+ decoder->imageTiming.duration = 1;
+ decoder->imageTiming.durationInTimescales = 1;
+ decoder->timescale = 1;
+ decoder->duration = 1;
+ decoder->durationInTimescales = 1;
+ }
+
+ data->codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreateForDecode(data->colorInput);
+ if (!data->codec[AVIF_CODEC_PLANES_COLOR]->decode(data->codec[AVIF_CODEC_PLANES_COLOR])) {
+ return AVIF_RESULT_DECODE_COLOR_FAILED;
+ }
+
+ if (data->alphaInput) {
+ decoder->data->codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreateForDecode(data->alphaInput);
+ if (!data->codec[AVIF_CODEC_PLANES_ALPHA]->decode(data->codec[AVIF_CODEC_PLANES_ALPHA])) {
+ return AVIF_RESULT_DECODE_ALPHA_FAILED;
}
}
+ return AVIF_RESULT_OK;
+}
- if ((colorOBUItem && colorOBUItem->ispePresent &&
- ((colorOBUItem->ispe.width != colorPlanesSize.width) || (colorOBUItem->ispe.height != colorPlanesSize.height))) ||
- (alphaOBUItem && alphaOBUItem->ispePresent &&
- ((alphaOBUItem->ispe.width != alphaPlanesSize.width) || (alphaOBUItem->ispe.height != alphaPlanesSize.height)))) {
- result = AVIF_RESULT_ISPE_SIZE_MISMATCH;
- goto cleanup;
- }
-
- if (colorOBUItem->colrPresent) {
- if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_ICC) {
- avifImageSetProfileICC(image, colorOBUItem->colr.icc, colorOBUItem->colr.iccSize);
- } else if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_NCLX) {
- avifImageSetProfileNCLX(image, &colorOBUItem->colr.nclx);
+avifResult avifDecoderNextImage(avifDecoder * decoder)
+{
+ avifCodec * colorCodec = decoder->data->codec[AVIF_CODEC_PLANES_COLOR];
+ if (!colorCodec->getNextImage(colorCodec, decoder->image)) {
+ if (decoder->image->width) {
+ // We've sent at least one image, but we've run out now.
+ return AVIF_RESULT_NO_IMAGES_REMAINING;
}
+ return AVIF_RESULT_DECODE_COLOR_FAILED;
}
- avifImageFreePlanes(image, AVIF_PLANES_ALL);
-
- avifResult imageResult = codec->getDecodedImage(codec, image);
- if (imageResult != AVIF_RESULT_OK) {
- result = imageResult;
- goto cleanup;
+ avifCodec * alphaCodec = decoder->data->codec[AVIF_CODEC_PLANES_ALPHA];
+ if (alphaCodec) {
+ if (!alphaCodec->getNextImage(alphaCodec, decoder->image)) {
+ return AVIF_RESULT_DECODE_ALPHA_FAILED;
+ }
+ } else {
+ avifImageFreePlanes(decoder->image, AVIF_PLANES_A);
}
#if defined(AVIF_FIX_STUDIO_ALPHA)
- if (hasAlpha && codec->alphaLimitedRange(codec)) {
+ if (alphaCodec && alphaCodec->alphaLimitedRange(alphaCodec)) {
// Naughty! Alpha planes are supposed to be full range. Correct that here.
- if (avifImageUsesU16(image)) {
- for (int j = 0; j < image->height; ++j) {
- for (int i = 0; i < image->height; ++i) {
- uint16_t * alpha = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)];
- *alpha = (uint16_t)avifLimitedToFullY(image->depth, *alpha);
+ avifImageCopyDecoderAlpha(decoder->image);
+ if (avifImageUsesU16(decoder->image)) {
+ for (int j = 0; j < decoder->image->height; ++j) {
+ for (int i = 0; i < decoder->image->height; ++i) {
+ uint16_t * alpha = (uint16_t *)&decoder->image->alphaPlane[(i * 2) + (j * decoder->image->alphaRowBytes)];
+ *alpha = (uint16_t)avifLimitedToFullY(decoder->image->depth, *alpha);
}
}
} else {
- for (int j = 0; j < image->height; ++j) {
- for (int i = 0; i < image->height; ++i) {
- uint8_t * alpha = &image->alphaPlane[i + (j * image->alphaRowBytes)];
- *alpha = (uint8_t)avifLimitedToFullY(image->depth, *alpha);
+ for (int j = 0; j < decoder->image->height; ++j) {
+ for (int i = 0; i < decoder->image->height; ++i) {
+ uint8_t * alpha = &decoder->image->alphaPlane[i + (j * decoder->image->alphaRowBytes)];
+ *alpha = (uint8_t)avifLimitedToFullY(decoder->image->depth, *alpha);
}
}
}
}
#endif
- decoder->ioStats.colorOBUSize = colorOBU.size;
- decoder->ioStats.alphaOBUSize = alphaOBU.size;
+ ++decoder->imageIndex;
+ if (decoder->data->sourceSampleTable) {
+ // Decoding from a track! Provide timing information.
- result = AVIF_RESULT_OK;
-cleanup:
- if (codec) {
- avifCodecDestroy(codec);
+ decoder->imageTiming.timescale = decoder->timescale;
+ decoder->imageTiming.ptsInTimescales += decoder->imageTiming.durationInTimescales;
+ decoder->imageTiming.durationInTimescales = avifSampleTableGetImageDelta(decoder->data->sourceSampleTable, decoder->imageIndex);
+
+ if (decoder->imageTiming.timescale > 0) {
+ decoder->imageTiming.pts = (double)decoder->imageTiming.ptsInTimescales / (double)decoder->imageTiming.timescale;
+ decoder->imageTiming.duration = (double)decoder->imageTiming.durationInTimescales / (double)decoder->imageTiming.timescale;
+ } else {
+ decoder->imageTiming.pts = 0.0;
+ decoder->imageTiming.duration = 0.0;
+ }
}
- if (data) {
- avifDataDestroy(data);
+ return AVIF_RESULT_OK;
+}
+
+avifResult avifDecoderRead(avifDecoder * decoder, avifImage * image, avifRawData * input)
+{
+ avifResult result = avifDecoderParse(decoder, input);
+ if (result != AVIF_RESULT_OK) {
+ return result;
}
- return result;
+ result = avifDecoderNextImage(decoder);
+ if (result != AVIF_RESULT_OK) {
+ return result;
+ }
+ if (!decoder->image) {
+ return AVIF_RESULT_NO_IMAGES_REMAINING;
+ }
+ avifImageCopy(image, decoder->image);
+ return AVIF_RESULT_OK;
}