Commit 950c3b3d authored by vrk@google.com's avatar vrk@google.com

Chrome-side implementation of media source timestamp offset

Adds functionality to signal an offset to be applied to the
buffers in ChunkDemuxer. Is not triggerable from Chrome yet.

BUG=139044
TEST=media_unittests

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148810 0039d316-1c4b-4281-b951-d872f2087c98
parent 3b693bdd
...@@ -74,7 +74,8 @@ class MEDIA_EXPORT StreamParser { ...@@ -74,7 +74,8 @@ class MEDIA_EXPORT StreamParser {
const NewBuffersCB& audio_cb, const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb, const NewBuffersCB& video_cb,
const NeedKeyCB& need_key_cb, const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb) = 0; const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb) = 0;
// Called when a seek occurs. This flushes the current parser state // Called when a seek occurs. This flushes the current parser state
// and puts the parser in a state where it can receive data for the new seek // and puts the parser in a state where it can receive data for the new seek
......
...@@ -111,7 +111,7 @@ static bool IsSupported(const std::string& type, ...@@ -111,7 +111,7 @@ static bool IsSupported(const std::string& type,
*has_audio = false; *has_audio = false;
*has_video = false; *has_video = false;
// Search for the SupportedTypeInfo for |type| // Search for the SupportedTypeInfo for |type|.
for (size_t i = 0; i < arraysize(kSupportedTypeInfo); ++i) { for (size_t i = 0; i < arraysize(kSupportedTypeInfo); ++i) {
const SupportedTypeInfo& type_info = kSupportedTypeInfo[i]; const SupportedTypeInfo& type_info = kSupportedTypeInfo[i];
if (type == type_info.type) { if (type == type_info.type) {
...@@ -637,19 +637,24 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id, ...@@ -637,19 +637,24 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
audio_cb, audio_cb,
video_cb, video_cb,
base::Bind(&ChunkDemuxer::OnNeedKey, base::Unretained(this)), base::Bind(&ChunkDemuxer::OnNeedKey, base::Unretained(this)),
base::Bind(&ChunkDemuxer::OnNewMediaSegment, base::Unretained(this), id)); base::Bind(&ChunkDemuxer::OnNewMediaSegment, base::Unretained(this), id),
base::Bind(&ChunkDemuxer::OnEndOfMediaSegment,
base::Unretained(this), id));
stream_parser_map_[id] = stream_parser.release(); stream_parser_map_[id] = stream_parser.release();
SourceInfo info = { base::TimeDelta(), true };
source_info_map_[id] = info;
return kOk; return kOk;
} }
void ChunkDemuxer::RemoveId(const std::string& id) { void ChunkDemuxer::RemoveId(const std::string& id) {
CHECK_GT(stream_parser_map_.count(id), 0u); CHECK(IsValidId(id));
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
delete stream_parser_map_[id]; delete stream_parser_map_[id];
stream_parser_map_.erase(id); stream_parser_map_.erase(id);
source_info_map_.erase(id);
if (source_id_audio_ == id && audio_) if (source_id_audio_ == id && audio_)
audio_->Shutdown(); audio_->Shutdown();
...@@ -660,7 +665,7 @@ void ChunkDemuxer::RemoveId(const std::string& id) { ...@@ -660,7 +665,7 @@ void ChunkDemuxer::RemoveId(const std::string& id) {
Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges(const std::string& id) const { Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges(const std::string& id) const {
DCHECK(!id.empty()); DCHECK(!id.empty());
DCHECK_GT(stream_parser_map_.count(id), 0u); DCHECK(IsValidId(id));
DCHECK(id == source_id_audio_ || id == source_id_video_); DCHECK(id == source_id_audio_ || id == source_id_video_);
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
...@@ -730,7 +735,7 @@ bool ChunkDemuxer::AppendData(const std::string& id, ...@@ -730,7 +735,7 @@ bool ChunkDemuxer::AppendData(const std::string& id,
switch (state_) { switch (state_) {
case INITIALIZING: case INITIALIZING:
case WAITING_FOR_START_TIME: case WAITING_FOR_START_TIME:
DCHECK_GT(stream_parser_map_.count(id), 0u); DCHECK(IsValidId(id));
if (!stream_parser_map_[id]->Parse(data, length)) { if (!stream_parser_map_[id]->Parse(data, length)) {
ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return true; return true;
...@@ -738,7 +743,7 @@ bool ChunkDemuxer::AppendData(const std::string& id, ...@@ -738,7 +743,7 @@ bool ChunkDemuxer::AppendData(const std::string& id,
break; break;
case INITIALIZED: { case INITIALIZED: {
DCHECK_GT(stream_parser_map_.count(id), 0u); DCHECK(IsValidId(id));
if (!stream_parser_map_[id]->Parse(data, length)) { if (!stream_parser_map_[id]->Parse(data, length)) {
ReportError_Locked(PIPELINE_ERROR_DECODE); ReportError_Locked(PIPELINE_ERROR_DECODE);
return true; return true;
...@@ -780,11 +785,24 @@ bool ChunkDemuxer::AppendData(const std::string& id, ...@@ -780,11 +785,24 @@ bool ChunkDemuxer::AppendData(const std::string& id,
void ChunkDemuxer::Abort(const std::string& id) { void ChunkDemuxer::Abort(const std::string& id) {
DVLOG(1) << "Abort(" << id << ")"; DVLOG(1) << "Abort(" << id << ")";
DCHECK(!id.empty()); DCHECK(!id.empty());
DCHECK_GT(stream_parser_map_.count(id), 0u); CHECK(IsValidId(id));
stream_parser_map_[id]->Flush(); stream_parser_map_[id]->Flush();
} }
bool ChunkDemuxer::SetTimestampOffset(const std::string& id, double offset) {
DVLOG(1) << "SetTimestampOffset(" << id << ", " << offset << ")";
CHECK(IsValidId(id));
if (!source_info_map_[id].can_update_offset)
return false;
TimeDelta time_offset = TimeDelta::FromMicroseconds(
offset * base::Time::kMicrosecondsPerSecond);
source_info_map_[id].timestamp_offset = time_offset;
return true;
}
bool ChunkDemuxer::EndOfStream(PipelineStatus status) { bool ChunkDemuxer::EndOfStream(PipelineStatus status) {
DVLOG(1) << "EndOfStream(" << status << ")"; DVLOG(1) << "EndOfStream(" << status << ")";
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
...@@ -983,6 +1001,10 @@ bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) { ...@@ -983,6 +1001,10 @@ bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) {
if (!audio_) if (!audio_)
return false; return false;
CHECK(IsValidId(source_id_audio_));
AdjustBufferTimestamps(
buffers, source_info_map_[source_id_audio_].timestamp_offset);
return audio_->Append(buffers); return audio_->Append(buffers);
} }
...@@ -993,6 +1015,10 @@ bool ChunkDemuxer::OnVideoBuffers(const StreamParser::BufferQueue& buffers) { ...@@ -993,6 +1015,10 @@ bool ChunkDemuxer::OnVideoBuffers(const StreamParser::BufferQueue& buffers) {
if (!video_) if (!video_)
return false; return false;
CHECK(IsValidId(source_id_video_));
AdjustBufferTimestamps(
buffers, source_info_map_[source_id_video_].timestamp_offset);
return video_->Append(buffers); return video_->Append(buffers);
} }
...@@ -1003,11 +1029,16 @@ bool ChunkDemuxer::OnNeedKey(scoped_array<uint8> init_data, ...@@ -1003,11 +1029,16 @@ bool ChunkDemuxer::OnNeedKey(scoped_array<uint8> init_data,
} }
void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id, void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id,
TimeDelta start_timestamp) { TimeDelta timestamp) {
DVLOG(2) << "OnNewMediaSegment(" << source_id << ", " DVLOG(2) << "OnNewMediaSegment(" << source_id << ", "
<< start_timestamp.InSecondsF() << ")"; << timestamp.InSecondsF() << ")";
lock_.AssertAcquired(); lock_.AssertAcquired();
CHECK(IsValidId(source_id));
source_info_map_[source_id].can_update_offset = false;
base::TimeDelta start_timestamp =
timestamp + source_info_map_[source_id].timestamp_offset;
if (start_time_ == kNoTimestamp()) { if (start_time_ == kNoTimestamp()) {
DCHECK(state_ == INITIALIZING || state_ == WAITING_FOR_START_TIME); DCHECK(state_ == INITIALIZING || state_ == WAITING_FOR_START_TIME);
// Use the first reported media segment start time as the |start_time_| // Use the first reported media segment start time as the |start_time_|
...@@ -1037,4 +1068,29 @@ void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id, ...@@ -1037,4 +1068,29 @@ void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id,
base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
} }
void ChunkDemuxer::OnEndOfMediaSegment(const std::string& source_id) {
DVLOG(2) << "OnEndOfMediaSegment(" << source_id << ")";
CHECK(IsValidId(source_id));
source_info_map_[source_id].can_update_offset = true;
}
void ChunkDemuxer::AdjustBufferTimestamps(
const StreamParser::BufferQueue& buffers,
base::TimeDelta timestamp_offset) {
if (timestamp_offset == base::TimeDelta())
return;
for (StreamParser::BufferQueue::const_iterator itr = buffers.begin();
itr != buffers.end(); ++itr) {
(*itr)->SetDecodeTimestamp(
(*itr)->GetDecodeTimestamp() + timestamp_offset);
(*itr)->SetTimestamp((*itr)->GetTimestamp() + timestamp_offset);
}
}
bool ChunkDemuxer::IsValidId(const std::string& source_id) const {
return source_info_map_.count(source_id) > 0u &&
stream_parser_map_.count(source_id) > 0u;
}
} // namespace media } // namespace media
...@@ -74,6 +74,12 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { ...@@ -74,6 +74,12 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
// it can accept a new segment. // it can accept a new segment.
void Abort(const std::string& id); void Abort(const std::string& id);
// Sets a time |offset| in seconds to be applied to subsequent buffers
// appended to the source buffer assicated with |id|. Returns true if the
// offset is set properly, false if the offset cannot be applied because we're
// in the middle of parsing a media segment.
bool SetTimestampOffset(const std::string& id, double offset);
// Signals an EndOfStream request. // Signals an EndOfStream request.
// Returns false if called in an unexpected state or if there is a gap between // Returns false if called in an unexpected state or if there is a gap between
// the current position and the end of the buffered data. // the current position and the end of the buffered data.
...@@ -117,11 +123,19 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { ...@@ -117,11 +123,19 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
bool OnNeedKey(scoped_array<uint8> init_data, int init_data_size); bool OnNeedKey(scoped_array<uint8> init_data, int init_data_size);
void OnNewMediaSegment(const std::string& source_id, void OnNewMediaSegment(const std::string& source_id,
base::TimeDelta start_timestamp); base::TimeDelta start_timestamp);
void OnEndOfMediaSegment(const std::string& source_id);
// Computes the intersection between the video & audio // Computes the intersection between the video & audio
// buffered ranges. // buffered ranges.
Ranges<base::TimeDelta> ComputeIntersection() const; Ranges<base::TimeDelta> ComputeIntersection() const;
// Applies |time_offset| to the timestamps of |buffers|.
void AdjustBufferTimestamps(const StreamParser::BufferQueue& buffers,
base::TimeDelta timestamp_offset);
// Returns true if |source_id| is valid, false otherwise.
bool IsValidId(const std::string& source_id) const;
mutable base::Lock lock_; mutable base::Lock lock_;
State state_; State state_;
...@@ -138,6 +152,14 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { ...@@ -138,6 +152,14 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
typedef std::map<std::string, StreamParser*> StreamParserMap; typedef std::map<std::string, StreamParser*> StreamParserMap;
StreamParserMap stream_parser_map_; StreamParserMap stream_parser_map_;
// Contains state belonging to a source id.
struct SourceInfo {
base::TimeDelta timestamp_offset;
bool can_update_offset;
};
typedef std::map<std::string, SourceInfo> SourceInfoMap;
SourceInfoMap source_info_map_;
// Used to ensure that (1) config data matches the type and codec provided in // Used to ensure that (1) config data matches the type and codec provided in
// AddId(), (2) only 1 audio and 1 video sources are added, and (3) ids may be // AddId(), (2) only 1 audio and 1 video sources are added, and (3) ids may be
// removed with RemoveID() but can not be re-added (yet). // removed with RemoveID() but can not be re-added (yet).
......
...@@ -2064,4 +2064,70 @@ TEST_F(ChunkDemuxerTest, TestConfigChange_Seek) { ...@@ -2064,4 +2064,70 @@ TEST_F(ChunkDemuxerTest, TestConfigChange_Seek) {
ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config())); ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
} }
TEST_F(ChunkDemuxerTest, TestTimestampPositiveOffset) {
ASSERT_TRUE(InitDemuxer(true, true, false));
ASSERT_TRUE(demuxer_->SetTimestampOffset(kSourceId, 30));
scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
GenerateExpectedReads(30000, 2, audio, video);
}
TEST_F(ChunkDemuxerTest, TestTimestampNegativeOffset) {
ASSERT_TRUE(InitDemuxer(true, true, false));
ASSERT_TRUE(demuxer_->SetTimestampOffset(kSourceId, -1));
scoped_ptr<Cluster> cluster = GenerateCluster(1000, 2);
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
GenerateExpectedReads(0, 2, audio, video);
}
TEST_F(ChunkDemuxerTest, TestTimestampOffsetSeparateStreams) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
scoped_ptr<Cluster> cluster_a(
GenerateSingleStreamCluster(
2500, 2500 + kAudioBlockDuration * 4, kAudioTrackNum,
kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v(
GenerateSingleStreamCluster(
0, kVideoBlockDuration * 4, kVideoTrackNum, kVideoBlockDuration));
ASSERT_TRUE(demuxer_->SetTimestampOffset(audio_id, -2.5));
ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
ASSERT_TRUE(demuxer_->SetTimestampOffset(video_id, 27.3));
ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
GenerateSingleStreamExpectedReads(27300, 4, video, kVideoBlockDuration);
}
TEST_F(ChunkDemuxerTest, TestTimestampOffsetMidParse) {
ASSERT_TRUE(InitDemuxer(true, true, false));
scoped_ptr<Cluster> cluster = GenerateCluster(0, 2);
// Append only part of the cluster data.
ASSERT_TRUE(AppendData(cluster->data(), cluster->size() - 13));
// Setting a timestamp should fail because we're in the middle of a cluster.
ASSERT_FALSE(demuxer_->SetTimestampOffset(kSourceId, 25));
}
} // namespace media } // namespace media
...@@ -982,7 +982,7 @@ SourceBufferRange* SourceBufferRange::SplitRange(base::TimeDelta timestamp) { ...@@ -982,7 +982,7 @@ SourceBufferRange* SourceBufferRange::SplitRange(base::TimeDelta timestamp) {
SourceBufferRange::KeyframeMap::iterator SourceBufferRange::KeyframeMap::iterator
SourceBufferRange::GetFirstKeyframeAt(base::TimeDelta timestamp, SourceBufferRange::GetFirstKeyframeAt(base::TimeDelta timestamp,
bool skip_given_timestamp) { bool skip_given_timestamp) {
KeyframeMap::iterator result = keyframe_map_.lower_bound(timestamp); KeyframeMap::iterator result = keyframe_map_.lower_bound(timestamp);
// lower_bound() returns the first element >= |timestamp|, so if we don't want // lower_bound() returns the first element >= |timestamp|, so if we don't want
// to include keyframes == |timestamp|, we have to increment the iterator // to include keyframes == |timestamp|, we have to increment the iterator
......
...@@ -37,13 +37,15 @@ void MP4StreamParser::Init(const InitCB& init_cb, ...@@ -37,13 +37,15 @@ void MP4StreamParser::Init(const InitCB& init_cb,
const NewBuffersCB& audio_cb, const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb, const NewBuffersCB& video_cb,
const NeedKeyCB& need_key_cb, const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb) { const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb) {
DCHECK_EQ(state_, kWaitingForInit); DCHECK_EQ(state_, kWaitingForInit);
DCHECK(init_cb_.is_null()); DCHECK(init_cb_.is_null());
DCHECK(!init_cb.is_null()); DCHECK(!init_cb.is_null());
DCHECK(!config_cb.is_null()); DCHECK(!config_cb.is_null());
DCHECK(!audio_cb.is_null() || !video_cb.is_null()); DCHECK(!audio_cb.is_null() || !video_cb.is_null());
DCHECK(!need_key_cb.is_null()); DCHECK(!need_key_cb.is_null());
DCHECK(!end_of_segment_cb.is_null());
ChangeState(kParsingBoxes); ChangeState(kParsingBoxes);
init_cb_ = init_cb; init_cb_ = init_cb;
...@@ -52,6 +54,7 @@ void MP4StreamParser::Init(const InitCB& init_cb, ...@@ -52,6 +54,7 @@ void MP4StreamParser::Init(const InitCB& init_cb,
video_cb_ = video_cb; video_cb_ = video_cb;
need_key_cb_ = need_key_cb; need_key_cb_ = need_key_cb;
new_segment_cb_ = new_segment_cb; new_segment_cb_ = new_segment_cb;
end_of_segment_cb_ = end_of_segment_cb;
} }
void MP4StreamParser::Flush() { void MP4StreamParser::Flush() {
...@@ -316,8 +319,11 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, ...@@ -316,8 +319,11 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
// Flush any buffers we've gotten in this chunk so that buffers don't // Flush any buffers we've gotten in this chunk so that buffers don't
// cross NewSegment() calls // cross NewSegment() calls
*err = !SendAndFlushSamples(audio_buffers, video_buffers); *err = !SendAndFlushSamples(audio_buffers, video_buffers);
if (*err) return false; if (*err)
return false;
ChangeState(kParsingBoxes); ChangeState(kParsingBoxes);
end_of_segment_cb_.Run();
return true; return true;
} }
...@@ -337,7 +343,8 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, ...@@ -337,7 +343,8 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
bool video = has_video_ && video_track_id_ == runs_->track_id(); bool video = has_video_ && video_track_id_ == runs_->track_id();
// Skip this entire track if it's not one we're interested in // Skip this entire track if it's not one we're interested in
if (!audio && !video) runs_->AdvanceRun(); if (!audio && !video)
runs_->AdvanceRun();
// Attempt to cache the auxiliary information first. Aux info is usually // Attempt to cache the auxiliary information first. Aux info is usually
// placed in a contiguous block before the sample data, rather than being // placed in a contiguous block before the sample data, rather than being
......
...@@ -31,7 +31,8 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser { ...@@ -31,7 +31,8 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser {
const NewBuffersCB& audio_cb, const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb, const NewBuffersCB& video_cb,
const NeedKeyCB& need_key_cb, const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb) OVERRIDE; const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb) OVERRIDE;
virtual void Flush() OVERRIDE; virtual void Flush() OVERRIDE;
virtual bool Parse(const uint8* buf, int size) OVERRIDE; virtual bool Parse(const uint8* buf, int size) OVERRIDE;
...@@ -70,6 +71,7 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser { ...@@ -70,6 +71,7 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser {
NewBuffersCB video_cb_; NewBuffersCB video_cb_;
NeedKeyCB need_key_cb_; NeedKeyCB need_key_cb_;
NewMediaSegmentCB new_segment_cb_; NewMediaSegmentCB new_segment_cb_;
base::Closure end_of_segment_cb_;
OffsetByteQueue queue_; OffsetByteQueue queue_;
......
...@@ -93,6 +93,10 @@ class MP4StreamParserTest : public testing::Test { ...@@ -93,6 +93,10 @@ class MP4StreamParserTest : public testing::Test {
segment_start_ = start_dts; segment_start_ = start_dts;
} }
void EndOfSegmentF() {
DVLOG(1) << "EndOfSegmentF()";
}
void InitializeParser() { void InitializeParser() {
parser_->Init( parser_->Init(
base::Bind(&MP4StreamParserTest::InitF, base::Unretained(this)), base::Bind(&MP4StreamParserTest::InitF, base::Unretained(this)),
...@@ -100,7 +104,9 @@ class MP4StreamParserTest : public testing::Test { ...@@ -100,7 +104,9 @@ class MP4StreamParserTest : public testing::Test {
base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)), base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)), base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
base::Bind(&MP4StreamParserTest::KeyNeededF, base::Unretained(this)), base::Bind(&MP4StreamParserTest::KeyNeededF, base::Unretained(this)),
base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this))); base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this)),
base::Bind(&MP4StreamParserTest::EndOfSegmentF,
base::Unretained(this)));
} }
bool ParseMP4File(const std::string& filename, int append_bytes) { bool ParseMP4File(const std::string& filename, int append_bytes) {
......
...@@ -43,6 +43,7 @@ WebMClusterParser::WebMClusterParser(int64 timecode_scale, ...@@ -43,6 +43,7 @@ WebMClusterParser::WebMClusterParser(int64 timecode_scale,
block_duration_(-1), block_duration_(-1),
cluster_timecode_(-1), cluster_timecode_(-1),
cluster_start_time_(kNoTimestamp()), cluster_start_time_(kNoTimestamp()),
cluster_ended_(false),
audio_(audio_track_num), audio_(audio_track_num),
video_(video_track_num) { video_(video_track_num) {
CHECK_GE(video_encryption_key_id_size, 0); CHECK_GE(video_encryption_key_id_size, 0);
...@@ -59,6 +60,7 @@ void WebMClusterParser::Reset() { ...@@ -59,6 +60,7 @@ void WebMClusterParser::Reset() {
last_block_timecode_ = -1; last_block_timecode_ = -1;
cluster_timecode_ = -1; cluster_timecode_ = -1;
cluster_start_time_ = kNoTimestamp(); cluster_start_time_ = kNoTimestamp();
cluster_ended_ = false;
parser_.Reset(); parser_.Reset();
audio_.Reset(); audio_.Reset();
video_.Reset(); video_.Reset();
...@@ -70,10 +72,13 @@ int WebMClusterParser::Parse(const uint8* buf, int size) { ...@@ -70,10 +72,13 @@ int WebMClusterParser::Parse(const uint8* buf, int size) {
int result = parser_.Parse(buf, size); int result = parser_.Parse(buf, size);
if (result <= 0) if (result <= 0) {
cluster_ended_ = false;
return result; return result;
}
if (parser_.IsParsingComplete()) { cluster_ended_ = parser_.IsParsingComplete();
if (cluster_ended_) {
// If there were no buffers in this cluster, set the cluster start time to // If there were no buffers in this cluster, set the cluster start time to
// be the |cluster_timecode_|. // be the |cluster_timecode_|.
if (cluster_start_time_ == kNoTimestamp()) { if (cluster_start_time_ == kNoTimestamp()) {
......
...@@ -44,6 +44,9 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient { ...@@ -44,6 +44,9 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
const BufferQueue& audio_buffers() const { return audio_.buffers(); } const BufferQueue& audio_buffers() const { return audio_.buffers(); }
const BufferQueue& video_buffers() const { return video_.buffers(); } const BufferQueue& video_buffers() const { return video_.buffers(); }
// Returns true if the last Parse() call stopped at the end of a cluster.
bool cluster_ended() const { return cluster_ended_; }
private: private:
// Helper class that manages per-track state. // Helper class that manages per-track state.
class Track { class Track {
...@@ -88,6 +91,7 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient { ...@@ -88,6 +91,7 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
int64 cluster_timecode_; int64 cluster_timecode_;
base::TimeDelta cluster_start_time_; base::TimeDelta cluster_start_time_;
bool cluster_ended_;
Track audio_; Track audio_;
Track video_; Track video_;
......
...@@ -192,7 +192,8 @@ void WebMStreamParser::Init(const InitCB& init_cb, ...@@ -192,7 +192,8 @@ void WebMStreamParser::Init(const InitCB& init_cb,
const NewBuffersCB& audio_cb, const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb, const NewBuffersCB& video_cb,
const NeedKeyCB& need_key_cb, const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb) { const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb) {
DCHECK_EQ(state_, kWaitingForInit); DCHECK_EQ(state_, kWaitingForInit);
DCHECK(init_cb_.is_null()); DCHECK(init_cb_.is_null());
DCHECK(!init_cb.is_null()); DCHECK(!init_cb.is_null());
...@@ -200,6 +201,7 @@ void WebMStreamParser::Init(const InitCB& init_cb, ...@@ -200,6 +201,7 @@ void WebMStreamParser::Init(const InitCB& init_cb,
DCHECK(!audio_cb.is_null() || !video_cb.is_null()); DCHECK(!audio_cb.is_null() || !video_cb.is_null());
DCHECK(!need_key_cb.is_null()); DCHECK(!need_key_cb.is_null());
DCHECK(!new_segment_cb.is_null()); DCHECK(!new_segment_cb.is_null());
DCHECK(!end_of_segment_cb.is_null());
ChangeState(kParsingHeaders); ChangeState(kParsingHeaders);
init_cb_ = init_cb; init_cb_ = init_cb;
...@@ -208,6 +210,7 @@ void WebMStreamParser::Init(const InitCB& init_cb, ...@@ -208,6 +210,7 @@ void WebMStreamParser::Init(const InitCB& init_cb,
video_cb_ = video_cb; video_cb_ = video_cb;
need_key_cb_ = need_key_cb; need_key_cb_ = need_key_cb;
new_segment_cb_ = new_segment_cb; new_segment_cb_ = new_segment_cb;
end_of_segment_cb_ = end_of_segment_cb;
} }
void WebMStreamParser::Flush() { void WebMStreamParser::Flush() {
...@@ -416,6 +419,7 @@ int WebMStreamParser::ParseCluster(const uint8* data, int size) { ...@@ -416,6 +419,7 @@ int WebMStreamParser::ParseCluster(const uint8* data, int size) {
const BufferQueue& audio_buffers = cluster_parser_->audio_buffers(); const BufferQueue& audio_buffers = cluster_parser_->audio_buffers();
const BufferQueue& video_buffers = cluster_parser_->video_buffers(); const BufferQueue& video_buffers = cluster_parser_->video_buffers();
base::TimeDelta cluster_start_time = cluster_parser_->cluster_start_time(); base::TimeDelta cluster_start_time = cluster_parser_->cluster_start_time();
bool cluster_ended = cluster_parser_->cluster_ended();
if (waiting_for_buffers_ && cluster_start_time != kNoTimestamp()) { if (waiting_for_buffers_ && cluster_start_time != kNoTimestamp()) {
new_segment_cb_.Run(cluster_start_time); new_segment_cb_.Run(cluster_start_time);
...@@ -428,6 +432,9 @@ int WebMStreamParser::ParseCluster(const uint8* data, int size) { ...@@ -428,6 +432,9 @@ int WebMStreamParser::ParseCluster(const uint8* data, int size) {
if (!video_buffers.empty() && !video_cb_.Run(video_buffers)) if (!video_buffers.empty() && !video_cb_.Run(video_buffers))
return -1; return -1;
if (cluster_ended)
end_of_segment_cb_.Run();
return bytes_parsed; return bytes_parsed;
} }
......
...@@ -26,7 +26,8 @@ class WebMStreamParser : public StreamParser { ...@@ -26,7 +26,8 @@ class WebMStreamParser : public StreamParser {
const NewBuffersCB& audio_cb, const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb, const NewBuffersCB& video_cb,
const NeedKeyCB& need_key_cb, const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb) OVERRIDE; const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb) OVERRIDE;
virtual void Flush() OVERRIDE; virtual void Flush() OVERRIDE;
virtual bool Parse(const uint8* buf, int size) OVERRIDE; virtual bool Parse(const uint8* buf, int size) OVERRIDE;
...@@ -66,6 +67,7 @@ class WebMStreamParser : public StreamParser { ...@@ -66,6 +67,7 @@ class WebMStreamParser : public StreamParser {
NewBuffersCB video_cb_; NewBuffersCB video_cb_;
NeedKeyCB need_key_cb_; NeedKeyCB need_key_cb_;
NewMediaSegmentCB new_segment_cb_; NewMediaSegmentCB new_segment_cb_;
base::Closure end_of_segment_cb_;
// True if a new cluster id has been seen, but no audio or video buffers have // True if a new cluster id has been seen, but no audio or video buffers have
// been parsed yet. // been parsed yet.
......
...@@ -653,6 +653,11 @@ WebKit::WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::sourceAddId( ...@@ -653,6 +653,11 @@ WebKit::WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::sourceAddId(
new_codecs)); new_codecs));
} }
bool WebMediaPlayerImpl::sourceTimestampOffset(
const WebKit::WebString& id, double offset) {
return proxy_->DemuxerSetTimestampOffset(id.utf8().data(), offset);
}
bool WebMediaPlayerImpl::sourceRemoveId(const WebKit::WebString& id) { bool WebMediaPlayerImpl::sourceRemoveId(const WebKit::WebString& id) {
DCHECK(!id.isEmpty()); DCHECK(!id.isEmpty());
proxy_->DemuxerRemoveId(id.utf8().data()); proxy_->DemuxerRemoveId(id.utf8().data());
......
...@@ -208,6 +208,8 @@ class WebMediaPlayerImpl ...@@ -208,6 +208,8 @@ class WebMediaPlayerImpl
unsigned length); unsigned length);
virtual bool sourceAbort(const WebKit::WebString& id); virtual bool sourceAbort(const WebKit::WebString& id);
virtual void sourceEndOfStream(EndOfStreamStatus status); virtual void sourceEndOfStream(EndOfStreamStatus status);
virtual bool sourceTimestampOffset(
const WebKit::WebString& id, double offset);
virtual MediaKeyException generateKeyRequest( virtual MediaKeyException generateKeyRequest(
const WebKit::WebString& key_system, const WebKit::WebString& key_system,
......
...@@ -196,6 +196,11 @@ media::ChunkDemuxer::Status WebMediaPlayerProxy::DemuxerAddId( ...@@ -196,6 +196,11 @@ media::ChunkDemuxer::Status WebMediaPlayerProxy::DemuxerAddId(
return chunk_demuxer_->AddId(id, type, codecs); return chunk_demuxer_->AddId(id, type, codecs);
} }
bool WebMediaPlayerProxy::DemuxerSetTimestampOffset(
const std::string& id, double offset) {
return chunk_demuxer_->SetTimestampOffset(id, offset);
}
void WebMediaPlayerProxy::DemuxerRemoveId(const std::string& id) { void WebMediaPlayerProxy::DemuxerRemoveId(const std::string& id) {
chunk_demuxer_->RemoveId(id); chunk_demuxer_->RemoveId(id);
} }
......
...@@ -105,6 +105,7 @@ class WebMediaPlayerProxy ...@@ -105,6 +105,7 @@ class WebMediaPlayerProxy
void DemuxerAbort(const std::string& id); void DemuxerAbort(const std::string& id);
void DemuxerEndOfStream(media::PipelineStatus status); void DemuxerEndOfStream(media::PipelineStatus status);
void DemuxerShutdown(); void DemuxerShutdown();
bool DemuxerSetTimestampOffset(const std::string& id, double offset);
// DecryptorClient implementation. // DecryptorClient implementation.
virtual void KeyAdded(const std::string& key_system, virtual void KeyAdded(const std::string& key_system,
......
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