Chromium Code Reviews| Index: media/webm/webm_parser.cc |
| diff --git a/media/webm/webm_parser.cc b/media/webm/webm_parser.cc |
| index 1e42815013c1238fe1e7fb94f19249a3dcc3a745..1de4883fcc85287668fadd3cc9eb4c23a78fe4d6 100644 |
| --- a/media/webm/webm_parser.cc |
| +++ b/media/webm/webm_parser.cc |
| @@ -20,6 +20,7 @@ namespace media { |
| static const int kMaxLevelDepth = 6; |
| enum ElementType { |
| + UNKNOWN, |
| LIST, |
| UINT, |
| FLOAT, |
| @@ -30,13 +31,13 @@ enum ElementType { |
| }; |
| struct ElementIdInfo { |
| - int level_; |
| ElementType type_; |
| int id_; |
| }; |
| struct ListElementInfo { |
| int id_; |
| + int level_; |
| const ElementIdInfo* id_info_; |
| int id_info_size_; |
| }; |
| @@ -47,84 +48,87 @@ struct ListElementInfo { |
| // marked as SKIP because they are valid, but we don't care about them |
| // right now. |
| static const ElementIdInfo kClusterIds[] = { |
| - {2, UINT, kWebMIdTimecode}, |
| - {2, SBLOCK, kWebMIdSimpleBlock}, |
| - {2, LIST, kWebMIdBlockGroup}, |
| + {UINT, kWebMIdTimecode}, |
| + {SBLOCK, kWebMIdSimpleBlock}, |
| + {LIST, kWebMIdBlockGroup}, |
| +}; |
| + |
| +static const ElementIdInfo kSegmentIds[] = { |
| + {SKIP, kWebMIdSeekHead}, // TODO(acolwell): add SeekHead info |
|
scherkus (not reviewing)
2011/12/09 18:18:04
two spaces before // comments
acolwell GONE FROM CHROMIUM
2011/12/09 19:06:10
Done.
|
| + {LIST, kWebMIdInfo}, |
| + {LIST, kWebMIdCluster}, |
| + {LIST, kWebMIdTracks}, |
| + {SKIP, kWebMIdCues}, // TODO(acolwell): add CUES info |
| }; |
| static const ElementIdInfo kInfoIds[] = { |
| - {2, SKIP, kWebMIdSegmentUID}, |
| - {2, UINT, kWebMIdTimecodeScale}, |
| - {2, FLOAT, kWebMIdDuration}, |
| - {2, SKIP, kWebMIdDateUTC}, |
| - {2, SKIP, kWebMIdTitle}, |
| - {2, SKIP, kWebMIdMuxingApp}, |
| - {2, SKIP, kWebMIdWritingApp}, |
| + {SKIP, kWebMIdSegmentUID}, |
| + {UINT, kWebMIdTimecodeScale}, |
| + {FLOAT, kWebMIdDuration}, |
| + {SKIP, kWebMIdDateUTC}, |
| + {SKIP, kWebMIdTitle}, |
| + {SKIP, kWebMIdMuxingApp}, |
| + {SKIP, kWebMIdWritingApp}, |
| }; |
| static const ElementIdInfo kTracksIds[] = { |
| - {2, LIST, kWebMIdTrackEntry}, |
| + {LIST, kWebMIdTrackEntry}, |
| }; |
| static const ElementIdInfo kTrackEntryIds[] = { |
| - {3, UINT, kWebMIdTrackNumber}, |
| - {3, SKIP, kWebMIdTrackUID}, |
| - {3, UINT, kWebMIdTrackType}, |
| - {3, SKIP, kWebMIdFlagEnabled}, |
| - {3, SKIP, kWebMIdFlagDefault}, |
| - {3, SKIP, kWebMIdFlagForced}, |
| - {3, UINT, kWebMIdFlagLacing}, |
| - {3, UINT, kWebMIdDefaultDuration}, |
| - {3, SKIP, kWebMIdName}, |
| - {3, SKIP, kWebMIdLanguage}, |
| - {3, STRING, kWebMIdCodecID}, |
| - {3, BINARY, kWebMIdCodecPrivate}, |
| - {3, SKIP, kWebMIdCodecName}, |
| - {3, LIST, kWebMIdVideo}, |
| - {3, LIST, kWebMIdAudio}, |
| + {UINT, kWebMIdTrackNumber}, |
| + {SKIP, kWebMIdTrackUID}, |
| + {UINT, kWebMIdTrackType}, |
| + {SKIP, kWebMIdFlagEnabled}, |
| + {SKIP, kWebMIdFlagDefault}, |
| + {SKIP, kWebMIdFlagForced}, |
| + {UINT, kWebMIdFlagLacing}, |
| + {UINT, kWebMIdDefaultDuration}, |
| + {SKIP, kWebMIdName}, |
| + {SKIP, kWebMIdLanguage}, |
| + {STRING, kWebMIdCodecID}, |
| + {BINARY, kWebMIdCodecPrivate}, |
| + {SKIP, kWebMIdCodecName}, |
| + {LIST, kWebMIdVideo}, |
| + {LIST, kWebMIdAudio}, |
| }; |
| static const ElementIdInfo kVideoIds[] = { |
| - {4, SKIP, kWebMIdFlagInterlaced}, |
| - {4, SKIP, kWebMIdStereoMode}, |
| - {4, UINT, kWebMIdPixelWidth}, |
| - {4, UINT, kWebMIdPixelHeight}, |
| - {4, SKIP, kWebMIdPixelCropBottom}, |
| - {4, SKIP, kWebMIdPixelCropTop}, |
| - {4, SKIP, kWebMIdPixelCropLeft}, |
| - {4, SKIP, kWebMIdPixelCropRight}, |
| - {4, SKIP, kWebMIdDisplayWidth}, |
| - {4, SKIP, kWebMIdDisplayHeight}, |
| - {4, SKIP, kWebMIdDisplayUnit}, |
| - {4, SKIP, kWebMIdAspectRatioType}, |
| + {SKIP, kWebMIdFlagInterlaced}, |
| + {SKIP, kWebMIdStereoMode}, |
| + {UINT, kWebMIdPixelWidth}, |
| + {UINT, kWebMIdPixelHeight}, |
| + {SKIP, kWebMIdPixelCropBottom}, |
| + {SKIP, kWebMIdPixelCropTop}, |
| + {SKIP, kWebMIdPixelCropLeft}, |
| + {SKIP, kWebMIdPixelCropRight}, |
| + {SKIP, kWebMIdDisplayWidth}, |
| + {SKIP, kWebMIdDisplayHeight}, |
| + {SKIP, kWebMIdDisplayUnit}, |
| + {SKIP, kWebMIdAspectRatioType}, |
| }; |
| static const ElementIdInfo kAudioIds[] = { |
| - {4, SKIP, kWebMIdSamplingFrequency}, |
| - {4, SKIP, kWebMIdOutputSamplingFrequency}, |
| - {4, UINT, kWebMIdChannels}, |
| - {4, SKIP, kWebMIdBitDepth}, |
| -}; |
| - |
| -static const ElementIdInfo kClustersOnly[] = { |
| - {1, LIST, kWebMIdCluster}, |
| + {SKIP, kWebMIdSamplingFrequency}, |
| + {SKIP, kWebMIdOutputSamplingFrequency}, |
| + {UINT, kWebMIdChannels}, |
| + {SKIP, kWebMIdBitDepth}, |
| }; |
| static const ListElementInfo kListElementInfo[] = { |
| - { kWebMIdCluster, kClusterIds, sizeof(kClusterIds) }, |
| - { kWebMIdInfo, kInfoIds, sizeof(kInfoIds) }, |
| - { kWebMIdTracks, kTracksIds, sizeof(kTracksIds) }, |
| - { kWebMIdTrackEntry, kTrackEntryIds, sizeof(kTrackEntryIds) }, |
| - { kWebMIdVideo, kVideoIds, sizeof(kVideoIds) }, |
| - { kWebMIdAudio, kAudioIds, sizeof(kAudioIds) }, |
| + { kWebMIdCluster, 1, kClusterIds, sizeof(kClusterIds) }, |
| + { kWebMIdSegment, 0, kSegmentIds, sizeof(kSegmentIds) }, |
| + { kWebMIdInfo, 1, kInfoIds, sizeof(kInfoIds) }, |
| + { kWebMIdTracks, 1, kTracksIds, sizeof(kTracksIds) }, |
| + { kWebMIdTrackEntry, 2, kTrackEntryIds, sizeof(kTrackEntryIds) }, |
| + { kWebMIdVideo, 3, kVideoIds, sizeof(kVideoIds) }, |
| + { kWebMIdAudio, 3, kAudioIds, sizeof(kAudioIds) }, |
| }; |
| // Number of elements in kListElementInfo. |
| const int kListElementInfoCount = |
| sizeof(kListElementInfo) / sizeof(ListElementInfo); |
|
scherkus (not reviewing)
2011/12/09 18:18:04
nit: I think we have an arraysize() macro or somet
acolwell GONE FROM CHROMIUM
2011/12/09 19:06:10
Done.
|
| -WebMParserClient::~WebMParserClient() {} |
| - |
| // Parses an element header id or size field. These fields are variable length |
| // encoded. The first byte indicates how many bytes the field occupies. |
| // |buf| - The buffer to parse. |
| @@ -206,17 +210,22 @@ int WebMParseElementHeader(const uint8* buf, int size, |
| return num_id_bytes + num_size_bytes; |
| } |
| -// Finds ElementIdInfo for a specific ID. |
| -static const ElementIdInfo* FindIdInfo(int id, |
| - const ElementIdInfo* id_info, |
| - int id_info_size) { |
| +// Finds ElementType for a specific ID. |
| +static const ElementType FindIdType(int id, |
| + const ElementIdInfo* id_info, |
| + int id_info_size) { |
| + |
| + // Check for global element IDs that can be anywhere. |
| + if (id == kWebMIdVoid || id == kWebMIdCRC32) |
| + return SKIP; |
| + |
| int count = id_info_size / sizeof(*id_info); |
| for (int i = 0; i < count; ++i) { |
| if (id == id_info[i].id_) |
| - return &id_info[i]; |
| + return id_info[i].type_; |
| } |
| - return NULL; |
| + return UNKNOWN; |
| } |
| // Finds ListElementInfo for a specific ID. |
| @@ -237,7 +246,7 @@ static int ParseSimpleBlock(const uint8* buf, int size, |
| // Return an error if the trackNum > 127. We just aren't |
| // going to support large track numbers right now. |
| if ((buf[0] & 0x80) != 0x80) { |
| - VLOG(1) << "TrackNumber over 127 not supported"; |
| + DVLOG(1) << "TrackNumber over 127 not supported"; |
| return -1; |
| } |
| @@ -247,7 +256,7 @@ static int ParseSimpleBlock(const uint8* buf, int size, |
| int lacing = (flags >> 1) & 0x3; |
| if (lacing != 0) { |
| - VLOG(1) << "Lacing " << lacing << " not supported yet."; |
| + DVLOG(1) << "Lacing " << lacing << " not supported yet."; |
| return -1; |
| } |
| @@ -265,39 +274,6 @@ static int ParseSimpleBlock(const uint8* buf, int size, |
| return size; |
| } |
| -static int ParseElements(const ElementIdInfo* id_info, |
| - int id_info_size, |
| - const uint8* buf, int size, int level, |
| - WebMParserClient* client); |
| - |
| -static int ParseElementList(const uint8* buf, int size, |
| - int id, int level, |
| - WebMParserClient* client) { |
| - const ListElementInfo* list_info = FindListInfo(id); |
| - |
| - if (!list_info) { |
| - VLOG(1) << "Failed to find list info for ID " << std::hex << id; |
| - return -1; |
| - } |
| - |
| - if (!client->OnListStart(id)) |
| - return -1; |
| - |
| - int result = ParseElements(list_info->id_info_, |
| - list_info->id_info_size_, |
| - buf, size, |
| - level + 1, |
| - client); |
| - |
| - if (result <= 0) |
| - return result; |
| - |
| - if (!client->OnListEnd(id)) |
| - return -1; |
| - |
| - DCHECK_EQ(result, size); |
| - return result; |
| -} |
| static int ParseUInt(const uint8* buf, int size, int id, |
| WebMParserClient* client) { |
| @@ -354,142 +330,282 @@ static int ParseFloat(const uint8* buf, int size, int id, |
| return size; |
| } |
| -static int ParseElements(const ElementIdInfo* id_info, |
| - int id_info_size, |
| - const uint8* buf, int size, int level, |
| - WebMParserClient* client) { |
| - DCHECK_GE(id_info_size, 0); |
| - DCHECK_GE(size, 0); |
| - DCHECK_GE(level, 0); |
| +static int ParseNonListElement(ElementType type, int id, int64 element_size, |
| + const uint8* buf, int size, |
| + WebMParserClient* client) { |
| + DCHECK_GE(size, element_size); |
| - const uint8* cur = buf; |
| - int cur_size = size; |
| - int used = 0; |
| + int result = -1; |
| + switch(type) { |
| + case SBLOCK: |
| + result = ParseSimpleBlock(buf, element_size, client); |
| + break; |
| + case LIST: |
| + NOTIMPLEMENTED(); |
| + result = -1; |
| + break; |
| + case UINT: |
| + result = ParseUInt(buf, element_size, id, client); |
| + break; |
| + case FLOAT: |
| + result = ParseFloat(buf, element_size, id, client); |
| + break; |
| + case BINARY: |
| + if (client->OnBinary(id, buf, element_size)) { |
| + result = element_size; |
| + } else { |
| + result = -1; |
| + } |
| + break; |
| + case STRING: |
| + if (client->OnString(id, |
| + std::string(reinterpret_cast<const char*>(buf), |
| + element_size))) { |
| + result = element_size; |
| + } else { |
| + result = -1; |
| + } |
| + break; |
| + case SKIP: |
| + result = element_size; |
| + break; |
| + default: |
| + DVLOG(1) << "Unhandled ID type " << type; |
| + return -1; |
| + }; |
| - if (level > kMaxLevelDepth) |
| - return -1; |
| + DCHECK_LE(result, size); |
| + return result; |
| +} |
| - while (cur_size > 0) { |
| - int id = 0; |
| - int64 element_size = 0; |
| - int result = WebMParseElementHeader(cur, cur_size, &id, &element_size); |
| +WebMParserClient::WebMParserClient() {} |
| +WebMParserClient::~WebMParserClient() {} |
| - if (result <= 0) |
| - return result; |
| +WebMListParser::WebMListParser(int id) |
| + : state_(NEED_LIST_HEADER), |
| + root_id_(id) { |
| + const ListElementInfo* list_info = FindListInfo(id); |
| - cur += result; |
| - cur_size -= result; |
| - used += result; |
| + DCHECK(list_info); |
| - // Check to see if the element is larger than the remaining data. |
| - if (element_size > cur_size) |
| - return 0; |
| + root_level_ = list_info->level_; |
| +} |
| - const ElementIdInfo* info = FindIdInfo(id, id_info, id_info_size); |
| +WebMListParser::~WebMListParser() {} |
| - if (info == NULL) { |
| - VLOG(1) << "No info for ID " << std::hex << id; |
| +void WebMListParser::Reset() { |
| + ChangeState(NEED_LIST_HEADER); |
| + list_state_stack_.clear(); |
| +} |
| - // TODO(acolwell): Change this to return -1 after the API has solidified. |
| - // We don't want to allow elements we don't recognize. |
| - cur += element_size; |
| - cur_size -= element_size; |
| - used += element_size; |
| - continue; |
| - } |
| +int WebMListParser::Parse(const uint8* buf, int size, |
| + WebMParserClient* client) { |
| + DCHECK(buf); |
| + DCHECK(client); |
| - if (info->level_ != level) { |
| - VLOG(1) << "ID " << std::hex << id << std::dec << " at level " |
| - << level << " instead of " << info->level_; |
| - return -1; |
| - } |
| + if (size < 0 || state_ == PARSE_ERROR || state_ == DONE_PARSING_LIST) |
| + return -1; |
| - switch(info->type_) { |
| - case SBLOCK: |
| - if (ParseSimpleBlock(cur, element_size, client) <= 0) |
| - return -1; |
| - break; |
| - case LIST: |
| - if (ParseElementList(cur, element_size, id, level, client) < 0) |
| - return -1; |
| - break; |
| - case UINT: |
| - if (ParseUInt(cur, element_size, id, client) <= 0) |
| + if (size == 0) |
| + return 0; |
| + |
| + const uint8* cur = buf; |
| + int cur_size = size; |
| + int bytes_parsed = 0; |
| + |
| + while (cur_size > 0 && state_ != PARSE_ERROR && state_ != DONE_PARSING_LIST) { |
| + int element_id = 0; |
| + int64 element_size = 0; |
| + int result = WebMParseElementHeader(cur, cur_size, &element_id, |
| + &element_size); |
| + |
| + if (result < 0) |
| + return result; |
| + |
| + if (result == 0) |
| + return bytes_parsed; |
| + |
| + switch(state_) { |
| + case NEED_LIST_HEADER: { |
| + if (element_id != root_id_) { |
| + ChangeState(PARSE_ERROR); |
| return -1; |
| - break; |
| - case FLOAT: |
| - if (ParseFloat(cur, element_size, id, client) <= 0) |
| + } |
| + |
| + // TODO(acolwell): Add support for lists of unknown size. |
| + if (element_size == kWebMUnknownSize) { |
| + ChangeState(PARSE_ERROR); |
| return -1; |
| - break; |
| - case BINARY: |
| - if (!client->OnBinary(id, cur, element_size)) |
| + } |
| + |
| + ChangeState(INSIDE_LIST); |
| + if (!OnListStart(root_id_, element_size, client)) |
| return -1; |
| - break; |
| - case STRING: |
| - if (!client->OnString(id, |
| - std::string(reinterpret_cast<const char*>(cur), |
| - element_size))) |
| + } break; |
|
scherkus (not reviewing)
2011/12/09 18:18:04
nit: this style is somewhat strange .. mind moving
acolwell GONE FROM CHROMIUM
2011/12/09 19:06:10
Done.
|
| + |
| + case INSIDE_LIST: { |
| + int header_size = result; |
| + const uint8* element_data = cur + header_size; |
| + int element_data_size = cur_size - header_size; |
| + |
| + if (element_size < element_data_size) |
| + element_data_size = element_size; |
| + |
| + result = ParseListElement(header_size, element_id, element_size, |
| + element_data, element_data_size, client); |
| + |
| + DCHECK_LE(result, header_size + element_data_size); |
| + if (result < 0) { |
| + ChangeState(PARSE_ERROR); |
| return -1; |
| + } |
| + |
| + if (result == 0) |
| + return bytes_parsed; |
| + } break; |
| + |
| + case DONE_PARSING_LIST: |
| + case PARSE_ERROR: |
| + // Shouldn't be able to get here. |
| + NOTIMPLEMENTED(); |
| break; |
| - case SKIP: |
| - // Do nothing. |
| - break; |
| - default: |
| - VLOG(1) << "Unhandled id type " << info->type_; |
| - return -1; |
| - }; |
| - |
| - cur += element_size; |
| - cur_size -= element_size; |
| - used += element_size; |
| + } |
| + |
| + cur += result; |
| + cur_size -= result; |
| + bytes_parsed += result; |
| } |
| - return used; |
| + return (state_ == PARSE_ERROR) ? -1 : bytes_parsed; |
| } |
| -// Parses a single list element that matches |id|. This method fails if the |
| -// buffer points to an element that does not match |id|. |
| -int WebMParseListElement(const uint8* buf, int size, int id, |
| - int level, WebMParserClient* client) { |
| - if (size < 0) |
| - return -1; |
| +bool WebMListParser::IsParsingComplete() const { |
| + return state_ == DONE_PARSING_LIST; |
| +} |
| - if (size == 0) |
| - return 0; |
| +void WebMListParser::ChangeState(State new_state) { |
| + state_ = new_state; |
| +} |
| - const uint8* cur = buf; |
| - int cur_size = size; |
| - int bytes_parsed = 0; |
| - int element_id = 0; |
| - int64 element_size = 0; |
| - int result = WebMParseElementHeader(cur, cur_size, &element_id, |
| - &element_size); |
| +int WebMListParser::ParseListElement(int header_size, |
| + int id, int64 element_size, |
| + const uint8* data, int size, |
| + WebMParserClient* client) { |
| + DCHECK_GT(list_state_stack_.size(), 0u); |
| - if (result <= 0) |
| - return result; |
| + ListState& list_state = list_state_stack_.back(); |
| + DCHECK(list_state.element_info_); |
| - cur += result; |
| - cur_size -= result; |
| - bytes_parsed += result; |
| + const ListElementInfo* element_info = list_state.element_info_; |
| + const ElementType id_type = |
| + FindIdType(id, element_info->id_info_, element_info->id_info_size_); |
| + |
| + // Unexpected ID. |
| + if (id_type == UNKNOWN) { |
| + DVLOG(1) << "No ElementType info for ID 0x" << std::hex << id; |
| + return -1; |
| + } |
| - if (element_id != id) |
| + // Make sure the whole element can fit inside the current list. |
| + int64 total_element_size = header_size + element_size; |
| + if (list_state.size_ != kWebMUnknownSize && |
| + list_state.size_ < list_state.bytes_parsed_ + total_element_size) { |
| return -1; |
| + } |
| + |
| + if (id_type == LIST) { |
| + list_state.bytes_parsed_ += header_size; |
| + |
| + if (!OnListStart(id, element_size, client)) |
| + return -1; |
| + return header_size; |
| + } |
| - if (element_size > cur_size) |
| + // Make sure we have the entire element before trying to parse a non-list |
| + // element. |
| + if (size < element_size) |
| return 0; |
| - if (element_size > 0) { |
| - result = ParseElementList(cur, element_size, element_id, level, client); |
| + int bytes_parsed = ParseNonListElement(id_type, id, element_size, |
| + data, size, client); |
| + DCHECK_LE(bytes_parsed, size); |
| - if (result <= 0) |
| - return result; |
| + // Return if an error occurred or we need more data. |
| + // Note: bytes_parsed is 0 for a successful parse of a size 0 element. We |
| + // need to check the element_size to disambiguate the "need more data" case |
| + // from a successful parse. |
| + if (bytes_parsed < 0 || (bytes_parsed == 0 && element_size != 0)) |
| + return bytes_parsed; |
| - cur += result; |
| - cur_size -= result; |
| - bytes_parsed += result; |
| + int result = header_size + bytes_parsed; |
| + list_state.bytes_parsed_ += result; |
| + |
| + // See if we have reached the end of the current list. |
| + if (list_state.bytes_parsed_ == list_state.size_) { |
| + if (!OnListEnd(client)) |
| + return -1; |
| + } |
| + |
| + return result; |
| +} |
| + |
| +bool WebMListParser::OnListStart(int id, int64 size, WebMParserClient* client) { |
| + ListState list_state = { id, size, 0, FindListInfo(id)}; |
| + |
| + if (!list_state.element_info_) |
| + return false; |
| + |
| + int current_level = root_level_ + list_state_stack_.size() - 1; |
| + if (current_level + 1 != list_state.element_info_->level_) |
| + return false; |
| + |
| + if (!list_state_stack_.empty()) { |
| + |
| + // Make sure the new list doesn't go past the end of the current list. |
| + ListState current_list = list_state_stack_.back(); |
| + if (current_list.size_ != kWebMUnknownSize && |
| + current_list.size_ < current_list.bytes_parsed_ + size) |
| + return false; |
| } |
| - return bytes_parsed; |
| + if (!client->OnListStart(id)) |
| + return false; |
| + |
| + list_state_stack_.push_back(list_state); |
| + |
| + if (size == 0) { |
| + return OnListEnd(client); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool WebMListParser::OnListEnd(WebMParserClient* client) { |
| + int lists_ended = 0; |
| + for (; !list_state_stack_.empty(); ++lists_ended) { |
| + const ListState& list_state = list_state_stack_.back(); |
| + |
| + if (list_state.bytes_parsed_ != list_state.size_) |
| + break; |
| + |
| + if (!client->OnListEnd(list_state.id_)) |
| + return false; |
| + |
| + int64 bytes_parsed = list_state.bytes_parsed_; |
| + list_state_stack_.pop_back(); |
| + |
| + if (!list_state_stack_.empty()) { |
| + // Update the bytes_parsed_ for the parent element. |
| + list_state_stack_.back().bytes_parsed_ += bytes_parsed; |
| + } |
| + } |
| + |
| + DCHECK_GE(lists_ended, 1); |
| + |
| + if (list_state_stack_.empty()) |
| + ChangeState(DONE_PARSING_LIST); |
| + |
| + return true; |
| } |
| } // namespace media |