Commit 31bc2039 authored by acolwell@chromium.org's avatar acolwell@chromium.org

Adding support for incremental cluster parsing.


BUG=104160
TEST=Covered by ChunkDemuxer unittests.


Review URL: http://codereview.chromium.org/8775035

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114030 0039d316-1c4b-4281-b951-d872f2087c98
parent 8453535e
......@@ -130,7 +130,7 @@ void ChunkDemuxerStream::Flush() {
bool ChunkDemuxerStream::CanAddBuffers(const BufferQueue& buffers) const {
base::AutoLock auto_lock(lock_);
// If we haven't seen any buffers yet than anything can be added.
// If we haven't seen any buffers yet, then anything can be added.
if (last_buffer_timestamp_ == kNoTimestamp)
return true;
......@@ -434,7 +434,7 @@ bool ChunkDemuxer::AppendData(const uint8* data, size_t length) {
int cur_size = 0;
int bytes_parsed = 0;
int result = -1;
bool parsed_a_cluster = false;
bool can_complete_seek = false;
byte_queue_.Peek(&cur, &cur_size);
......@@ -449,16 +449,19 @@ bool ChunkDemuxer::AppendData(const uint8* data, size_t length) {
}
break;
case INITIALIZED:
result = ParseCluster_Locked(cur, cur_size);
case INITIALIZED: {
bool buffers_added = false;
result = ParseCluster_Locked(cur, cur_size, &buffers_added);
if (result < 0) {
VLOG(1) << "AppendData(): parsing data failed";
ReportError_Locked(PIPELINE_ERROR_DECODE);
return true;
}
parsed_a_cluster = (result > 0);
break;
// We can complete the seek if we have successfully parsed
// some data and buffers were added to one of the DemuxerStreams.
can_complete_seek |= (result > 0 && buffers_added);
} break;
case WAITING_FOR_INIT:
case ENDED:
......@@ -477,7 +480,7 @@ bool ChunkDemuxer::AppendData(const uint8* data, size_t length) {
byte_queue_.Pop(bytes_parsed);
if (parsed_a_cluster && seek_waits_for_data_) {
if (can_complete_seek && seek_waits_for_data_) {
seek_waits_for_data_ = false;
if (!seek_cb_.is_null())
......@@ -730,7 +733,8 @@ bool ChunkDemuxer::SetupStreams() {
return !no_supported_streams;
}
int ChunkDemuxer::ParseCluster_Locked(const uint8* data, int size) {
int ChunkDemuxer::ParseCluster_Locked(const uint8* data, int size,
bool* buffers_added) {
lock_.AssertAcquired();
if (!cluster_parser_.get())
return -1;
......@@ -749,9 +753,6 @@ int ChunkDemuxer::ParseCluster_Locked(const uint8* data, int size) {
}
// Skip the element.
return result + element_size;
} else if (id != kWebMIdCluster) {
VLOG(1) << "Unexpected ID 0x" << std::hex << id;
return -1;
}
int bytes_parsed = cluster_parser_->Parse(data, size);
......@@ -759,20 +760,25 @@ int ChunkDemuxer::ParseCluster_Locked(const uint8* data, int size) {
if (bytes_parsed <= 0)
return bytes_parsed;
// Make sure we can add the buffers to both streams before we actutally
// add them. This allows us to accept all of the data or none of it.
if ((audio_.get() &&
!audio_->CanAddBuffers(cluster_parser_->audio_buffers())) ||
(video_.get() &&
!video_->CanAddBuffers(cluster_parser_->video_buffers()))) {
return -1;
}
if (!cluster_parser_->audio_buffers().empty() ||
!cluster_parser_->video_buffers().empty()) {
// Make sure we can add the buffers to both streams before we actually
// add them. This allows us to accept all of the data or none of it.
if ((audio_.get() &&
!audio_->CanAddBuffers(cluster_parser_->audio_buffers())) ||
(video_.get() &&
!video_->CanAddBuffers(cluster_parser_->video_buffers()))) {
return -1;
}
if (audio_.get())
audio_->AddBuffers(cluster_parser_->audio_buffers());
if (audio_.get())
audio_->AddBuffers(cluster_parser_->audio_buffers());
if (video_.get())
video_->AddBuffers(cluster_parser_->video_buffers());
if (video_.get())
video_->AddBuffers(cluster_parser_->video_buffers());
*buffers_added = true;
}
// TODO(acolwell) : make this more representative of what is actually
// buffered.
......
......@@ -81,13 +81,18 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
// found.
bool SetupStreams();
// Parse a cluster add add the buffers to the appropriate DemxuerStream.
// |data| is expected to point to the beginning of a cluster element.
// Parse a cluster and add the buffers to the appropriate DemuxerStream. This
// method also skips over CUES elements if it happens to encounter them.
//
// |data| is expected to point to the beginning of an element.
//
// |buffers_added| - Indicates whether Buffers were added to DemuxerStreams
// during the call. This is only valid if the return value > 0.
//
// Returns -1 if the parse fails.
// Returns 0 if more data is needed.
// Returns the number of bytes parsed on success.
int ParseCluster_Locked(const uint8* data, int size);
int ParseCluster_Locked(const uint8* data, int size, bool* buffers_added);
// Reports an error and puts the demuxer in a state where it won't accept more
// data.
......
......@@ -621,6 +621,7 @@
'video/capture/video_capture_device_unittest.cc',
'webm/cluster_builder.cc',
'webm/cluster_builder.h',
'webm/webm_parser_unittest.cc',
],
'conditions': [
['os_posix==1 and OS!="mac"', {
......
......@@ -26,6 +26,7 @@ WebMClusterParser::WebMClusterParser(int64 timecode_scale,
audio_default_duration_(audio_default_duration),
video_track_num_(video_track_num),
video_default_duration_(video_default_duration),
parser_(kWebMIdCluster),
last_block_timecode_(-1),
cluster_timecode_(-1) {
}
......@@ -33,12 +34,25 @@ WebMClusterParser::WebMClusterParser(int64 timecode_scale,
WebMClusterParser::~WebMClusterParser() {}
int WebMClusterParser::Parse(const uint8* buf, int size) {
last_block_timecode_ = -1;
cluster_timecode_ = -1;
audio_buffers_.clear();
video_buffers_.clear();
return WebMParseListElement(buf, size, kWebMIdCluster, 1, this);
int result = parser_.Parse(buf, size, this);
if (result <= 0)
return result;
if (parser_.IsParsingComplete()) {
// Reset the parser if we're done parsing so that
// it is ready to accept another cluster on the next
// call.
parser_.Reset();
last_block_timecode_ = -1;
cluster_timecode_ = -1;
}
return result;
}
bool WebMClusterParser::OnListStart(int id) {
......@@ -67,17 +81,17 @@ bool WebMClusterParser::OnUInt(int id, int64 val) {
}
bool WebMClusterParser::OnFloat(int id, double val) {
VLOG(1) << "Unexpected float element with ID " << std::hex << id;
DVLOG(1) << "Unexpected float element with ID " << std::hex << id;
return false;
}
bool WebMClusterParser::OnBinary(int id, const uint8* data, int size) {
VLOG(1) << "Unexpected binary element with ID " << std::hex << id;
DVLOG(1) << "Unexpected binary element with ID " << std::hex << id;
return false;
}
bool WebMClusterParser::OnString(int id, const std::string& str) {
VLOG(1) << "Unexpected string element with ID " << std::hex << id;
DVLOG(1) << "Unexpected string element with ID " << std::hex << id;
return false;
}
......@@ -85,17 +99,17 @@ bool WebMClusterParser::OnSimpleBlock(int track_num, int timecode,
int flags,
const uint8* data, int size) {
if (cluster_timecode_ == -1) {
VLOG(1) << "Got SimpleBlock before cluster timecode.";
DVLOG(1) << "Got SimpleBlock before cluster timecode.";
return false;
}
if (timecode < 0) {
VLOG(1) << "Got SimpleBlock with negative timecode offset " << timecode;
DVLOG(1) << "Got SimpleBlock with negative timecode offset " << timecode;
return false;
}
if (last_block_timecode_ != -1 && timecode < last_block_timecode_) {
VLOG(1) << "Got SimpleBlock with a timecode before the previous block.";
DVLOG(1) << "Got SimpleBlock with a timecode before the previous block.";
return false;
}
......@@ -115,13 +129,13 @@ bool WebMClusterParser::OnSimpleBlock(int track_num, int timecode,
buffer->SetDuration(video_default_duration_);
queue = &video_buffers_;
} else {
VLOG(1) << "Unexpected track number " << track_num;
DVLOG(1) << "Unexpected track number " << track_num;
return false;
}
if (!queue->empty() &&
buffer->GetTimestamp() == queue->back()->GetTimestamp()) {
VLOG(1) << "Got SimpleBlock timecode is not strictly monotonically "
DVLOG(1) << "Got SimpleBlock timecode is not strictly monotonically "
<< "increasing for track " << track_num;
return false;
}
......
......@@ -53,6 +53,8 @@ class WebMClusterParser : public WebMParserClient {
int video_track_num_;
base::TimeDelta video_default_duration_;
WebMListParser parser_;
int64 last_block_timecode_;
int64 cluster_timecode_;
......
......@@ -64,6 +64,8 @@ const int kWebMIdVideo = 0xE0;
const int kWebMIdVoid = 0xEC;
const int kWebMIdWritingApp = 0x5741;
const int64 kWebMUnknownSize = GG_LONGLONG(0x00FFFFFFFFFFFFFF);
// Default timecode scale if the TimecodeScale element is
// not specified in the INFO element.
const int kWebMDefaultTimecodeScale = 1000000;
......
......@@ -17,7 +17,17 @@ WebMInfoParser::WebMInfoParser()
WebMInfoParser::~WebMInfoParser() {}
int WebMInfoParser::Parse(const uint8* buf, int size) {
return WebMParseListElement(buf, size, kWebMIdInfo, 1, this);
timecode_scale_ = -1;
duration_ = -1;
WebMListParser parser(kWebMIdInfo);
int result = parser.Parse(buf, size, this);
if (result <= 0)
return result;
// For now we do all or nothing parsing.
return parser.IsParsingComplete() ? result : 0;
}
bool WebMInfoParser::OnListStart(int id) { return true; }
......@@ -36,7 +46,7 @@ bool WebMInfoParser::OnUInt(int id, int64 val) {
return true;
if (timecode_scale_ != -1) {
VLOG(1) << "Multiple values for id " << std::hex << id << " specified";
DVLOG(1) << "Multiple values for id " << std::hex << id << " specified";
return false;
}
......@@ -46,12 +56,12 @@ bool WebMInfoParser::OnUInt(int id, int64 val) {
bool WebMInfoParser::OnFloat(int id, double val) {
if (id != kWebMIdDuration) {
VLOG(1) << "Unexpected float for id" << std::hex << id;
DVLOG(1) << "Unexpected float for id" << std::hex << id;
return false;
}
if (duration_ != -1) {
VLOG(1) << "Multiple values for duration.";
DVLOG(1) << "Multiple values for duration.";
return false;
}
......
......@@ -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,83 @@ 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
{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);
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,22 +206,27 @@ 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 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.
static const ListElementInfo* FindListInfo(int id) {
for (int i = 0; i < kListElementInfoCount; ++i) {
for (size_t i = 0; i < arraysize(kListElementInfo); ++i) {
if (id == kListElementInfo[i].id_)
return &kListElementInfo[i];
}
......@@ -237,7 +242,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 +252,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 +270,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 +326,285 @@ 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)))
}
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 SKIP:
// Do nothing.
}
case DONE_PARSING_LIST:
case PARSE_ERROR:
// Shouldn't be able to get here.
NOTIMPLEMENTED();
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_;
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
......@@ -6,8 +6,10 @@
#define MEDIA_WEBM_WEBM_PARSER_H_
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "media/base/media_export.h"
namespace media {
......@@ -21,7 +23,7 @@ namespace media {
// indicates whether the parsed data is valid. If false is returned
// then the parse is immediately terminated and an error is reported by the
// parser.
class WebMParserClient {
class MEDIA_EXPORT WebMParserClient {
public:
virtual ~WebMParserClient();
......@@ -34,6 +36,106 @@ class WebMParserClient {
virtual bool OnSimpleBlock(int track_num, int timecode,
int flags,
const uint8* data, int size) = 0;
protected:
WebMParserClient();
DISALLOW_COPY_AND_ASSIGN(WebMParserClient);
};
struct ListElementInfo;
// Parses a WebM list element and all of its children. This
// class supports incremental parsing of the list so Parse()
// can be called multiple times with pieces of the list.
// IsParsingComplete() will return true once the entire list has
// been parsed.
class MEDIA_EXPORT WebMListParser {
public:
// |id| - Element ID of the list we intend to parse.
explicit WebMListParser(int id);
~WebMListParser();
// Resets the state of the parser so it can start parsing a new list.
void Reset();
// Parses list data contained in |buf|.
// |client| Called as different elements in the list are parsed.
//
// Returns < 0 if the parse fails.
// Returns 0 if more data is needed.
// Returning > 0 indicates success & the number of bytes parsed.
int Parse(const uint8* buf, int size, WebMParserClient* client);
// Returns true if the entire list has been parsed.
bool IsParsingComplete() const;
private:
enum State {
NEED_LIST_HEADER,
INSIDE_LIST,
DONE_PARSING_LIST,
PARSE_ERROR,
};
struct ListState {
int id_;
int size_;
int bytes_parsed_;
const ListElementInfo* element_info_;
};
void ChangeState(State new_state);
// Parses a single element in the current list.
//
// |header_size| - The size of the element header
// |id| - The ID of the element being parsed.
// |element_size| - The size of the element body.
// |data| - Pointer to the element contents.
// |size| - Number of bytes in |data|
// |client| - Client to pass the parsed data to.
//
// Returns < 0 if the parse fails.
// Returns 0 if more data is needed.
// Returning > 0 indicates success & the number of bytes parsed.
int ParseListElement(int header_size,
int id, int64 element_size,
const uint8* data, int size,
WebMParserClient* client);
// Called when starting to parse a new list.
//
// |id| - The ID of the new list.
// |size| - The size of the new list.
// |client| - The client object to notify that a new list is being parsed.
//
// Returns true if this list can be started in the current context. False
// if starting this list causes some sort of parse error.
bool OnListStart(int id, int64 size, WebMParserClient* client);
// Called when the end of the current list has been reached. This may also
// signal the end of the current list's ancestors if the current list happens
// to be at the end of its parent.
//
// |client| - The client to notify about lists ending.
//
// Returns true if no errors occurred while ending this list(s).
bool OnListEnd(WebMParserClient* client);
State state_;
// Element ID passed to the constructor.
int root_id_;
// Element level for |root_id_|. Used to verify that elements appear at
// the correct level.
int root_level_;
// Stack of state for all the lists currently being parsed. Lists are
// added and removed from this stack as they are parsed.
std::vector<ListState> list_state_stack_;
DISALLOW_COPY_AND_ASSIGN(WebMListParser);
};
// Parses an element header & returns the ID and element size.
......@@ -47,15 +149,6 @@ class WebMParserClient {
int WebMParseElementHeader(const uint8* buf, int size,
int* id, int64* element_size);
// Parses a single list element that matches |id|. This method fails if the
// buffer points to an element that does not match |id|.
//
// Returns -1 if the parse fails.
// Returns 0 if more data is needed.
// Returns the number of bytes parsed on success.
int WebMParseListElement(const uint8* buf, int size, int id,
int level, WebMParserClient* client);
} // namespace media
#endif // MEDIA_WEBM_WEBM_PARSER_H_
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/webm/cluster_builder.h"
#include "media/webm/webm_constants.h"
#include "media/webm/webm_parser.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::InSequence;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::_;
namespace media {
class MockWebMParserClient : public WebMParserClient {
public:
virtual ~MockWebMParserClient() {}
// WebMParserClient methods.
MOCK_METHOD1(OnListStart, bool(int));
MOCK_METHOD1(OnListEnd, bool(int));
MOCK_METHOD2(OnUInt, bool(int, int64));
MOCK_METHOD2(OnFloat, bool(int, double));
MOCK_METHOD3(OnBinary, bool(int, const uint8*, int));
MOCK_METHOD2(OnString, bool(int, const std::string&));
MOCK_METHOD5(OnSimpleBlock, bool(int, int, int, const uint8*, int));
};
class WebMParserTest : public testing::Test {
protected:
StrictMock<MockWebMParserClient> client_;
};
struct SimpleBlockInfo {
int track_num;
int timestamp;
};
static void AddSimpleBlock(ClusterBuilder* cb, int track_num,
int64 timecode) {
uint8 data[] = { 0x00 };
cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data));
}
static Cluster* CreateCluster(int timecode,
const SimpleBlockInfo* block_info,
int block_count) {
ClusterBuilder cb;
cb.SetClusterTimecode(0);
for (int i = 0; i < block_count; i++)
AddSimpleBlock(&cb, block_info[i].track_num, block_info[i].timestamp);
return cb.Finish();
}
static void CreateClusterExpectations(int timecode,
const SimpleBlockInfo* block_info,
int block_count,
MockWebMParserClient* client) {
InSequence s;
EXPECT_CALL(*client, OnListStart(kWebMIdCluster)).WillOnce(Return(true));
EXPECT_CALL(*client, OnUInt(kWebMIdTimecode, 0)).WillOnce(Return(true));
for (int i = 0; i < block_count; i++) {
EXPECT_CALL(*client, OnSimpleBlock(block_info[i].track_num,
block_info[i].timestamp,
_, _, _))
.WillOnce(Return(true));
}
EXPECT_CALL(*client, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
}
TEST_F(WebMParserTest, EmptyCluster) {
const uint8 kEmptyCluster[] = {
0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0)
};
int size = sizeof(kEmptyCluster);
InSequence s;
EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
WebMListParser parser(kWebMIdCluster);
int result = parser.Parse(kEmptyCluster, size, &client_);
EXPECT_EQ(size, result);
EXPECT_TRUE(parser.IsParsingComplete());
}
TEST_F(WebMParserTest, EmptyClusterInSegment) {
const uint8 kBuffer[] = {
0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5)
0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
};
int size = sizeof(kBuffer);
InSequence s;
EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
WebMListParser parser(kWebMIdSegment);
int result = parser.Parse(kBuffer, size, &client_);
EXPECT_EQ(size, result);
EXPECT_TRUE(parser.IsParsingComplete());
}
// Test the case where a non-list child element has a size
// that is beyond the end of the parent.
TEST_F(WebMParserTest, ChildNonListLargerThanParent) {
const uint8 kBuffer[] = {
0x1F, 0x43, 0xB6, 0x75, 0x81, // CLUSTER (size = 1)
0xE7, 0x81, 0x01, // Timecode (size=1, value=1)
};
int size = sizeof(kBuffer);
InSequence s;
EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(true));
WebMListParser parser(kWebMIdCluster);
int result = parser.Parse(kBuffer, size, &client_);
EXPECT_EQ(-1, result);
EXPECT_FALSE(parser.IsParsingComplete());
}
// Test the case where a list child element has a size
// that is beyond the end of the parent.
TEST_F(WebMParserTest, ChildListLargerThanParent) {
const uint8 kBuffer[] = {
0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5)
0x1F, 0x43, 0xB6, 0x75, 0x81, 0x11 // CLUSTER (size = 1)
};
int size = sizeof(kBuffer);
InSequence s;
EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(true));
WebMListParser parser(kWebMIdSegment);
int result = parser.Parse(kBuffer, size, &client_);
EXPECT_EQ(-1, result);
EXPECT_FALSE(parser.IsParsingComplete());
}
// Expecting to parse a Cluster, but get a Segment.
TEST_F(WebMParserTest, ListIdDoesNotMatch) {
const uint8 kBuffer[] = {
0x18, 0x53, 0x80, 0x67, 0x80, // SEGMENT (size = 0)
};
int size = sizeof(kBuffer);
WebMListParser parser(kWebMIdCluster);
int result = parser.Parse(kBuffer, size, &client_);
EXPECT_EQ(-1, result);
EXPECT_FALSE(parser.IsParsingComplete());
}
TEST_F(WebMParserTest, InvalidElementInList) {
const uint8 kBuffer[] = {
0x18, 0x53, 0x80, 0x67, 0x82, // SEGMENT (size = 2)
0xAE, 0x80, // TrackEntry (size = 0)
};
int size = sizeof(kBuffer);
InSequence s;
EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(true));
WebMListParser parser(kWebMIdSegment);
int result = parser.Parse(kBuffer, size, &client_);
EXPECT_EQ(-1, result);
EXPECT_FALSE(parser.IsParsingComplete());
}
TEST_F(WebMParserTest, VoidAndCRC32InList) {
const uint8 kBuffer[] = {
0x18, 0x53, 0x80, 0x67, 0x99, // SEGMENT (size = 25)
0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3)
0x1F, 0x43, 0xB6, 0x75, 0x8A, // CLUSTER (size = 10)
0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3)
};
int size = sizeof(kBuffer);
InSequence s;
EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
WebMListParser parser(kWebMIdSegment);
int result = parser.Parse(kBuffer, size, &client_);
EXPECT_EQ(size, result);
EXPECT_TRUE(parser.IsParsingComplete());
}
TEST_F(WebMParserTest, ParseListElementWithSingleCall) {
const SimpleBlockInfo kBlockInfo[] = {
{ 0, 1 },
{ 1, 2 },
{ 0, 3 },
{ 0, 4 },
{ 1, 4 },
};
int block_count = arraysize(kBlockInfo);
scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
CreateClusterExpectations(0, kBlockInfo, block_count, &client_);
WebMListParser parser(kWebMIdCluster);
int result = parser.Parse(cluster->data(), cluster->size(), &client_);
EXPECT_EQ(cluster->size(), result);
EXPECT_TRUE(parser.IsParsingComplete());
}
TEST_F(WebMParserTest, ParseListElementWithMultipleCalls) {
const SimpleBlockInfo kBlockInfo[] = {
{ 0, 1 },
{ 1, 2 },
{ 0, 3 },
{ 0, 4 },
{ 1, 4 },
};
int block_count = arraysize(kBlockInfo);
scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
CreateClusterExpectations(0, kBlockInfo, block_count, &client_);
const uint8* data = cluster->data();
int size = cluster->size();
int default_parse_size = 3;
WebMListParser parser(kWebMIdCluster);
int parse_size = std::min(default_parse_size, size);
while (size > 0) {
int result = parser.Parse(data, parse_size, &client_);
EXPECT_GE(result, 0);
EXPECT_LE(result, parse_size);
if (result == 0) {
// The parser needs more data so increase the parse_size a little.
EXPECT_FALSE(parser.IsParsingComplete());
parse_size += default_parse_size;
parse_size = std::min(parse_size, size);
continue;
}
parse_size = default_parse_size;
data += result;
size -= result;
EXPECT_EQ((size == 0), parser.IsParsingComplete());
}
EXPECT_TRUE(parser.IsParsingComplete());
}
} // namespace media
......@@ -9,21 +9,39 @@
namespace media {
static const base::TimeDelta kNoDuration =
base::TimeDelta::FromMicroseconds(-1);
WebMTracksParser::WebMTracksParser(int64 timecode_scale)
: timecode_scale_(timecode_scale),
track_type_(-1),
track_num_(-1),
track_default_duration_(-1),
audio_track_num_(-1),
audio_default_duration_(base::TimeDelta::FromMicroseconds(-1)),
audio_default_duration_(kNoDuration),
video_track_num_(-1),
video_default_duration_(base::TimeDelta::FromMicroseconds(-1)) {
video_default_duration_(kNoDuration) {
}
WebMTracksParser::~WebMTracksParser() {}
int WebMTracksParser::Parse(const uint8* buf, int size) {
return WebMParseListElement(buf, size, kWebMIdTracks, 1, this);
track_type_ =-1;
track_num_ = -1;
track_default_duration_ = -1;
audio_track_num_ = -1;
audio_default_duration_ = kNoDuration;
video_track_num_ = -1;
video_default_duration_ = kNoDuration;
WebMListParser parser(kWebMIdTracks);
int result = parser.Parse(buf, size, this);
if (result <= 0)
return result;
// For now we do all or nothing parsing.
return parser.IsParsingComplete() ? result : 0;
}
......@@ -40,9 +58,9 @@ bool WebMTracksParser::OnListStart(int id) {
bool WebMTracksParser::OnListEnd(int id) {
if (id == kWebMIdTrackEntry) {
if (track_type_ == -1 || track_num_ == -1) {
VLOG(1) << "Missing TrackEntry data"
<< " TrackType " << track_type_
<< " TrackNum " << track_num_;
DVLOG(1) << "Missing TrackEntry data"
<< " TrackType " << track_type_
<< " TrackNum " << track_num_;
return false;
}
......@@ -57,7 +75,7 @@ bool WebMTracksParser::OnListEnd(int id) {
audio_track_num_ = track_num_;
audio_default_duration_ = default_duration;
} else {
VLOG(1) << "Unexpected TrackType " << track_type_;
DVLOG(1) << "Unexpected TrackType " << track_type_;
return false;
}
......@@ -86,7 +104,7 @@ bool WebMTracksParser::OnUInt(int id, int64 val) {
}
if (*dst != -1) {
VLOG(1) << "Multiple values for id " << std::hex << id << " specified";
DVLOG(1) << "Multiple values for id " << std::hex << id << " specified";
return false;
}
......@@ -95,7 +113,7 @@ bool WebMTracksParser::OnUInt(int id, int64 val) {
}
bool WebMTracksParser::OnFloat(int id, double val) {
VLOG(1) << "Unexpected float for id" << std::hex << id;
DVLOG(1) << "Unexpected float for id" << std::hex << id;
return false;
}
......@@ -108,7 +126,7 @@ bool WebMTracksParser::OnString(int id, const std::string& str) {
return false;
if (str != "A_VORBIS" && str != "V_VP8") {
VLOG(1) << "Unexpected CodecID " << str;
DVLOG(1) << "Unexpected CodecID " << str;
return false;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment