Commit 29dcc87a authored by vrk@google.com's avatar vrk@google.com

Cap sourceBuffered() on duration and truncate duration on EoS

Adds logic + test to truncate the duration on Media Source streams when
EndOfStream is signalled. Also adds some logic to make sure buffered streams
never exceed the reported duration.

BUG=139899

Review URL: https://chromiumcodereview.appspot.com/10829108

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149702 0039d316-1c4b-4281-b951-d872f2087c98
parent a9d31ef4
......@@ -64,9 +64,6 @@ class MEDIA_EXPORT DemuxerStream
// if type() != VIDEO.
virtual const VideoDecoderConfig& video_decoder_config() = 0;
// Returns time ranges known to have been seen by this stream.
virtual Ranges<base::TimeDelta> GetBufferedRanges() = 0;
// Returns the type of stream.
virtual Type type() = 0;
......
......@@ -39,7 +39,7 @@ class Ranges {
void clear();
// Computes the intersection between this range and |other|.
Ranges<T> IntersectionWith(const Ranges<T>& other);
Ranges<T> IntersectionWith(const Ranges<T>& other) const;
private:
// Wrapper around DCHECK_LT allowing comparisons of operator<<'able T's.
......@@ -133,7 +133,7 @@ void Ranges<T>::clear() {
}
template<class T>
Ranges<T> Ranges<T>::IntersectionWith(const Ranges<T>& other) {
Ranges<T> Ranges<T>::IntersectionWith(const Ranges<T>& other) const {
Ranges<T> result;
size_t i = 0;
......
......@@ -176,6 +176,9 @@ class ChunkDemuxerStream : public DemuxerStream {
// Returns true if buffers were successfully added.
bool Append(const StreamParser::BufferQueue& buffers);
// Returns the range of buffered data in this stream, capped at |duration|.
Ranges<TimeDelta> GetBufferedRanges(base::TimeDelta duration) const;
// Signal to the stream that buffers handed in through subsequent calls to
// Append() belong to a media segment that starts at |start_timestamp|.
void OnNewMediaSegment(TimeDelta start_timestamp);
......@@ -199,7 +202,6 @@ class ChunkDemuxerStream : public DemuxerStream {
virtual void EnableBitstreamConverter() OVERRIDE;
virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
virtual Ranges<TimeDelta> GetBufferedRanges() OVERRIDE;
protected:
virtual ~ChunkDemuxerStream();
......@@ -318,9 +320,20 @@ bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) {
return true;
}
Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges() {
Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges(
base::TimeDelta duration) const {
base::AutoLock auto_lock(lock_);
return stream_->GetBufferedTime();
Ranges<TimeDelta> range = stream_->GetBufferedTime();
if (range.size() == 0u)
return range;
// Clamp the end of the stream's buffered ranges to fit within the duration.
// This can be done by intersecting the stream's range with the valid time
// range.
Ranges<TimeDelta> valid_time_range;
valid_time_range.Add(range.start(0), duration);
return range.IntersectionWith(valid_time_range);
}
bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig& config) {
......@@ -671,12 +684,12 @@ Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges(const std::string& id) const {
if (id == source_id_audio_ && id != source_id_video_) {
// Only include ranges that have been buffered in |audio_|
return audio_ ? audio_->GetBufferedRanges() : Ranges<TimeDelta>();
return audio_ ? audio_->GetBufferedRanges(duration_) : Ranges<TimeDelta>();
}
if (id != source_id_audio_ && id == source_id_video_) {
// Only include ranges that have been buffered in |video_|
return video_ ? video_->GetBufferedRanges() : Ranges<TimeDelta>();
return video_ ? video_->GetBufferedRanges(duration_) : Ranges<TimeDelta>();
}
return ComputeIntersection();
......@@ -689,8 +702,8 @@ Ranges<TimeDelta> ChunkDemuxer::ComputeIntersection() const {
return Ranges<TimeDelta>();
// Include ranges that have been buffered in both |audio_| and |video_|.
Ranges<TimeDelta> audio_ranges = audio_->GetBufferedRanges();
Ranges<TimeDelta> video_ranges = video_->GetBufferedRanges();
Ranges<TimeDelta> audio_ranges = audio_->GetBufferedRanges(duration_);
Ranges<TimeDelta> video_ranges = video_->GetBufferedRanges(duration_);
Ranges<TimeDelta> result = audio_ranges.IntersectionWith(video_ranges);
if (state_ == ENDED && result.size() > 0) {
......@@ -763,13 +776,7 @@ bool ChunkDemuxer::AppendData(const std::string& id,
std::swap(cb, seek_cb_);
}
if (audio_ && !video_) {
ranges = audio_->GetBufferedRanges();
} else if (!audio_ && video_) {
ranges = video_->GetBufferedRanges();
} else {
ranges = ComputeIntersection();
}
ranges = GetBufferedRanges();
}
for (size_t i = 0; i < ranges.size(); ++i)
......@@ -825,10 +832,12 @@ bool ChunkDemuxer::EndOfStream(PipelineStatus status) {
if (video_)
video_->EndOfStream();
if (status != PIPELINE_OK)
if (status != PIPELINE_OK) {
ReportError_Locked(status);
else
} else {
ChangeState_Locked(ENDED);
DecreaseDurationIfNecessary();
}
return true;
}
......@@ -1121,7 +1130,7 @@ void ChunkDemuxer::IncreaseDurationIfNecessary(
if (buffers.back()->GetTimestamp() <= duration_)
return;
Ranges<TimeDelta> ranges = stream->GetBufferedRanges();
Ranges<TimeDelta> ranges = stream->GetBufferedRanges(kInfiniteDuration());
DCHECK_GT(ranges.size(), 0u);
base::TimeDelta last_timestamp_buffered = ranges.end(ranges.size() - 1);
......@@ -1129,4 +1138,22 @@ void ChunkDemuxer::IncreaseDurationIfNecessary(
UpdateDuration(last_timestamp_buffered);
}
void ChunkDemuxer::DecreaseDurationIfNecessary() {
Ranges<TimeDelta> ranges = GetBufferedRanges();
if (ranges.size() == 0u)
return;
base::TimeDelta last_timestamp_buffered = ranges.end(ranges.size() - 1);
if (last_timestamp_buffered < duration_)
UpdateDuration(last_timestamp_buffered);
}
Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges() const {
if (audio_ && !video_)
return audio_->GetBufferedRanges(duration_);
else if (!audio_ && video_)
return video_->GetBufferedRanges(duration_);
return ComputeIntersection();
}
} // namespace media
......@@ -143,9 +143,16 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
const StreamParser::BufferQueue& buffers,
const scoped_refptr<ChunkDemuxerStream>& stream);
// Decreases |duration_| if the buffered region is less than |duration_| when
// EndOfStream() is called.
void DecreaseDurationIfNecessary();
// Sets |duration_| to |new_duration| and notifies |host_|.
void UpdateDuration(base::TimeDelta new_duration);
// Returns the ranges representing the buffered data in the demuxer.
Ranges<base::TimeDelta> GetBufferedRanges() const;
mutable base::Lock lock_;
State state_;
......
......@@ -47,6 +47,8 @@ static const int kVideoBlockDuration = 33;
static const char* kSourceId = "SourceId";
static const char* kDefaultFirstClusterRange = "{ [0,46) }";
static const int kDefaultFirstClusterEndTimestamp = 66;
static const int kDefaultSecondClusterEndTimestamp = 132;
base::TimeDelta kDefaultDuration() {
return base::TimeDelta::FromMilliseconds(201224);
......@@ -1100,6 +1102,8 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) {
end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
end_of_stream_helper_2.CheckIfReadDonesWereCalled(false);
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kVideoBlockDuration)));
demuxer_->EndOfStream(PIPELINE_OK);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(true);
......@@ -1139,6 +1143,8 @@ TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) {
EXPECT_TRUE(video_read_done_1);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kVideoBlockDuration)));
EXPECT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
end_of_stream_helper_1.CheckIfReadDonesWereCalled(true);
......@@ -1697,6 +1703,7 @@ TEST_F(ChunkDemuxerTest, GetBufferedRanges_EndOfStream) {
CheckExpectedRanges("{ [0,90) }");
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(100)));
demuxer_->EndOfStream(PIPELINE_OK);
CheckExpectedRanges("{ [0,100) }");
......@@ -1813,6 +1820,7 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamFailures) {
// Make sure that end of stream fails because there is a gap between
// the current position(0) and the end of the appended data.
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(50)));
ASSERT_FALSE(demuxer_->EndOfStream(PIPELINE_OK));
// Seek to an time that is inside the last ranges for both streams
......@@ -1863,6 +1871,8 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamDuringSeek) {
demuxer_->StartWaitingForSeek();
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kDefaultSecondClusterEndTimestamp)));
demuxer_->EndOfStream(PIPELINE_OK);
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
......@@ -2133,20 +2143,36 @@ TEST_F(ChunkDemuxerTest, TestTimestampOffsetMidParse) {
TEST_F(ChunkDemuxerTest, TestDurationChange) {
ASSERT_TRUE(InitDemuxer(true, true, false));
static const int kStreamDuration = kDefaultDuration().InMilliseconds();
// Add data leading up to the currently set duration.
scoped_ptr<Cluster> first_cluster = GenerateCluster(
kDefaultDuration().InMilliseconds() - kAudioBlockDuration,
kDefaultDuration().InMilliseconds() - kVideoBlockDuration, 2);
kStreamDuration - kAudioBlockDuration,
kStreamDuration - kVideoBlockDuration, 2);
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
// Now add data past the duration and expect a new duration to be signalled.
CheckExpectedRanges(kSourceId, "{ [201191,201224) }");
// Add data at the currently set duration. The duration should not increase.
scoped_ptr<Cluster> second_cluster = GenerateCluster(
kDefaultDuration().InMilliseconds(), 4);
EXPECT_CALL(host_, SetDuration(
kDefaultDuration() + base::TimeDelta::FromMilliseconds(
kAudioBlockDuration * 2)));
kDefaultDuration().InMilliseconds(), 2);
ASSERT_TRUE(AppendData(second_cluster->data(), second_cluster->size()));
// Range should not be affected.
CheckExpectedRanges(kSourceId, "{ [201191,201224) }");
// Now add data past the duration and expect a new duration to be signalled.
static const int kNewStreamDuration =
kStreamDuration + kAudioBlockDuration * 2;
scoped_ptr<Cluster> third_cluster = GenerateCluster(
kStreamDuration + kAudioBlockDuration,
kStreamDuration + kVideoBlockDuration, 2);
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kNewStreamDuration)));
ASSERT_TRUE(AppendData(third_cluster->data(), third_cluster->size()));
// See that the range has increased appropriately.
CheckExpectedRanges(kSourceId, "{ [201191,201270) }");
}
TEST_F(ChunkDemuxerTest, TestDurationChangeTimestampOffset) {
......@@ -2162,4 +2188,15 @@ TEST_F(ChunkDemuxerTest, TestDurationChangeTimestampOffset) {
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamTruncateDuration) {
ASSERT_TRUE(InitDemuxer(true, true, false));
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kDefaultFirstClusterEndTimestamp)));
demuxer_->EndOfStream(PIPELINE_OK);
}
} // namespace media
......@@ -32,10 +32,6 @@ void DummyDemuxerStream::Read(const ReadCB& read_cb) {}
void DummyDemuxerStream::EnableBitstreamConverter() {}
Ranges<base::TimeDelta> DummyDemuxerStream::GetBufferedRanges() {
return Ranges<base::TimeDelta>();
}
DummyDemuxer::DummyDemuxer(bool has_video, bool has_audio) {
streams_.resize(DemuxerStream::NUM_TYPES);
if (has_audio)
......
......@@ -28,7 +28,6 @@ class DummyDemuxerStream : public DemuxerStream {
virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
virtual void EnableBitstreamConverter() OVERRIDE;
virtual Ranges<base::TimeDelta> GetBufferedRanges() OVERRIDE;
protected:
virtual ~DummyDemuxerStream();
......
......@@ -237,7 +237,7 @@ base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const {
return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts);
}
Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() {
Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const {
base::AutoLock auto_lock(lock_);
return buffered_ranges_;
}
......@@ -323,7 +323,12 @@ void FFmpegDemuxer::Initialize(DemuxerHost* host,
scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(
DemuxerStream::Type type) {
StreamVector::iterator iter;
return GetFFmpegStream(type);
}
scoped_refptr<FFmpegDemuxerStream> FFmpegDemuxer::GetFFmpegStream(
DemuxerStream::Type type) const {
StreamVector::const_iterator iter;
for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
if (*iter && (*iter)->type() == type) {
return *iter;
......@@ -717,9 +722,10 @@ void FFmpegDemuxer::SignalReadCompleted(int size) {
void FFmpegDemuxer::NotifyBufferingChanged() {
DCHECK_EQ(MessageLoop::current(), message_loop_);
Ranges<base::TimeDelta> buffered;
scoped_refptr<DemuxerStream> audio =
audio_disabled_ ? NULL : GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video = GetStream(DemuxerStream::VIDEO);
scoped_refptr<FFmpegDemuxerStream> audio =
audio_disabled_ ? NULL : GetFFmpegStream(DemuxerStream::AUDIO);
scoped_refptr<FFmpegDemuxerStream> video =
GetFFmpegStream(DemuxerStream::VIDEO);
if (audio && video) {
buffered = audio->GetBufferedRanges().IntersectionWith(
video->GetBufferedRanges());
......
......@@ -84,7 +84,9 @@ class FFmpegDemuxerStream : public DemuxerStream {
virtual void EnableBitstreamConverter() OVERRIDE;
virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
virtual Ranges<base::TimeDelta> GetBufferedRanges() OVERRIDE;
// Returns the range of buffered data in this stream.
Ranges<base::TimeDelta> GetBufferedRanges() const;
// Returns elapsed time based on the already queued packets.
// Used to determine stream duration when it's not known ahead of time.
......@@ -210,6 +212,11 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer, public FFmpegURLProtocol {
// read or kReadError in case of error.
virtual void SignalReadCompleted(int size);
// Returns the stream from |streams_| that matches |type| as an
// FFmpegDemuxerStream.
scoped_refptr<FFmpegDemuxerStream> GetFFmpegStream(
DemuxerStream::Type type) const;
DemuxerHost* host_;
MessageLoop* message_loop_;
......
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