summaryrefslogtreecommitdiffstats
path: root/media/formats
diff options
context:
space:
mode:
authorwolenetz@chromium.org <wolenetz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-28 13:31:13 +0000
committerwolenetz@chromium.org <wolenetz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-28 13:31:13 +0000
commita3a5ddb0212025a3a10faad46deb1d9e46cba46f (patch)
tree3fe0d6c3ba9e1845f2285cfdd8b12c82ec852c13 /media/formats
parent3652585de27f6fc39a385aef701496d701d4463e (diff)
downloadchromium_src-a3a5ddb0212025a3a10faad46deb1d9e46cba46f.zip
chromium_src-a3a5ddb0212025a3a10faad46deb1d9e46cba46f.tar.gz
chromium_src-a3a5ddb0212025a3a10faad46deb1d9e46cba46f.tar.bz2
MSE: Make WebMClusterParser hold back buffers at or beyond buffer missing duration
If a buffer is missing duration and is being held back, then the cluster parser now holds back all other tracks' buffers that have same or higher (decode) timestamp. This keeps the timestamps emitted for a cluster monotonically non-decreasing and in same order as parsed, even across tracks within the cluster. Adds a related new WebMClusterParserTest, updates related ChunkDemuxerTests and removes an obsolete TODO now that bug 361786 is fixed by r265340. R=acolwell@chromium.org BUG=363421 TEST=All media_unittests and MSE http layout tests pass locally on Linux Review URL: https://codereview.chromium.org/239343007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266538 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/formats')
-rw-r--r--media/formats/webm/webm_cluster_parser.cc153
-rw-r--r--media/formats/webm/webm_cluster_parser.h100
-rw-r--r--media/formats/webm/webm_cluster_parser_unittest.cc104
3 files changed, 310 insertions, 47 deletions
diff --git a/media/formats/webm/webm_cluster_parser.cc b/media/formats/webm/webm_cluster_parser.cc
index 41bc8e3..3816fdb 100644
--- a/media/formats/webm/webm_cluster_parser.cc
+++ b/media/formats/webm/webm_cluster_parser.cc
@@ -42,14 +42,15 @@ WebMClusterParser::WebMClusterParser(
cluster_timecode_(-1),
cluster_start_time_(kNoTimestamp()),
cluster_ended_(false),
- audio_(audio_track_num, false, audio_default_duration),
- video_(video_track_num, true, video_default_duration),
+ audio_(audio_track_num, false, audio_default_duration, log_cb),
+ video_(video_track_num, true, video_default_duration, log_cb),
+ ready_buffer_upper_bound_(kNoTimestamp()),
log_cb_(log_cb) {
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
it != text_tracks.end();
++it) {
text_track_map_.insert(std::make_pair(
- it->first, Track(it->first, false, kNoTimestamp())));
+ it->first, Track(it->first, false, kNoTimestamp(), log_cb_)));
}
}
@@ -64,12 +65,14 @@ void WebMClusterParser::Reset() {
audio_.Reset();
video_.Reset();
ResetTextTracks();
+ ready_buffer_upper_bound_ = kNoTimestamp();
}
int WebMClusterParser::Parse(const uint8* buf, int size) {
- audio_.ClearBuffersButKeepLastIfMissingDuration();
- video_.ClearBuffersButKeepLastIfMissingDuration();
- ResetTextTracks();
+ audio_.ClearReadyBuffers();
+ video_.ClearReadyBuffers();
+ ClearTextTrackReadyBuffers();
+ ready_buffer_upper_bound_ = kNoTimestamp();
int result = parser_.Parse(buf, size);
@@ -105,29 +108,31 @@ int WebMClusterParser::Parse(const uint8* buf, int size) {
}
const WebMClusterParser::BufferQueue& WebMClusterParser::GetAudioBuffers() {
- if (cluster_ended_)
- audio_.ApplyDurationEstimateIfNeeded();
- return audio_.buffers();
+ if (ready_buffer_upper_bound_ == kNoTimestamp())
+ UpdateReadyBuffers();
+
+ return audio_.ready_buffers();
}
const WebMClusterParser::BufferQueue& WebMClusterParser::GetVideoBuffers() {
- if (cluster_ended_)
- video_.ApplyDurationEstimateIfNeeded();
- return video_.buffers();
+ if (ready_buffer_upper_bound_ == kNoTimestamp())
+ UpdateReadyBuffers();
+
+ return video_.ready_buffers();
}
const WebMClusterParser::TextBufferQueueMap&
WebMClusterParser::GetTextBuffers() {
+ if (ready_buffer_upper_bound_ == kNoTimestamp())
+ UpdateReadyBuffers();
+
// Translate our |text_track_map_| into |text_buffers_map_|, inserting rows in
- // the output only for non-empty text buffer queues in |text_track_map_|.
+ // the output only for non-empty ready_buffer() queues in |text_track_map_|.
text_buffers_map_.clear();
for (TextTrackMap::const_iterator itr = text_track_map_.begin();
itr != text_track_map_.end();
++itr) {
- // Per OnBlock(), all text buffers should already have valid durations, so
- // there is no need to call
- // itr->second.ApplyDurationEstimateIfNeeded() here.
- const BufferQueue& text_buffers = itr->second.buffers();
+ const BufferQueue& text_buffers = itr->second.ready_buffers();
if (!text_buffers.empty())
text_buffers_map_.insert(std::make_pair(itr->first, text_buffers));
}
@@ -418,18 +423,63 @@ bool WebMClusterParser::OnBlock(bool is_simple_block, int track_num,
return track->AddBuffer(buffer);
}
-WebMClusterParser::Track::Track(int track_num, bool is_video,
- base::TimeDelta default_duration)
+WebMClusterParser::Track::Track(int track_num,
+ bool is_video,
+ base::TimeDelta default_duration,
+ const LogCB& log_cb)
: track_num_(track_num),
is_video_(is_video),
default_duration_(default_duration),
- estimated_next_frame_duration_(kNoTimestamp()) {
+ estimated_next_frame_duration_(kNoTimestamp()),
+ log_cb_(log_cb) {
DCHECK(default_duration_ == kNoTimestamp() ||
default_duration_ > base::TimeDelta());
}
WebMClusterParser::Track::~Track() {}
+base::TimeDelta WebMClusterParser::Track::GetReadyUpperBound() {
+ DCHECK(ready_buffers_.empty());
+ if (last_added_buffer_missing_duration_)
+ return last_added_buffer_missing_duration_->GetDecodeTimestamp();
+
+ return kInfiniteDuration();
+}
+
+void WebMClusterParser::Track::ExtractReadyBuffers(
+ const base::TimeDelta before_timestamp) {
+ DCHECK(ready_buffers_.empty());
+ DCHECK(base::TimeDelta() <= before_timestamp);
+ DCHECK(kNoTimestamp() != before_timestamp);
+
+ if (buffers_.empty())
+ return;
+
+ if (buffers_.back()->GetDecodeTimestamp() < before_timestamp) {
+ // All of |buffers_| are ready.
+ ready_buffers_.swap(buffers_);
+ DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " All "
+ << ready_buffers_.size() << " are ready: before upper bound ts "
+ << before_timestamp.InSecondsF();
+ return;
+ }
+
+ // Not all of |buffers_| are ready yet. Move any that are ready to
+ // |ready_buffers_|.
+ while (true) {
+ const scoped_refptr<StreamParserBuffer>& buffer = buffers_.front();
+ if (buffer->GetDecodeTimestamp() >= before_timestamp)
+ break;
+ ready_buffers_.push_back(buffer);
+ buffers_.pop_front();
+ DCHECK(!buffers_.empty());
+ }
+
+ DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " Only "
+ << ready_buffers_.size() << " ready, " << buffers_.size()
+ << " at or after upper bound ts " << before_timestamp.InSecondsF();
+}
+
bool WebMClusterParser::Track::AddBuffer(
const scoped_refptr<StreamParserBuffer>& buffer) {
DVLOG(2) << "AddBuffer() : " << track_num_
@@ -486,14 +536,15 @@ void WebMClusterParser::Track::ApplyDurationEstimateIfNeeded() {
last_added_buffer_missing_duration_ = NULL;
}
-void WebMClusterParser::Track::ClearBuffersButKeepLastIfMissingDuration() {
- // Note that |estimated_next_frame_duration_| is not reset, so it can be
- // reused on subsequent buffers added to this instance.
- buffers_.clear();
+void WebMClusterParser::Track::ClearReadyBuffers() {
+ // Note that |buffers_| are kept and |estimated_next_frame_duration_| is not
+ // reset here.
+ ready_buffers_.clear();
}
void WebMClusterParser::Track::Reset() {
- ClearBuffersButKeepLastIfMissingDuration();
+ ClearReadyBuffers();
+ buffers_.clear();
last_added_buffer_missing_duration_ = NULL;
}
@@ -523,10 +574,17 @@ bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const {
bool WebMClusterParser::Track::QueueBuffer(
const scoped_refptr<StreamParserBuffer>& buffer) {
DCHECK(!last_added_buffer_missing_duration_);
+
+ // WebMClusterParser::OnBlock() gives MEDIA_LOG and parse error on decreasing
+ // block timecode detection within a cluster. Therefore, we should not see
+ // those here.
+ base::TimeDelta previous_buffers_timestamp = buffers_.empty() ?
+ base::TimeDelta() : buffers_.back()->GetDecodeTimestamp();
+ CHECK(previous_buffers_timestamp <= buffer->GetDecodeTimestamp());
+
base::TimeDelta duration = buffer->duration();
if (duration < base::TimeDelta() || duration == kNoTimestamp()) {
- DVLOG(2) << "QueueBuffer() : Invalid buffer duration: "
- << duration.InSecondsF();
+ MEDIA_LOG(log_cb_) << "Invalid buffer duration: " << duration.InSecondsF();
return false;
}
@@ -566,15 +624,54 @@ base::TimeDelta WebMClusterParser::Track::GetDurationEstimate() {
return duration;
}
-void WebMClusterParser::ResetTextTracks() {
+void WebMClusterParser::ClearTextTrackReadyBuffers() {
text_buffers_map_.clear();
for (TextTrackMap::iterator it = text_track_map_.begin();
it != text_track_map_.end();
++it) {
+ it->second.ClearReadyBuffers();
+ }
+}
+
+void WebMClusterParser::ResetTextTracks() {
+ ClearTextTrackReadyBuffers();
+ for (TextTrackMap::iterator it = text_track_map_.begin();
+ it != text_track_map_.end();
+ ++it) {
it->second.Reset();
}
}
+void WebMClusterParser::UpdateReadyBuffers() {
+ DCHECK(ready_buffer_upper_bound_ == kNoTimestamp());
+ DCHECK(text_buffers_map_.empty());
+
+ if (cluster_ended_) {
+ audio_.ApplyDurationEstimateIfNeeded();
+ video_.ApplyDurationEstimateIfNeeded();
+ // Per OnBlock(), all text buffers should already have valid durations, so
+ // there is no need to call ApplyDurationEstimateIfNeeded() on text tracks
+ // here.
+ ready_buffer_upper_bound_ = kInfiniteDuration();
+ DCHECK(ready_buffer_upper_bound_ == audio_.GetReadyUpperBound());
+ DCHECK(ready_buffer_upper_bound_ == video_.GetReadyUpperBound());
+ } else {
+ ready_buffer_upper_bound_ = std::min(audio_.GetReadyUpperBound(),
+ video_.GetReadyUpperBound());
+ DCHECK(base::TimeDelta() <= ready_buffer_upper_bound_);
+ DCHECK(kNoTimestamp() != ready_buffer_upper_bound_);
+ }
+
+ // Prepare each track's ready buffers for retrieval.
+ audio_.ExtractReadyBuffers(ready_buffer_upper_bound_);
+ video_.ExtractReadyBuffers(ready_buffer_upper_bound_);
+ for (TextTrackMap::iterator itr = text_track_map_.begin();
+ itr != text_track_map_.end();
+ ++itr) {
+ itr->second.ExtractReadyBuffers(ready_buffer_upper_bound_);
+ }
+}
+
WebMClusterParser::Track*
WebMClusterParser::FindTextTrack(int track_num) {
const TextTrackMap::iterator it = text_track_map_.find(track_num);
diff --git a/media/formats/webm/webm_cluster_parser.h b/media/formats/webm/webm_cluster_parser.h
index add21f2..ab1d4a1 100644
--- a/media/formats/webm/webm_cluster_parser.h
+++ b/media/formats/webm/webm_cluster_parser.h
@@ -23,6 +23,8 @@ namespace media {
class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
public:
typedef StreamParser::TrackId TrackId;
+ typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
+ typedef std::map<TrackId, const BufferQueue> TextBufferQueueMap;
// Arbitrarily-chosen numbers to estimate the duration of a buffer if none is
// set and there is not enough information to get a better estimate.
@@ -37,13 +39,24 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
// Helper class that manages per-track state.
class Track {
public:
- Track(int track_num, bool is_video, base::TimeDelta default_duration);
+ Track(int track_num,
+ bool is_video,
+ base::TimeDelta default_duration,
+ const LogCB& log_cb);
~Track();
int track_num() const { return track_num_; }
- const std::deque<scoped_refptr<StreamParserBuffer> >& buffers() const {
- return buffers_;
- }
+
+ // If a buffer is currently held aside pending duration calculation, returns
+ // its decode timestamp. Otherwise, returns kInfiniteDuration().
+ base::TimeDelta GetReadyUpperBound();
+
+ // Prepares |ready_buffers_| for retrieval. Prior to calling,
+ // |ready_buffers_| must be empty. Moves all |buffers_| with timestamp
+ // before |before_timestamp| to |ready_buffers_|, preserving their order.
+ void ExtractReadyBuffers(const base::TimeDelta before_timestamp);
+
+ const BufferQueue& ready_buffers() const { return ready_buffers_; }
// If |last_added_buffer_missing_duration_| is set, updates its duration
// relative to |buffer|'s timestamp, and adds it to |buffers_| and unsets
@@ -59,12 +72,14 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
// emit all buffers in a media segment before signaling end of segment.)
void ApplyDurationEstimateIfNeeded();
- // Clears all buffer state, except a possibly held-aside buffer that is
+ // Clears |ready_buffers_| (use ExtractReadyBuffers() to fill it again).
+ // Leaves as-is |buffers_| and any possibly held-aside buffer that is
// missing duration.
- void ClearBuffersButKeepLastIfMissingDuration();
+ void ClearReadyBuffers();
// Clears all buffer state, including any possibly held-aside buffer that
- // was missing duration.
+ // was missing duration, and all contents of |buffers_| and
+ // |ready_buffers_|.
void Reset();
// Helper function used to inspect block data to determine if the
@@ -87,27 +102,37 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
base::TimeDelta GetDurationEstimate();
int track_num_;
- std::deque<scoped_refptr<StreamParserBuffer> > buffers_;
bool is_video_;
+
+ // Parsed track buffers, each with duration and in (decode) timestamp order,
+ // that have not yet been extracted into |ready_buffers_|. Note that up to
+ // one additional buffer missing duration may be tracked by
+ // |last_added_buffer_missing_duration_|.
+ BufferQueue buffers_;
scoped_refptr<StreamParserBuffer> last_added_buffer_missing_duration_;
+ // Buffers in (decode) timestamp order that were previously parsed into and
+ // extracted from |buffers_|. Buffers are moved from |buffers_| to
+ // |ready_buffers_| by ExtractReadyBuffers() if they are below a specified
+ // upper bound timestamp. Track users can therefore extract only those
+ // parsed buffers which are "ready" for emission (all before some maximum
+ // timestamp).
+ BufferQueue ready_buffers_;
+
// If kNoTimestamp(), then |estimated_next_frame_duration_| will be used.
base::TimeDelta default_duration_;
+
// If kNoTimestamp(), then a default value will be used. This estimate is
// the maximum duration seen or derived so far for this track, and is valid
// only if |default_duration_| is kNoTimestamp().
- //
- // TODO(wolenetz): Add unittests for duration estimation and default
- // duration handling. http://crbug.com/361786 .
base::TimeDelta estimated_next_frame_duration_;
+
+ LogCB log_cb_;
};
typedef std::map<int, Track> TextTrackMap;
public:
- typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
- typedef std::map<TrackId, const BufferQueue> TextBufferQueueMap;
-
WebMClusterParser(int64 timecode_scale,
int audio_track_num,
base::TimeDelta audio_default_duration,
@@ -132,17 +157,32 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
base::TimeDelta cluster_start_time() const { return cluster_start_time_; }
- // Get the buffers resulting from Parse().
+ // Get the current ready buffers resulting from Parse().
// If the parse reached the end of cluster and the last buffer was held aside
// due to missing duration, the buffer is given an estimated duration and
// included in the result.
+ // Otherwise, if there are is a buffer held aside due to missing duration for
+ // any of the tracks, no buffers with same or greater (decode) timestamp will
+ // be included in the buffers.
+ // The returned deques are cleared by Parse() or Reset() and updated by the
+ // next calls to Get{Audio,Video}Buffers().
+ // If no Parse() or Reset() has occurred since the last call to Get{Audio,
+ // Video,Text}Buffers(), then the previous BufferQueue& is returned again
+ // without any recalculation.
const BufferQueue& GetAudioBuffers();
const BufferQueue& GetVideoBuffers();
// Constructs and returns a subset of |text_track_map_| containing only
- // tracks with non-empty buffer queues produced by the last Parse().
+ // tracks with non-empty buffer queues produced by the last Parse() and
+ // filtered to exclude any buffers that have (decode) timestamp same or
+ // greater than the lowest (decode) timestamp across all tracks of any buffer
+ // held aside due to missing duration (unless the end of cluster has been
+ // reached).
// The returned map is cleared by Parse() or Reset() and updated by the next
// call to GetTextBuffers().
+ // If no Parse() or Reset() has occurred since the last call to
+ // GetTextBuffers(), then the previous TextBufferQueueMap& is returned again
+ // without any recalculation.
const TextBufferQueueMap& GetTextBuffers();
// Returns true if the last Parse() call stopped at the end of a cluster.
@@ -166,6 +206,22 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
// Resets the Track objects associated with each text track.
void ResetTextTracks();
+ // Clears the the ready buffers associated with each text track.
+ void ClearTextTrackReadyBuffers();
+
+ // Helper method for Get{Audio,Video,Text}Buffers() that recomputes
+ // |ready_buffer_upper_bound_| and calls ExtractReadyBuffers() on each track.
+ // If |cluster_ended_| is true, first applies duration estimate if needed for
+ // |audio_| and |video_| and sets |ready_buffer_upper_bound_| to
+ // kInfiniteDuration(). Otherwise, sets |ready_buffer_upper_bound_| to the
+ // minimum upper bound across |audio_| and |video_|. (Text tracks can have no
+ // buffers missing duration, so they are not involved in calculating the upper
+ // bound.)
+ // Parse() or Reset() must be called between calls to UpdateReadyBuffers() to
+ // clear each track's ready buffers and to reset |ready_buffer_upper_bound_|
+ // to kNoTimestamp().
+ void UpdateReadyBuffers();
+
// Search for the indicated track_num among the text tracks. Returns NULL
// if that track num is not a text track.
Track* FindTextTrack(int track_num);
@@ -197,10 +253,18 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
TextTrackMap text_track_map_;
// Subset of |text_track_map_| maintained by GetTextBuffers(), and cleared by
- // ResetTextTracks(). Callers of GetTextBuffers() get a const-ref to this
- // member.
+ // ClearTextTrackReadyBuffers(). Callers of GetTextBuffers() get a const-ref
+ // to this member.
TextBufferQueueMap text_buffers_map_;
+ // Limits the range of buffers returned by Get{Audio,Video,Text}Buffers() to
+ // this exclusive upper bound. Set to kNoTimestamp(), meaning not yet
+ // calculated, by Reset() and Parse(). If kNoTimestamp(), then
+ // Get{Audio,Video,Text}Buffers() will calculate it to be the minimum (decode)
+ // timestamp across all tracks' |last_buffer_missing_duration_|, or
+ // kInfiniteDuration() if no buffers are currently missing duration.
+ base::TimeDelta ready_buffer_upper_bound_;
+
LogCB log_cb_;
DISALLOW_IMPLICIT_CONSTRUCTORS(WebMClusterParser);
diff --git a/media/formats/webm/webm_cluster_parser_unittest.cc b/media/formats/webm/webm_cluster_parser_unittest.cc
index 7364878..55dc791 100644
--- a/media/formats/webm/webm_cluster_parser_unittest.cc
+++ b/media/formats/webm/webm_cluster_parser_unittest.cc
@@ -117,6 +117,14 @@ static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
const WebMClusterParser::BufferQueue& text_buffers,
const BlockInfo* block_info,
int block_count) {
+ int buffer_count = audio_buffers.size() + video_buffers.size() +
+ text_buffers.size();
+ if (block_count != buffer_count) {
+ DVLOG(1) << __FUNCTION__ << " : block_count (" << block_count
+ << ") mismatches buffer_count (" << buffer_count << ")";
+ return false;
+ }
+
size_t audio_offset = 0;
size_t video_offset = 0;
size_t text_offset = 0;
@@ -142,8 +150,12 @@ static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
return false;
}
- if (*offset >= buffers->size())
+ if (*offset >= buffers->size()) {
+ DVLOG(1) << __FUNCTION__ << " : Too few buffers (" << buffers->size()
+ << ") for track_num (" << block_info[i].track_num
+ << "), expected at least " << *offset + 1 << " buffers";
return false;
+ }
scoped_refptr<StreamParserBuffer> buffer = (*buffers)[(*offset)++];
@@ -267,6 +279,96 @@ class WebMClusterParserTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
};
+TEST_F(WebMClusterParserTest, HeldBackBufferHoldsBackAllTracks) {
+ // If a buffer is missing duration and is being held back, then all other
+ // tracks' buffers that have same or higher (decode) timestamp should be held
+ // back too to keep the timestamps emitted for a cluster monotonically
+ // non-decreasing and in same order as parsed.
+ InSequence s;
+
+ // Reset the parser to have 3 tracks: text, video (no default frame duration),
+ // and audio (with a default frame duration).
+ TextTracks text_tracks;
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum),
+ TextTrackConfig(kTextSubtitles, "", "",
+ "")));
+ base::TimeDelta default_audio_duration =
+ base::TimeDelta::FromMilliseconds(kTestAudioFrameDefaultDurationInMs);
+ ASSERT_GE(default_audio_duration, base::TimeDelta());
+ ASSERT_NE(kNoTimestamp(), default_audio_duration);
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ default_audio_duration,
+ kVideoTrackNum,
+ kNoTimestamp(),
+ text_tracks,
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kBlockInfo[] = {
+ { kVideoTrackNum, 0, 33, true },
+ { kAudioTrackNum, 0, 23, false },
+ { kTextTrackNum, 10, 42, false },
+ { kAudioTrackNum, 23, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 33, 33, true },
+ { kAudioTrackNum, 36, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 66, 33, true },
+ { kAudioTrackNum, 70, kTestAudioFrameDefaultDurationInMs, true },
+ { kAudioTrackNum, 83, kTestAudioFrameDefaultDurationInMs, true },
+ };
+
+ const int kExpectedBuffersOnPartialCluster[] = {
+ 0, // Video simple block without DefaultDuration should be held back
+ 0, // Audio buffer ready, but not emitted because its TS >= held back video
+ 0, // Text buffer ready, but not emitted because its TS >= held back video
+ 0, // 2nd audio buffer ready, also not emitted for same reason as first
+ 4, // All previous buffers emitted, 2nd video held back with no duration
+ 4, // 2nd video still has no duration, 3rd audio ready but not emitted
+ 6, // All previous buffers emitted, 3rd video held back with no duration
+ 6, // 3rd video still has no duration, 4th audio ready but not emitted
+ 9, // Cluster end emits all buffers and 3rd video's duration is estimated
+ };
+
+ ASSERT_EQ(arraysize(kBlockInfo), arraysize(kExpectedBuffersOnPartialCluster));
+ int block_count = arraysize(kBlockInfo);
+
+ // Iteratively create a cluster containing the first N+1 blocks and parse all
+ // but the last byte of the cluster (except when N==|block_count|, just parse
+ // the whole cluster). Verify that the corresponding entry in
+ // |kExpectedBuffersOnPartialCluster| identifies the exact subset of
+ // |kBlockInfo| returned by the parser.
+ for (int i = 0; i < block_count; ++i) {
+ if (i > 0)
+ parser_->Reset();
+ // Since we don't know exactly the offsets of each block in the full
+ // cluster, build a cluster with exactly one additional block so that
+ // parse of all but one byte should deterministically parse all but the
+ // last full block. Don't |exceed block_count| blocks though.
+ int blocks_in_cluster = std::min(i + 2, block_count);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo,
+ blocks_in_cluster));
+ // Parse all but the last byte unless we need to parse the full cluster.
+ bool parse_full_cluster = i == (block_count - 1);
+ int result = parser_->Parse(cluster->data(), parse_full_cluster ?
+ cluster->size() : cluster->size() - 1);
+ if (parse_full_cluster) {
+ DVLOG(1) << "Verifying parse result of full cluster of "
+ << blocks_in_cluster << " blocks";
+ EXPECT_EQ(cluster->size(), result);
+ } else {
+ DVLOG(1) << "Verifying parse result of cluster of "
+ << blocks_in_cluster << " blocks with last block incomplete";
+ EXPECT_GT(cluster->size(), result);
+ EXPECT_LT(0, result);
+ }
+
+ EXPECT_TRUE(VerifyBuffers(parser_, kBlockInfo,
+ kExpectedBuffersOnPartialCluster[i]));
+ }
+}
+
TEST_F(WebMClusterParserTest, Reset) {
InSequence s;