diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-27 18:28:06 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-27 18:28:06 +0000 |
commit | cf8a3ca85acf493baf39650dc1b1bb70c1341948 (patch) | |
tree | de9e3c1f6132502bb5188010ab0fb6a9f1843e7d /media | |
parent | 5e59a06c7fc05e7d89e2c0b5f5ee14290ac73fd8 (diff) | |
download | chromium_src-cf8a3ca85acf493baf39650dc1b1bb70c1341948.zip chromium_src-cf8a3ca85acf493baf39650dc1b1bb70c1341948.tar.gz chromium_src-cf8a3ca85acf493baf39650dc1b1bb70c1341948.tar.bz2 |
Refactor PTS processing logic from FFmpegVideoDecoder::FindPtsAndDuration() into PtsStream.
This simplifies its reuse in other video decoders. An instance of this class is added to OmxVideoDecoder filter.
The PTS processing is mostly left intact. The only difference is addition of PtsStream::Seek() method, which
resets the current PTS. Without this method seeking brakes AV synchronization if the video decoder relies on last
known PTS and frame rate to calculate timestamps for decoded frames.
Patch by ostrovsm@gmail.com:
http://codereview.chromium.org/6902032/
BUG=none
TEST=The easiest way I found to reproduce the problem is to use an .avi file. It doesn't happen all the time but
sometimes AVI demuxer reports invalid timestamps for video packets, and the decoder heavily relies on the PTS
estimation logic (last PTS + frame rate). A sample file that triggers the behavior can be downloaded from [1].
The FFmpeg library has to be reconfigured to support MPEG4 decoding and AVI demuxing. Also to simplify the
experiment "video/x-msvideo" has to be added to the list of supported media types in net/base/mime_util.cc.
[1] http://www.dvdloc8.com/clip.php?movieid=284&clipid=1
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83180 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/pts_stream.cc | 79 | ||||
-rw-r--r-- | media/base/pts_stream.h | 70 | ||||
-rw-r--r-- | media/base/pts_stream_unittest.cc | 101 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.cc | 5 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.h | 3 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.cc | 69 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.h | 30 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder_unittest.cc | 122 | ||||
-rw-r--r-- | media/filters/omx_video_decoder.cc | 23 | ||||
-rw-r--r-- | media/filters/omx_video_decoder.h | 4 | ||||
-rw-r--r-- | media/media.gyp | 3 |
11 files changed, 325 insertions, 184 deletions
diff --git a/media/base/pts_stream.cc b/media/base/pts_stream.cc new file mode 100644 index 0000000..0223cdb --- /dev/null +++ b/media/base/pts_stream.cc @@ -0,0 +1,79 @@ +// 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 "base/logging.h" +#include "media/base/buffers.h" +#include "media/base/pts_stream.h" + +namespace media { + +PtsStream::PtsStream() {} + +PtsStream::~PtsStream() {} + +void PtsStream::Initialize(const base::TimeDelta& frame_duration) { + default_duration_ = frame_duration; + current_pts_ = base::TimeDelta(); + current_duration_ = base::TimeDelta(); +} + +void PtsStream::Seek(const base::TimeDelta& timestamp) { + current_pts_ = timestamp; + current_duration_ = base::TimeDelta(); + Flush(); +} + +void PtsStream::Flush() { + while (!pts_heap_.IsEmpty()) + pts_heap_.Pop(); +} + +void PtsStream::EnqueuePts(StreamSample* sample) { + DCHECK(sample); + if (!sample->IsEndOfStream() && sample->GetTimestamp() != kNoTimestamp) { + pts_heap_.Push(sample->GetTimestamp()); + } +} + +void PtsStream::UpdatePtsAndDuration(StreamSample* sample) { + // First search the |sample| for the pts. This is the most authoritative. + // Make a special exclusion for the value pts == 0. Though this is + // technically a valid value, it seems a number of FFmpeg codecs will + // mistakenly always set pts to 0. + // + // TODO(scherkus): FFmpegVideoDecodeEngine should be able to detect this + // situation and set the timestamp to kInvalidTimestamp. + DCHECK(sample); + base::TimeDelta timestamp = sample->GetTimestamp(); + if (timestamp != kNoTimestamp && + timestamp.ToInternalValue() != 0) { + current_pts_ = timestamp; + // We need to clean up the timestamp we pushed onto the |pts_heap_|. + if (!pts_heap_.IsEmpty()) + pts_heap_.Pop(); + } else if (!pts_heap_.IsEmpty()) { + // If the frame did not have pts, try to get the pts from the |pts_heap|. + current_pts_ = pts_heap_.Top(); + pts_heap_.Pop(); + } else if (current_pts_ != kNoTimestamp) { + // Guess assuming this frame was the same as the last frame. + current_pts_ = current_pts_ + current_duration_; + } else { + // Now we really have no clue!!! Mark an invalid timestamp and let the + // video renderer handle it (i.e., drop frame). + current_pts_ = kNoTimestamp; + } + + // Fill in the duration, using the frame itself as the authoratative source. + base::TimeDelta duration = sample->GetDuration(); + if (duration != kNoTimestamp && + duration.ToInternalValue() != 0) { + current_duration_ = duration; + } else { + // Otherwise assume a normal frame duration. + current_duration_ = default_duration_; + } +} + +} // namespace media diff --git a/media/base/pts_stream.h b/media/base/pts_stream.h new file mode 100644 index 0000000..f3a6e95 --- /dev/null +++ b/media/base/pts_stream.h @@ -0,0 +1,70 @@ +// 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. + +#ifndef MEDIA_BASE_PTS_STREAM_H_ +#define MEDIA_BASE_PTS_STREAM_H_ + +// Under some conditions the decoded frames can get invalid or wrong timestamps: +// - compressed frames are often in decode timestamp (dts) order, which +// may not always be in presentation timestamp (pts) order; +// - decoder may report invalid timestamps for the decoded frames; +// - parser may report invalid timestamps for the compressed frames. +// +// To ensure that the decoded frames are displayed in the proper order, the +// PtsStream class assembles the time information from different sources and +// combines it into the "best guess" timestamp and duration for the current +// frame. Data inside the decoded frame (if provided) is trusted the most, +// followed by data from the packet stream. Estimation based on the last known +// PTS and frame rate is reserved as a last-ditch effort. + +#include "base/time.h" +#include "media/base/pts_heap.h" + +namespace media { + +class StreamSample; + +class PtsStream { + public: + PtsStream(); + ~PtsStream(); + + // Initializes an instance using |frame_duration| as default. In absence of + // other PTS information PtsStream will produce timestamps separated in time + // by this duration. + void Initialize(const base::TimeDelta& frame_duration); + + // Sets the |current_pts_| to specified |timestamp| and flushes all enqueued + // timestamps. + void Seek(const base::TimeDelta& timestamp); + + // Clears the PTS queue. + void Flush(); + + // Puts timestamp from the stream packet |sample| into a queue, which is used + // as PTS source if decoded frames don't have a valid timestamp. Only valid + // timestamps are enqueued. + void EnqueuePts(StreamSample* sample); + + // Combines data from the decoded |sample|, PTS queue, and PTS estimator + // into the final PTS and duration. + void UpdatePtsAndDuration(StreamSample* sample); + + base::TimeDelta current_pts() const { return current_pts_; } + base::TimeDelta current_duration() const { return current_duration_; } + + private: + base::TimeDelta default_duration_; // Frame duration based on the frame rate. + + PtsHeap pts_heap_; // Heap of presentation timestamps. + + base::TimeDelta current_pts_; + base::TimeDelta current_duration_; + + DISALLOW_COPY_AND_ASSIGN(PtsStream); +}; + +} // namespace media + +#endif // MEDIA_BASE_PTS_STREAM_H_ diff --git a/media/base/pts_stream_unittest.cc b/media/base/pts_stream_unittest.cc new file mode 100644 index 0000000..abab227 --- /dev/null +++ b/media/base/pts_stream_unittest.cc @@ -0,0 +1,101 @@ +// 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/base/pts_stream.h" +#include "media/base/video_frame.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class PtsStreamTest : public testing::Test { + public: + PtsStreamTest() { + VideoFrame::CreateBlackFrame(16, 16, &video_frame_); + + // Use typical frame rate of 25 fps. + base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(40000); + pts_stream_.Initialize(frame_duration); + } + + virtual ~PtsStreamTest() {} + + protected: + PtsStream pts_stream_; + scoped_refptr<VideoFrame> video_frame_; + + private: + DISALLOW_COPY_AND_ASSIGN(PtsStreamTest); +}; + +TEST_F(PtsStreamTest, NoTimestamp) { + // Simulate an uninitialized |video_frame| where we cannot determine a + // timestamp at all. + video_frame_->SetTimestamp(kNoTimestamp); + video_frame_->SetDuration(kNoTimestamp); + pts_stream_.UpdatePtsAndDuration(video_frame_); + EXPECT_EQ(0, pts_stream_.current_pts().InMicroseconds()); + EXPECT_EQ(40000, pts_stream_.current_duration().InMicroseconds()); +} + +TEST_F(PtsStreamTest, LastKnownTimestamp) { + // Setup the last known pts to be at 100 microseconds with 16 microsecond + // duration. + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(100)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(16)); + pts_stream_.UpdatePtsAndDuration(video_frame_); + + // Simulate an uninitialized |video_frame| where last known pts will be used + // to generate a timestamp and |frame_duration| will be used to generate a + // duration. + video_frame_->SetTimestamp(kNoTimestamp); + video_frame_->SetDuration(kNoTimestamp); + pts_stream_.UpdatePtsAndDuration(video_frame_); + EXPECT_EQ(116, pts_stream_.current_pts().InMicroseconds()); + EXPECT_EQ(40000, pts_stream_.current_duration().InMicroseconds()); +} + +TEST_F(PtsStreamTest, TimestampIsZero) { + // Test that having pts == 0 in the frame also behaves like the pts is not + // provided. This is because FFmpeg set the pts to zero when there is no + // data for the frame, which means that value is useless to us. + // + // TODO(scherkus): FFmpegVideoDecodeEngine should be able to detect this + // situation and set the timestamp to kInvalidTimestamp. + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(100)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(16)); + pts_stream_.UpdatePtsAndDuration(video_frame_); + EXPECT_EQ(100, pts_stream_.current_pts().InMicroseconds()); + EXPECT_EQ(16, pts_stream_.current_duration().InMicroseconds()); + + // Should use estimation and default frame rate. + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(0)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(0)); + pts_stream_.UpdatePtsAndDuration(video_frame_); + EXPECT_EQ(116, pts_stream_.current_pts().InMicroseconds()); + EXPECT_EQ(40000, pts_stream_.current_duration().InMicroseconds()); + + // Should override estimation but still use default frame rate. + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(200)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(0)); + pts_stream_.EnqueuePts(video_frame_); + + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(0)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(0)); + pts_stream_.UpdatePtsAndDuration(video_frame_); + EXPECT_EQ(200, pts_stream_.current_pts().InMicroseconds()); + EXPECT_EQ(40000, pts_stream_.current_duration().InMicroseconds()); + + // Should override estimation and frame rate. + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(456)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(0)); + pts_stream_.EnqueuePts(video_frame_); + + video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(0)); + video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(789)); + pts_stream_.UpdatePtsAndDuration(video_frame_); + EXPECT_EQ(456, pts_stream_.current_pts().InMicroseconds()); + EXPECT_EQ(789, pts_stream_.current_duration().InMicroseconds()); +} + +} // namespace media diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index fc59709..9f5eaeb 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -61,6 +61,11 @@ CodecID VideoCodecToCodecID(VideoCodec video_codec) { return CODEC_ID_NONE; } +base::TimeDelta GetFrameDuration(AVStream* stream) { + AVRational time_base = { stream->r_frame_rate.den, stream->r_frame_rate.num }; + return ConvertFromTimeBase(time_base, 1); +} + bool GetSeekTimeAfter(AVStream* stream, const base::TimeDelta& timestamp, base::TimeDelta* seek_time) { DCHECK(stream); diff --git a/media/ffmpeg/ffmpeg_common.h b/media/ffmpeg/ffmpeg_common.h index 4db601c..196b57e 100644 --- a/media/ffmpeg/ffmpeg_common.h +++ b/media/ffmpeg/ffmpeg_common.h @@ -66,6 +66,9 @@ int64 ConvertToTimeBase(const AVRational& time_base, VideoCodec CodecIDToVideoCodec(CodecID codec_id); CodecID VideoCodecToCodecID(VideoCodec video_codec); +// Calculates duration of one frame in the |stream| based on its frame rate. +base::TimeDelta GetFrameDuration(AVStream* stream); + // Get the timestamp of the next seek point after |timestamp|. // Returns true if a valid seek point was found after |timestamp| and // |seek_time| was set. Returns false if a seek point could not be diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc index fd5f15f..c8626b2 100644 --- a/media/filters/ffmpeg_video_decoder.cc +++ b/media/filters/ffmpeg_video_decoder.cc @@ -22,7 +22,6 @@ namespace media { FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop* message_loop, VideoDecodeContext* decode_context) : message_loop_(message_loop), - time_base_(new AVRational()), state_(kUnInitialized), decode_engine_(new FFmpegVideoDecodeEngine()), decode_context_(decode_context) { @@ -60,8 +59,7 @@ void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, return; } - time_base_->den = av_stream->r_frame_rate.num; - time_base_->num = av_stream->r_frame_rate.den; + pts_stream_.Initialize(GetFrameDuration(av_stream)); int width = av_stream->codec->coded_width; int height = av_stream->codec->coded_height; @@ -178,8 +176,7 @@ void FFmpegVideoDecoder::OnFlushComplete() { AutoCallbackRunner done_runner(flush_callback_.release()); // Everything in the presentation time queue is invalid, clear the queue. - while (!pts_heap_.IsEmpty()) - pts_heap_.Pop(); + pts_stream_.Flush(); // Mark flush operation had been done. state_ = kNormal; @@ -199,6 +196,7 @@ void FFmpegVideoDecoder::Seek(base::TimeDelta time, DCHECK_EQ(MessageLoop::current(), message_loop_); DCHECK(!seek_callback_.get()); + pts_stream_.Seek(time); seek_callback_.reset(callback); decode_engine_->Seek(); } @@ -267,9 +265,8 @@ void FFmpegVideoDecoder::OnReadCompleteTask(scoped_refptr<Buffer> buffer) { // // TODO(ajwong): This push logic, along with the pop logic below needs to // be reevaluated to correctly handle decode errors. - if (state_ == kNormal && !buffer->IsEndOfStream() && - buffer->GetTimestamp() != kNoTimestamp) { - pts_heap_.Push(buffer->GetTimestamp()); + if (state_ == kNormal) { + pts_stream_.EnqueuePts(buffer.get()); } // Otherwise, attempt to decode a single frame. @@ -326,11 +323,10 @@ void FFmpegVideoDecoder::ConsumeVideoFrame( } // If we actually got data back, enqueue a frame. - last_pts_ = FindPtsAndDuration(*time_base_, &pts_heap_, last_pts_, - video_frame.get()); + pts_stream_.UpdatePtsAndDuration(video_frame.get()); - video_frame->SetTimestamp(last_pts_.timestamp); - video_frame->SetDuration(last_pts_.duration); + video_frame->SetTimestamp(pts_stream_.current_pts()); + video_frame->SetDuration(pts_stream_.current_duration()); VideoFrameReady(video_frame); } else { @@ -356,55 +352,6 @@ void FFmpegVideoDecoder::ProduceVideoSample( NewCallback(this, &FFmpegVideoDecoder::OnReadComplete)); } -FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration( - const AVRational& time_base, - PtsHeap* pts_heap, - const TimeTuple& last_pts, - const VideoFrame* frame) { - TimeTuple pts; - - // First search the VideoFrame for the pts. This is the most authoritative. - // Make a special exclusion for the value pts == 0. Though this is - // technically a valid value, it seems a number of FFmpeg codecs will - // mistakenly always set pts to 0. - // - // TODO(scherkus): FFmpegVideoDecodeEngine should be able to detect this - // situation and set the timestamp to kInvalidTimestamp. - DCHECK(frame); - base::TimeDelta timestamp = frame->GetTimestamp(); - if (timestamp != kNoTimestamp && - timestamp.ToInternalValue() != 0) { - pts.timestamp = timestamp; - // We need to clean up the timestamp we pushed onto the |pts_heap|. - if (!pts_heap->IsEmpty()) - pts_heap->Pop(); - } else if (!pts_heap->IsEmpty()) { - // If the frame did not have pts, try to get the pts from the |pts_heap|. - pts.timestamp = pts_heap->Top(); - pts_heap->Pop(); - } else if (last_pts.timestamp != kNoTimestamp && - last_pts.duration != kNoTimestamp) { - // Guess assuming this frame was the same as the last frame. - pts.timestamp = last_pts.timestamp + last_pts.duration; - } else { - // Now we really have no clue!!! Mark an invalid timestamp and let the - // video renderer handle it (i.e., drop frame). - pts.timestamp = kNoTimestamp; - } - - // Fill in the duration, using the frame itself as the authoratative source. - base::TimeDelta duration = frame->GetDuration(); - if (duration != kNoTimestamp && - duration.ToInternalValue() != 0) { - pts.duration = duration; - } else { - // Otherwise assume a normal frame duration. - pts.duration = ConvertFromTimeBase(time_base, 1); - } - - return pts; -} - bool FFmpegVideoDecoder::ProvidesBuffer() { DCHECK(info_.success); return info_.provides_buffers; diff --git a/media/filters/ffmpeg_video_decoder.h b/media/filters/ffmpeg_video_decoder.h index cd73631..ab27aad 100644 --- a/media/filters/ffmpeg_video_decoder.h +++ b/media/filters/ffmpeg_video_decoder.h @@ -10,14 +10,11 @@ #include "base/gtest_prod_util.h" #include "base/time.h" #include "media/base/filters.h" -#include "media/base/pts_heap.h" +#include "media/base/pts_stream.h" #include "media/base/video_frame.h" #include "media/filters/decoder_base.h" #include "media/video/video_decode_engine.h" -// FFmpeg types. -struct AVRational; - namespace media { class VideoDecodeEngine; @@ -25,12 +22,6 @@ class VideoDecodeEngine; class FFmpegVideoDecoder : public VideoDecoder, public VideoDecodeEngine::EventHandler { public: - // Holds timestamp and duration data needed for properly enqueuing a frame. - struct TimeTuple { - base::TimeDelta timestamp; - base::TimeDelta duration; - }; - FFmpegVideoDecoder(MessageLoop* message_loop, VideoDecodeContext* decode_context); virtual ~FFmpegVideoDecoder(); @@ -63,7 +54,7 @@ class FFmpegVideoDecoder : public VideoDecoder, friend class DecoderPrivateMock; friend class FFmpegVideoDecoderTest; - FRIEND_TEST_ALL_PREFIXES(FFmpegVideoDecoderTest, FindPtsAndDuration); + FRIEND_TEST_ALL_PREFIXES(FFmpegVideoDecoderTest, PtsStream); FRIEND_TEST_ALL_PREFIXES(FFmpegVideoDecoderTest, DoDecode_EnqueueVideoFrameError); FRIEND_TEST_ALL_PREFIXES(FFmpegVideoDecoderTest, @@ -94,19 +85,6 @@ class FFmpegVideoDecoder : public VideoDecoder, // Flush the output buffers that we had held in Paused state. void FlushBuffers(); - // Attempt to get the PTS and Duration for this frame by examining the time - // info provided via packet stream (stored in |pts_heap|), or the info - // written into the AVFrame itself. If no data is available in either, then - // attempt to generate a best guess of the pts based on the last known pts. - // - // Data inside the AVFrame (if provided) is trusted the most, followed - // by data from the packet stream. Estimation based on the |last_pts| is - // reserved as a last-ditch effort. - virtual TimeTuple FindPtsAndDuration(const AVRational& time_base, - PtsHeap* pts_heap, - const TimeTuple& last_pts, - const VideoFrame* frame); - // Injection point for unittest to provide a mock engine. Takes ownership of // the provided engine. virtual void SetVideoDecodeEngineForTest(VideoDecodeEngine* engine); @@ -114,9 +92,7 @@ class FFmpegVideoDecoder : public VideoDecoder, MessageLoop* message_loop_; MediaFormat media_format_; - PtsHeap pts_heap_; // Heap of presentation timestamps. - TimeTuple last_pts_; - scoped_ptr<AVRational> time_base_; // Pointer to avoid needing full type. + PtsStream pts_stream_; // Stream of presentation timestamps. DecoderState state_; scoped_ptr<VideoDecodeEngine> decode_engine_; scoped_ptr<VideoDecodeContext> decode_context_; diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index bcbd42b..a56619c 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -35,13 +35,20 @@ namespace media { static const int kWidth = 1280; static const int kHeight = 720; -static const FFmpegVideoDecoder::TimeTuple kTestPts1 = + +// Holds timestamp and duration data needed for properly enqueuing a frame. +struct TimeTuple { + base::TimeDelta timestamp; + base::TimeDelta duration; +}; + +static const TimeTuple kTestPts1 = { base::TimeDelta::FromMicroseconds(123), base::TimeDelta::FromMicroseconds(50) }; -static const FFmpegVideoDecoder::TimeTuple kTestPts2 = +static const TimeTuple kTestPts2 = { base::TimeDelta::FromMicroseconds(456), base::TimeDelta::FromMicroseconds(60) }; -static const FFmpegVideoDecoder::TimeTuple kTestPts3 = +static const TimeTuple kTestPts3 = { base::TimeDelta::FromMicroseconds(789), base::TimeDelta::FromMicroseconds(60) }; static const PipelineStatistics kStatistics; @@ -261,79 +268,6 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_Successful) { EXPECT_EQ(kHeight, height); } -TEST_F(FFmpegVideoDecoderTest, FindPtsAndDuration) { - // Start with an empty timestamp queue. - PtsHeap pts_heap; - - // Use 1/2 second for simple results. Thus, calculated durations should be - // 500000 microseconds. - AVRational time_base = {1, 2}; - - FFmpegVideoDecoder::TimeTuple last_pts; - last_pts.timestamp = kNoTimestamp; - last_pts.duration = kNoTimestamp; - - // Simulate an uninitialized |video_frame| and |last_pts| where we cannot - // determine a timestamp at all. - video_frame_->SetTimestamp(kNoTimestamp); - video_frame_->SetDuration(kNoTimestamp); - FFmpegVideoDecoder::TimeTuple result_pts = - decoder_->FindPtsAndDuration(time_base, &pts_heap, - last_pts, video_frame_.get()); - EXPECT_EQ(kNoTimestamp.InMicroseconds(), - result_pts.timestamp.InMicroseconds()); - EXPECT_EQ(500000, result_pts.duration.InMicroseconds()); - - // Setup the last known pts to be at 100 microseconds with 16 microsecond - // duration. - last_pts.timestamp = base::TimeDelta::FromMicroseconds(100); - last_pts.duration = base::TimeDelta::FromMicroseconds(16); - - // Simulate an uninitialized |video_frame| where |last_pts| will be used to - // generate a timestamp and |time_base| will be used to generate a duration. - video_frame_->SetTimestamp(kNoTimestamp); - video_frame_->SetDuration(kNoTimestamp); - result_pts = - decoder_->FindPtsAndDuration(time_base, &pts_heap, - last_pts, video_frame_.get()); - EXPECT_EQ(116, result_pts.timestamp.InMicroseconds()); - EXPECT_EQ(500000, result_pts.duration.InMicroseconds()); - - // Test that having pts == 0 in the frame also behaves like the pts is not - // provided. This is because FFmpeg set the pts to zero when there is no - // data for the frame, which means that value is useless to us. - // - // TODO(scherkus): FFmpegVideoDecodeEngine should be able to detect this - // situation and set the timestamp to kInvalidTimestamp. - video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(0)); - result_pts = - decoder_->FindPtsAndDuration(time_base,&pts_heap, - last_pts, video_frame_.get()); - EXPECT_EQ(116, result_pts.timestamp.InMicroseconds()); - EXPECT_EQ(500000, result_pts.duration.InMicroseconds()); - - // Add a pts to the |pts_heap| and make sure it overrides estimation. - pts_heap.Push(base::TimeDelta::FromMicroseconds(123)); - result_pts = decoder_->FindPtsAndDuration(time_base, - &pts_heap, - last_pts, - video_frame_.get()); - EXPECT_EQ(123, result_pts.timestamp.InMicroseconds()); - EXPECT_EQ(500000, result_pts.duration.InMicroseconds()); - - // Set a pts and duration on |video_frame_| and make sure it overrides - // |pts_heap|. - pts_heap.Push(base::TimeDelta::FromMicroseconds(123)); - video_frame_->SetTimestamp(base::TimeDelta::FromMicroseconds(456)); - video_frame_->SetDuration(base::TimeDelta::FromMicroseconds(789)); - result_pts = decoder_->FindPtsAndDuration(time_base, - &pts_heap, - last_pts, - video_frame_.get()); - EXPECT_EQ(456, result_pts.timestamp.InMicroseconds()); - EXPECT_EQ(789, result_pts.duration.InMicroseconds()); -} - ACTION_P2(ReadFromDemux, decoder, buffer) { decoder->ProduceVideoSample(buffer); } @@ -350,6 +284,7 @@ ACTION_P4(DecodeComplete, decoder, video_frame, time_tuple, statistics) { video_frame->SetDuration(time_tuple.duration); decoder->ConsumeVideoFrame(video_frame, statistics); } + ACTION_P3(DecodeNotComplete, decoder, buffer, statistics) { scoped_refptr<VideoFrame> null_frame; if (buffer->IsEndOfStream()) // We had started flushing. @@ -380,8 +315,8 @@ TEST_F(FFmpegVideoDecoderTest, DoDecode_TestStateTransition) { // Setup initial state and check that it is sane. ASSERT_EQ(FFmpegVideoDecoder::kNormal, decoder_->state_); - ASSERT_TRUE(base::TimeDelta() == decoder_->last_pts_.timestamp); - ASSERT_TRUE(base::TimeDelta() == decoder_->last_pts_.duration); + ASSERT_TRUE(base::TimeDelta() == decoder_->pts_stream_.current_pts()); + ASSERT_TRUE(base::TimeDelta() == decoder_->pts_stream_.current_duration()); // Setup decoder to buffer one frame, decode one frame, fail one frame, // decode one more, and then fail the last one to end decoding. @@ -422,9 +357,8 @@ TEST_F(FFmpegVideoDecoderTest, DoDecode_TestStateTransition) { decoder_->ProduceVideoFrame(video_frame_); message_loop_.RunAllPending(); EXPECT_EQ(FFmpegVideoDecoder::kNormal, decoder_->state_); - ASSERT_TRUE(kTestPts1.timestamp == decoder_->last_pts_.timestamp); - ASSERT_TRUE(kTestPts1.duration == decoder_->last_pts_.duration); - EXPECT_FALSE(decoder_->pts_heap_.IsEmpty()); + ASSERT_TRUE(kTestPts1.timestamp == decoder_->pts_stream_.current_pts()); + ASSERT_TRUE(kTestPts1.duration == decoder_->pts_stream_.current_duration()); // Second request from renderer: at first round decode engine did not produce // any frame. Decoder will issue another read from demuxer. at second round @@ -432,9 +366,8 @@ TEST_F(FFmpegVideoDecoderTest, DoDecode_TestStateTransition) { decoder_->ProduceVideoFrame(video_frame_); message_loop_.RunAllPending(); EXPECT_EQ(FFmpegVideoDecoder::kFlushCodec, decoder_->state_); - EXPECT_TRUE(kTestPts2.timestamp == decoder_->last_pts_.timestamp); - EXPECT_TRUE(kTestPts2.duration == decoder_->last_pts_.duration); - EXPECT_FALSE(decoder_->pts_heap_.IsEmpty()); + EXPECT_TRUE(kTestPts2.timestamp == decoder_->pts_stream_.current_pts()); + EXPECT_TRUE(kTestPts2.duration == decoder_->pts_stream_.current_duration()); // Third request from renderer: decode engine will return frame on the // first round. Input stream had reach EOS, therefore we had entered @@ -442,18 +375,16 @@ TEST_F(FFmpegVideoDecoderTest, DoDecode_TestStateTransition) { decoder_->ProduceVideoFrame(video_frame_); message_loop_.RunAllPending(); EXPECT_EQ(FFmpegVideoDecoder::kFlushCodec, decoder_->state_); - EXPECT_TRUE(kTestPts3.timestamp == decoder_->last_pts_.timestamp); - EXPECT_TRUE(kTestPts3.duration == decoder_->last_pts_.duration); - EXPECT_TRUE(decoder_->pts_heap_.IsEmpty()); + EXPECT_TRUE(kTestPts3.timestamp == decoder_->pts_stream_.current_pts()); + EXPECT_TRUE(kTestPts3.duration == decoder_->pts_stream_.current_duration()); // Fourth request from renderer: Both input/output reach EOF. therefore // we had reached the kDecodeFinished state after this call. decoder_->ProduceVideoFrame(video_frame_); message_loop_.RunAllPending(); EXPECT_EQ(FFmpegVideoDecoder::kDecodeFinished, decoder_->state_); - EXPECT_TRUE(kTestPts3.timestamp == decoder_->last_pts_.timestamp); - EXPECT_TRUE(kTestPts3.duration == decoder_->last_pts_.duration); - EXPECT_TRUE(decoder_->pts_heap_.IsEmpty()); + EXPECT_TRUE(kTestPts3.timestamp == decoder_->pts_stream_.current_pts()); + EXPECT_TRUE(kTestPts3.duration == decoder_->pts_stream_.current_duration()); } TEST_F(FFmpegVideoDecoderTest, DoSeek) { @@ -474,9 +405,12 @@ TEST_F(FFmpegVideoDecoderTest, DoSeek) { SCOPED_TRACE(Message() << "Iteration " << i); // Push in some timestamps. - decoder_->pts_heap_.Push(kTestPts1.timestamp); - decoder_->pts_heap_.Push(kTestPts2.timestamp); - decoder_->pts_heap_.Push(kTestPts3.timestamp); + buffer_->SetTimestamp(kTestPts1.timestamp); + decoder_->pts_stream_.EnqueuePts(buffer_); + buffer_->SetTimestamp(kTestPts2.timestamp); + decoder_->pts_stream_.EnqueuePts(buffer_); + buffer_->SetTimestamp(kTestPts3.timestamp); + decoder_->pts_stream_.EnqueuePts(buffer_); decoder_->state_ = kStates[i]; @@ -490,7 +424,7 @@ TEST_F(FFmpegVideoDecoderTest, DoSeek) { .WillOnce(EngineSeek(engine_)); decoder_->Seek(kZero, NewExpectedCallback()); - EXPECT_TRUE(decoder_->pts_heap_.IsEmpty()); + EXPECT_TRUE(kZero == decoder_->pts_stream_.current_duration()); EXPECT_EQ(FFmpegVideoDecoder::kNormal, decoder_->state_); } } diff --git a/media/filters/omx_video_decoder.cc b/media/filters/omx_video_decoder.cc index 69a80d4..0dd551f 100644 --- a/media/filters/omx_video_decoder.cc +++ b/media/filters/omx_video_decoder.cc @@ -59,6 +59,8 @@ void OmxVideoDecoder::Initialize(DemuxerStream* demuxer_stream, return; } + pts_stream_.Initialize(GetFrameDuration(av_stream)); + int width = av_stream->codec->coded_width; int height = av_stream->codec->coded_height; if (width > Limits::kMaxDimension || @@ -149,6 +151,8 @@ void OmxVideoDecoder::OnFlushComplete() { DCHECK(flush_callback_.get()); AutoCallbackRunner done_runner(flush_callback_.release()); + + pts_stream_.Flush(); } void OmxVideoDecoder::Seek(base::TimeDelta time, @@ -165,6 +169,7 @@ void OmxVideoDecoder::Seek(base::TimeDelta time, DCHECK_EQ(MessageLoop::current(), message_loop_); DCHECK(!seek_callback_.get()); + pts_stream_.Seek(time); seek_callback_.reset(callback); decode_engine_->Seek(); } @@ -194,6 +199,14 @@ void OmxVideoDecoder::ConsumeVideoFrame(scoped_refptr<VideoFrame> frame, const PipelineStatistics& statistics) { DCHECK_EQ(message_loop_, MessageLoop::current()); statistics_callback_->Run(statistics); + + if (frame.get()) { + pts_stream_.UpdatePtsAndDuration(frame.get()); + + frame->SetTimestamp(pts_stream_.current_pts()); + frame->SetDuration(pts_stream_.current_duration()); + } + VideoFrameReady(frame); } @@ -220,8 +233,14 @@ void OmxVideoDecoder::DemuxCompleteTask(Buffer* buffer) { DCHECK(decode_engine_.get()); message_loop_->PostTask( FROM_HERE, - NewRunnableMethod(decode_engine_.get(), - &VideoDecodeEngine::ConsumeVideoSample, ref_buffer)); + NewRunnableMethod(this, + &OmxVideoDecoder::ConsumeVideoSample, ref_buffer)); +} + +void OmxVideoDecoder::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { + if (buffer.get()) + pts_stream_.EnqueuePts(buffer.get()); + decode_engine_->ConsumeVideoSample(buffer); } } // namespace media diff --git a/media/filters/omx_video_decoder.h b/media/filters/omx_video_decoder.h index 5a2837c..c86b2fe 100644 --- a/media/filters/omx_video_decoder.h +++ b/media/filters/omx_video_decoder.h @@ -9,6 +9,7 @@ #include "media/base/filters.h" #include "media/base/media_format.h" +#include "media/base/pts_stream.h" #include "media/video/video_decode_context.h" #include "media/video/video_decode_engine.h" @@ -53,6 +54,7 @@ class OmxVideoDecoder : public VideoDecoder, // TODO(hclam): This is very ugly that we keep reference instead of // scoped_refptr. void DemuxCompleteTask(Buffer* buffer); + void ConsumeVideoSample(scoped_refptr<Buffer> buffer); MessageLoop* message_loop_; @@ -70,6 +72,8 @@ class OmxVideoDecoder : public VideoDecoder, VideoCodecInfo info_; + PtsStream pts_stream_; // Stream of presentation timestamps. + DISALLOW_COPY_AND_ASSIGN(OmxVideoDecoder); }; diff --git a/media/media.gyp b/media/media.gyp index 31961e8..a72afde 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -116,6 +116,8 @@ 'base/pipeline_status.h', 'base/pts_heap.cc', 'base/pts_heap.h', + 'base/pts_stream.cc', + 'base/pts_stream.h', 'base/seekable_buffer.cc', 'base/seekable_buffer.h', 'base/state_matrix.cc', @@ -375,6 +377,7 @@ 'base/mock_task.h', 'base/pipeline_impl_unittest.cc', 'base/pts_heap_unittest.cc', + 'base/pts_stream_unittest.cc', 'base/run_all_unittests.cc', 'base/seekable_buffer_unittest.cc', 'base/state_matrix_unittest.cc', |