1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the COPYING file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 // Read APIs for mux.
11 //
12 // Authors: Urvang (urvang@google.com)
13 // Vikas (vikasa@google.com)
14
15 #include <assert.h>
16 #include "./muxi.h"
17 #include "../utils/utils.h"
18
19 #if defined(__cplusplus) || defined(c_plusplus)
20 extern "C" {
21 #endif
22
23 //------------------------------------------------------------------------------
24 // Helper method(s).
25
26 // Handy MACRO.
27 #define SWITCH_ID_LIST(INDEX, LIST) \
28 if (idx == (INDEX)) { \
29 const WebPChunk* const chunk = ChunkSearchList((LIST), nth, \
30 kChunks[(INDEX)].tag); \
31 if (chunk) { \
32 *data = chunk->data_; \
33 return WEBP_MUX_OK; \
34 } else { \
35 return WEBP_MUX_NOT_FOUND; \
36 } \
37 }
38
MuxGet(const WebPMux * const mux,CHUNK_INDEX idx,uint32_t nth,WebPData * const data)39 static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
40 uint32_t nth, WebPData* const data) {
41 assert(mux != NULL);
42 assert(!IsWPI(kChunks[idx].id));
43 WebPDataInit(data);
44
45 SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
46 SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
47 SWITCH_ID_LIST(IDX_ANIM, mux->anim_);
48 SWITCH_ID_LIST(IDX_EXIF, mux->exif_);
49 SWITCH_ID_LIST(IDX_XMP, mux->xmp_);
50 SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
51 return WEBP_MUX_NOT_FOUND;
52 }
53 #undef SWITCH_ID_LIST
54
55 // Fill the chunk with the given data (includes chunk header bytes), after some
56 // verifications.
ChunkVerifyAndAssign(WebPChunk * chunk,const uint8_t * data,size_t data_size,size_t riff_size,int copy_data)57 static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
58 const uint8_t* data, size_t data_size,
59 size_t riff_size, int copy_data) {
60 uint32_t chunk_size;
61 WebPData chunk_data;
62
63 // Sanity checks.
64 if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;
65 chunk_size = GetLE32(data + TAG_SIZE);
66
67 {
68 const size_t chunk_disk_size = SizeWithPadding(chunk_size);
69 if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;
70 if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;
71 }
72
73 // Data assignment.
74 chunk_data.bytes = data + CHUNK_HEADER_SIZE;
75 chunk_data.size = chunk_size;
76 return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
77 }
78
MuxImageParse(const WebPChunk * const chunk,int copy_data,WebPMuxImage * const wpi)79 static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
80 WebPMuxImage* const wpi) {
81 const uint8_t* bytes = chunk->data_.bytes;
82 size_t size = chunk->data_.size;
83 const uint8_t* const last = bytes + size;
84 WebPChunk subchunk;
85 size_t subchunk_size;
86 ChunkInit(&subchunk);
87
88 assert(chunk->tag_ == kChunks[IDX_ANMF].tag ||
89 chunk->tag_ == kChunks[IDX_FRGM].tag);
90 assert(!wpi->is_partial_);
91
92 // ANMF/FRGM.
93 {
94 const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ?
95 ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
96 const WebPData temp = { bytes, hdr_size };
97 // Each of ANMF and FRGM chunk contain a header at the beginning. So, its
98 // size should at least be 'hdr_size'.
99 if (size < hdr_size) goto Fail;
100 ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
101 }
102 ChunkSetNth(&subchunk, &wpi->header_, 1);
103 wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks.
104
105 // Rest of the chunks.
106 subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
107 bytes += subchunk_size;
108 size -= subchunk_size;
109
110 while (bytes != last) {
111 ChunkInit(&subchunk);
112 if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
113 copy_data) != WEBP_MUX_OK) {
114 goto Fail;
115 }
116 switch (ChunkGetIdFromTag(subchunk.tag_)) {
117 case WEBP_CHUNK_ALPHA:
118 if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks.
119 if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
120 wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
121 break;
122 case WEBP_CHUNK_IMAGE:
123 if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
124 wpi->is_partial_ = 0; // wpi is completely filled.
125 break;
126 default:
127 goto Fail;
128 break;
129 }
130 subchunk_size = ChunkDiskSize(&subchunk);
131 bytes += subchunk_size;
132 size -= subchunk_size;
133 }
134 if (wpi->is_partial_) goto Fail;
135 return 1;
136
137 Fail:
138 ChunkRelease(&subchunk);
139 return 0;
140 }
141
142 //------------------------------------------------------------------------------
143 // Create a mux object from WebP-RIFF data.
144
WebPMuxCreateInternal(const WebPData * bitstream,int copy_data,int version)145 WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
146 int version) {
147 size_t riff_size;
148 uint32_t tag;
149 const uint8_t* end;
150 WebPMux* mux = NULL;
151 WebPMuxImage* wpi = NULL;
152 const uint8_t* data;
153 size_t size;
154 WebPChunk chunk;
155 ChunkInit(&chunk);
156
157 // Sanity checks.
158 if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
159 return NULL; // version mismatch
160 }
161 if (bitstream == NULL) return NULL;
162
163 data = bitstream->bytes;
164 size = bitstream->size;
165
166 if (data == NULL) return NULL;
167 if (size < RIFF_HEADER_SIZE) return NULL;
168 if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
169 GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
170 return NULL;
171 }
172
173 mux = WebPMuxNew();
174 if (mux == NULL) return NULL;
175
176 if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
177
178 tag = GetLE32(data + RIFF_HEADER_SIZE);
179 if (tag != kChunks[IDX_VP8].tag &&
180 tag != kChunks[IDX_VP8L].tag &&
181 tag != kChunks[IDX_VP8X].tag) {
182 goto Err; // First chunk should be VP8, VP8L or VP8X.
183 }
184
185 riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
186 if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
187 goto Err;
188 } else {
189 if (riff_size < size) { // Redundant data after last chunk.
190 size = riff_size; // To make sure we don't read any data beyond mux_size.
191 }
192 }
193
194 end = data + size;
195 data += RIFF_HEADER_SIZE;
196 size -= RIFF_HEADER_SIZE;
197
198 wpi = (WebPMuxImage*)malloc(sizeof(*wpi));
199 if (wpi == NULL) goto Err;
200 MuxImageInit(wpi);
201
202 // Loop over chunks.
203 while (data != end) {
204 size_t data_size;
205 WebPChunkId id;
206 WebPChunk** chunk_list;
207 if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
208 copy_data) != WEBP_MUX_OK) {
209 goto Err;
210 }
211 data_size = ChunkDiskSize(&chunk);
212 id = ChunkGetIdFromTag(chunk.tag_);
213 switch (id) {
214 case WEBP_CHUNK_ALPHA:
215 if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
216 if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
217 wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
218 break;
219 case WEBP_CHUNK_IMAGE:
220 if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
221 wpi->is_partial_ = 0; // wpi is completely filled.
222 PushImage:
223 // Add this to mux->images_ list.
224 if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
225 MuxImageInit(wpi); // Reset for reading next image.
226 break;
227 case WEBP_CHUNK_ANMF:
228 #ifdef WEBP_EXPERIMENTAL_FEATURES
229 case WEBP_CHUNK_FRGM:
230 #endif
231 if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete.
232 if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
233 ChunkRelease(&chunk);
234 goto PushImage;
235 break;
236 default: // A non-image chunk.
237 if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before
238 // getting all chunks of an image.
239 chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk.
240 if (chunk_list == NULL) chunk_list = &mux->unknown_;
241 if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
242 break;
243 }
244 data += data_size;
245 size -= data_size;
246 ChunkInit(&chunk);
247 }
248
249 // Validate mux if complete.
250 if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
251
252 MuxImageDelete(wpi);
253 return mux; // All OK;
254
255 Err: // Something bad happened.
256 ChunkRelease(&chunk);
257 MuxImageDelete(wpi);
258 WebPMuxDelete(mux);
259 return NULL;
260 }
261
262 //------------------------------------------------------------------------------
263 // Get API(s).
264
WebPMuxGetFeatures(const WebPMux * mux,uint32_t * flags)265 WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {
266 WebPData data;
267
268 if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;
269 *flags = 0;
270
271 // Check if VP8X chunk is present.
272 if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
273 if (data.size < CHUNK_SIZE_BYTES) return WEBP_MUX_BAD_DATA;
274 *flags = GetLE32(data.bytes); // All OK. Fill up flags.
275 } else {
276 WebPMuxError err = MuxValidateForImage(mux); // Check for single image.
277 if (err != WEBP_MUX_OK) return err;
278 if (MuxHasLosslessImages(mux->images_)) {
279 const WebPData* const vp8l_data = &mux->images_->img_->data_;
280 int has_alpha = 0;
281 if (!VP8LGetInfo(vp8l_data->bytes, vp8l_data->size, NULL, NULL,
282 &has_alpha)) {
283 return WEBP_MUX_BAD_DATA;
284 }
285 if (has_alpha) {
286 *flags = ALPHA_FLAG;
287 }
288 }
289 }
290
291 return WEBP_MUX_OK;
292 }
293
EmitVP8XChunk(uint8_t * const dst,int width,int height,uint32_t flags)294 static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,
295 int height, uint32_t flags) {
296 const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
297 assert(width >= 1 && height >= 1);
298 assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
299 assert(width * (uint64_t)height < MAX_IMAGE_AREA);
300 PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
301 PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
302 PutLE32(dst + CHUNK_HEADER_SIZE, flags);
303 PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
304 PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
305 return dst + vp8x_size;
306 }
307
308 // Assemble a single image WebP bitstream from 'wpi'.
SynthesizeBitstream(const WebPMuxImage * const wpi,WebPData * const bitstream)309 static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
310 WebPData* const bitstream) {
311 uint8_t* dst;
312
313 // Allocate data.
314 const int need_vp8x = (wpi->alpha_ != NULL);
315 const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
316 const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
317 // Note: No need to output ANMF/FRGM chunk for a single image.
318 const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +
319 ChunkDiskSize(wpi->img_);
320 uint8_t* const data = (uint8_t*)malloc(size);
321 if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
322
323 // Main RIFF header.
324 dst = MuxEmitRiffHeader(data, size);
325
326 if (need_vp8x) {
327 int w, h;
328 WebPMuxError err;
329 assert(wpi->img_ != NULL);
330 err = MuxGetImageWidthHeight(wpi->img_, &w, &h);
331 if (err != WEBP_MUX_OK) {
332 free(data);
333 return err;
334 }
335 dst = EmitVP8XChunk(dst, w, h, ALPHA_FLAG); // VP8X.
336 dst = ChunkListEmit(wpi->alpha_, dst); // ALPH.
337 }
338
339 // Bitstream.
340 dst = ChunkListEmit(wpi->img_, dst);
341 assert(dst == data + size);
342
343 // Output.
344 bitstream->bytes = data;
345 bitstream->size = size;
346 return WEBP_MUX_OK;
347 }
348
WebPMuxGetChunk(const WebPMux * mux,const char fourcc[4],WebPData * chunk_data)349 WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
350 WebPData* chunk_data) {
351 CHUNK_INDEX idx;
352 if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
353 return WEBP_MUX_INVALID_ARGUMENT;
354 }
355 idx = ChunkGetIndexFromFourCC(fourcc);
356 if (IsWPI(kChunks[idx].id)) { // An image chunk.
357 return WEBP_MUX_INVALID_ARGUMENT;
358 } else if (idx != IDX_UNKNOWN) { // A known chunk type.
359 return MuxGet(mux, idx, 1, chunk_data);
360 } else { // An unknown chunk type.
361 const WebPChunk* const chunk =
362 ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));
363 if (chunk == NULL) return WEBP_MUX_NOT_FOUND;
364 *chunk_data = chunk->data_;
365 return WEBP_MUX_OK;
366 }
367 }
368
MuxGetImageInternal(const WebPMuxImage * const wpi,WebPMuxFrameInfo * const info)369 static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
370 WebPMuxFrameInfo* const info) {
371 // Set some defaults for unrelated fields.
372 info->x_offset = 0;
373 info->y_offset = 0;
374 info->duration = 1;
375 // Extract data for related fields.
376 info->id = ChunkGetIdFromTag(wpi->img_->tag_);
377 return SynthesizeBitstream(wpi, &info->bitstream);
378 }
379
MuxGetFrameFragmentInternal(const WebPMuxImage * const wpi,WebPMuxFrameInfo * const frame)380 static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi,
381 WebPMuxFrameInfo* const frame) {
382 const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
383 const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM;
384 const WebPData* frame_frgm_data;
385 #ifndef WEBP_EXPERIMENTAL_FEATURES
386 if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;
387 #endif
388 assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame().
389 // Get frame/fragment chunk.
390 frame_frgm_data = &wpi->header_->data_;
391 if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA;
392 // Extract info.
393 frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0);
394 frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3);
395 frame->duration = is_frame ? GetLE24(frame_frgm_data->bytes + 12) : 1;
396 frame->dispose_method =
397 is_frame ? (WebPMuxAnimDispose)(frame_frgm_data->bytes[15] & 1)
398 : WEBP_MUX_DISPOSE_NONE;
399 frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
400 return SynthesizeBitstream(wpi, &frame->bitstream);
401 }
402
WebPMuxGetFrame(const WebPMux * mux,uint32_t nth,WebPMuxFrameInfo * frame)403 WebPMuxError WebPMuxGetFrame(
404 const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {
405 WebPMuxError err;
406 WebPMuxImage* wpi;
407
408 // Sanity checks.
409 if (mux == NULL || frame == NULL) {
410 return WEBP_MUX_INVALID_ARGUMENT;
411 }
412
413 // Get the nth WebPMuxImage.
414 err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);
415 if (err != WEBP_MUX_OK) return err;
416
417 // Get frame info.
418 if (wpi->header_ == NULL) {
419 return MuxGetImageInternal(wpi, frame);
420 } else {
421 return MuxGetFrameFragmentInternal(wpi, frame);
422 }
423 }
424
WebPMuxGetAnimationParams(const WebPMux * mux,WebPMuxAnimParams * params)425 WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
426 WebPMuxAnimParams* params) {
427 WebPData anim;
428 WebPMuxError err;
429
430 if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
431
432 err = MuxGet(mux, IDX_ANIM, 1, &anim);
433 if (err != WEBP_MUX_OK) return err;
434 if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
435 params->bgcolor = GetLE32(anim.bytes);
436 params->loop_count = GetLE16(anim.bytes + 4);
437
438 return WEBP_MUX_OK;
439 }
440
441 // Get chunk index from chunk id. Returns IDX_NIL if not found.
ChunkGetIndexFromId(WebPChunkId id)442 static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {
443 int i;
444 for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {
445 if (id == kChunks[i].id) return i;
446 }
447 return IDX_NIL;
448 }
449
450 // Count number of chunks matching 'tag' in the 'chunk_list'.
451 // If tag == NIL_TAG, any tag will be matched.
CountChunks(const WebPChunk * const chunk_list,uint32_t tag)452 static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {
453 int count = 0;
454 const WebPChunk* current;
455 for (current = chunk_list; current != NULL; current = current->next_) {
456 if (tag == NIL_TAG || current->tag_ == tag) {
457 count++; // Count chunks whose tags match.
458 }
459 }
460 return count;
461 }
462
WebPMuxNumChunks(const WebPMux * mux,WebPChunkId id,int * num_elements)463 WebPMuxError WebPMuxNumChunks(const WebPMux* mux,
464 WebPChunkId id, int* num_elements) {
465 if (mux == NULL || num_elements == NULL) {
466 return WEBP_MUX_INVALID_ARGUMENT;
467 }
468
469 if (IsWPI(id)) {
470 *num_elements = MuxImageCount(mux->images_, id);
471 } else {
472 WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);
473 if (chunk_list == NULL) {
474 *num_elements = 0;
475 } else {
476 const CHUNK_INDEX idx = ChunkGetIndexFromId(id);
477 *num_elements = CountChunks(*chunk_list, kChunks[idx].tag);
478 }
479 }
480
481 return WEBP_MUX_OK;
482 }
483
484 //------------------------------------------------------------------------------
485
486 #if defined(__cplusplus) || defined(c_plusplus)
487 } // extern "C"
488 #endif
489