Index: media/filters/ffmpeg_demuxer_unittest.cc |
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc |
index 7ef340b668fe986e2b59eb606dd0f1d2c96a3274..ae43b504034896ee3708655ef8977c8ef6eb73ae 100644 |
--- a/media/filters/ffmpeg_demuxer_unittest.cc |
+++ b/media/filters/ffmpeg_demuxer_unittest.cc |
@@ -278,6 +278,10 @@ class FFmpegDemuxerTest : public testing::Test { |
demuxer_->duration_ = kInfiniteDuration; |
} |
+ void SetStreamCapacity(FFmpegDemuxerStream* s, base::TimeDelta capacity) { |
+ s->stream_capacity_ = capacity; |
+ } |
+ |
// Fixture members. |
base::test::ScopedTaskScheduler task_scheduler_; |
@@ -1660,4 +1664,112 @@ TEST_F(FFmpegDemuxerTest, StreamStatusNotifications) { |
CheckStreamStatusNotifications(demuxer_.get(), video_stream); |
} |
+// This test verifies that when a video stream is restarted, we do an internal |
+// seek in the ffmpeg demuxer (because we need to read video frames starting |
+// with the previous video key frame before the current playback position, so |
+// that the video renderer is able to resume playback from the current media |
+// time), but then we'd drop all audio packets up to the last_audio_packet_pos_, |
+// because those have already been processed and are sitting in the audio stream |
+// buffer queue, we don't want to duplicate those. |
+TEST_F(FFmpegDemuxerTest, SeekAndDropSeenAudioPacketsAfterVideoRestart) { |
+ CreateDemuxer("bear-320x240.webm"); |
+ InitializeDemuxer(); |
+ FFmpegDemuxerStream* audio = |
+ static_cast<FFmpegDemuxerStream*>(GetStream(DemuxerStream::AUDIO)); |
+ FFmpegDemuxerStream* video = |
+ static_cast<FFmpegDemuxerStream*>(GetStream(DemuxerStream::VIDEO)); |
+ EXPECT_NE(nullptr, audio); |
+ EXPECT_NE(nullptr, video); |
+ |
+ // The audio stream in the test file has audio frames with timestamps about 3 |
+ // milliseconds apart (0, 3, 6, 9, 12, 15, 17, 20, 23etc). So setting stream |
+ // capacity to 5 millisec allows the demuxer to buffer 3 frames. We'll read |
+ // two of them immediately, so the demuxer will have read 5 frames from FFmpeg |
+ // by the time the video track is re-enabled. |
+ SetStreamCapacity(audio, base::TimeDelta::FromMilliseconds(5)); |
+ |
+ // Disable the video stream and read a few frames from the audio stream. |
+ demuxer_->OnSelectedVideoTrackChanged(base::nullopt, base::TimeDelta()); |
+ |
+ audio->Read(NewReadCB(FROM_HERE, 29, 0, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ audio->Read(NewReadCB(FROM_HERE, 27, 3000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ |
+ // At this point the demuxer have read frames with timestamps 0-15 millisec. |
+ // Now re-enable the video stream, which will cause an FFmpeg read position to |
+ // be seeked back to 0 (since the previous video key frame is at 0). |
+ demuxer_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"), base::TimeDelta()); |
+ |
+ // Now verify that frames read from the audio stream are continuous, as |
+ // expected. There should be no duplicates or missing frames. |
+ |
+ // The first three frames were read and buffered before the video track was |
+ // re-enabled. |
+ audio->Read(NewReadCB(FROM_HERE, 31, 6000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ audio->Read(NewReadCB(FROM_HERE, 30, 9000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ audio->Read(NewReadCB(FROM_HERE, 32, 12000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ |
+ // And the following three frames were read when we freed up capacity by |
+ // reading the previous 3 frames above (i.e. after the video track was |
+ // restarted). These should continue the audio stream reading sequence. |
+ audio->Read(NewReadCB(FROM_HERE, 29, 15000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ audio->Read(NewReadCB(FROM_HERE, 31, 17000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+ audio->Read(NewReadCB(FROM_HERE, 36, 20000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+} |
+ |
+TEST_F(FFmpegDemuxerTest, SeekDuringVideoStreamRestart) { |
+ CreateDemuxer("bear-320x240.webm"); |
+ InitializeDemuxer(); |
+ FFmpegDemuxerStream* video = |
+ static_cast<FFmpegDemuxerStream*>(GetStream(DemuxerStream::VIDEO)); |
+ EXPECT_NE(nullptr, video); |
+ |
+ // Disable and then re-enable the video stream, this will initiate an |
+ // internal seek for the video stream. |
+ demuxer_->OnSelectedVideoTrackChanged(base::nullopt, base::TimeDelta()); |
+ demuxer_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"), base::TimeDelta()); |
+ |
+ // Now immediately request a seek to a different position. This should cancel |
+ // the previous seek and kick off a new seek. |
+ Seek(base::TimeDelta::FromMilliseconds(500)); |
+ |
+ // Verify that the next read returns the video buffer from the expected |
+ // position, matching the latest seek. |
+ video->Read(NewReadCB(FROM_HERE, 5636, 400000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+} |
+ |
+TEST_F(FFmpegDemuxerTest, VideoStreamRestartDuringSeek) { |
+ CreateDemuxer("bear-320x240.webm"); |
+ InitializeDemuxer(); |
+ FFmpegDemuxerStream* video = |
+ static_cast<FFmpegDemuxerStream*>(GetStream(DemuxerStream::VIDEO)); |
+ EXPECT_NE(nullptr, video); |
+ |
+ demuxer_->OnSelectedVideoTrackChanged(base::nullopt, base::TimeDelta()); |
+ |
+ // Initiate a seek and re-enable the video track without waiting for the seek |
+ // to be completed. |
+ base::TimeDelta seek_target = base::TimeDelta::FromMilliseconds(500); |
+ WaitableMessageLoopEvent seek_event; |
+ demuxer_->Seek(seek_target, seek_event.GetPipelineStatusCB()); |
+ |
+ demuxer_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"), base::TimeDelta()); |
+ |
+ // Wait for the seek to finish. |
+ seek_event.RunAndWaitForStatus(PIPELINE_OK); |
+ |
+ // Verify that the next read returns the video buffer from the expected |
+ // position, matching the latest seek. |
+ video->Read(NewReadCB(FROM_HERE, 5636, 400000, true, DemuxerStream::kOk)); |
+ base::RunLoop().Run(); |
+} |
+ |
} // namespace media |