More codec API tuning to allow multiple frame samples to be emitted during encoding
diff --git a/include/avif/internal.h b/include/avif/internal.h index 3f80251..924e809 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h
@@ -135,8 +135,13 @@ typedef avifBool (*avifCodecOpenFunc)(struct avifCodec * codec, uint32_t firstSampleIndex); typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec, avifImage * image); -// avifCodecEncodeImageFunc: if either OBU* is null, skip its encode. alpha should always be lossless -typedef avifBool (*avifCodecEncodeImageFunc)(struct avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha); +// EncodeImage and EncodeFinish are not required to always emit an OBU, but when all images are +// encoded and Finish is called, the number of OBUs emitted must match the number of submitted frames. +typedef avifBool (*avifCodecEncodeImageFunc)(struct avifCodec * codec, + const avifImage * image, + avifEncoder * encoder, + avifBool alpha, + avifRWData * obu); typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifRWData * obu); typedef void (*avifCodecDestroyInternalFunc)(struct avifCodec * codec);
diff --git a/src/codec_aom.c b/src/codec_aom.c index b313a12..fb36328 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c
@@ -235,7 +235,7 @@ return fmt; } -static avifBool aomCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha) +static avifBool aomCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha, avifRWData * obu) { if (!codec->internal->encoderInitialized) { // Map encoder speed to AOM usage + CpuUsed: @@ -392,6 +392,19 @@ } aom_codec_encode(&codec->internal->encoder, aomImage, 0, 1, 0); + + aom_codec_iter_t iter = NULL; + for (;;) { + const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&codec->internal->encoder, &iter); + if (pkt == NULL) { + break; + } + if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { + avifRWDataSet(obu, pkt->data.frame.buf, pkt->data.frame.sz); + break; + } + } + aom_img_free(aomImage); return AVIF_TRUE; }
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 02bccf9..acf987e 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c
@@ -31,7 +31,7 @@ return AVIF_TRUE; } -static avifBool rav1eCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha) +static avifBool rav1eCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha, avifRWData * obu) { (void)codec; // unused @@ -151,11 +151,16 @@ if (encoderStatus != 0) { goto cleanup; } - encoderStatus = rav1e_send_frame(codec->internal->rav1eContext, NULL); // flush + + RaPacket * pkt = NULL; + encoderStatus = rav1e_receive_packet(codec->internal->rav1eContext, &pkt); if (encoderStatus != 0) { goto cleanup; + } else if (pkt && pkt->data && (pkt->len > 0)) { + avifRWDataSet(obu, pkt->data, pkt->len); + rav1e_packet_unref(pkt); + pkt = NULL; } - success = AVIF_TRUE; cleanup: if (rav1eFrame) { @@ -171,15 +176,22 @@ static avifBool rav1eCodecEncodeFinish(avifCodec * codec, avifRWData * obu) { + RaEncoderStatus encoderStatus = rav1e_send_frame(codec->internal->rav1eContext, NULL); // flush + if (encoderStatus != 0) { + return AVIF_FALSE; + } + RaPacket * pkt = NULL; - RaEncoderStatus encoderStatus = rav1e_receive_packet(codec->internal->rav1eContext, &pkt); - if ((encoderStatus == 0) && pkt && pkt->data && (pkt->len > 0)) { + encoderStatus = rav1e_receive_packet(codec->internal->rav1eContext, &pkt); + if (encoderStatus != 0) { + return AVIF_FALSE; + } + if (pkt && pkt->data && (pkt->len > 0)) { avifRWDataSet(obu, pkt->data, pkt->len); rav1e_packet_unref(pkt); pkt = NULL; - return AVIF_TRUE; } - return AVIF_FALSE; + return AVIF_TRUE; } const char * avifCodecVersionRav1e(void)
diff --git a/src/write.c b/src/write.c index 434e95d..989dfe0 100644 --- a/src/write.c +++ b/src/write.c
@@ -37,8 +37,9 @@ { uint16_t id; uint8_t type[4]; - avifCodec * codec; // only present on type==av01 - avifRWData content; // OBU data on av01, metadata payload for Exif/XMP + avifCodec * codec; // only present on type==av01 + avifRWDataArray samples; // AV1 sample data for image sequences + avifRWData content; // OBU data on av01 items-based image, metadata payload for Exif/XMP avifBool alpha; const char * infeName; @@ -65,6 +66,7 @@ avifEncoderItem * alphaItem; uint16_t lastItemID; uint16_t primaryItemID; + uint32_t receivedFrameCount; // incremented on each call to avifEncoderAddImage() } avifEncoderData; static avifEncoderData * avifEncoderDataCreate() @@ -84,6 +86,7 @@ memcpy(item->type, type, sizeof(item->type)); item->infeName = infeName; item->infeNameSize = infeNameSize; + avifArrayCreate(&item->samples, sizeof(avifRWData), 1); return item; } @@ -94,6 +97,10 @@ if (item->codec) { avifCodecDestroy(item->codec); } + for (uint32_t sampleIndex = 0; sampleIndex < item->samples.count; ++sampleIndex) { + avifRWDataFree(&item->samples.raw[sampleIndex]); + } + avifArrayDestroy(&item->samples); avifRWDataFree(&item->content); } avifImageDestroy(data->imageMetadata); @@ -238,11 +245,18 @@ for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->codec) { - if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha)) { + avifRWData tmpSampleData = AVIF_DATA_EMPTY; + if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha, &tmpSampleData)) { return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } + if (tmpSampleData.data && tmpSampleData.size) { + avifRWData * sampleData = (avifRWData *)avifArrayPushPtr(&item->samples); + memcpy(sampleData, &tmpSampleData, sizeof(avifRWData)); + } } } + + ++encoder->data->receivedFrameCount; return AVIF_RESULT_OK; } @@ -258,18 +272,41 @@ for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->codec) { - if (!item->codec->encodeFinish(item->codec, &item->content)) { + avifRWData tmpSampleData = AVIF_DATA_EMPTY; + if (!item->codec->encodeFinish(item->codec, &tmpSampleData)) { + return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; + } + if (tmpSampleData.data && tmpSampleData.size) { + avifRWData * sampleData = (avifRWData *)avifArrayPushPtr(&item->samples); + memcpy(sampleData, &tmpSampleData, sizeof(avifRWData)); + } + + if (item->samples.count != encoder->data->receivedFrameCount) { return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } + size_t obuSize = 0; + for (uint32_t sampleIndex = 0; sampleIndex < item->samples.count; ++sampleIndex) { + obuSize += item->samples.raw[sampleIndex].size; + } if (item->alpha) { - encoder->ioStats.alphaOBUSize = item->content.size; + encoder->ioStats.alphaOBUSize = obuSize; } else { - encoder->ioStats.colorOBUSize = item->content.size; + encoder->ioStats.colorOBUSize = obuSize; + } + + if (item->samples.count == 1) { + // Detected a single image (non-sequence). Hand over the only sample to item->content + // so that the image is encoded with items instead of tracks. + memcpy(&item->content, &item->samples.raw[0], sizeof(avifRWData)); + memset(&item->samples.raw[0], 0, sizeof(avifRWData)); + item->samples.count = 0; } } } + // TODO: If encoder->data->receivedFrameCount > 1, encode with tracks instead of items + // ----------------------------------------------------------------------- // Begin write stream