Upstream version 10.38.220.0
[platform/framework/web/crosswalk.git] / src / media / filters / ffmpeg_demuxer_unittest.cc
index 0a3fe64..e7fc7eb 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
+#include "base/logging.h"
 #include "base/path_service.h"
 #include "base/threading/thread.h"
 #include "media/base/decrypt_config.h"
@@ -17,6 +18,7 @@
 #include "media/ffmpeg/ffmpeg_common.h"
 #include "media/filters/ffmpeg_demuxer.h"
 #include "media/filters/file_data_source.h"
+#include "media/formats/mp4/avc.h"
 #include "media/formats/webm/webm_crypto_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -75,8 +77,6 @@ class FFmpegDemuxerTest : public testing::Test {
   void CreateDemuxer(const std::string& name) {
     CHECK(!demuxer_);
 
-    EXPECT_CALL(host_, SetTotalBytes(_)).Times(AnyNumber());
-    EXPECT_CALL(host_, AddBufferedByteRange(_, _)).Times(AnyNumber());
     EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
 
     CreateDataSource(name);
@@ -92,13 +92,19 @@ class FFmpegDemuxerTest : public testing::Test {
 
   MOCK_METHOD1(CheckPoint, void(int v));
 
-  void InitializeDemuxerText(bool enable_text) {
+  void InitializeDemuxerWithTimelineOffset(bool enable_text,
+                                           base::Time timeline_offset) {
     EXPECT_CALL(host_, SetDuration(_));
     WaitableMessageLoopEvent event;
     demuxer_->Initialize(&host_, event.GetPipelineStatusCB(), enable_text);
+    demuxer_->timeline_offset_ = timeline_offset;
     event.RunAndWaitForStatus(PIPELINE_OK);
   }
 
+  void InitializeDemuxerText(bool enable_text) {
+    InitializeDemuxerWithTimelineOffset(enable_text, base::Time());
+  }
+
   void InitializeDemuxer() {
     InitializeDemuxerText(false);
   }
@@ -109,7 +115,9 @@ class FFmpegDemuxerTest : public testing::Test {
   // |location| simply indicates where the call to this function was made.
   // This makes it easier to track down where test failures occur.
   void OnReadDone(const tracked_objects::Location& location,
-                  int size, int64 timestampInMicroseconds,
+                  int size,
+                  int64 timestamp_us,
+                  base::TimeDelta discard_front_padding,
                   DemuxerStream::Status status,
                   const scoped_refptr<DecoderBuffer>& buffer) {
     std::string location_str;
@@ -117,21 +125,39 @@ class FFmpegDemuxerTest : public testing::Test {
     location_str += "\n";
     SCOPED_TRACE(location_str);
     EXPECT_EQ(status, DemuxerStream::kOk);
-    OnReadDoneCalled(size, timestampInMicroseconds);
+    OnReadDoneCalled(size, timestamp_us);
     EXPECT_TRUE(buffer.get() != NULL);
     EXPECT_EQ(size, buffer->data_size());
-    EXPECT_EQ(base::TimeDelta::FromMicroseconds(timestampInMicroseconds),
-              buffer->timestamp());
-
+    EXPECT_EQ(timestamp_us, buffer->timestamp().InMicroseconds());
+    EXPECT_EQ(discard_front_padding, buffer->discard_padding().first);
     DCHECK_EQ(&message_loop_, base::MessageLoop::current());
     message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
   }
 
   DemuxerStream::ReadCB NewReadCB(const tracked_objects::Location& location,
-                                  int size, int64 timestampInMicroseconds) {
-    EXPECT_CALL(*this, OnReadDoneCalled(size, timestampInMicroseconds));
-    return base::Bind(&FFmpegDemuxerTest::OnReadDone, base::Unretained(this),
-                      location, size, timestampInMicroseconds);
+                                  int size,
+                                  int64 timestamp_us) {
+    EXPECT_CALL(*this, OnReadDoneCalled(size, timestamp_us));
+    return base::Bind(&FFmpegDemuxerTest::OnReadDone,
+                      base::Unretained(this),
+                      location,
+                      size,
+                      timestamp_us,
+                      base::TimeDelta());
+  }
+
+  DemuxerStream::ReadCB NewReadCBWithCheckedDiscard(
+      const tracked_objects::Location& location,
+      int size,
+      int64 timestamp_us,
+      base::TimeDelta discard_front_padding) {
+    EXPECT_CALL(*this, OnReadDoneCalled(size, timestamp_us));
+    return base::Bind(&FFmpegDemuxerTest::OnReadDone,
+                      base::Unretained(this),
+                      location,
+                      size,
+                      timestamp_us,
+                      discard_front_padding);
   }
 
   // TODO(xhwang): This is a workaround of the issue that move-only parameters
@@ -167,6 +193,10 @@ class FFmpegDemuxerTest : public testing::Test {
     return demuxer_->glue_->format_context();
   }
 
+  int preferred_seeking_stream_index() const {
+    return demuxer_->preferred_stream_for_seeking_.first;
+  }
+
   void ReadUntilEndOfStream(DemuxerStream* stream) {
     bool got_eos_buffer = false;
     const int kMaxBuffers = 170;
@@ -386,25 +416,159 @@ TEST_F(FFmpegDemuxerTest, Read_Text) {
   message_loop_.Run();
 }
 
-TEST_F(FFmpegDemuxerTest, Read_VideoNonZeroStart) {
+TEST_F(FFmpegDemuxerTest, SeekInitialized_NoVideoStartTime) {
+  CreateDemuxer("audio-start-time-only.webm");
+  InitializeDemuxer();
+  EXPECT_EQ(0, preferred_seeking_stream_index());
+}
+
+TEST_F(FFmpegDemuxerTest, Read_VideoPositiveStartTime) {
+  const int64 kTimelineOffsetMs = 1352550896000LL;
+
   // Test the start time is the first timestamp of the video and audio stream.
   CreateDemuxer("nonzero-start-time.webm");
+  InitializeDemuxerWithTimelineOffset(
+      false, base::Time::FromJsTime(kTimelineOffsetMs));
+
+  // Attempt a read from the video stream and run the message loop until done.
+  DemuxerStream* video = demuxer_->GetStream(DemuxerStream::VIDEO);
+  DemuxerStream* audio = demuxer_->GetStream(DemuxerStream::AUDIO);
+
+  const base::TimeDelta video_start_time =
+      base::TimeDelta::FromMicroseconds(400000);
+  const base::TimeDelta audio_start_time =
+      base::TimeDelta::FromMicroseconds(396000);
+
+  // Run the test twice with a seek in between.
+  for (int i = 0; i < 2; ++i) {
+    video->Read(NewReadCB(FROM_HERE, 5636, video_start_time.InMicroseconds()));
+    message_loop_.Run();
+    audio->Read(NewReadCB(FROM_HERE, 165, audio_start_time.InMicroseconds()));
+    message_loop_.Run();
+
+    // Verify that the start time is equal to the lowest timestamp (ie the
+    // audio).
+    EXPECT_EQ(audio_start_time, demuxer_->start_time());
+
+    // Verify that the timeline offset has not been adjusted by the start time.
+    EXPECT_EQ(kTimelineOffsetMs, demuxer_->GetTimelineOffset().ToJavaTime());
+
+    // Seek back to the beginning and repeat the test.
+    WaitableMessageLoopEvent event;
+    demuxer_->Seek(base::TimeDelta(), event.GetPipelineStatusCB());
+    event.RunAndWaitForStatus(PIPELINE_OK);
+  }
+}
+
+TEST_F(FFmpegDemuxerTest, Read_AudioNoStartTime) {
+  // FFmpeg does not set timestamps when demuxing wave files.  Ensure that the
+  // demuxer sets a start time of zero in this case.
+  CreateDemuxer("sfx_s24le.wav");
+  InitializeDemuxer();
+
+  // Run the test twice with a seek in between.
+  for (int i = 0; i < 2; ++i) {
+    demuxer_->GetStream(DemuxerStream::AUDIO)
+        ->Read(NewReadCB(FROM_HERE, 4095, 0));
+    message_loop_.Run();
+    EXPECT_EQ(base::TimeDelta(), demuxer_->start_time());
+
+    // Seek back to the beginning and repeat the test.
+    WaitableMessageLoopEvent event;
+    demuxer_->Seek(base::TimeDelta(), event.GetPipelineStatusCB());
+    event.RunAndWaitForStatus(PIPELINE_OK);
+  }
+}
+
+// TODO(dalecurtis): Test is disabled since FFmpeg does not currently guarantee
+// the order of demuxed packets in OGG containers.  Re-enable once we decide to
+// either workaround it or attempt a fix upstream.  See http://crbug.com/387996.
+TEST_F(FFmpegDemuxerTest,
+       DISABLED_Read_AudioNegativeStartTimeAndOggDiscard_Bear) {
+  // Many ogg files have negative starting timestamps, so ensure demuxing and
+  // seeking work correctly with a negative start time.
+  CreateDemuxer("bear.ogv");
   InitializeDemuxer();
 
   // Attempt a read from the video stream and run the message loop until done.
   DemuxerStream* video = demuxer_->GetStream(DemuxerStream::VIDEO);
   DemuxerStream* audio = demuxer_->GetStream(DemuxerStream::AUDIO);
 
-  // Check first buffer in video stream.
-  video->Read(NewReadCB(FROM_HERE, 5636, 400000));
-  message_loop_.Run();
+  // Run the test twice with a seek in between.
+  for (int i = 0; i < 2; ++i) {
+    audio->Read(
+        NewReadCBWithCheckedDiscard(FROM_HERE, 40, 0, kInfiniteDuration()));
+    message_loop_.Run();
+    audio->Read(
+        NewReadCBWithCheckedDiscard(FROM_HERE, 41, 2903, kInfiniteDuration()));
+    message_loop_.Run();
+    audio->Read(NewReadCBWithCheckedDiscard(
+        FROM_HERE, 173, 5805, base::TimeDelta::FromMicroseconds(10159)));
+    message_loop_.Run();
 
-  // Check first buffer in audio stream.
-  audio->Read(NewReadCB(FROM_HERE, 165, 396000));
-  message_loop_.Run();
+    audio->Read(NewReadCB(FROM_HERE, 148, 18866));
+    message_loop_.Run();
+    EXPECT_EQ(base::TimeDelta::FromMicroseconds(-15964),
+              demuxer_->start_time());
+
+    video->Read(NewReadCB(FROM_HERE, 5751, 0));
+    message_loop_.Run();
+
+    video->Read(NewReadCB(FROM_HERE, 846, 33367));
+    message_loop_.Run();
+
+    video->Read(NewReadCB(FROM_HERE, 1255, 66733));
+    message_loop_.Run();
 
-  // Verify that the start time is equal to the lowest timestamp (ie the audio).
-  EXPECT_EQ(demuxer_->GetStartTime().InMicroseconds(), 396000);
+    // Seek back to the beginning and repeat the test.
+    WaitableMessageLoopEvent event;
+    demuxer_->Seek(base::TimeDelta(), event.GetPipelineStatusCB());
+    event.RunAndWaitForStatus(PIPELINE_OK);
+  }
+}
+
+// Same test above, but using sync2.ogv which has video stream muxed before the
+// audio stream, so seeking based only on start time will fail since ffmpeg is
+// essentially just seeking based on file position.
+TEST_F(FFmpegDemuxerTest, Read_AudioNegativeStartTimeAndOggDiscard_Sync) {
+  // Many ogg files have negative starting timestamps, so ensure demuxing and
+  // seeking work correctly with a negative start time.
+  CreateDemuxer("sync2.ogv");
+  InitializeDemuxer();
+
+  // Attempt a read from the video stream and run the message loop until done.
+  DemuxerStream* video = demuxer_->GetStream(DemuxerStream::VIDEO);
+  DemuxerStream* audio = demuxer_->GetStream(DemuxerStream::AUDIO);
+
+  // Run the test twice with a seek in between.
+  for (int i = 0; i < 2; ++i) {
+    audio->Read(NewReadCBWithCheckedDiscard(
+        FROM_HERE, 1, 0, base::TimeDelta::FromMicroseconds(2902)));
+    message_loop_.Run();
+
+    audio->Read(NewReadCB(FROM_HERE, 1, 2902));
+    message_loop_.Run();
+    EXPECT_EQ(base::TimeDelta::FromMicroseconds(-2902),
+              demuxer_->start_time());
+
+    // Though the internal start time may be below zero, the exposed media time
+    // must always be greater than zero.
+    EXPECT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
+
+    video->Read(NewReadCB(FROM_HERE, 9997, 0));
+    message_loop_.Run();
+
+    video->Read(NewReadCB(FROM_HERE, 16, 33241));
+    message_loop_.Run();
+
+    video->Read(NewReadCB(FROM_HERE, 631, 66482));
+    message_loop_.Run();
+
+    // Seek back to the beginning and repeat the test.
+    WaitableMessageLoopEvent event;
+    demuxer_->Seek(base::TimeDelta(), event.GetPipelineStatusCB());
+    event.RunAndWaitForStatus(PIPELINE_OK);
+  }
 }
 
 TEST_F(FFmpegDemuxerTest, Read_EndOfStream) {
@@ -462,6 +626,16 @@ TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration_AudioOnly) {
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::AUDIO));
 }
 
+TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration_UnsupportedStream) {
+  // Verify that end of stream buffers are created and we don't crash
+  // if there are streams in the file that we don't support.
+  CreateDemuxer("vorbis_audio_wmv_video.mkv");
+  InitializeDemuxer();
+  set_duration_known(false);
+  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(1014)));
+  ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::AUDIO));
+}
+
 TEST_F(FFmpegDemuxerTest, Seek) {
   // We're testing that the demuxer frees all queued packets when it receives
   // a Seek().
@@ -590,40 +764,6 @@ TEST_F(FFmpegDemuxerTest, Stop) {
   demuxer_.reset();
 }
 
-TEST_F(FFmpegDemuxerTest, DisableAudioStream) {
-  // We are doing the following things here:
-  // 1. Initialize the demuxer with audio and video stream.
-  // 2. Send a "disable audio stream" message to the demuxer.
-  // 3. Demuxer will free audio packets even if audio stream was initialized.
-  CreateDemuxer("bear-320x240.webm");
-  InitializeDemuxer();
-
-  // Submit a "disable audio stream" message to the demuxer.
-  demuxer_->OnAudioRendererDisabled();
-  message_loop_.RunUntilIdle();
-
-  // Get our streams.
-  DemuxerStream* video = demuxer_->GetStream(DemuxerStream::VIDEO);
-  DemuxerStream* audio = demuxer_->GetStream(DemuxerStream::AUDIO);
-  ASSERT_TRUE(video);
-  ASSERT_TRUE(audio);
-
-  // The audio stream should have been prematurely stopped.
-  EXPECT_FALSE(IsStreamStopped(DemuxerStream::VIDEO));
-  EXPECT_TRUE(IsStreamStopped(DemuxerStream::AUDIO));
-
-  // Attempt a read from the video stream: it should return valid data.
-  video->Read(NewReadCB(FROM_HERE, 22084, 0));
-  message_loop_.Run();
-
-  // Attempt a read from the audio stream: it should immediately return end of
-  // stream without requiring the message loop to read data.
-  bool got_eos_buffer = false;
-  audio->Read(base::Bind(&EosOnReadDone, &got_eos_buffer));
-  message_loop_.RunUntilIdle();
-  EXPECT_TRUE(got_eos_buffer);
-}
-
 // Verify that seek works properly when the WebM cues data is at the start of
 // the file instead of at the end.
 TEST_F(FFmpegDemuxerTest, SeekWithCuesBeforeFirstCluster) {
@@ -663,24 +803,21 @@ TEST_F(FFmpegDemuxerTest, SeekWithCuesBeforeFirstCluster) {
   message_loop_.Run();
 }
 
+#if defined(USE_PROPRIETARY_CODECS)
 // Ensure ID3v1 tag reading is disabled.  id3_test.mp3 has an ID3v1 tag with the
 // field "title" set to "sample for id3 test".
 TEST_F(FFmpegDemuxerTest, NoID3TagData) {
-#if !defined(USE_PROPRIETARY_CODECS)
-  return;
-#endif
   CreateDemuxer("id3_test.mp3");
   InitializeDemuxer();
   EXPECT_FALSE(av_dict_get(format_context()->metadata, "title", NULL, 0));
 }
+#endif
 
+#if defined(USE_PROPRIETARY_CODECS)
 // Ensure MP3 files with large image/video based ID3 tags demux okay.  FFmpeg
 // will hand us a video stream to the data which will likely be in a format we
 // don't accept as video; e.g. PNG.
 TEST_F(FFmpegDemuxerTest, Mp3WithVideoStreamID3TagData) {
-#if !defined(USE_PROPRIETARY_CODECS)
-  return;
-#endif
   CreateDemuxer("id3_png_test.mp3");
   InitializeDemuxer();
 
@@ -688,6 +825,7 @@ TEST_F(FFmpegDemuxerTest, Mp3WithVideoStreamID3TagData) {
   EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::VIDEO));
   EXPECT_TRUE(demuxer_->GetStream(DemuxerStream::AUDIO));
 }
+#endif
 
 // Ensure a video with an unsupported audio track still results in the video
 // stream being demuxed.
@@ -711,15 +849,110 @@ TEST_F(FFmpegDemuxerTest, UnsupportedVideoSupportedAudioDemux) {
   EXPECT_TRUE(demuxer_->GetStream(DemuxerStream::AUDIO));
 }
 
+#if defined(USE_PROPRIETARY_CODECS)
 // FFmpeg returns null data pointers when samples have zero size, leading to
 // mistakenly creating end of stream buffers http://crbug.com/169133
 TEST_F(FFmpegDemuxerTest, MP4_ZeroStszEntry) {
-#if !defined(USE_PROPRIETARY_CODECS)
-  return;
-#endif
   CreateDemuxer("bear-1280x720-zero-stsz-entry.mp4");
   InitializeDemuxer();
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::AUDIO));
 }
 
+
+static void ValidateAnnexB(DemuxerStream* stream,
+                           DemuxerStream::Status status,
+                           const scoped_refptr<DecoderBuffer>& buffer) {
+  EXPECT_EQ(status, DemuxerStream::kOk);
+
+  if (buffer->end_of_stream()) {
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+    return;
+  }
+
+  std::vector<SubsampleEntry> subsamples;
+
+  if (buffer->decrypt_config())
+    subsamples = buffer->decrypt_config()->subsamples();
+
+  bool is_valid =
+      mp4::AVC::IsValidAnnexB(buffer->data(), buffer->data_size(),
+                              subsamples);
+  EXPECT_TRUE(is_valid);
+
+  if (!is_valid) {
+    LOG(ERROR) << "Buffer contains invalid Annex B data.";
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+    return;
+  }
+
+  stream->Read(base::Bind(&ValidateAnnexB, stream));
+};
+
+TEST_F(FFmpegDemuxerTest, IsValidAnnexB) {
+  const char* files[] = {
+    "bear-1280x720-av_frag.mp4",
+    "bear-1280x720-av_with-aud-nalus_frag.mp4"
+  };
+
+  for (size_t i = 0; i < arraysize(files); ++i) {
+    DVLOG(1) << "Testing " << files[i];
+    CreateDemuxer(files[i]);
+    InitializeDemuxer();
+
+    // Ensure the expected streams are present.
+    DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
+    ASSERT_TRUE(stream);
+    stream->EnableBitstreamConverter();
+
+    stream->Read(base::Bind(&ValidateAnnexB, stream));
+    message_loop_.Run();
+
+    WaitableMessageLoopEvent event;
+    demuxer_->Stop(event.GetClosure());
+    event.RunAndWait();
+    demuxer_.reset();
+    data_source_.reset();
+  }
+}
+
+TEST_F(FFmpegDemuxerTest, Rotate_Metadata_0) {
+  CreateDemuxer("bear_rotate_0.mp4");
+  InitializeDemuxer();
+
+  DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
+  ASSERT_TRUE(stream);
+  ASSERT_EQ(VIDEO_ROTATION_0, stream->video_rotation());
+}
+
+TEST_F(FFmpegDemuxerTest, Rotate_Metadata_90) {
+  CreateDemuxer("bear_rotate_90.mp4");
+  InitializeDemuxer();
+
+  DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
+  ASSERT_TRUE(stream);
+  ASSERT_EQ(VIDEO_ROTATION_90, stream->video_rotation());
+}
+
+TEST_F(FFmpegDemuxerTest, Rotate_Metadata_180) {
+  CreateDemuxer("bear_rotate_180.mp4");
+  InitializeDemuxer();
+
+  DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
+  ASSERT_TRUE(stream);
+  ASSERT_EQ(VIDEO_ROTATION_180, stream->video_rotation());
+}
+
+TEST_F(FFmpegDemuxerTest, Rotate_Metadata_270) {
+  CreateDemuxer("bear_rotate_270.mp4");
+  InitializeDemuxer();
+
+  DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
+  ASSERT_TRUE(stream);
+  ASSERT_EQ(VIDEO_ROTATION_270, stream->video_rotation());
+}
+
+#endif
+
 }  // namespace media