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/CHANGELOG.md b/CHANGELOG.md
index 9979fdc..e3308bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,19 @@
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+### 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
## [0.3.9] - 2019-09-25
### Changed
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 1e0edd1..6e4abc5 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -399,8 +399,15 @@
avifResult avifDecoderSetSource(avifDecoder * decoder, avifDecoderSource source);
avifResult avifDecoderParse(avifDecoder * decoder, avifROData * input);
avifResult avifDecoderNextImage(avifDecoder * decoder);
+avifResult avifDecoderNthImage(avifDecoder * decoder, uint32_t frameIndex);
avifResult avifDecoderReset(avifDecoder * decoder);
+// Keyframe information
+// frameIndex - 0-based, matching avifDecoder->imageIndex, bound by avifDecoder->imageCount
+// "nearest" keyframe means the keyframe prior to this frame index (returns frameIndex if it is a keyframe)
+avifBool avifDecoderIsKeyframe(avifDecoder * decoder, uint32_t frameIndex);
+uint32_t avifDecoderNearestKeyframe(avifDecoder * decoder, uint32_t frameIndex);
+
// avifEncoder notes:
// * if avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree()
// * if (maxThreads < 2), multithreading is disabled
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 0de7181..5185953 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -72,9 +72,16 @@
// ---------------------------------------------------------------------------
// avifCodecDecodeInput
+typedef struct avifSample
+{
+ avifROData data;
+ avifBool sync; // is sync sample (keyframe)
+} avifSample;
+AVIF_ARRAY_DECLARE(avifSampleArray, avifSample, sample);
+
typedef struct avifCodecDecodeInput
{
- avifRODataArray samples;
+ avifSampleArray samples;
avifBool alpha; // if true, this is decoding an alpha plane
} avifCodecDecodeInput;
@@ -119,7 +126,7 @@
struct avifCodec;
struct avifCodecInternal;
-typedef avifBool (*avifCodecDecodeFunc)(struct avifCodec * codec);
+typedef avifBool (*avifCodecOpenFunc)(struct avifCodec * codec);
// avifCodecAlphaLimitedRangeFunc: returns AVIF_TRUE if an alpha plane exists and was encoded with limited range
typedef avifBool (*avifCodecAlphaLimitedRangeFunc)(struct avifCodec * codec);
typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec, avifImage * image);
@@ -133,7 +140,7 @@
avifCodecDecodeInput * decodeInput;
struct avifCodecInternal * internal; // up to each codec to use how it wants
- avifCodecDecodeFunc decode;
+ avifCodecOpenFunc open;
avifCodecAlphaLimitedRangeFunc alphaLimitedRange;
avifCodecGetNextImageFunc getNextImage;
avifCodecEncodeImageFunc encodeImage;
diff --git a/src/codec_aom.c b/src/codec_aom.c
index fd4c9ea..eb6a8fd 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -48,7 +48,7 @@
avifFree(codec->internal);
}
-static avifBool aomCodecDecode(struct avifCodec * codec)
+static avifBool aomCodecOpen(struct avifCodec * codec)
{
aom_codec_iface_t * decoder_interface = aom_codec_av1_dx();
if (aom_codec_dec_init(&codec->internal->decoder, decoder_interface, NULL, 0)) {
@@ -83,10 +83,10 @@
break;
} else if (codec->internal->inputSampleIndex < codec->decodeInput->samples.count) {
// Feed another sample
- avifROData * sample = &codec->decodeInput->samples.raw[codec->internal->inputSampleIndex];
+ avifSample * sample = &codec->decodeInput->samples.sample[codec->internal->inputSampleIndex];
++codec->internal->inputSampleIndex;
codec->internal->iter = NULL;
- if (aom_codec_decode(&codec->internal->decoder, sample->data, sample->size, NULL)) {
+ if (aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL)) {
return AVIF_FALSE;
}
} else {
@@ -427,7 +427,7 @@
{
avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
memset(codec, 0, sizeof(struct avifCodec));
- codec->decode = aomCodecDecode;
+ codec->open = aomCodecOpen;
codec->alphaLimitedRange = aomCodecAlphaLimitedRange;
codec->getNextImage = aomCodecGetNextImage;
codec->encodeImage = aomCodecEncodeImage;
diff --git a/src/codec_dav1d.c b/src/codec_dav1d.c
index b48c10f..5d164d7 100644
--- a/src/codec_dav1d.c
+++ b/src/codec_dav1d.c
@@ -38,12 +38,12 @@
dav1d_data_unref(&codec->internal->dav1dData);
if (codec->internal->inputSampleIndex < codec->decodeInput->samples.count) {
- avifROData * sample = &codec->decodeInput->samples.raw[codec->internal->inputSampleIndex];
+ avifSample * sample = &codec->decodeInput->samples.sample[codec->internal->inputSampleIndex];
++codec->internal->inputSampleIndex;
// OPTIMIZE: Carefully switch this to use dav1d_data_wrap or dav1d_data_wrap_user_data
- uint8_t * dav1dDataPtr = dav1d_data_create(&codec->internal->dav1dData, sample->size);
- memcpy(dav1dDataPtr, sample->data, sample->size);
+ uint8_t * dav1dDataPtr = dav1d_data_create(&codec->internal->dav1dData, sample->data.size);
+ memcpy(dav1dDataPtr, sample->data.data, sample->data.size);
} else {
// No more data
return AVIF_FALSE;
@@ -57,7 +57,7 @@
return AVIF_TRUE;
}
-static avifBool dav1dCodecDecode(avifCodec * codec)
+static avifBool dav1dCodecOpen(avifCodec * codec)
{
if (codec->internal->dav1dContext == NULL) {
if (dav1d_open(&codec->internal->dav1dContext, &codec->internal->dav1dSettings) != 0) {
@@ -66,7 +66,7 @@
}
codec->internal->inputSampleIndex = 0;
- return dav1dFeedData(codec);
+ return AVIF_TRUE;
}
static avifBool dav1dCodecAlphaLimitedRange(avifCodec * codec)
@@ -184,7 +184,7 @@
{
avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
memset(codec, 0, sizeof(struct avifCodec));
- codec->decode = dav1dCodecDecode;
+ codec->open = dav1dCodecOpen;
codec->alphaLimitedRange = dav1dCodecAlphaLimitedRange;
codec->getNextImage = dav1dCodecGetNextImage;
codec->destroyInternal = dav1dCodecDestroyInternal;
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);
diff --git a/tests/aviffuzz.c b/tests/aviffuzz.c
index 01c5365..7704682 100644
--- a/tests/aviffuzz.c
+++ b/tests/aviffuzz.c
@@ -50,10 +50,12 @@
printf(" * %2.2f seconds, %d images\n", decoder->duration, decoder->imageCount);
int frameIndex = 0;
while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) {
- printf(" * Decoded frame [%d] [pts %2.2f] [duration %2.2f]: %dx%d\n",
+ printf(" * Decoded frame [%d] [pts %2.2f] [duration %2.2f] [keyframe:%s nearest:%u]: %dx%d\n",
frameIndex,
decoder->imageTiming.pts,
decoder->imageTiming.duration,
+ avifDecoderIsKeyframe(decoder, frameIndex) ? "true" : "false",
+ avifDecoderNearestKeyframe(decoder, frameIndex),
decoder->image->width,
decoder->image->height);
++frameIndex;
@@ -73,6 +75,20 @@
printf("ERROR: Failed to decode image: %s\n", avifResultToString(result));
}
+#if 0
+ int frameIndex = 25;
+ if (avifDecoderNthImage(decoder, frameIndex) == AVIF_RESULT_OK) {
+ printf(" * Decoded frame [%d] [pts %2.2f] [duration %2.2f] [keyframe:%s nearest:%u]: %dx%d\n",
+ frameIndex,
+ decoder->imageTiming.pts,
+ decoder->imageTiming.duration,
+ avifDecoderIsKeyframe(decoder, frameIndex) ? "true" : "false",
+ avifDecoderNearestKeyframe(decoder, frameIndex),
+ decoder->image->width,
+ decoder->image->height);
+ }
+#endif
+
avifRWDataFree(&raw);
avifDecoderDestroy(decoder);
return 0;