Keyframe info and Nth image support
Added:
- stss box parsing for keyframe information
- avifBool avifDecoderIsKeyframe(avifDecoder * decoder, uint32_t frameIndex);
- uint32_t avifDecoderNearestKeyframe(avifDecoder * decoder, uint32_t frameIndex);
- avifResult avifDecoderNthImage(avifDecoder * decoder, uint32_t frameIndex);
- aviffuzz prints keyframe information as it repeatedly decodes
Changed:
- internally renamed codec function "decode" to "open", as that's all it does
- dav1d codec's open function no longer does an initial unnecessary feed
- avifCodecDecodeInput now stores an array of avifSample which know if they're keyframes
- moved codec flushing code into avifDecoderFlush() so it is available to avifDecoderNthImage
- ptsInTimescales is now calculated independently of frame decode order
diff --git a/src/read.c b/src/read.c
index 06e87ea..ca1a1de 100644
--- a/src/read.c
+++ b/src/read.c
@@ -116,12 +116,19 @@
} avifSampleTableTimeToSample;
AVIF_ARRAY_DECLARE(avifSampleTableTimeToSampleArray, avifSampleTableTimeToSample, timeToSample);
+typedef struct avifSyncSample
+{
+ uint32_t sampleNumber;
+} avifSyncSample;
+AVIF_ARRAY_DECLARE(avifSyncSampleArray, avifSyncSample, syncSample);
+
typedef struct avifSampleTable
{
avifSampleTableChunkArray chunks;
avifSampleTableSampleToChunkArray sampleToChunks;
avifSampleTableSampleSizeArray sampleSizes;
avifSampleTableTimeToSampleArray timeToSamples;
+ avifSyncSampleArray syncSamples;
} avifSampleTable;
static avifSampleTable * avifSampleTableCreate()
@@ -132,6 +139,7 @@
avifArrayCreate(&sampleTable->sampleToChunks, sizeof(avifSampleTableSampleToChunk), 16);
avifArrayCreate(&sampleTable->sampleSizes, sizeof(avifSampleTableSampleSize), 16);
avifArrayCreate(&sampleTable->timeToSamples, sizeof(avifSampleTableTimeToSample), 16);
+ avifArrayCreate(&sampleTable->syncSamples, sizeof(avifSampleTable), 16);
return sampleTable;
}
@@ -141,6 +149,7 @@
avifArrayDestroy(&sampleTable->sampleToChunks);
avifArrayDestroy(&sampleTable->sampleSizes);
avifArrayDestroy(&sampleTable->timeToSamples);
+ avifArrayDestroy(&sampleTable->syncSamples);
avifFree(sampleTable);
}
@@ -177,7 +186,7 @@
{
avifCodecDecodeInput * decodeInput = (avifCodecDecodeInput *)avifAlloc(sizeof(avifCodecDecodeInput));
memset(decodeInput, 0, sizeof(avifCodecDecodeInput));
- avifArrayCreate(&decodeInput->samples, sizeof(avifROData), 1);
+ avifArrayCreate(&decodeInput->samples, sizeof(avifSample), 1);
return decodeInput;
}
@@ -216,9 +225,10 @@
avifSampleTableSampleSize * sampleSize = &sampleTable->sampleSizes.sampleSize[sampleSizeIndex];
- avifROData * rawSample = (avifROData *)avifArrayPushPtr(&decodeInput->samples);
- rawSample->data = rawInput->data + sampleOffset;
- rawSample->size = sampleSize->size;
+ avifSample * sample = (avifSample *)avifArrayPushPtr(&decodeInput->samples);
+ sample->data.data = rawInput->data + sampleOffset;
+ sample->data.size = sampleSize->size;
+ sample->sync = AVIF_FALSE; // to potentially be set to true following the outer loop
if (sampleOffset > (uint64_t)rawInput->size) {
return AVIF_FALSE;
@@ -228,6 +238,19 @@
++sampleSizeIndex;
}
}
+
+ // Mark appropriate samples as sync
+ for (uint32_t syncSampleIndex = 0; syncSampleIndex < sampleTable->syncSamples.count; ++syncSampleIndex) {
+ uint32_t frameIndex = sampleTable->syncSamples.syncSample[syncSampleIndex].sampleNumber - 1; // sampleNumber is 1-based
+ if (frameIndex < decodeInput->samples.count) {
+ decodeInput->samples.sample[frameIndex].sync = AVIF_TRUE;
+ }
+ }
+
+ // Assume frame 0 is sync, just in case the stss box is absent in the BMFF. (Unnecessary?)
+ if (decodeInput->samples.count > 0) {
+ decodeInput->samples.sample[0].sync = AVIF_TRUE;
+ }
return AVIF_TRUE;
}
@@ -814,6 +837,25 @@
return AVIF_TRUE;
}
+static avifBool avifParseSyncSampleBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+{
+ BEGIN_STREAM(s, raw, rawLen);
+ (void)data;
+
+ CHECK(avifROStreamReadAndEnforceVersion(&s, 0));
+
+ uint32_t entryCount;
+ CHECK(avifROStreamReadU32(&s, &entryCount)); // unsigned int(32) entry_count;
+
+ for (uint32_t i = 0; i < entryCount; ++i) {
+ uint32_t sampleNumber = 0;
+ CHECK(avifROStreamReadU32(&s, &sampleNumber)); // unsigned int(32) sample_number;
+ avifSyncSample * syncSample = (avifSyncSample *)avifArrayPushPtr(&sampleTable->syncSamples);
+ syncSample->sampleNumber = sampleNumber;
+ }
+ return AVIF_TRUE;
+}
+
static avifBool avifParseTimeToSampleBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
@@ -854,6 +896,8 @@
CHECK(avifParseSampleToChunkBox(data, track->sampleTable, avifROStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "stsz", 4)) {
CHECK(avifParseSampleSizeBox(data, track->sampleTable, avifROStreamCurrent(&s), header.size));
+ } else if (!memcmp(header.type, "stss", 4)) {
+ CHECK(avifParseSyncSampleBox(data, track->sampleTable, avifROStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "stts", 4)) {
CHECK(avifParseTimeToSampleBox(data, track->sampleTable, avifROStreamCurrent(&s), header.size));
}
@@ -1151,6 +1195,24 @@
return codec;
}
+static avifResult avifDecoderFlush(avifDecoder * decoder)
+{
+ avifDataResetCodec(decoder->data);
+
+ decoder->data->codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreateForDecode(decoder->data->colorInput);
+ if (!decoder->data->codec[AVIF_CODEC_PLANES_COLOR]->open(decoder->data->codec[AVIF_CODEC_PLANES_COLOR])) {
+ return AVIF_RESULT_DECODE_COLOR_FAILED;
+ }
+
+ if (decoder->data->alphaInput) {
+ decoder->data->codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreateForDecode(decoder->data->alphaInput);
+ if (!decoder->data->codec[AVIF_CODEC_PLANES_ALPHA]->open(decoder->data->codec[AVIF_CODEC_PLANES_ALPHA])) {
+ return AVIF_RESULT_DECODE_ALPHA_FAILED;
+ }
+ }
+ return AVIF_RESULT_OK;
+}
+
avifResult avifDecoderReset(avifDecoder * decoder)
{
avifData * data = decoder->data;
@@ -1318,12 +1380,14 @@
}
data->colorInput = avifCodecDecodeInputCreate();
- avifROData * rawColorInput = (avifROData *)avifArrayPushPtr(&data->colorInput->samples);
- memcpy(rawColorInput, &colorOBU, sizeof(avifROData));
+ avifSample * colorSample = (avifSample *)avifArrayPushPtr(&data->colorInput->samples);
+ memcpy(&colorSample->data, &colorOBU, sizeof(avifROData));
+ colorSample->sync = AVIF_TRUE;
if (alphaOBU.size > 0) {
data->alphaInput = avifCodecDecodeInputCreate();
- avifROData * rawAlphaInput = (avifROData *)avifArrayPushPtr(&data->alphaInput->samples);
- memcpy(rawAlphaInput, &alphaOBU, sizeof(avifROData));
+ avifSample * alphaSample = (avifSample *)avifArrayPushPtr(&data->alphaInput->samples);
+ memcpy(&alphaSample->data, &alphaOBU, sizeof(avifROData));
+ alphaSample->sync = AVIF_TRUE;
data->alphaInput->alpha = AVIF_TRUE;
}
@@ -1343,18 +1407,7 @@
decoder->ioStats.alphaOBUSize = alphaOBU.size;
}
- 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;
+ return avifDecoderFlush(decoder);
}
avifResult avifDecoderNextImage(avifDecoder * decoder)
@@ -1404,7 +1457,10 @@
// Decoding from a track! Provide timing information.
decoder->imageTiming.timescale = decoder->timescale;
- decoder->imageTiming.ptsInTimescales += decoder->imageTiming.durationInTimescales;
+ decoder->imageTiming.ptsInTimescales = 0;
+ for (int imageIndex = 0; imageIndex < decoder->imageIndex; ++imageIndex) {
+ decoder->imageTiming.ptsInTimescales += avifSampleTableGetImageDelta(decoder->data->sourceSampleTable, imageIndex);
+ }
decoder->imageTiming.durationInTimescales = avifSampleTableGetImageDelta(decoder->data->sourceSampleTable, decoder->imageIndex);
if (decoder->imageTiming.timescale > 0) {
@@ -1418,6 +1474,60 @@
return AVIF_RESULT_OK;
}
+avifResult avifDecoderNthImage(avifDecoder * decoder, uint32_t frameIndex)
+{
+ int requestedIndex = (int)frameIndex;
+ if (requestedIndex == decoder->imageIndex) {
+ // We're here already, nothing to do
+ return AVIF_RESULT_OK;
+ }
+
+ if (requestedIndex == (decoder->imageIndex + 1)) {
+ // it's just the next image, nothing special here
+ return avifDecoderNextImage(decoder);
+ }
+
+ if (requestedIndex >= decoder->imageCount) {
+ // Impossible index
+ return AVIF_RESULT_NO_IMAGES_REMAINING;
+ }
+
+ // If we get here, a decoder flush is necessary
+ avifDecoderFlush(decoder);
+ decoder->imageIndex = ((int)avifDecoderNearestKeyframe(decoder, frameIndex)) - 1; // prepare to read nearest keyframe
+ for (;;) {
+ avifResult result = avifDecoderNextImage(decoder);
+ if (result != AVIF_RESULT_OK) {
+ return result;
+ }
+
+ if (requestedIndex == decoder->imageIndex) {
+ break;
+ }
+ };
+ return AVIF_RESULT_OK;
+}
+
+avifBool avifDecoderIsKeyframe(avifDecoder * decoder, uint32_t frameIndex)
+{
+ if (decoder->data->colorInput) {
+ if (frameIndex < decoder->data->colorInput->samples.count) {
+ return decoder->data->colorInput->samples.sample[frameIndex].sync;
+ }
+ }
+ return AVIF_FALSE;
+}
+
+uint32_t avifDecoderNearestKeyframe(avifDecoder * decoder, uint32_t frameIndex)
+{
+ for (; frameIndex != 0; --frameIndex) {
+ if (avifDecoderIsKeyframe(decoder, frameIndex)) {
+ break;
+ }
+ }
+ return frameIndex;
+}
+
avifResult avifDecoderRead(avifDecoder * decoder, avifImage * image, avifROData * input)
{
avifResult result = avifDecoderParse(decoder, input);