Design change in ANMF and FRGM chunks:

- Make ANMF and FRGM chunks hierarchical so that they encompass all chunks of
  that frame.
- Use this in demuxer: stop parsing a frame if all image data for it isn't
  available yet. Thus, we have a frame-level incremental support; that is,
  all frames that are fully available can be parsed.
- Note: We still keep incremental support for single images - so that they can
  be decoded with incremental decoding.

Change-Id: Id1585b16b06caee1d84009c42a25d2de29fa6135
This commit is contained in:
Urvang Joshi 2012-10-31 17:19:31 -07:00
parent f903cbab9a
commit 81b8a741ed
3 changed files with 141 additions and 53 deletions

View File

@ -194,11 +194,11 @@ static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) {
} }
// Store image bearing chunks to 'frame'. // Store image bearing chunks to 'frame'.
static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem, static ParseStatus StoreFrame(int frame_num, uint32_t min_size,
Frame* const frame) { MemBuffer* const mem, Frame* const frame) {
int alpha_chunks = 0; int alpha_chunks = 0;
int image_chunks = 0; int image_chunks = 0;
int done = (MemDataSize(mem) < CHUNK_HEADER_SIZE); int done = (MemDataSize(mem) < min_size);
ParseStatus status = PARSE_OK; ParseStatus status = PARSE_OK;
if (done) return PARSE_NEED_MORE_DATA; if (done) return PARSE_NEED_MORE_DATA;
@ -275,10 +275,10 @@ static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem,
// Returns PARSE_OK on success with *frame pointing to the new Frame. // Returns PARSE_OK on success with *frame pointing to the new Frame.
// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise. // Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
static ParseStatus NewFrame(const MemBuffer* const mem, static ParseStatus NewFrame(const MemBuffer* const mem,
uint32_t min_size, uint32_t expected_size, uint32_t min_size, uint32_t actual_size,
uint32_t actual_size, Frame** frame) { Frame** frame) {
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR; if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
if (actual_size < expected_size) return PARSE_ERROR; if (actual_size < min_size) return PARSE_ERROR;
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
*frame = (Frame*)calloc(1, sizeof(**frame)); *frame = (Frame*)calloc(1, sizeof(**frame));
@ -290,12 +290,14 @@ static ParseStatus NewFrame(const MemBuffer* const mem,
static ParseStatus ParseFrame( static ParseStatus ParseFrame(
WebPDemuxer* const dmux, uint32_t frame_chunk_size) { WebPDemuxer* const dmux, uint32_t frame_chunk_size) {
const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG);
const uint32_t min_size = frame_chunk_size + CHUNK_HEADER_SIZE; const uint32_t padding = (ANMF_CHUNK_SIZE & 1);
const uint32_t anmf_payload_size =
frame_chunk_size - (ANMF_CHUNK_SIZE + padding);
int added_frame = 0; int added_frame = 0;
MemBuffer* const mem = &dmux->mem_; MemBuffer* const mem = &dmux->mem_;
Frame* frame; Frame* frame;
ParseStatus status = ParseStatus status =
NewFrame(mem, min_size, ANMF_CHUNK_SIZE, frame_chunk_size, &frame); NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame);
if (status != PARSE_OK) return status; if (status != PARSE_OK) return status;
frame->x_offset_ = 2 * GetLE24s(mem); frame->x_offset_ = 2 * GetLE24s(mem);
@ -303,14 +305,14 @@ static ParseStatus ParseFrame(
frame->width_ = 1 + GetLE24s(mem); frame->width_ = 1 + GetLE24s(mem);
frame->height_ = 1 + GetLE24s(mem); frame->height_ = 1 + GetLE24s(mem);
frame->duration_ = 1 + GetLE24s(mem); frame->duration_ = 1 + GetLE24s(mem);
Skip(mem, frame_chunk_size - ANMF_CHUNK_SIZE); // skip any trailing data. Skip(mem, padding);
if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) { if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) {
return PARSE_ERROR; return PARSE_ERROR;
} }
// Store a (potentially partial) frame only if the animation flag is set // Store a frame only if the animation flag is set and all data for this frame
// and there is some data in 'frame'. // is available.
status = StoreFrame(dmux->num_frames_ + 1, mem, frame); status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame);
if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) { if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) {
added_frame = AddFrame(dmux, frame); added_frame = AddFrame(dmux, frame);
if (added_frame) { if (added_frame) {
@ -329,22 +331,24 @@ static ParseStatus ParseFrame(
static ParseStatus ParseTile(WebPDemuxer* const dmux, static ParseStatus ParseTile(WebPDemuxer* const dmux,
uint32_t tile_chunk_size) { uint32_t tile_chunk_size) {
const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG); const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG);
const uint32_t min_size = tile_chunk_size + CHUNK_HEADER_SIZE; const uint32_t padding = (FRGM_CHUNK_SIZE & 1);
const uint32_t frgm_payload_size =
tile_chunk_size - (FRGM_CHUNK_SIZE + padding);
int added_tile = 0; int added_tile = 0;
MemBuffer* const mem = &dmux->mem_; MemBuffer* const mem = &dmux->mem_;
Frame* frame; Frame* frame;
ParseStatus status = ParseStatus status =
NewFrame(mem, min_size, FRGM_CHUNK_SIZE, tile_chunk_size, &frame); NewFrame(mem, FRGM_CHUNK_SIZE, tile_chunk_size, &frame);
if (status != PARSE_OK) return status; if (status != PARSE_OK) return status;
frame->is_tile_ = 1; frame->is_tile_ = 1;
frame->x_offset_ = 2 * GetLE24s(mem); frame->x_offset_ = 2 * GetLE24s(mem);
frame->y_offset_ = 2 * GetLE24s(mem); frame->y_offset_ = 2 * GetLE24s(mem);
Skip(mem, tile_chunk_size - FRGM_CHUNK_SIZE); // skip any trailing data. Skip(mem, padding);
// Store a (potentially partial) tile only if the tile flag is set // Store a tile only if the tile flag is set and all data for this tile
// and the tile contains some data. // is available.
status = StoreFrame(dmux->num_frames_, mem, frame); status = StoreFrame(dmux->num_frames_, frgm_payload_size, mem, frame);
if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) { if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) {
// Note num_frames_ is incremented only when all tiles have been consumed. // Note num_frames_ is incremented only when all tiles have been consumed.
added_tile = AddFrame(dmux, frame); added_tile = AddFrame(dmux, frame);
@ -411,7 +415,9 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) {
frame = (Frame*)calloc(1, sizeof(*frame)); frame = (Frame*)calloc(1, sizeof(*frame));
if (frame == NULL) return PARSE_ERROR; if (frame == NULL) return PARSE_ERROR;
status = StoreFrame(1, &dmux->mem_, frame); // For the single image case, we allow parsing of a partial frame. But we need
// at least CHUNK_HEADER_SIZE for parsing.
status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame);
if (status != PARSE_ERROR) { if (status != PARSE_ERROR) {
const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG); const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG);
// Clear any alpha when the alpha flag is missing. // Clear any alpha when the alpha flag is missing.

View File

@ -399,13 +399,32 @@ size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) {
return size; return size;
} }
// Special case as ANMF/FRGM chunk encapsulates other image chunks.
static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
size_t total_size, uint8_t* dst) {
const size_t header_size = header->data_.size;
const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
assert(header->tag_ == kChunks[IDX_ANMF].tag ||
header->tag_ == kChunks[IDX_FRGM].tag);
PutLE32(dst + 0, header->tag_);
PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
assert(header_size == (uint32_t)header_size);
memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
if (header_size & 1) {
dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.
}
return dst + ChunkDiskSize(header);
}
uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) { uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
// Ordering of chunks to be emitted is strictly as follows: // Ordering of chunks to be emitted is strictly as follows:
// 1. ANMF/FRGM chunk (if present). // 1. ANMF/FRGM chunk (if present).
// 2. ALPH chunk (if present). // 2. ALPH chunk (if present).
// 3. VP8/VP8L chunk. // 3. VP8/VP8L chunk.
assert(wpi); assert(wpi);
if (wpi->header_ != NULL) dst = ChunkEmit(wpi->header_, dst); if (wpi->header_ != NULL) {
dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
}
if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst); if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst); if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
return dst; return dst;

View File

@ -51,10 +51,9 @@ static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
// Fill the chunk with the given data (includes chunk header bytes), after some // Fill the chunk with the given data (includes chunk header bytes), after some
// verifications. // verifications.
static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk, static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
const uint8_t* data, const uint8_t* data, size_t data_size,
size_t data_size, size_t riff_size, size_t riff_size, int copy_data) {
int copy_data) {
uint32_t chunk_size; uint32_t chunk_size;
WebPData chunk_data; WebPData chunk_data;
@ -74,6 +73,66 @@ static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0)); return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
} }
static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
WebPMuxImage* const wpi) {
const uint8_t* bytes = chunk->data_.bytes;
size_t size = chunk->data_.size;
const uint8_t* const last = bytes + size;
WebPChunk subchunk;
size_t subchunk_size;
ChunkInit(&subchunk);
assert(chunk->tag_ == kChunks[IDX_ANMF].tag ||
chunk->tag_ == kChunks[IDX_FRGM].tag);
assert(!wpi->is_partial_);
// ANMF/FRGM.
{
const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ?
ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
const WebPData temp = { bytes, hdr_size };
ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
}
ChunkSetNth(&subchunk, &wpi->header_, 1);
wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks.
// Rest of the chunks.
subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
bytes += subchunk_size;
size -= subchunk_size;
while (bytes != last) {
ChunkInit(&subchunk);
if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
copy_data) != WEBP_MUX_OK) {
goto Fail;
}
switch (ChunkGetIdFromTag(subchunk.tag_)) {
case WEBP_CHUNK_ALPHA:
if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks.
if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
break;
case WEBP_CHUNK_IMAGE:
if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
wpi->is_partial_ = 0; // wpi is completely filled.
break;
default:
goto Fail;
break;
}
subchunk_size = ChunkDiskSize(&subchunk);
bytes += subchunk_size;
size -= subchunk_size;
}
if (wpi->is_partial_) goto Fail;
return 1;
Fail:
ChunkRelease(&subchunk);
return 0;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Create a mux object from WebP-RIFF data. // Create a mux object from WebP-RIFF data.
@ -136,42 +195,46 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
// Loop over chunks. // Loop over chunks.
while (data != end) { while (data != end) {
size_t data_size;
WebPChunkId id; WebPChunkId id;
WebPMuxError err; WebPChunk** chunk_list;
if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data); copy_data) != WEBP_MUX_OK) {
if (err != WEBP_MUX_OK) goto Err; goto Err;
}
data_size = ChunkDiskSize(&chunk);
id = ChunkGetIdFromTag(chunk.tag_); id = ChunkGetIdFromTag(chunk.tag_);
switch (id) {
if (IsWPI(id)) { // An image chunk (frame/tile/alpha/vp8). case WEBP_CHUNK_ALPHA:
WebPChunk** wpi_chunk_ptr = if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
MuxImageGetListFromId(wpi, id); // Image chunk to set. if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
assert(wpi_chunk_ptr != NULL); wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
if (*wpi_chunk_ptr != NULL) goto Err; // Consecutive alpha chunks or break;
// consecutive frame/tile chunks. case WEBP_CHUNK_IMAGE:
if (ChunkSetNth(&chunk, wpi_chunk_ptr, 1) != WEBP_MUX_OK) goto Err; if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
if (id == WEBP_CHUNK_IMAGE) {
wpi->is_partial_ = 0; // wpi is completely filled. wpi->is_partial_ = 0; // wpi is completely filled.
PushImage:
// Add this to mux->images_ list. // Add this to mux->images_ list.
if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err; if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
MuxImageInit(wpi); // Reset for reading next image. MuxImageInit(wpi); // Reset for reading next image.
} else { break;
wpi->is_partial_ = 1; // wpi is only partially filled. case WEBP_CHUNK_ANMF:
} case WEBP_CHUNK_FRGM:
} else { // A non-image chunk. if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete.
WebPChunk** chunk_list; if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before ChunkRelease(&chunk);
// getting all chunks of an image. goto PushImage;
chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. break;
if (chunk_list == NULL) chunk_list = &mux->unknown_; default: // A non-image chunk.
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before
} // getting all chunks of an image.
{ chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk.
const size_t data_size = ChunkDiskSize(&chunk); if (chunk_list == NULL) chunk_list = &mux->unknown_;
data += data_size; if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
size -= data_size; break;
} }
data += data_size;
size -= data_size;
ChunkInit(&chunk); ChunkInit(&chunk);
} }