Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1 | // Copyright 2019 Joe Drago. All rights reserved. |
| 2 | // SPDX-License-Identifier: BSD-2-Clause |
| 3 | |
| 4 | #include "avif/internal.h" |
| 5 | |
Joe Drago | ceb2fa0 | 2021-01-29 18:14:55 -0800 | [diff] [blame] | 6 | #include <assert.h> |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 7 | #include <string.h> |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 8 | #include <time.h> |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 9 | |
Joe Drago | 224b018 | 2019-02-08 10:42:29 -0800 | [diff] [blame] | 10 | #define MAX_ASSOCIATIONS 16 |
| 11 | struct ipmaArray |
| 12 | { |
| 13 | uint8_t associations[MAX_ASSOCIATIONS]; |
Joe Drago | 0da2997 | 2020-04-23 11:55:10 -0700 | [diff] [blame] | 14 | avifBool essential[MAX_ASSOCIATIONS]; |
Joe Drago | 224b018 | 2019-02-08 10:42:29 -0800 | [diff] [blame] | 15 | uint8_t count; |
| 16 | }; |
Joe Drago | 0da2997 | 2020-04-23 11:55:10 -0700 | [diff] [blame] | 17 | static void ipmaPush(struct ipmaArray * ipma, uint8_t assoc, avifBool essential) |
Joe Drago | 224b018 | 2019-02-08 10:42:29 -0800 | [diff] [blame] | 18 | { |
| 19 | ipma->associations[ipma->count] = assoc; |
Joe Drago | 0da2997 | 2020-04-23 11:55:10 -0700 | [diff] [blame] | 20 | ipma->essential[ipma->count] = essential; |
Joe Drago | 224b018 | 2019-02-08 10:42:29 -0800 | [diff] [blame] | 21 | ++ipma->count; |
| 22 | } |
| 23 | |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 24 | // Used to store offsets in meta boxes which need to point at mdat offsets that |
| 25 | // aren't known yet. When an item's mdat payload is written, all registered fixups |
| 26 | // will have this now-known offset "fixed up". |
| 27 | typedef struct avifOffsetFixup |
| 28 | { |
| 29 | size_t offset; |
| 30 | } avifOffsetFixup; |
| 31 | AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup); |
| 32 | |
Joe Drago | cd1e4c3 | 2019-02-08 11:26:31 -0800 | [diff] [blame] | 33 | static const char alphaURN[] = URN_ALPHA0; |
| 34 | static const size_t alphaURNSize = sizeof(alphaURN); |
| 35 | |
Joe Drago | f6a4227 | 2019-11-21 15:21:41 -0800 | [diff] [blame] | 36 | static const char xmpContentType[] = CONTENT_TYPE_XMP; |
| 37 | static const size_t xmpContentTypeSize = sizeof(xmpContentType); |
| 38 | |
Wan-Teh Chang | e184dc1 | 2020-05-11 12:47:21 -0700 | [diff] [blame] | 39 | static avifBool avifImageIsOpaque(const avifImage * image); |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 40 | static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 41 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 42 | // --------------------------------------------------------------------------- |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 43 | // avifCodecEncodeOutput |
| 44 | |
| 45 | avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void) |
| 46 | { |
| 47 | avifCodecEncodeOutput * encodeOutput = (avifCodecEncodeOutput *)avifAlloc(sizeof(avifCodecEncodeOutput)); |
| 48 | memset(encodeOutput, 0, sizeof(avifCodecEncodeOutput)); |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 49 | if (!avifArrayCreate(&encodeOutput->samples, sizeof(avifEncodeSample), 1)) { |
| 50 | goto error; |
| 51 | } |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 52 | return encodeOutput; |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 53 | |
| 54 | error: |
| 55 | avifCodecEncodeOutputDestroy(encodeOutput); |
| 56 | return NULL; |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | void avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync) |
| 60 | { |
| 61 | avifEncodeSample * sample = (avifEncodeSample *)avifArrayPushPtr(&encodeOutput->samples); |
| 62 | avifRWDataSet(&sample->data, data, len); |
| 63 | sample->sync = sync; |
| 64 | } |
| 65 | |
| 66 | void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput) |
| 67 | { |
| 68 | for (uint32_t sampleIndex = 0; sampleIndex < encodeOutput->samples.count; ++sampleIndex) { |
| 69 | avifRWDataFree(&encodeOutput->samples.sample[sampleIndex].data); |
| 70 | } |
| 71 | avifArrayDestroy(&encodeOutput->samples); |
| 72 | avifFree(encodeOutput); |
| 73 | } |
| 74 | |
| 75 | // --------------------------------------------------------------------------- |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 76 | // avifEncoderItem |
| 77 | |
| 78 | // one "item" worth for encoder |
| 79 | typedef struct avifEncoderItem |
| 80 | { |
| 81 | uint16_t id; |
| 82 | uint8_t type[4]; |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 83 | avifCodec * codec; // only present on type==av01 |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 84 | avifCodecEncodeOutput * encodeOutput; // AV1 sample data |
| 85 | avifRWData metadataPayload; // Exif/XMP data |
Joe Drago | 2172ed0 | 2020-11-04 18:04:44 -0800 | [diff] [blame] | 86 | avifCodecConfigurationBox av1C; // Harvested in avifEncoderFinish(), if encodeOutput has samples |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 87 | uint32_t cellIndex; // Which row-major cell index corresponds to this item. ignored on non-av01 types |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 88 | avifBool alpha; |
Wan-Teh Chang | 0f41891 | 2022-02-17 09:16:05 -0800 | [diff] [blame] | 89 | avifBool hiddenImage; // A hidden image item has (flags & 1) equal to 1 in its ItemInfoEntry. |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 90 | |
| 91 | const char * infeName; |
| 92 | size_t infeNameSize; |
| 93 | const char * infeContentType; |
| 94 | size_t infeContentTypeSize; |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 95 | avifOffsetFixupArray mdatFixups; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 96 | |
| 97 | uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID |
| 98 | const char * irefType; |
| 99 | |
Wan-Teh Chang | f9fca86 | 2020-12-22 15:08:12 -0800 | [diff] [blame] | 100 | uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item |
| 101 | uint32_t gridRows; // if non-zero (legal range [1-256]), this is a grid item |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 102 | |
| 103 | uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id |
| 104 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 105 | struct ipmaArray ipma; |
| 106 | } avifEncoderItem; |
| 107 | AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item); |
| 108 | |
| 109 | // --------------------------------------------------------------------------- |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 110 | // avifEncoderFrame |
| 111 | |
| 112 | typedef struct avifEncoderFrame |
| 113 | { |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 114 | uint64_t durationInTimescales; |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 115 | } avifEncoderFrame; |
| 116 | AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, frame); |
| 117 | |
| 118 | // --------------------------------------------------------------------------- |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 119 | // avifEncoderData |
| 120 | |
| 121 | typedef struct avifEncoderData |
| 122 | { |
| 123 | avifEncoderItemArray items; |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 124 | avifEncoderFrameArray frames; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 125 | avifImage * imageMetadata; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 126 | uint16_t lastItemID; |
| 127 | uint16_t primaryItemID; |
Joe Drago | aba51d8 | 2020-11-19 13:12:29 -0800 | [diff] [blame] | 128 | avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage() |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 129 | avifBool alphaPresent; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 130 | } avifEncoderData; |
| 131 | |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 132 | static void avifEncoderDataDestroy(avifEncoderData * data); |
| 133 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 134 | static avifEncoderData * avifEncoderDataCreate() |
| 135 | { |
| 136 | avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData)); |
| 137 | memset(data, 0, sizeof(avifEncoderData)); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 138 | data->imageMetadata = avifImageCreateEmpty(); |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 139 | if (!avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8)) { |
| 140 | goto error; |
| 141 | } |
| 142 | if (!avifArrayCreate(&data->frames, sizeof(avifEncoderFrame), 1)) { |
| 143 | goto error; |
| 144 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 145 | return data; |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 146 | |
| 147 | error: |
| 148 | avifEncoderDataDestroy(data); |
| 149 | return NULL; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 150 | } |
| 151 | |
Wan-Teh Chang | 8adbb97 | 2020-12-18 14:12:51 -0800 | [diff] [blame] | 152 | static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex) |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 153 | { |
| 154 | avifEncoderItem * item = (avifEncoderItem *)avifArrayPushPtr(&data->items); |
Wan-Teh Chang | d9cffc5 | 2022-01-29 22:31:01 -0800 | [diff] [blame] | 155 | ++data->lastItemID; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 156 | item->id = data->lastItemID; |
| 157 | memcpy(item->type, type, sizeof(item->type)); |
| 158 | item->infeName = infeName; |
| 159 | item->infeNameSize = infeNameSize; |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 160 | item->encodeOutput = avifCodecEncodeOutputCreate(); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 161 | item->cellIndex = cellIndex; |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 162 | if (!avifArrayCreate(&item->mdatFixups, sizeof(avifOffsetFixup), 4)) { |
| 163 | goto error; |
| 164 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 165 | return item; |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 166 | |
| 167 | error: |
| 168 | avifCodecEncodeOutputDestroy(item->encodeOutput); |
Wan-Teh Chang | d9cffc5 | 2022-01-29 22:31:01 -0800 | [diff] [blame] | 169 | --data->lastItemID; |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 170 | avifArrayPop(&data->items); |
| 171 | return NULL; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 172 | } |
| 173 | |
Joe Drago | ceb2fa0 | 2021-01-29 18:14:55 -0800 | [diff] [blame] | 174 | static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint16_t id) |
| 175 | { |
| 176 | for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) { |
| 177 | avifEncoderItem * item = &data->items.item[itemIndex]; |
| 178 | if (item->id == id) { |
| 179 | return item; |
| 180 | } |
| 181 | } |
| 182 | return NULL; |
| 183 | } |
| 184 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 185 | static void avifEncoderDataDestroy(avifEncoderData * data) |
| 186 | { |
| 187 | for (uint32_t i = 0; i < data->items.count; ++i) { |
| 188 | avifEncoderItem * item = &data->items.item[i]; |
| 189 | if (item->codec) { |
| 190 | avifCodecDestroy(item->codec); |
| 191 | } |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 192 | avifCodecEncodeOutputDestroy(item->encodeOutput); |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 193 | avifRWDataFree(&item->metadataPayload); |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 194 | avifArrayDestroy(&item->mdatFixups); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 195 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 196 | avifImageDestroy(data->imageMetadata); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 197 | avifArrayDestroy(&data->items); |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 198 | avifArrayDestroy(&data->frames); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 199 | avifFree(data); |
| 200 | } |
| 201 | |
Wan-Teh Chang | 9f89df9 | 2020-07-08 18:54:26 -0700 | [diff] [blame] | 202 | static void avifEncoderItemAddMdatFixup(avifEncoderItem * item, const avifRWStream * s) |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 203 | { |
| 204 | avifOffsetFixup * fixup = (avifOffsetFixup *)avifArrayPushPtr(&item->mdatFixups); |
| 205 | fixup->offset = avifRWStreamOffset(s); |
| 206 | } |
| 207 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 208 | // --------------------------------------------------------------------------- |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 209 | // avifItemPropertyDedup - Provides ipco deduplication |
| 210 | |
| 211 | typedef struct avifItemProperty |
| 212 | { |
| 213 | uint8_t index; |
| 214 | size_t offset; |
| 215 | size_t size; |
| 216 | } avifItemProperty; |
| 217 | AVIF_ARRAY_DECLARE(avifItemPropertyArray, avifItemProperty, property); |
| 218 | |
| 219 | typedef struct avifItemPropertyDedup |
| 220 | { |
| 221 | avifItemPropertyArray properties; |
| 222 | avifRWStream s; // Temporary stream for each new property, checked against already-written boxes for deduplications |
| 223 | avifRWData buffer; // Temporary storage for 's' |
| 224 | uint8_t nextIndex; // 1-indexed, incremented every time another unique property is finished |
| 225 | } avifItemPropertyDedup; |
| 226 | |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 227 | static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup); |
| 228 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 229 | static avifItemPropertyDedup * avifItemPropertyDedupCreate(void) |
| 230 | { |
| 231 | avifItemPropertyDedup * dedup = (avifItemPropertyDedup *)avifAlloc(sizeof(avifItemPropertyDedup)); |
| 232 | memset(dedup, 0, sizeof(avifItemPropertyDedup)); |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 233 | if (!avifArrayCreate(&dedup->properties, sizeof(avifItemProperty), 8)) { |
| 234 | goto error; |
| 235 | } |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 236 | avifRWDataRealloc(&dedup->buffer, 2048); // This will resize automatically (if necessary) |
| 237 | return dedup; |
Wan-Teh Chang | f732a4d | 2022-01-21 15:56:35 -0800 | [diff] [blame] | 238 | |
| 239 | error: |
| 240 | avifItemPropertyDedupDestroy(dedup); |
| 241 | return NULL; |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 242 | } |
| 243 | |
| 244 | static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup) |
| 245 | { |
| 246 | avifArrayDestroy(&dedup->properties); |
| 247 | avifRWDataFree(&dedup->buffer); |
| 248 | avifFree(dedup); |
| 249 | } |
| 250 | |
| 251 | // Resets the dedup's temporary write stream in preparation for a single item property's worth of writing |
| 252 | static void avifItemPropertyDedupStart(avifItemPropertyDedup * dedup) |
| 253 | { |
| 254 | avifRWStreamStart(&dedup->s, &dedup->buffer); |
| 255 | } |
| 256 | |
| 257 | // This compares the newly written item property (in the dedup's temporary storage buffer) to |
| 258 | // already-written properties (whose offsets/sizes in outputStream are recorded in the dedup). If a |
| 259 | // match is found, the previous item's index is used. If this new property is unique, it is |
| 260 | // assigned the next available property index, written to the output stream, and its offset/size in |
| 261 | // the output stream is recorded in the dedup for future comparisons. |
| 262 | // |
| 263 | // This function always returns a valid 1-indexed property index for usage in a property association |
| 264 | // (ipma) box later. If the most recent property was a duplicate of a previous property, the return |
| 265 | // value will be the index of the original property, otherwise it will be the index of the newly |
| 266 | // created property. |
| 267 | static uint8_t avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, avifRWStream * outputStream) |
| 268 | { |
| 269 | const size_t newPropertySize = avifRWStreamOffset(&dedup->s); |
| 270 | |
| 271 | for (size_t i = 0; i < dedup->properties.count; ++i) { |
| 272 | avifItemProperty * property = &dedup->properties.property[i]; |
| 273 | if ((property->size == newPropertySize) && |
| 274 | !memcmp(&outputStream->raw->data[property->offset], dedup->buffer.data, newPropertySize)) { |
| 275 | // We've already written this exact property, reuse it |
| 276 | return property->index; |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | // Write a new property, and remember its location in the output stream for future deduplication |
| 281 | avifItemProperty * property = (avifItemProperty *)avifArrayPushPtr(&dedup->properties); |
| 282 | property->index = ++dedup->nextIndex; // preincrement so the first new index is 1 (as ipma is 1-indexed) |
| 283 | property->size = newPropertySize; |
| 284 | property->offset = avifRWStreamOffset(outputStream); |
| 285 | avifRWStreamWrite(outputStream, dedup->buffer.data, newPropertySize); |
| 286 | return property->index; |
| 287 | } |
| 288 | |
| 289 | // --------------------------------------------------------------------------- |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 290 | |
Joe Drago | 0b05eee | 2019-06-12 13:24:39 -0700 | [diff] [blame] | 291 | avifEncoder * avifEncoderCreate(void) |
| 292 | { |
| 293 | avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder)); |
| 294 | memset(encoder, 0, sizeof(avifEncoder)); |
| 295 | encoder->maxThreads = 1; |
Joe Drago | 46ea058 | 2019-07-22 15:55:47 -0700 | [diff] [blame] | 296 | encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS; |
| 297 | encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; |
Joe Drago | 56fa2bc | 2020-03-03 13:29:48 -0800 | [diff] [blame] | 298 | encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; |
| 299 | encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; |
Joe Drago | 4bf0187 | 2019-11-15 18:09:19 -0800 | [diff] [blame] | 300 | encoder->tileRowsLog2 = 0; |
| 301 | encoder->tileColsLog2 = 0; |
| 302 | encoder->speed = AVIF_SPEED_DEFAULT; |
Joe Drago | d685024 | 2020-06-16 16:05:37 -0700 | [diff] [blame] | 303 | encoder->keyframeInterval = 0; |
Wan-Teh Chang | 2792c0a | 2020-08-28 16:10:31 -0700 | [diff] [blame] | 304 | encoder->timescale = 1; |
| 305 | encoder->data = avifEncoderDataCreate(); |
Joe Drago | e1097bd | 2020-08-27 18:55:03 -0700 | [diff] [blame] | 306 | encoder->csOptions = avifCodecSpecificOptionsCreate(); |
Joe Drago | 0b05eee | 2019-06-12 13:24:39 -0700 | [diff] [blame] | 307 | return encoder; |
| 308 | } |
| 309 | |
| 310 | void avifEncoderDestroy(avifEncoder * encoder) |
| 311 | { |
Joe Drago | e1097bd | 2020-08-27 18:55:03 -0700 | [diff] [blame] | 312 | avifCodecSpecificOptionsDestroy(encoder->csOptions); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 313 | avifEncoderDataDestroy(encoder->data); |
Joe Drago | 0b05eee | 2019-06-12 13:24:39 -0700 | [diff] [blame] | 314 | avifFree(encoder); |
| 315 | } |
| 316 | |
Joe Drago | e1097bd | 2020-08-27 18:55:03 -0700 | [diff] [blame] | 317 | void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value) |
| 318 | { |
| 319 | avifCodecSpecificOptionsSet(encoder->csOptions, key, value); |
| 320 | } |
| 321 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 322 | // This function is used in two codepaths: |
| 323 | // * writing color *item* properties |
| 324 | // * writing color *track* properties |
| 325 | // |
| 326 | // Item properties must have property associations with them and can be deduplicated (by reusing |
| 327 | // these associations), so this function leverages the ipma and dedup arguments to do this. |
| 328 | // |
| 329 | // Track properties, however, are implicitly associated by the track in which they are contained, so |
| 330 | // there is no need to build a property association box (ipma), and no way to deduplicate/reuse a |
| 331 | // property. In this case, the ipma and dedup properties should/will be set to NULL, and this |
| 332 | // function will avoid using them. |
| 333 | static void avifEncoderWriteColorProperties(avifRWStream * outputStream, |
| 334 | const avifImage * imageMetadata, |
| 335 | struct ipmaArray * ipma, |
| 336 | avifItemPropertyDedup * dedup) |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 337 | { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 338 | avifRWStream * s = outputStream; |
| 339 | if (dedup) { |
| 340 | assert(ipma); |
| 341 | |
| 342 | // Use the dedup's temporary stream for box writes |
| 343 | s = &dedup->s; |
| 344 | } |
| 345 | |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 346 | if (imageMetadata->icc.size > 0) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 347 | if (dedup) { |
| 348 | avifItemPropertyDedupStart(dedup); |
| 349 | } |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 350 | avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD); |
| 351 | avifRWStreamWriteChars(s, "prof", 4); // unsigned int(32) colour_type; |
| 352 | avifRWStreamWrite(s, imageMetadata->icc.data, imageMetadata->icc.size); |
| 353 | avifRWStreamFinishBox(s, colr); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 354 | if (dedup) { |
| 355 | ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 356 | } |
Joe Drago | bf58fe7 | 2020-11-05 13:25:14 -0800 | [diff] [blame] | 357 | } |
| 358 | |
| 359 | // HEIF 6.5.5.1, from Amendment 3 allows multiple colr boxes: "at most one for a given value of colour type" |
| 360 | // Therefore, *always* writing an nclx box, even if an a prof box was already written above. |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 361 | if (dedup) { |
| 362 | avifItemPropertyDedupStart(dedup); |
| 363 | } |
Joe Drago | bf58fe7 | 2020-11-05 13:25:14 -0800 | [diff] [blame] | 364 | avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD); |
| 365 | avifRWStreamWriteChars(s, "nclx", 4); // unsigned int(32) colour_type; |
Joe Drago | fb5a5f0 | 2021-01-31 20:43:57 -0800 | [diff] [blame] | 366 | avifRWStreamWriteU16(s, imageMetadata->colorPrimaries); // unsigned int(16) colour_primaries; |
| 367 | avifRWStreamWriteU16(s, imageMetadata->transferCharacteristics); // unsigned int(16) transfer_characteristics; |
| 368 | avifRWStreamWriteU16(s, imageMetadata->matrixCoefficients); // unsigned int(16) matrix_coefficients; |
Joe Drago | bf58fe7 | 2020-11-05 13:25:14 -0800 | [diff] [blame] | 369 | avifRWStreamWriteU8(s, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 0x80 : 0); // unsigned int(1) full_range_flag; |
| 370 | // unsigned int(7) reserved = 0; |
| 371 | avifRWStreamFinishBox(s, colr); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 372 | if (dedup) { |
| 373 | ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 374 | } |
| 375 | |
| 376 | // Write (Optional) Transformations |
| 377 | if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 378 | if (dedup) { |
| 379 | avifItemPropertyDedupStart(dedup); |
| 380 | } |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 381 | avifBoxMarker pasp = avifRWStreamWriteBox(s, "pasp", AVIF_BOX_SIZE_TBD); |
| 382 | avifRWStreamWriteU32(s, imageMetadata->pasp.hSpacing); // unsigned int(32) hSpacing; |
| 383 | avifRWStreamWriteU32(s, imageMetadata->pasp.vSpacing); // unsigned int(32) vSpacing; |
| 384 | avifRWStreamFinishBox(s, pasp); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 385 | if (dedup) { |
| 386 | ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 387 | } |
| 388 | } |
| 389 | if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 390 | if (dedup) { |
| 391 | avifItemPropertyDedupStart(dedup); |
| 392 | } |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 393 | avifBoxMarker clap = avifRWStreamWriteBox(s, "clap", AVIF_BOX_SIZE_TBD); |
| 394 | avifRWStreamWriteU32(s, imageMetadata->clap.widthN); // unsigned int(32) cleanApertureWidthN; |
| 395 | avifRWStreamWriteU32(s, imageMetadata->clap.widthD); // unsigned int(32) cleanApertureWidthD; |
| 396 | avifRWStreamWriteU32(s, imageMetadata->clap.heightN); // unsigned int(32) cleanApertureHeightN; |
| 397 | avifRWStreamWriteU32(s, imageMetadata->clap.heightD); // unsigned int(32) cleanApertureHeightD; |
| 398 | avifRWStreamWriteU32(s, imageMetadata->clap.horizOffN); // unsigned int(32) horizOffN; |
| 399 | avifRWStreamWriteU32(s, imageMetadata->clap.horizOffD); // unsigned int(32) horizOffD; |
| 400 | avifRWStreamWriteU32(s, imageMetadata->clap.vertOffN); // unsigned int(32) vertOffN; |
| 401 | avifRWStreamWriteU32(s, imageMetadata->clap.vertOffD); // unsigned int(32) vertOffD; |
| 402 | avifRWStreamFinishBox(s, clap); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 403 | if (dedup) { |
| 404 | ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 405 | } |
| 406 | } |
| 407 | if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 408 | if (dedup) { |
| 409 | avifItemPropertyDedupStart(dedup); |
| 410 | } |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 411 | avifBoxMarker irot = avifRWStreamWriteBox(s, "irot", AVIF_BOX_SIZE_TBD); |
| 412 | uint8_t angle = imageMetadata->irot.angle & 0x3; |
| 413 | avifRWStreamWrite(s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle; |
| 414 | avifRWStreamFinishBox(s, irot); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 415 | if (dedup) { |
| 416 | ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 417 | } |
| 418 | } |
| 419 | if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 420 | if (dedup) { |
| 421 | avifItemPropertyDedupStart(dedup); |
| 422 | } |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 423 | avifBoxMarker imir = avifRWStreamWriteBox(s, "imir", AVIF_BOX_SIZE_TBD); |
Joe Drago | b551bb3 | 2021-06-03 15:22:05 -0700 | [diff] [blame] | 424 | uint8_t mode = imageMetadata->imir.mode & 0x1; |
| 425 | avifRWStreamWrite(s, &mode, 1); // unsigned int (7) reserved = 0; unsigned int (1) mode; |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 426 | avifRWStreamFinishBox(s, imir); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 427 | if (dedup) { |
| 428 | ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 429 | } |
| 430 | } |
| 431 | } |
| 432 | |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 433 | // Write unassociated metadata items (EXIF, XMP) to a small meta box inside of a trak box. |
| 434 | // These items are implicitly associated with the track they are contained within. |
| 435 | static void avifEncoderWriteTrackMetaBox(avifEncoder * encoder, avifRWStream * s) |
| 436 | { |
| 437 | // Count how many non-av01 items (such as EXIF/XMP) are being written |
| 438 | uint32_t metadataItemCount = 0; |
| 439 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 440 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 441 | if (memcmp(item->type, "av01", 4) != 0) { |
| 442 | ++metadataItemCount; |
| 443 | } |
| 444 | } |
| 445 | if (metadataItemCount == 0) { |
| 446 | // Don't even bother writing the trak meta box |
| 447 | return; |
| 448 | } |
| 449 | |
| 450 | avifBoxMarker meta = avifRWStreamWriteFullBox(s, "meta", AVIF_BOX_SIZE_TBD, 0, 0); |
| 451 | |
| 452 | avifBoxMarker hdlr = avifRWStreamWriteFullBox(s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0); |
| 453 | avifRWStreamWriteU32(s, 0); // unsigned int(32) pre_defined = 0; |
| 454 | avifRWStreamWriteChars(s, "pict", 4); // unsigned int(32) handler_type; |
| 455 | avifRWStreamWriteZeros(s, 12); // const unsigned int(32)[3] reserved = 0; |
| 456 | avifRWStreamWriteChars(s, "libavif", 8); // string name; (writing null terminator) |
| 457 | avifRWStreamFinishBox(s, hdlr); |
| 458 | |
| 459 | avifBoxMarker iloc = avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0); |
| 460 | uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; |
| 461 | // unsigned int(4) length_size; |
| 462 | avifRWStreamWrite(s, &offsetSizeAndLengthSize, 1); // |
| 463 | avifRWStreamWriteZeros(s, 1); // unsigned int(4) base_offset_size; |
| 464 | // unsigned int(4) reserved; |
| 465 | avifRWStreamWriteU16(s, (uint16_t)metadataItemCount); // unsigned int(16) item_count; |
| 466 | for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) { |
| 467 | avifEncoderItem * item = &encoder->data->items.item[trakItemIndex]; |
| 468 | if (memcmp(item->type, "av01", 4) == 0) { |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 469 | // Skip over all non-metadata items |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 470 | continue; |
| 471 | } |
| 472 | |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 473 | avifRWStreamWriteU16(s, item->id); // unsigned int(16) item_ID; |
| 474 | avifRWStreamWriteU16(s, 0); // unsigned int(16) data_reference_index; |
| 475 | avifRWStreamWriteU16(s, 1); // unsigned int(16) extent_count; |
| 476 | avifEncoderItemAddMdatFixup(item, s); // |
| 477 | avifRWStreamWriteU32(s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; |
| 478 | avifRWStreamWriteU32(s, (uint32_t)item->metadataPayload.size); // unsigned int(length_size*8) extent_length; |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 479 | } |
| 480 | avifRWStreamFinishBox(s, iloc); |
| 481 | |
| 482 | avifBoxMarker iinf = avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0); |
| 483 | avifRWStreamWriteU16(s, (uint16_t)metadataItemCount); // unsigned int(16) entry_count; |
| 484 | for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) { |
| 485 | avifEncoderItem * item = &encoder->data->items.item[trakItemIndex]; |
| 486 | if (memcmp(item->type, "av01", 4) == 0) { |
| 487 | continue; |
| 488 | } |
| 489 | |
Wan-Teh Chang | 0f41891 | 2022-02-17 09:16:05 -0800 | [diff] [blame] | 490 | assert(!item->hiddenImage); |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 491 | avifBoxMarker infe = avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0); |
| 492 | avifRWStreamWriteU16(s, item->id); // unsigned int(16) item_ID; |
| 493 | avifRWStreamWriteU16(s, 0); // unsigned int(16) item_protection_index; |
| 494 | avifRWStreamWrite(s, item->type, 4); // unsigned int(32) item_type; |
| 495 | avifRWStreamWriteChars(s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator) |
| 496 | if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator) |
| 497 | avifRWStreamWriteChars(s, item->infeContentType, item->infeContentTypeSize); |
| 498 | } |
| 499 | avifRWStreamFinishBox(s, infe); |
| 500 | } |
| 501 | avifRWStreamFinishBox(s, iinf); |
| 502 | |
| 503 | avifRWStreamFinishBox(s, meta); |
| 504 | } |
| 505 | |
Wan-Teh Chang | f9fca86 | 2020-12-22 15:08:12 -0800 | [diff] [blame] | 506 | static void avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 507 | { |
| 508 | // ISO/IEC 23008-12 6.6.2.3.2 |
| 509 | // aligned(8) class ImageGrid { |
| 510 | // unsigned int(8) version = 0; |
| 511 | // unsigned int(8) flags; |
| 512 | // FieldLength = ((flags & 1) + 1) * 16; |
| 513 | // unsigned int(8) rows_minus_one; |
| 514 | // unsigned int(8) columns_minus_one; |
| 515 | // unsigned int(FieldLength) output_width; |
| 516 | // unsigned int(FieldLength) output_height; |
| 517 | // } |
| 518 | |
| 519 | uint32_t gridWidth = firstCell->width * gridCols; |
| 520 | uint32_t gridHeight = firstCell->height * gridRows; |
| 521 | uint8_t gridFlags = ((gridWidth > 65535) || (gridHeight > 65535)) ? 1 : 0; |
| 522 | |
| 523 | avifRWStream s; |
| 524 | avifRWStreamStart(&s, data); |
Wan-Teh Chang | 8adbb97 | 2020-12-18 14:12:51 -0800 | [diff] [blame] | 525 | avifRWStreamWriteU8(&s, 0); // unsigned int(8) version = 0; |
| 526 | avifRWStreamWriteU8(&s, gridFlags); // unsigned int(8) flags; |
| 527 | avifRWStreamWriteU8(&s, (uint8_t)(gridRows - 1)); // unsigned int(8) rows_minus_one; |
| 528 | avifRWStreamWriteU8(&s, (uint8_t)(gridCols - 1)); // unsigned int(8) columns_minus_one; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 529 | if (gridFlags & 1) { |
| 530 | avifRWStreamWriteU32(&s, gridWidth); // unsigned int(FieldLength) output_width; |
| 531 | avifRWStreamWriteU32(&s, gridHeight); // unsigned int(FieldLength) output_height; |
| 532 | } else { |
| 533 | uint16_t tmpWidth = (uint16_t)gridWidth; |
| 534 | uint16_t tmpHeight = (uint16_t)gridHeight; |
| 535 | avifRWStreamWriteU16(&s, tmpWidth); // unsigned int(FieldLength) output_width; |
| 536 | avifRWStreamWriteU16(&s, tmpHeight); // unsigned int(FieldLength) output_height; |
| 537 | } |
| 538 | avifRWStreamFinishWrite(&s); |
| 539 | } |
| 540 | |
| 541 | static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, |
Wan-Teh Chang | f9fca86 | 2020-12-22 15:08:12 -0800 | [diff] [blame] | 542 | uint32_t gridCols, |
| 543 | uint32_t gridRows, |
Wan-Teh Chang | 2b7f04e | 2020-12-14 09:51:01 -0800 | [diff] [blame] | 544 | const avifImage * const * cellImages, |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 545 | uint64_t durationInTimescales, |
Joe Drago | e7c95e0 | 2021-05-06 12:48:55 -0700 | [diff] [blame] | 546 | avifAddImageFlags addImageFlags) |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 547 | { |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 548 | // ----------------------------------------------------------------------- |
Joe Drago | c534e70 | 2020-10-30 17:56:42 -0700 | [diff] [blame] | 549 | // Verify encoding is possible |
| 550 | |
| 551 | if (!avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE)) { |
| 552 | return AVIF_RESULT_NO_CODEC_AVAILABLE; |
| 553 | } |
| 554 | |
| 555 | // ----------------------------------------------------------------------- |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 556 | // Validate images |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 557 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 558 | const uint32_t cellCount = gridCols * gridRows; |
| 559 | if (cellCount == 0) { |
| 560 | return AVIF_RESULT_INVALID_ARGUMENT; |
Joe Drago | 7ad3ad6 | 2019-02-07 11:17:34 -0800 | [diff] [blame] | 561 | } |
| 562 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 563 | const avifImage * firstCell = cellImages[0]; |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 564 | if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12)) { |
| 565 | return AVIF_RESULT_UNSUPPORTED_DEPTH; |
| 566 | } |
| 567 | |
| 568 | if (!firstCell->width || !firstCell->height) { |
| 569 | return AVIF_RESULT_NO_CONTENT; |
| 570 | } |
| 571 | |
Yannis Guyon | bf28a92 | 2022-02-09 23:19:32 +0100 | [diff] [blame] | 572 | if ((cellCount > 1) && !avifAreGridDimensionsValid(firstCell->yuvFormat, |
| 573 | gridCols * firstCell->width, |
| 574 | gridRows * firstCell->height, |
| 575 | firstCell->width, |
| 576 | firstCell->height, |
| 577 | &encoder->diag)) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 578 | return AVIF_RESULT_INVALID_IMAGE_GRID; |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 579 | } |
| 580 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 581 | for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { |
| 582 | const avifImage * cellImage = cellImages[cellIndex]; |
Yannis Guyon | bf28a92 | 2022-02-09 23:19:32 +0100 | [diff] [blame] | 583 | // HEIF (ISO 23008-12:2017), Section 6.6.2.3.1: |
| 584 | // All input images shall have exactly the same width and height; call those tile_width and tile_height. |
| 585 | // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1: |
| 586 | // All input images of a grid image item shall use the same coding format, chroma sampling format, and the |
| 587 | // same decoder configuration (see 7.3.6.2). |
| 588 | if ((cellImage->width != firstCell->width) || (cellImage->height != firstCell->height) || |
| 589 | (cellImage->depth != firstCell->depth) || (cellImage->yuvFormat != firstCell->yuvFormat) || |
| 590 | (cellImage->yuvRange != firstCell->yuvRange) || (cellImage->colorPrimaries != firstCell->colorPrimaries) || |
| 591 | (cellImage->transferCharacteristics != firstCell->transferCharacteristics) || |
| 592 | (cellImage->matrixCoefficients != firstCell->matrixCoefficients) || (cellImage->alphaRange != firstCell->alphaRange) || |
| 593 | (!!cellImage->alphaPlane != !!firstCell->alphaPlane) || (cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 594 | return AVIF_RESULT_INVALID_IMAGE_GRID; |
| 595 | } |
| 596 | |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 597 | if (!cellImage->yuvPlanes[AVIF_CHAN_Y]) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 598 | return AVIF_RESULT_NO_CONTENT; |
| 599 | } |
| 600 | |
| 601 | if (cellImage->yuvFormat == AVIF_PIXEL_FORMAT_NONE) { |
| 602 | return AVIF_RESULT_NO_YUV_FORMAT_SELECTED; |
| 603 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 604 | } |
| 605 | |
| 606 | // ----------------------------------------------------------------------- |
Joe Drago | aba51d8 | 2020-11-19 13:12:29 -0800 | [diff] [blame] | 607 | // Validate flags |
| 608 | |
| 609 | if (encoder->data->singleImage) { |
| 610 | // The previous call to avifEncoderAddImage() set AVIF_ADD_IMAGE_FLAG_SINGLE. |
| 611 | // avifEncoderAddImage() cannot be called again for this encode. |
| 612 | return AVIF_RESULT_ENCODE_COLOR_FAILED; |
| 613 | } |
| 614 | |
| 615 | if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) { |
| 616 | encoder->data->singleImage = AVIF_TRUE; |
| 617 | |
| 618 | if (encoder->data->items.count > 0) { |
| 619 | // AVIF_ADD_IMAGE_FLAG_SINGLE may only be set on the first and only image. |
| 620 | return AVIF_RESULT_INVALID_ARGUMENT; |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | // ----------------------------------------------------------------------- |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 625 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 626 | if (durationInTimescales == 0) { |
| 627 | durationInTimescales = 1; |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 628 | } |
| 629 | |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 630 | if (encoder->data->items.count == 0) { |
| 631 | // Make a copy of the first image's metadata (sans pixels) for future writing/validation |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 632 | avifImageCopy(encoder->data->imageMetadata, firstCell, 0); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 633 | |
| 634 | // Prepare all AV1 items |
| 635 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 636 | uint16_t gridColorID = 0; |
| 637 | if (cellCount > 1) { |
| 638 | avifEncoderItem * gridColorItem = avifEncoderDataCreateItem(encoder->data, "grid", "Color", 6, 0); |
| 639 | avifWriteGridPayload(&gridColorItem->metadataPayload, gridCols, gridRows, firstCell); |
| 640 | gridColorItem->gridCols = gridCols; |
| 641 | gridColorItem->gridRows = gridRows; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 642 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 643 | gridColorID = gridColorItem->id; |
| 644 | encoder->data->primaryItemID = gridColorID; |
| 645 | } |
| 646 | |
| 647 | for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { |
| 648 | avifEncoderItem * item = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6, cellIndex); |
| 649 | item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); |
| 650 | if (!item->codec) { |
| 651 | // Just bail out early, we're not surviving this function without an encoder compiled in |
| 652 | return AVIF_RESULT_NO_CODEC_AVAILABLE; |
| 653 | } |
| 654 | item->codec->csOptions = encoder->csOptions; |
Joe Drago | bfc5aca | 2021-05-17 12:00:39 -0700 | [diff] [blame] | 655 | item->codec->diag = &encoder->diag; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 656 | |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 657 | if (cellCount > 1) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 658 | item->dimgFromID = gridColorID; |
Wan-Teh Chang | 0f41891 | 2022-02-17 09:16:05 -0800 | [diff] [blame] | 659 | item->hiddenImage = AVIF_TRUE; |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 660 | } else { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 661 | encoder->data->primaryItemID = item->id; |
| 662 | } |
| 663 | } |
| 664 | |
| 665 | encoder->data->alphaPresent = (firstCell->alphaPlane != NULL); |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 666 | if (encoder->data->alphaPresent && (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) { |
Joe Drago | 96e0918 | 2020-07-09 15:32:47 -0700 | [diff] [blame] | 667 | // If encoding a single image in which the alpha plane exists but is entirely opaque, |
| 668 | // simply skip writing an alpha AV1 payload entirely, as it'll be interpreted as opaque |
| 669 | // and is less bytes. |
| 670 | // |
| 671 | // However, if encoding an image sequence, the first frame's alpha plane being entirely |
| 672 | // opaque could be a false positive for removing the alpha AV1 payload, as it might simply |
| 673 | // be a fade out later in the sequence. This is why avifImageIsOpaque() is only called |
| 674 | // when encoding a single image. |
| 675 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 676 | encoder->data->alphaPresent = AVIF_FALSE; |
| 677 | for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { |
| 678 | const avifImage * cellImage = cellImages[cellIndex]; |
| 679 | if (!avifImageIsOpaque(cellImage)) { |
| 680 | encoder->data->alphaPresent = AVIF_TRUE; |
| 681 | break; |
| 682 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 683 | } |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 684 | } |
| 685 | |
| 686 | if (encoder->data->alphaPresent) { |
| 687 | uint16_t gridAlphaID = 0; |
| 688 | if (cellCount > 1) { |
| 689 | avifEncoderItem * gridAlphaItem = avifEncoderDataCreateItem(encoder->data, "grid", "Alpha", 6, 0); |
| 690 | avifWriteGridPayload(&gridAlphaItem->metadataPayload, gridCols, gridRows, firstCell); |
| 691 | gridAlphaItem->alpha = AVIF_TRUE; |
| 692 | gridAlphaItem->irefToID = encoder->data->primaryItemID; |
| 693 | gridAlphaItem->irefType = "auxl"; |
Wan-Teh Chang | 69c32ef | 2020-12-18 12:16:42 -0800 | [diff] [blame] | 694 | gridAlphaItem->gridCols = gridCols; |
| 695 | gridAlphaItem->gridRows = gridRows; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 696 | gridAlphaID = gridAlphaItem->id; |
Yuan Tong | e4850be | 2021-01-22 14:21:25 +0800 | [diff] [blame] | 697 | |
| 698 | if (encoder->data->imageMetadata->alphaPremultiplied) { |
Joe Drago | ceb2fa0 | 2021-01-29 18:14:55 -0800 | [diff] [blame] | 699 | avifEncoderItem * primaryItem = avifEncoderDataFindItemByID(encoder->data, encoder->data->primaryItemID); |
| 700 | assert(primaryItem); |
| 701 | primaryItem->irefType = "prem"; |
| 702 | primaryItem->irefToID = gridAlphaID; |
Yuan Tong | e4850be | 2021-01-22 14:21:25 +0800 | [diff] [blame] | 703 | } |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 704 | } |
| 705 | |
| 706 | for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { |
Wan-Teh Chang | 69c32ef | 2020-12-18 12:16:42 -0800 | [diff] [blame] | 707 | avifEncoderItem * item = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6, cellIndex); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 708 | item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); |
| 709 | if (!item->codec) { |
| 710 | return AVIF_RESULT_NO_CODEC_AVAILABLE; |
| 711 | } |
| 712 | item->codec->csOptions = encoder->csOptions; |
Joe Drago | bfc5aca | 2021-05-17 12:00:39 -0700 | [diff] [blame] | 713 | item->codec->diag = &encoder->diag; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 714 | item->alpha = AVIF_TRUE; |
| 715 | |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 716 | if (cellCount > 1) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 717 | item->dimgFromID = gridAlphaID; |
Wan-Teh Chang | 0f41891 | 2022-02-17 09:16:05 -0800 | [diff] [blame] | 718 | item->hiddenImage = AVIF_TRUE; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 719 | } else { |
| 720 | item->irefToID = encoder->data->primaryItemID; |
| 721 | item->irefType = "auxl"; |
Yuan Tong | e4850be | 2021-01-22 14:21:25 +0800 | [diff] [blame] | 722 | |
| 723 | if (encoder->data->imageMetadata->alphaPremultiplied) { |
Joe Drago | ceb2fa0 | 2021-01-29 18:14:55 -0800 | [diff] [blame] | 724 | avifEncoderItem * primaryItem = avifEncoderDataFindItemByID(encoder->data, encoder->data->primaryItemID); |
| 725 | assert(primaryItem); |
| 726 | primaryItem->irefType = "prem"; |
| 727 | primaryItem->irefToID = item->id; |
Yuan Tong | e4850be | 2021-01-22 14:21:25 +0800 | [diff] [blame] | 728 | } |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 729 | } |
| 730 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 731 | } |
| 732 | |
| 733 | // ----------------------------------------------------------------------- |
| 734 | // Create metadata items (Exif, XMP) |
| 735 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 736 | if (firstCell->exif.size > 0) { |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 737 | // Validate Exif payload (if any) and find TIFF header offset |
| 738 | uint32_t exifTiffHeaderOffset = 0; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 739 | if (firstCell->exif.size < 4) { |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 740 | // Can't even fit the TIFF header, something is wrong |
| 741 | return AVIF_RESULT_INVALID_EXIF_PAYLOAD; |
| 742 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 743 | |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 744 | const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 }; |
| 745 | const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 }; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 746 | for (; exifTiffHeaderOffset < (firstCell->exif.size - 4); ++exifTiffHeaderOffset) { |
| 747 | if (!memcmp(&firstCell->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) { |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 748 | break; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 749 | } |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 750 | if (!memcmp(&firstCell->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) { |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 751 | break; |
| 752 | } |
| 753 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 754 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 755 | if (exifTiffHeaderOffset >= firstCell->exif.size - 4) { |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 756 | // Couldn't find the TIFF header |
| 757 | return AVIF_RESULT_INVALID_EXIF_PAYLOAD; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 758 | } |
| 759 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 760 | avifEncoderItem * exifItem = avifEncoderDataCreateItem(encoder->data, "Exif", "Exif", 5, 0); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 761 | exifItem->irefToID = encoder->data->primaryItemID; |
| 762 | exifItem->irefType = "cdsc"; |
| 763 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 764 | avifRWDataRealloc(&exifItem->metadataPayload, sizeof(uint32_t) + firstCell->exif.size); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 765 | exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset); |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 766 | memcpy(exifItem->metadataPayload.data, &exifTiffHeaderOffset, sizeof(uint32_t)); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 767 | memcpy(exifItem->metadataPayload.data + sizeof(uint32_t), firstCell->exif.data, firstCell->exif.size); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 768 | } |
| 769 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 770 | if (firstCell->xmp.size > 0) { |
| 771 | avifEncoderItem * xmpItem = avifEncoderDataCreateItem(encoder->data, "mime", "XMP", 4, 0); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 772 | xmpItem->irefToID = encoder->data->primaryItemID; |
| 773 | xmpItem->irefType = "cdsc"; |
| 774 | |
| 775 | xmpItem->infeContentType = xmpContentType; |
| 776 | xmpItem->infeContentTypeSize = xmpContentTypeSize; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 777 | avifRWDataSet(&xmpItem->metadataPayload, firstCell->xmp.data, firstCell->xmp.size); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 778 | } |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 779 | } else { |
| 780 | // Another frame in an image sequence |
Joe Drago | 96e0918 | 2020-07-09 15:32:47 -0700 | [diff] [blame] | 781 | |
Wan-Teh Chang | 0b7c20e | 2020-12-18 16:21:57 -0800 | [diff] [blame] | 782 | if (encoder->data->alphaPresent && !firstCell->alphaPlane) { |
| 783 | // If the first image in the sequence had an alpha plane (even if fully opaque), all |
| 784 | // subsequence images must have alpha as well. |
| 785 | return AVIF_RESULT_ENCODE_ALPHA_FAILED; |
Joe Drago | 96e0918 | 2020-07-09 15:32:47 -0700 | [diff] [blame] | 786 | } |
wantehchang | cbfab6b | 2020-03-11 11:53:53 -0700 | [diff] [blame] | 787 | } |
| 788 | |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 789 | // ----------------------------------------------------------------------- |
| 790 | // Encode AV1 OBUs |
| 791 | |
Joe Drago | d685024 | 2020-06-16 16:05:37 -0700 | [diff] [blame] | 792 | if (encoder->keyframeInterval && ((encoder->data->frames.count % encoder->keyframeInterval) == 0)) { |
| 793 | addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME; |
Joe Drago | 3736df9 | 2020-06-16 15:39:55 -0700 | [diff] [blame] | 794 | } |
| 795 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 796 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 797 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 798 | if (item->codec) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 799 | const avifImage * cellImage = cellImages[item->cellIndex]; |
Joe Drago | e1097bd | 2020-08-27 18:55:03 -0700 | [diff] [blame] | 800 | avifResult encodeResult = |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 801 | item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, addImageFlags, item->encodeOutput); |
Joe Drago | e1097bd | 2020-08-27 18:55:03 -0700 | [diff] [blame] | 802 | if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { |
| 803 | encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; |
| 804 | } |
| 805 | if (encodeResult != AVIF_RESULT_OK) { |
| 806 | return encodeResult; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 807 | } |
| 808 | } |
| 809 | } |
Joe Drago | b3fdc9e | 2020-06-01 12:33:16 -0700 | [diff] [blame] | 810 | |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 811 | avifEncoderFrame * frame = (avifEncoderFrame *)avifArrayPushPtr(&encoder->data->frames); |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 812 | frame->durationInTimescales = durationInTimescales; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 813 | return AVIF_RESULT_OK; |
| 814 | } |
| 815 | |
Joe Drago | e7c95e0 | 2021-05-06 12:48:55 -0700 | [diff] [blame] | 816 | avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 817 | { |
Joe Drago | bfc5aca | 2021-05-17 12:00:39 -0700 | [diff] [blame] | 818 | avifDiagnosticsClearError(&encoder->diag); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 819 | return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags); |
| 820 | } |
| 821 | |
Joe Drago | e7c95e0 | 2021-05-06 12:48:55 -0700 | [diff] [blame] | 822 | avifResult avifEncoderAddImageGrid(avifEncoder * encoder, |
| 823 | uint32_t gridCols, |
| 824 | uint32_t gridRows, |
| 825 | const avifImage * const * cellImages, |
| 826 | avifAddImageFlags addImageFlags) |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 827 | { |
Joe Drago | bfc5aca | 2021-05-17 12:00:39 -0700 | [diff] [blame] | 828 | avifDiagnosticsClearError(&encoder->diag); |
Wan-Teh Chang | f9fca86 | 2020-12-22 15:08:12 -0800 | [diff] [blame] | 829 | if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { |
| 830 | return AVIF_RESULT_INVALID_IMAGE_GRID; |
| 831 | } |
Joe Drago | 11d2359 | 2021-01-05 14:18:57 -0800 | [diff] [blame] | 832 | return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 833 | } |
| 834 | |
Wan-Teh Chang | 2b7f04e | 2020-12-14 09:51:01 -0800 | [diff] [blame] | 835 | static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 836 | { |
| 837 | const size_t mdatCurrentOffset = avifRWStreamOffset(s); |
| 838 | const size_t mdatSearchSize = mdatCurrentOffset - mdatStartOffset; |
| 839 | if (mdatSearchSize < size) { |
| 840 | return 0; |
| 841 | } |
Wan-Teh Chang | 8adbb97 | 2020-12-18 14:12:51 -0800 | [diff] [blame] | 842 | const size_t mdatEndSearchOffset = mdatCurrentOffset - size; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 843 | for (size_t searchOffset = mdatStartOffset; searchOffset <= mdatEndSearchOffset; ++searchOffset) { |
| 844 | if (!memcmp(data, &s->raw->data[searchOffset], size)) { |
| 845 | return searchOffset; |
| 846 | } |
| 847 | } |
| 848 | return 0; |
| 849 | } |
| 850 | |
Joe Drago | c7e1d75 | 2020-06-15 19:46:50 -0700 | [diff] [blame] | 851 | avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 852 | { |
Joe Drago | bfc5aca | 2021-05-17 12:00:39 -0700 | [diff] [blame] | 853 | avifDiagnosticsClearError(&encoder->diag); |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 854 | if (encoder->data->items.count == 0) { |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 855 | return AVIF_RESULT_NO_CONTENT; |
| 856 | } |
| 857 | |
| 858 | // ----------------------------------------------------------------------- |
| 859 | // Finish up AV1 encoding |
| 860 | |
| 861 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 862 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 863 | if (item->codec) { |
Joe Drago | 5b596c4 | 2020-06-02 17:13:38 -0700 | [diff] [blame] | 864 | if (!item->codec->encodeFinish(item->codec, item->encodeOutput)) { |
Joe Drago | b3fdc9e | 2020-06-01 12:33:16 -0700 | [diff] [blame] | 865 | return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; |
| 866 | } |
Joe Drago | b3fdc9e | 2020-06-01 12:33:16 -0700 | [diff] [blame] | 867 | |
Joe Drago | 8210c1b | 2020-06-02 17:40:28 -0700 | [diff] [blame] | 868 | if (item->encodeOutput->samples.count != encoder->data->frames.count) { |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 869 | return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 870 | } |
Joe Drago | 46ea058 | 2019-07-22 15:55:47 -0700 | [diff] [blame] | 871 | } |
| 872 | } |
| 873 | |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 874 | // ----------------------------------------------------------------------- |
Joe Drago | 2172ed0 | 2020-11-04 18:04:44 -0800 | [diff] [blame] | 875 | // Harvest av1C properties from AV1 sequence headers |
| 876 | |
| 877 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 878 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 879 | if (item->encodeOutput->samples.count > 0) { |
Wan-Teh Chang | 9fbcb9b | 2020-11-06 18:48:58 -0800 | [diff] [blame] | 880 | const avifEncodeSample * firstSample = &item->encodeOutput->samples.sample[0]; |
Joe Drago | 2172ed0 | 2020-11-04 18:04:44 -0800 | [diff] [blame] | 881 | avifSequenceHeader sequenceHeader; |
| 882 | if (avifSequenceHeaderParse(&sequenceHeader, (const avifROData *)&firstSample->data)) { |
Wan-Teh Chang | 21961f4 | 2022-03-14 16:25:44 -0700 | [diff] [blame^] | 883 | item->av1C = sequenceHeader.av1C; |
Joe Drago | 2172ed0 | 2020-11-04 18:04:44 -0800 | [diff] [blame] | 884 | } else { |
| 885 | // This must be an invalid AV1 payload |
| 886 | return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; |
| 887 | } |
| 888 | } |
| 889 | } |
| 890 | |
| 891 | // ----------------------------------------------------------------------- |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 892 | // Begin write stream |
| 893 | |
Wan-Teh Chang | b11ae43 | 2020-07-08 18:25:24 -0700 | [diff] [blame] | 894 | const avifImage * imageMetadata = encoder->data->imageMetadata; |
| 895 | // The epoch for creation_time and modification_time is midnight, Jan. 1, |
| 896 | // 1904, in UTC time. Add the number of seconds between that epoch and the |
| 897 | // Unix epoch. |
| 898 | uint64_t now = (uint64_t)time(NULL) + 2082844800; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 899 | |
| 900 | avifRWStream s; |
| 901 | avifRWStreamStart(&s, output); |
| 902 | |
| 903 | // ----------------------------------------------------------------------- |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 904 | // Write ftyp |
| 905 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 906 | const char * majorBrand = "avif"; |
| 907 | if (encoder->data->frames.count > 1) { |
| 908 | majorBrand = "avis"; |
| 909 | } |
| 910 | |
| 911 | avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD); |
| 912 | avifRWStreamWriteChars(&s, majorBrand, 4); // unsigned int(32) major_brand; |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 913 | avifRWStreamWriteU32(&s, 0); // unsigned int(32) minor_version; |
| 914 | avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) compatible_brands[]; |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 915 | if (encoder->data->frames.count > 1) { // |
| 916 | avifRWStreamWriteChars(&s, "avis", 4); // ... compatible_brands[] |
Joe Drago | 70f5a2e | 2020-06-03 17:54:19 -0700 | [diff] [blame] | 917 | avifRWStreamWriteChars(&s, "msf1", 4); // ... compatible_brands[] |
Vignesh Venkatasubramanian | 0cf1333 | 2022-02-17 14:06:50 -0800 | [diff] [blame] | 918 | avifRWStreamWriteChars(&s, "iso8", 4); // ... compatible_brands[] |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 919 | } // |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 920 | avifRWStreamWriteChars(&s, "mif1", 4); // ... compatible_brands[] |
| 921 | avifRWStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[] |
| 922 | if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) { // |
| 923 | if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { // |
| 924 | avifRWStreamWriteChars(&s, "MA1B", 4); // ... compatible_brands[] |
| 925 | } else if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { // |
| 926 | avifRWStreamWriteChars(&s, "MA1A", 4); // ... compatible_brands[] |
Joe Drago | eb652d8 | 2019-04-23 16:29:07 -0700 | [diff] [blame] | 927 | } |
| 928 | } |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 929 | avifRWStreamFinishBox(&s, ftyp); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 930 | |
| 931 | // ----------------------------------------------------------------------- |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 932 | // Start meta |
| 933 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 934 | avifBoxMarker meta = avifRWStreamWriteFullBox(&s, "meta", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 935 | |
| 936 | // ----------------------------------------------------------------------- |
| 937 | // Write hdlr |
| 938 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 939 | avifBoxMarker hdlr = avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 940 | avifRWStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0; |
| 941 | avifRWStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type; |
| 942 | avifRWStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0; |
| 943 | avifRWStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator) |
| 944 | avifRWStreamFinishBox(&s, hdlr); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 945 | |
| 946 | // ----------------------------------------------------------------------- |
| 947 | // Write pitm |
| 948 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 949 | if (encoder->data->primaryItemID != 0) { |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 950 | avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 951 | avifRWStreamWriteU16(&s, encoder->data->primaryItemID); // unsigned int(16) item_ID; |
Joe Drago | f6a4227 | 2019-11-21 15:21:41 -0800 | [diff] [blame] | 952 | } |
| 953 | |
| 954 | // ----------------------------------------------------------------------- |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 955 | // Write iloc |
| 956 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 957 | avifBoxMarker iloc = avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 958 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 959 | uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; |
| 960 | // unsigned int(4) length_size; |
| 961 | avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1); // |
| 962 | avifRWStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size; |
| 963 | // unsigned int(4) reserved; |
| 964 | avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) item_count; |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 965 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 966 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 967 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 968 | |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 969 | uint32_t contentSize = (uint32_t)item->metadataPayload.size; |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 970 | if (item->encodeOutput->samples.count > 0) { |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 971 | // This is choosing sample 0's size as there are two cases here: |
Joe Drago | d6aa99c | 2020-07-09 14:27:46 -0700 | [diff] [blame] | 972 | // * This is a single image, in which case this is correct |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 973 | // * This is an image sequence, but this file should still be a valid single-image avif, |
| 974 | // so there must still be a primary item pointing at a sync sample. Since the first |
| 975 | // frame of the image sequence is guaranteed to be a sync sample, it is chosen here. |
| 976 | // |
Joe Drago | 746325a | 2020-07-09 14:22:55 -0700 | [diff] [blame] | 977 | // TODO: Offer the ability for a user to specify which frame in the sequence should |
Joe Drago | 25fe127 | 2020-07-09 14:20:39 -0700 | [diff] [blame] | 978 | // become the primary item's image, and force that frame to be a keyframe. |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 979 | contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size; |
| 980 | } |
| 981 | |
| 982 | avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; |
| 983 | avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; |
| 984 | avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 985 | avifEncoderItemAddMdatFixup(item, &s); // |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 986 | avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; |
| 987 | avifRWStreamWriteU32(&s, (uint32_t)contentSize); // unsigned int(length_size*8) extent_length; |
Joe Drago | f6a4227 | 2019-11-21 15:21:41 -0800 | [diff] [blame] | 988 | } |
| 989 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 990 | avifRWStreamFinishBox(&s, iloc); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 991 | |
| 992 | // ----------------------------------------------------------------------- |
| 993 | // Write iinf |
| 994 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 995 | avifBoxMarker iinf = avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 996 | avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) entry_count; |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 997 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 998 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 999 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 1000 | |
Wan-Teh Chang | 0f41891 | 2022-02-17 09:16:05 -0800 | [diff] [blame] | 1001 | uint32_t flags = item->hiddenImage ? 1 : 0; |
| 1002 | avifBoxMarker infe = avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, 2, flags); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1003 | avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; |
Joe Drago | f6a4227 | 2019-11-21 15:21:41 -0800 | [diff] [blame] | 1004 | avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1005 | avifRWStreamWrite(&s, item->type, 4); // unsigned int(32) item_type; |
| 1006 | avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator) |
| 1007 | if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator) |
| 1008 | avifRWStreamWriteChars(&s, item->infeContentType, item->infeContentTypeSize); |
| 1009 | } |
| 1010 | avifRWStreamFinishBox(&s, infe); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1011 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1012 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1013 | avifRWStreamFinishBox(&s, iinf); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1014 | |
| 1015 | // ----------------------------------------------------------------------- |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1016 | // Write iref boxes |
Joe Drago | 8f7a300 | 2019-02-07 19:35:37 -0800 | [diff] [blame] | 1017 | |
Joe Drago | 3818cf4 | 2020-07-31 13:08:37 -0700 | [diff] [blame] | 1018 | avifBoxMarker iref = 0; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1019 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 1020 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1021 | |
| 1022 | // Count how many other items refer to this item with dimgFromID |
| 1023 | uint16_t dimgCount = 0; |
| 1024 | for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) { |
| 1025 | avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex]; |
| 1026 | if (dimgItem->dimgFromID == item->id) { |
| 1027 | ++dimgCount; |
| 1028 | } |
| 1029 | } |
| 1030 | |
| 1031 | if (dimgCount > 0) { |
| 1032 | if (!iref) { |
| 1033 | iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1034 | } |
| 1035 | avifBoxMarker refType = avifRWStreamWriteBox(&s, "dimg", AVIF_BOX_SIZE_TBD); |
| 1036 | avifRWStreamWriteU16(&s, item->id); // unsigned int(16) from_item_ID; |
| 1037 | avifRWStreamWriteU16(&s, dimgCount); // unsigned int(16) reference_count; |
| 1038 | for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) { |
| 1039 | avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex]; |
| 1040 | if (dimgItem->dimgFromID == item->id) { |
| 1041 | avifRWStreamWriteU16(&s, dimgItem->id); // unsigned int(16) to_item_ID; |
| 1042 | } |
| 1043 | } |
| 1044 | avifRWStreamFinishBox(&s, refType); |
| 1045 | } |
| 1046 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1047 | if (item->irefToID != 0) { |
Joe Drago | 3818cf4 | 2020-07-31 13:08:37 -0700 | [diff] [blame] | 1048 | if (!iref) { |
| 1049 | iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1050 | } |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1051 | avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1052 | avifRWStreamWriteU16(&s, item->id); // unsigned int(16) from_item_ID; |
| 1053 | avifRWStreamWriteU16(&s, 1); // unsigned int(16) reference_count; |
| 1054 | avifRWStreamWriteU16(&s, item->irefToID); // unsigned int(16) to_item_ID; |
| 1055 | avifRWStreamFinishBox(&s, refType); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1056 | } |
Joe Drago | 8f7a300 | 2019-02-07 19:35:37 -0800 | [diff] [blame] | 1057 | } |
Joe Drago | 3818cf4 | 2020-07-31 13:08:37 -0700 | [diff] [blame] | 1058 | if (iref) { |
| 1059 | avifRWStreamFinishBox(&s, iref); |
| 1060 | } |
Joe Drago | 8f7a300 | 2019-02-07 19:35:37 -0800 | [diff] [blame] | 1061 | |
| 1062 | // ----------------------------------------------------------------------- |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1063 | // Write iprp -> ipco/ipma |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1064 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1065 | avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD); |
Joe Drago | 224b018 | 2019-02-08 10:42:29 -0800 | [diff] [blame] | 1066 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1067 | avifItemPropertyDedup * dedup = avifItemPropertyDedupCreate(); |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1068 | avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1069 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 1070 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1071 | const avifBool isGrid = (item->gridCols > 0); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1072 | memset(&item->ipma, 0, sizeof(item->ipma)); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1073 | if (!item->codec && !isGrid) { |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1074 | // No ipma to write for this item |
| 1075 | continue; |
| 1076 | } |
Joe Drago | e4dba56 | 2019-02-07 21:52:32 -0800 | [diff] [blame] | 1077 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1078 | if (item->dimgFromID) { |
| 1079 | // All image cells from a grid should share the exact same properties, so see if we've |
| 1080 | // already written properties out for another cell in this grid, and if so, just steal |
| 1081 | // their ipma and move on. This is a sneaky way to provide iprp deduplication. |
| 1082 | |
| 1083 | avifBool foundPreviousCell = AVIF_FALSE; |
| 1084 | for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) { |
| 1085 | avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex]; |
| 1086 | if (item->dimgFromID == dedupItem->dimgFromID) { |
| 1087 | // We've already written dedup's items out. Steal their ipma indices and move on! |
Wan-Teh Chang | 21961f4 | 2022-03-14 16:25:44 -0700 | [diff] [blame^] | 1088 | item->ipma = dedupItem->ipma; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1089 | foundPreviousCell = AVIF_TRUE; |
| 1090 | break; |
| 1091 | } |
| 1092 | } |
| 1093 | if (foundPreviousCell) { |
| 1094 | continue; |
| 1095 | } |
| 1096 | } |
| 1097 | |
Wan-Teh Chang | 8adbb97 | 2020-12-18 14:12:51 -0800 | [diff] [blame] | 1098 | uint32_t imageWidth = imageMetadata->width; |
| 1099 | uint32_t imageHeight = imageMetadata->height; |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1100 | if (isGrid) { |
| 1101 | imageWidth = imageMetadata->width * item->gridCols; |
| 1102 | imageHeight = imageMetadata->height * item->gridRows; |
| 1103 | } |
| 1104 | |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1105 | // Properties all av01 items need |
| 1106 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1107 | avifItemPropertyDedupStart(dedup); |
| 1108 | avifBoxMarker ispe = avifRWStreamWriteFullBox(&dedup->s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1109 | avifRWStreamWriteU32(&dedup->s, imageWidth); // unsigned int(32) image_width; |
| 1110 | avifRWStreamWriteU32(&dedup->s, imageHeight); // unsigned int(32) image_height; |
| 1111 | avifRWStreamFinishBox(&dedup->s, ispe); |
| 1112 | ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1113 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1114 | avifItemPropertyDedupStart(dedup); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 1115 | uint8_t channelCount = (item->alpha || (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3; |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1116 | avifBoxMarker pixi = avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1117 | avifRWStreamWriteU8(&dedup->s, channelCount); // unsigned int (8) num_channels; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1118 | for (uint8_t chan = 0; chan < channelCount; ++chan) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1119 | avifRWStreamWriteU8(&dedup->s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1120 | } |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1121 | avifRWStreamFinishBox(&dedup->s, pixi); |
| 1122 | ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1123 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1124 | if (item->codec) { |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1125 | avifItemPropertyDedupStart(dedup); |
| 1126 | writeConfigBox(&dedup->s, &item->av1C); |
| 1127 | ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_TRUE); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1128 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1129 | |
| 1130 | if (item->alpha) { |
| 1131 | // Alpha specific properties |
| 1132 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1133 | avifItemPropertyDedupStart(dedup); |
| 1134 | avifBoxMarker auxC = avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1135 | avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize); // string aux_type; |
| 1136 | avifRWStreamFinishBox(&dedup->s, auxC); |
| 1137 | ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1138 | } else { |
| 1139 | // Color specific properties |
| 1140 | |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1141 | avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup); |
Joe Drago | e4dba56 | 2019-02-07 21:52:32 -0800 | [diff] [blame] | 1142 | } |
Joe Drago | e4dba56 | 2019-02-07 21:52:32 -0800 | [diff] [blame] | 1143 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1144 | avifRWStreamFinishBox(&s, ipco); |
Joe Drago | 5779fc4 | 2021-09-08 11:15:26 -0700 | [diff] [blame] | 1145 | avifItemPropertyDedupDestroy(dedup); |
| 1146 | dedup = NULL; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1147 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1148 | avifBoxMarker ipma = avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1149 | { |
| 1150 | int ipmaCount = 0; |
| 1151 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 1152 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 1153 | if (item->ipma.count > 0) { |
| 1154 | ++ipmaCount; |
| 1155 | } |
| 1156 | } |
| 1157 | avifRWStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count; |
| 1158 | |
| 1159 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 1160 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 1161 | if (item->ipma.count == 0) { |
| 1162 | continue; |
| 1163 | } |
| 1164 | |
Joe Drago | 0da2997 | 2020-04-23 11:55:10 -0700 | [diff] [blame] | 1165 | avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; |
| 1166 | avifRWStreamWriteU8(&s, item->ipma.count); // unsigned int(8) association_count; |
| 1167 | for (int i = 0; i < item->ipma.count; ++i) { // |
| 1168 | uint8_t essentialAndIndex = item->ipma.associations[i]; |
| 1169 | if (item->ipma.essential[i]) { |
| 1170 | essentialAndIndex |= 0x80; |
| 1171 | } |
| 1172 | avifRWStreamWriteU8(&s, essentialAndIndex); // bit(1) essential; unsigned int(7) property_index; |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1173 | } |
| 1174 | } |
| 1175 | } |
| 1176 | avifRWStreamFinishBox(&s, ipma); |
| 1177 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1178 | avifRWStreamFinishBox(&s, iprp); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1179 | |
| 1180 | // ----------------------------------------------------------------------- |
Joe Drago | eb652d8 | 2019-04-23 16:29:07 -0700 | [diff] [blame] | 1181 | // Finish meta box |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1182 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1183 | avifRWStreamFinishBox(&s, meta); |
Joe Drago | eb652d8 | 2019-04-23 16:29:07 -0700 | [diff] [blame] | 1184 | |
| 1185 | // ----------------------------------------------------------------------- |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1186 | // Write tracks (if an image sequence) |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 1187 | |
| 1188 | if (encoder->data->frames.count > 1) { |
Vignesh Venkatasubramanian | e234f67 | 2022-02-16 11:04:52 -0800 | [diff] [blame] | 1189 | static const uint8_t unityMatrix[9][4] = { |
| 1190 | /* clang-format off */ |
| 1191 | { 0x00, 0x01, 0x00, 0x00 }, |
| 1192 | { 0 }, |
| 1193 | { 0 }, |
| 1194 | { 0 }, |
| 1195 | { 0x00, 0x01, 0x00, 0x00 }, |
| 1196 | { 0 }, |
| 1197 | { 0 }, |
| 1198 | { 0 }, |
| 1199 | { 0x40, 0x00, 0x00, 0x00 } |
| 1200 | /* clang-format on */ |
| 1201 | }; |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1202 | |
| 1203 | uint64_t durationInTimescales = 0; |
| 1204 | for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) { |
Wan-Teh Chang | b139eed | 2020-07-08 18:04:14 -0700 | [diff] [blame] | 1205 | const avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex]; |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1206 | durationInTimescales += frame->durationInTimescales; |
| 1207 | } |
| 1208 | |
| 1209 | // ------------------------------------------------------------------- |
| 1210 | // Start moov |
| 1211 | |
| 1212 | avifBoxMarker moov = avifRWStreamWriteBox(&s, "moov", AVIF_BOX_SIZE_TBD); |
| 1213 | |
| 1214 | avifBoxMarker mvhd = avifRWStreamWriteFullBox(&s, "mvhd", AVIF_BOX_SIZE_TBD, 1, 0); |
| 1215 | avifRWStreamWriteU64(&s, now); // unsigned int(64) creation_time; |
| 1216 | avifRWStreamWriteU64(&s, now); // unsigned int(64) modification_time; |
| 1217 | avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale); // unsigned int(32) timescale; |
| 1218 | avifRWStreamWriteU64(&s, durationInTimescales); // unsigned int(64) duration; |
| 1219 | avifRWStreamWriteU32(&s, 0x00010000); // template int(32) rate = 0x00010000; // typically 1.0 |
| 1220 | avifRWStreamWriteU16(&s, 0x0100); // template int(16) volume = 0x0100; // typically, full volume |
| 1221 | avifRWStreamWriteU16(&s, 0); // const bit(16) reserved = 0; |
| 1222 | avifRWStreamWriteZeros(&s, 8); // const unsigned int(32)[2] reserved = 0; |
Wan-Teh Chang | 8557cd6 | 2020-07-08 18:42:10 -0700 | [diff] [blame] | 1223 | avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix)); |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1224 | avifRWStreamWriteZeros(&s, 24); // bit(32)[6] pre_defined = 0; |
| 1225 | avifRWStreamWriteU32(&s, encoder->data->items.count); // unsigned int(32) next_track_ID; |
| 1226 | avifRWStreamFinishBox(&s, mvhd); |
| 1227 | |
| 1228 | // ------------------------------------------------------------------- |
| 1229 | // Write tracks |
| 1230 | |
| 1231 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 1232 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
| 1233 | if (item->encodeOutput->samples.count == 0) { |
| 1234 | continue; |
| 1235 | } |
| 1236 | |
| 1237 | uint32_t syncSamplesCount = 0; |
| 1238 | for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) { |
| 1239 | avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex]; |
| 1240 | if (sample->sync) { |
| 1241 | ++syncSamplesCount; |
| 1242 | } |
| 1243 | } |
| 1244 | |
| 1245 | avifBoxMarker trak = avifRWStreamWriteBox(&s, "trak", AVIF_BOX_SIZE_TBD); |
| 1246 | |
Joe Drago | 85e387a | 2020-06-03 17:12:53 -0700 | [diff] [blame] | 1247 | avifBoxMarker tkhd = avifRWStreamWriteFullBox(&s, "tkhd", AVIF_BOX_SIZE_TBD, 1, 1); |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1248 | avifRWStreamWriteU64(&s, now); // unsigned int(64) creation_time; |
| 1249 | avifRWStreamWriteU64(&s, now); // unsigned int(64) modification_time; |
| 1250 | avifRWStreamWriteU32(&s, itemIndex + 1); // unsigned int(32) track_ID; |
| 1251 | avifRWStreamWriteU32(&s, 0); // const unsigned int(32) reserved = 0; |
| 1252 | avifRWStreamWriteU64(&s, durationInTimescales); // unsigned int(64) duration; |
| 1253 | avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 2); // const unsigned int(32)[2] reserved = 0; |
| 1254 | avifRWStreamWriteU16(&s, 0); // template int(16) layer = 0; |
| 1255 | avifRWStreamWriteU16(&s, 0); // template int(16) alternate_group = 0; |
| 1256 | avifRWStreamWriteU16(&s, 0); // template int(16) volume = {if track_is_audio 0x0100 else 0}; |
| 1257 | avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0; |
Wan-Teh Chang | 8557cd6 | 2020-07-08 18:42:10 -0700 | [diff] [blame] | 1258 | avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix)); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1259 | avifRWStreamWriteU32(&s, imageMetadata->width << 16); // unsigned int(32) width; |
| 1260 | avifRWStreamWriteU32(&s, imageMetadata->height << 16); // unsigned int(32) height; |
| 1261 | avifRWStreamFinishBox(&s, tkhd); |
| 1262 | |
| 1263 | if (item->irefToID != 0) { |
| 1264 | avifBoxMarker tref = avifRWStreamWriteBox(&s, "tref", AVIF_BOX_SIZE_TBD); |
| 1265 | avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD); |
| 1266 | avifRWStreamWriteU32(&s, (uint32_t)item->irefToID); |
| 1267 | avifRWStreamFinishBox(&s, refType); |
| 1268 | avifRWStreamFinishBox(&s, tref); |
| 1269 | } |
| 1270 | |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 1271 | if (!item->alpha) { |
| 1272 | avifEncoderWriteTrackMetaBox(encoder, &s); |
| 1273 | } |
| 1274 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1275 | avifBoxMarker mdia = avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD); |
| 1276 | |
| 1277 | avifBoxMarker mdhd = avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0); |
| 1278 | avifRWStreamWriteU64(&s, now); // unsigned int(64) creation_time; |
| 1279 | avifRWStreamWriteU64(&s, now); // unsigned int(64) modification_time; |
| 1280 | avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale); // unsigned int(32) timescale; |
| 1281 | avifRWStreamWriteU64(&s, durationInTimescales); // unsigned int(64) duration; |
| 1282 | avifRWStreamWriteU16(&s, 21956); // bit(1) pad = 0; unsigned int(5)[3] language; ("und") |
| 1283 | avifRWStreamWriteU16(&s, 0); // unsigned int(16) pre_defined = 0; |
| 1284 | avifRWStreamFinishBox(&s, mdhd); |
| 1285 | |
| 1286 | avifBoxMarker hdlrTrak = avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1287 | avifRWStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0; |
| 1288 | avifRWStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type; |
| 1289 | avifRWStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0; |
| 1290 | avifRWStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator) |
| 1291 | avifRWStreamFinishBox(&s, hdlrTrak); |
| 1292 | |
| 1293 | avifBoxMarker minf = avifRWStreamWriteBox(&s, "minf", AVIF_BOX_SIZE_TBD); |
| 1294 | |
| 1295 | avifBoxMarker vmhd = avifRWStreamWriteFullBox(&s, "vmhd", AVIF_BOX_SIZE_TBD, 0, 1); |
| 1296 | avifRWStreamWriteU16(&s, 0); // template unsigned int(16) graphicsmode = 0; (copy over the existing image) |
| 1297 | avifRWStreamWriteZeros(&s, 6); // template unsigned int(16)[3] opcolor = {0, 0, 0}; |
| 1298 | avifRWStreamFinishBox(&s, vmhd); |
| 1299 | |
Joe Drago | 20ec176 | 2020-06-03 17:34:43 -0700 | [diff] [blame] | 1300 | avifBoxMarker dinf = avifRWStreamWriteBox(&s, "dinf", AVIF_BOX_SIZE_TBD); |
| 1301 | avifBoxMarker dref = avifRWStreamWriteFullBox(&s, "dref", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1302 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count; |
| 1303 | avifRWStreamWriteFullBox(&s, "url ", 0, 0, 1); // flags:1 means data is in this file |
| 1304 | avifRWStreamFinishBox(&s, dref); |
| 1305 | avifRWStreamFinishBox(&s, dinf); |
| 1306 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1307 | avifBoxMarker stbl = avifRWStreamWriteBox(&s, "stbl", AVIF_BOX_SIZE_TBD); |
| 1308 | |
| 1309 | avifBoxMarker stco = avifRWStreamWriteFullBox(&s, "stco", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | a72da5b | 2020-06-15 19:40:17 -0700 | [diff] [blame] | 1310 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count; |
| 1311 | avifEncoderItemAddMdatFixup(item, &s); // |
| 1312 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) chunk_offset; (set later) |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1313 | avifRWStreamFinishBox(&s, stco); |
| 1314 | |
| 1315 | avifBoxMarker stsc = avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1316 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count; |
| 1317 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) first_chunk; |
| 1318 | avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) samples_per_chunk; |
| 1319 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) sample_description_index; |
| 1320 | avifRWStreamFinishBox(&s, stsc); |
| 1321 | |
| 1322 | avifBoxMarker stsz = avifRWStreamWriteFullBox(&s, "stsz", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1323 | avifRWStreamWriteU32(&s, 0); // unsigned int(32) sample_size; |
| 1324 | avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) sample_count; |
| 1325 | for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) { |
| 1326 | avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex]; |
| 1327 | avifRWStreamWriteU32(&s, (uint32_t)sample->data.size); // unsigned int(32) entry_size; |
| 1328 | } |
| 1329 | avifRWStreamFinishBox(&s, stsz); |
| 1330 | |
| 1331 | avifBoxMarker stss = avifRWStreamWriteFullBox(&s, "stss", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1332 | avifRWStreamWriteU32(&s, syncSamplesCount); // unsigned int(32) entry_count; |
| 1333 | for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) { |
| 1334 | avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex]; |
| 1335 | if (sample->sync) { |
| 1336 | avifRWStreamWriteU32(&s, sampleIndex + 1); // unsigned int(32) sample_number; |
| 1337 | } |
| 1338 | } |
| 1339 | avifRWStreamFinishBox(&s, stss); |
| 1340 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1341 | avifBoxMarker stts = avifRWStreamWriteFullBox(&s, "stts", AVIF_BOX_SIZE_TBD, 0, 0); |
Joe Drago | 85e387a | 2020-06-03 17:12:53 -0700 | [diff] [blame] | 1342 | size_t sttsEntryCountOffset = avifRWStreamOffset(&s); |
| 1343 | uint32_t sttsEntryCount = 0; |
| 1344 | avifRWStreamWriteU32(&s, 0); // unsigned int(32) entry_count; |
| 1345 | for (uint32_t sampleCount = 0, frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) { |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1346 | avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex]; |
Joe Drago | 85e387a | 2020-06-03 17:12:53 -0700 | [diff] [blame] | 1347 | ++sampleCount; |
| 1348 | if (frameIndex < (encoder->data->frames.count - 1)) { |
| 1349 | avifEncoderFrame * nextFrame = &encoder->data->frames.frame[frameIndex + 1]; |
| 1350 | if (frame->durationInTimescales == nextFrame->durationInTimescales) { |
| 1351 | continue; |
| 1352 | } |
| 1353 | } |
| 1354 | avifRWStreamWriteU32(&s, sampleCount); // unsigned int(32) sample_count; |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1355 | avifRWStreamWriteU32(&s, (uint32_t)frame->durationInTimescales); // unsigned int(32) sample_delta; |
Joe Drago | 85e387a | 2020-06-03 17:12:53 -0700 | [diff] [blame] | 1356 | sampleCount = 0; |
| 1357 | ++sttsEntryCount; |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1358 | } |
Joe Drago | 85e387a | 2020-06-03 17:12:53 -0700 | [diff] [blame] | 1359 | size_t prevOffset = avifRWStreamOffset(&s); |
| 1360 | avifRWStreamSetOffset(&s, sttsEntryCountOffset); |
| 1361 | avifRWStreamWriteU32(&s, sttsEntryCount); |
| 1362 | avifRWStreamSetOffset(&s, prevOffset); |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1363 | avifRWStreamFinishBox(&s, stts); |
| 1364 | |
| 1365 | avifBoxMarker stsd = avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1366 | avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count; |
| 1367 | avifBoxMarker av01 = avifRWStreamWriteBox(&s, "av01", AVIF_BOX_SIZE_TBD); |
| 1368 | avifRWStreamWriteZeros(&s, 6); // const unsigned int(8)[6] reserved = 0; |
| 1369 | avifRWStreamWriteU16(&s, 1); // unsigned int(16) data_reference_index; |
| 1370 | avifRWStreamWriteU16(&s, 0); // unsigned int(16) pre_defined = 0; |
| 1371 | avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0; |
| 1372 | avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3); // unsigned int(32)[3] pre_defined = 0; |
| 1373 | avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width); // unsigned int(16) width; |
| 1374 | avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height); // unsigned int(16) height; |
| 1375 | avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) horizresolution |
| 1376 | avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) vertresolution |
| 1377 | avifRWStreamWriteU32(&s, 0); // const unsigned int(32) reserved = 0; |
| 1378 | avifRWStreamWriteU16(&s, 1); // template unsigned int(16) frame_count = 1; |
Wan-Teh Chang | 1a32f22 | 2020-07-09 15:44:25 -0700 | [diff] [blame] | 1379 | avifRWStreamWriteChars(&s, "\012AOM Coding", 11); // string[32] compressorname; |
| 1380 | avifRWStreamWriteZeros(&s, 32 - 11); // |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1381 | avifRWStreamWriteU16(&s, 0x0018); // template unsigned int(16) depth = 0x0018; |
| 1382 | avifRWStreamWriteU16(&s, (uint16_t)0xffff); // int(16) pre_defined = -1; |
Joe Drago | 2172ed0 | 2020-11-04 18:04:44 -0800 | [diff] [blame] | 1383 | writeConfigBox(&s, &item->av1C); |
Joe Drago | 332180d | 2020-06-15 16:55:16 -0700 | [diff] [blame] | 1384 | if (!item->alpha) { |
| 1385 | avifEncoderWriteColorProperties(&s, imageMetadata, NULL, NULL); |
| 1386 | } |
Vignesh Venkatasubramanian | f18f54e | 2022-02-17 14:10:17 -0800 | [diff] [blame] | 1387 | |
| 1388 | avifBoxMarker ccst = avifRWStreamWriteFullBox(&s, "ccst", AVIF_BOX_SIZE_TBD, 0, 0); |
| 1389 | const uint8_t ccstValue = (0 << 7) | // unsigned int(1) all_ref_pics_intra; |
| 1390 | (1 << 6) | // unsigned int(1) intra_pred_used; |
| 1391 | (15 << 2); // unsigned int(4) max_ref_per_pic; |
| 1392 | avifRWStreamWriteU8(&s, ccstValue); |
| 1393 | avifRWStreamWriteZeros(&s, 3); // unsigned int(26) reserved; (two zero bits are written along with ccstValue). |
| 1394 | avifRWStreamFinishBox(&s, ccst); |
| 1395 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1396 | avifRWStreamFinishBox(&s, av01); |
| 1397 | avifRWStreamFinishBox(&s, stsd); |
| 1398 | |
| 1399 | avifRWStreamFinishBox(&s, stbl); |
| 1400 | |
| 1401 | avifRWStreamFinishBox(&s, minf); |
| 1402 | avifRWStreamFinishBox(&s, mdia); |
| 1403 | avifRWStreamFinishBox(&s, trak); |
| 1404 | } |
| 1405 | |
| 1406 | // ------------------------------------------------------------------- |
| 1407 | // Finish moov box |
| 1408 | |
| 1409 | avifRWStreamFinishBox(&s, moov); |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 1410 | } |
| 1411 | |
| 1412 | // ----------------------------------------------------------------------- |
Joe Drago | eb652d8 | 2019-04-23 16:29:07 -0700 | [diff] [blame] | 1413 | // Write mdat |
| 1414 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1415 | encoder->ioStats.colorOBUSize = 0; |
| 1416 | encoder->ioStats.alphaOBUSize = 0; |
| 1417 | |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1418 | avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", AVIF_BOX_SIZE_TBD); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1419 | const size_t mdatStartOffset = avifRWStreamOffset(&s); |
Joe Drago | 76e421e | 2020-09-21 13:20:24 -0700 | [diff] [blame] | 1420 | for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) { |
| 1421 | // Use multiple passes to pack in the following order: |
| 1422 | // * Pass 0: metadata (Exif/XMP) |
| 1423 | // * Pass 1: alpha (AV1) |
| 1424 | // * Pass 2: all other item data (AV1 color) |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1425 | // |
Wan-Teh Chang | 277d0d7 | 2020-10-08 10:24:41 -0700 | [diff] [blame] | 1426 | // See here for the discussion on alpha coming before color: |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1427 | // https://github.com/AOMediaCodec/libavif/issues/287 |
| 1428 | // |
Joe Drago | 76e421e | 2020-09-21 13:20:24 -0700 | [diff] [blame] | 1429 | // Exif and XMP are packed first as they're required to be fully available |
| 1430 | // by avifDecoderParse() before it returns AVIF_RESULT_OK, unless ignoreXMP |
| 1431 | // and ignoreExif are enabled. |
| 1432 | // |
| 1433 | const avifBool metadataPass = (itemPasses == 0); |
| 1434 | const avifBool alphaPass = (itemPasses == 1); |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1435 | |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1436 | for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { |
| 1437 | avifEncoderItem * item = &encoder->data->items.item[itemIndex]; |
Joe Drago | 0e13ce1 | 2021-04-01 17:18:53 -0700 | [diff] [blame] | 1438 | const avifBool isGrid = (item->gridCols > 0); // Grids store their payload in metadataPayload, so use this to distinguish grid payloads from XMP/Exif |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1439 | if ((item->metadataPayload.size == 0) && (item->encodeOutput->samples.count == 0)) { |
Joe Drago | 76e421e | 2020-09-21 13:20:24 -0700 | [diff] [blame] | 1440 | // this item has nothing for the mdat box |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1441 | continue; |
Joe Drago | 2c4e714 | 2020-06-03 10:31:46 -0700 | [diff] [blame] | 1442 | } |
Joe Drago | e568f73 | 2021-03-31 00:13:37 -0700 | [diff] [blame] | 1443 | if (!isGrid && (metadataPass != (item->metadataPayload.size > 0))) { |
Joe Drago | 0e13ce1 | 2021-04-01 17:18:53 -0700 | [diff] [blame] | 1444 | // only process metadata (XMP/Exif) payloads when metadataPass is true |
Joe Drago | 76e421e | 2020-09-21 13:20:24 -0700 | [diff] [blame] | 1445 | continue; |
| 1446 | } |
| 1447 | if (alphaPass != item->alpha) { |
| 1448 | // only process alpha payloads when alphaPass is true |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1449 | continue; |
| 1450 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1451 | |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1452 | size_t chunkOffset = 0; |
| 1453 | |
| 1454 | // Deduplication - See if an identical chunk to this has already been written |
Qiang Zhou | 295cab3 | 2020-12-31 23:20:37 +0800 | [diff] [blame] | 1455 | if (item->encodeOutput->samples.count > 0) { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1456 | avifEncodeSample * sample = &item->encodeOutput->samples.sample[0]; |
| 1457 | chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, sample->data.data, sample->data.size); |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1458 | } else { |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1459 | chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, item->metadataPayload.data, item->metadataPayload.size); |
| 1460 | } |
| 1461 | |
| 1462 | if (!chunkOffset) { |
| 1463 | // We've never seen this chunk before; write it out |
Wan-Teh Chang | 8adbb97 | 2020-12-18 14:12:51 -0800 | [diff] [blame] | 1464 | chunkOffset = avifRWStreamOffset(&s); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1465 | if (item->encodeOutput->samples.count > 0) { |
| 1466 | for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) { |
| 1467 | avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex]; |
| 1468 | avifRWStreamWrite(&s, sample->data.data, sample->data.size); |
| 1469 | |
| 1470 | if (item->alpha) { |
| 1471 | encoder->ioStats.alphaOBUSize += sample->data.size; |
| 1472 | } else { |
| 1473 | encoder->ioStats.colorOBUSize += sample->data.size; |
| 1474 | } |
| 1475 | } |
| 1476 | } else { |
| 1477 | avifRWStreamWrite(&s, item->metadataPayload.data, item->metadataPayload.size); |
| 1478 | } |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1479 | } |
| 1480 | |
| 1481 | for (uint32_t fixupIndex = 0; fixupIndex < item->mdatFixups.count; ++fixupIndex) { |
| 1482 | avifOffsetFixup * fixup = &item->mdatFixups.fixup[fixupIndex]; |
| 1483 | size_t prevOffset = avifRWStreamOffset(&s); |
| 1484 | avifRWStreamSetOffset(&s, fixup->offset); |
Joe Drago | aec9cff | 2020-12-11 14:23:37 -0800 | [diff] [blame] | 1485 | avifRWStreamWriteU32(&s, (uint32_t)chunkOffset); |
Joe Drago | e888d54 | 2020-09-08 20:42:09 -0700 | [diff] [blame] | 1486 | avifRWStreamSetOffset(&s, prevOffset); |
| 1487 | } |
Joe Drago | 800b47f | 2020-03-18 16:22:37 -0700 | [diff] [blame] | 1488 | } |
Joe Drago | f6a4227 | 2019-11-21 15:21:41 -0800 | [diff] [blame] | 1489 | } |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1490 | avifRWStreamFinishBox(&s, mdat); |
Joe Drago | eb652d8 | 2019-04-23 16:29:07 -0700 | [diff] [blame] | 1491 | |
| 1492 | // ----------------------------------------------------------------------- |
| 1493 | // Finish up stream |
| 1494 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1495 | avifRWStreamFinishWrite(&s); |
Joe Drago | 0a5a0e4 | 2019-04-11 11:32:56 -0700 | [diff] [blame] | 1496 | |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 1497 | return AVIF_RESULT_OK; |
| 1498 | } |
Joe Drago | 0b05eee | 2019-06-12 13:24:39 -0700 | [diff] [blame] | 1499 | |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 1500 | avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output) |
| 1501 | { |
Joe Drago | d685024 | 2020-06-16 16:05:37 -0700 | [diff] [blame] | 1502 | avifResult addImageResult = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE); |
Joe Drago | 250221a | 2020-06-01 11:11:06 -0700 | [diff] [blame] | 1503 | if (addImageResult != AVIF_RESULT_OK) { |
| 1504 | return addImageResult; |
| 1505 | } |
| 1506 | return avifEncoderFinish(encoder, output); |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1507 | } |
| 1508 | |
Wan-Teh Chang | e184dc1 | 2020-05-11 12:47:21 -0700 | [diff] [blame] | 1509 | static avifBool avifImageIsOpaque(const avifImage * image) |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1510 | { |
Joe Drago | 7ad3ad6 | 2019-02-07 11:17:34 -0800 | [diff] [blame] | 1511 | if (!image->alphaPlane) { |
| 1512 | return AVIF_TRUE; |
| 1513 | } |
| 1514 | |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1515 | int maxChannel = (1 << image->depth) - 1; |
Joe Drago | 7ad3ad6 | 2019-02-07 11:17:34 -0800 | [diff] [blame] | 1516 | if (avifImageUsesU16(image)) { |
Joe Drago | 341a6a6 | 2019-07-23 16:12:59 -0700 | [diff] [blame] | 1517 | for (uint32_t j = 0; j < image->height; ++j) { |
| 1518 | for (uint32_t i = 0; i < image->width; ++i) { |
Joe Drago | 7ad3ad6 | 2019-02-07 11:17:34 -0800 | [diff] [blame] | 1519 | uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)]; |
| 1520 | if (*p != maxChannel) { |
| 1521 | return AVIF_FALSE; |
| 1522 | } |
| 1523 | } |
| 1524 | } |
| 1525 | } else { |
Joe Drago | 341a6a6 | 2019-07-23 16:12:59 -0700 | [diff] [blame] | 1526 | for (uint32_t j = 0; j < image->height; ++j) { |
| 1527 | for (uint32_t i = 0; i < image->width; ++i) { |
Joe Drago | 7ad3ad6 | 2019-02-07 11:17:34 -0800 | [diff] [blame] | 1528 | if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) { |
| 1529 | return AVIF_FALSE; |
| 1530 | } |
Joe Drago | 444f051 | 2019-01-23 17:03:24 -0800 | [diff] [blame] | 1531 | } |
| 1532 | } |
| 1533 | } |
| 1534 | return AVIF_TRUE; |
| 1535 | } |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1536 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1537 | static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg) |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1538 | { |
Joe Drago | 4a25c19 | 2020-06-03 16:29:58 -0700 | [diff] [blame] | 1539 | avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", AVIF_BOX_SIZE_TBD); |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1540 | |
| 1541 | // unsigned int (1) marker = 1; |
| 1542 | // unsigned int (7) version = 1; |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1543 | avifRWStreamWriteU8(s, 0x80 | 0x1); |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1544 | |
| 1545 | // unsigned int (3) seq_profile; |
| 1546 | // unsigned int (5) seq_level_idx_0; |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1547 | avifRWStreamWriteU8(s, (uint8_t)((cfg->seqProfile & 0x7) << 5) | (uint8_t)(cfg->seqLevelIdx0 & 0x1f)); |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1548 | |
| 1549 | uint8_t bits = 0; |
| 1550 | bits |= (cfg->seqTier0 & 0x1) << 7; // unsigned int (1) seq_tier_0; |
| 1551 | bits |= (cfg->highBitdepth & 0x1) << 6; // unsigned int (1) high_bitdepth; |
| 1552 | bits |= (cfg->twelveBit & 0x1) << 5; // unsigned int (1) twelve_bit; |
| 1553 | bits |= (cfg->monochrome & 0x1) << 4; // unsigned int (1) monochrome; |
| 1554 | bits |= (cfg->chromaSubsamplingX & 0x1) << 3; // unsigned int (1) chroma_subsampling_x; |
| 1555 | bits |= (cfg->chromaSubsamplingY & 0x1) << 2; // unsigned int (1) chroma_subsampling_y; |
| 1556 | bits |= (cfg->chromaSamplePosition & 0x3); // unsigned int (2) chroma_sample_position; |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1557 | avifRWStreamWriteU8(s, bits); |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1558 | |
| 1559 | // unsigned int (3) reserved = 0; |
| 1560 | // unsigned int (1) initial_presentation_delay_present; |
| 1561 | // if (initial_presentation_delay_present) { |
| 1562 | // unsigned int (4) initial_presentation_delay_minus_one; |
| 1563 | // } else { |
| 1564 | // unsigned int (4) reserved = 0; |
| 1565 | // } |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1566 | avifRWStreamWriteU8(s, 0); |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1567 | |
Joe Drago | 345aaa1 | 2019-09-25 13:42:12 -0700 | [diff] [blame] | 1568 | avifRWStreamFinishBox(s, av1C); |
Joe Drago | 1585797 | 2019-02-13 17:48:28 -0800 | [diff] [blame] | 1569 | } |