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'.
static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem,
Frame* const frame) {
static ParseStatus StoreFrame(int frame_num, uint32_t min_size,
MemBuffer* const mem, Frame* const frame) {
int alpha_chunks = 0;
int image_chunks = 0;
int done = (MemDataSize(mem) < CHUNK_HEADER_SIZE);
int done = (MemDataSize(mem) < min_size);
ParseStatus status = PARSE_OK;
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_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
static ParseStatus NewFrame(const MemBuffer* const mem,
uint32_t min_size, uint32_t expected_size,
uint32_t actual_size, Frame** frame) {
uint32_t min_size, uint32_t actual_size,
Frame** frame) {
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;
*frame = (Frame*)calloc(1, sizeof(**frame));
@ -290,12 +290,14 @@ static ParseStatus NewFrame(const MemBuffer* const mem,
static ParseStatus ParseFrame(
WebPDemuxer* const dmux, uint32_t frame_chunk_size) {
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;
MemBuffer* const mem = &dmux->mem_;
Frame* frame;
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;
frame->x_offset_ = 2 * GetLE24s(mem);
@ -303,14 +305,14 @@ static ParseStatus ParseFrame(
frame->width_ = 1 + GetLE24s(mem);
frame->height_ = 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) {
return PARSE_ERROR;
}
// Store a (potentially partial) frame only if the animation flag is set
// and there is some data in 'frame'.
status = StoreFrame(dmux->num_frames_ + 1, mem, frame);
// Store a frame only if the animation flag is set and all data for this frame
// is available.
status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame);
if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) {
added_frame = AddFrame(dmux, frame);
if (added_frame) {
@ -329,22 +331,24 @@ static ParseStatus ParseFrame(
static ParseStatus ParseTile(WebPDemuxer* const dmux,
uint32_t tile_chunk_size) {
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;
MemBuffer* const mem = &dmux->mem_;
Frame* frame;
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;
frame->is_tile_ = 1;
frame->x_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
// and the tile contains some data.
status = StoreFrame(dmux->num_frames_, mem, frame);
// Store a tile only if the tile flag is set and all data for this tile
// is available.
status = StoreFrame(dmux->num_frames_, frgm_payload_size, mem, frame);
if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) {
// Note num_frames_ is incremented only when all tiles have been consumed.
added_tile = AddFrame(dmux, frame);
@ -411,7 +415,9 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) {
frame = (Frame*)calloc(1, sizeof(*frame));
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) {
const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG);
// Clear any alpha when the alpha flag is missing.

View File

@ -399,13 +399,32 @@ size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) {
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) {
// Ordering of chunks to be emitted is strictly as follows:
// 1. ANMF/FRGM chunk (if present).
// 2. ALPH chunk (if present).
// 3. VP8/VP8L chunk.
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->img_ != NULL) dst = ChunkEmit(wpi->img_, 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
// verifications.
static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
const uint8_t* data,
size_t data_size, size_t riff_size,
int copy_data) {
static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
const uint8_t* data, size_t data_size,
size_t riff_size, int copy_data) {
uint32_t chunk_size;
WebPData chunk_data;
@ -74,6 +73,66 @@ static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
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.
@ -136,42 +195,46 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
// Loop over chunks.
while (data != end) {
size_t data_size;
WebPChunkId id;
WebPMuxError err;
err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data);
if (err != WEBP_MUX_OK) goto Err;
WebPChunk** chunk_list;
if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
copy_data) != WEBP_MUX_OK) {
goto Err;
}
data_size = ChunkDiskSize(&chunk);
id = ChunkGetIdFromTag(chunk.tag_);
if (IsWPI(id)) { // An image chunk (frame/tile/alpha/vp8).
WebPChunk** wpi_chunk_ptr =
MuxImageGetListFromId(wpi, id); // Image chunk to set.
assert(wpi_chunk_ptr != NULL);
if (*wpi_chunk_ptr != NULL) goto Err; // Consecutive alpha chunks or
// consecutive frame/tile chunks.
if (ChunkSetNth(&chunk, wpi_chunk_ptr, 1) != WEBP_MUX_OK) goto Err;
if (id == WEBP_CHUNK_IMAGE) {
switch (id) {
case WEBP_CHUNK_ALPHA:
if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
break;
case WEBP_CHUNK_IMAGE:
if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
wpi->is_partial_ = 0; // wpi is completely filled.
PushImage:
// Add this to mux->images_ list.
if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
MuxImageInit(wpi); // Reset for reading next image.
} else {
wpi->is_partial_ = 1; // wpi is only partially filled.
}
} else { // A non-image chunk.
WebPChunk** chunk_list;
break;
case WEBP_CHUNK_ANMF:
case WEBP_CHUNK_FRGM:
if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete.
if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
ChunkRelease(&chunk);
goto PushImage;
break;
default: // A non-image chunk.
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.
if (chunk_list == NULL) chunk_list = &mux->unknown_;
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
break;
}
{
const size_t data_size = ChunkDiskSize(&chunk);
data += data_size;
size -= data_size;
}
ChunkInit(&chunk);
}