Bug 910897 - Use MP3FrameParser in DirectShow in order to calculate the duration. r=padenot
authorChris Pearce <cpearce@mozilla.com>
Tue, 10 Sep 2013 12:45:33 +1200
changeset 159209 1e15819af724ae710cfdfa9fa1ef408bcab4bfe6
parent 159208 9f7c4970b7610607979c9081a8e07b5dcbed0662
child 159210 4db58a9366f1e71984a0f53cfccb6abdeb44e27c
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs910897
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 910897 - Use MP3FrameParser in DirectShow in order to calculate the duration. r=padenot
content/media/AbstractMediaDecoder.h
content/media/BufferDecoder.cpp
content/media/BufferDecoder.h
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/directshow/DirectShowReader.cpp
content/media/directshow/DirectShowReader.h
content/media/omx/OmxDecoder.cpp
--- a/content/media/AbstractMediaDecoder.h
+++ b/content/media/AbstractMediaDecoder.h
@@ -70,17 +70,17 @@ public:
   virtual int64_t GetMediaDuration() = 0;
 
   // Set the duration of the media in microseconds.
   virtual void SetMediaDuration(int64_t aDuration) = 0;
 
   // Sets the duration of the media in microseconds. The MediaDecoder
   // fires a durationchange event to its owner (e.g., an HTML audio
   // tag).
-  virtual void UpdateMediaDuration(int64_t aDuration) = 0;
+  virtual void UpdateEstimatedMediaDuration(int64_t aDuration) = 0;
 
   // Set the media as being seekable or not.
   virtual void SetMediaSeekable(bool aMediaSeekable) = 0;
 
   // Set the transport level as being seekable or not.
   virtual void SetTransportSeekable(bool aTransportSeekable) = 0;
 
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
--- a/content/media/BufferDecoder.cpp
+++ b/content/media/BufferDecoder.cpp
@@ -104,17 +104,17 @@ BufferDecoder::GetMediaDuration()
 
 void
 BufferDecoder::SetMediaDuration(int64_t aDuration)
 {
   // ignore
 }
 
 void
-BufferDecoder::UpdateMediaDuration(int64_t aDuration)
+BufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   // ignore
 }
 
 void
 BufferDecoder::SetMediaSeekable(bool aMediaSeekable)
 {
   // ignore
--- a/content/media/BufferDecoder.h
+++ b/content/media/BufferDecoder.h
@@ -45,17 +45,17 @@ public:
   void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE;
 
   int64_t GetEndMediaTime() const MOZ_FINAL MOZ_OVERRIDE;
 
   int64_t GetMediaDuration() MOZ_FINAL MOZ_OVERRIDE;
 
   void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
 
-  void UpdateMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
+  void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
 
   void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
 
   void SetTransportSeekable(bool aTransportSeekable) MOZ_OVERRIDE;
 
   VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
   layers::ImageContainer* GetImageContainer() MOZ_OVERRIDE;
 
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -1267,20 +1267,24 @@ void MediaDecoder::SetDuration(double aD
 }
 
 void MediaDecoder::SetMediaDuration(int64_t aDuration)
 {
   NS_ENSURE_TRUE_VOID(GetStateMachine());
   GetStateMachine()->SetDuration(aDuration);
 }
 
-void MediaDecoder::UpdateMediaDuration(int64_t aDuration)
+void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mPlayState <= PLAY_STATE_LOADING) {
+    return;
+  }
   NS_ENSURE_TRUE_VOID(GetStateMachine());
-  GetStateMachine()->UpdateDuration(aDuration);
+  GetStateMachine()->UpdateEstimatedDuration(aDuration);
 }
 
 void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
   mMediaSeekable = aMediaSeekable;
   if (mDecoderStateMachine) {
     mDecoderStateMachine->SetMediaSeekable(aMediaSeekable);
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -492,18 +492,28 @@ public:
   // Call on the main thread only.
   virtual bool IsEnded() const;
 
   // Set the duration of the media resource in units of seconds.
   // This is called via a channel listener if it can pick up the duration
   // from a content header. Must be called from the main thread only.
   virtual void SetDuration(double aDuration);
 
+  // Sets the initial duration of the media. Called while the media metadata
+  // is being read and the decode is being setup.
   void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
-  void UpdateMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
+  // Updates the media duration. This is called while the media is being
+  // played, calls before the media has reached loaded metadata are ignored.
+  // The duration is assumed to be an estimate, and so a degree of
+  // instability is expected; if the incoming duration is not significantly
+  // different from the existing duration, the change request is ignored.
+  // If the incoming duration is significantly different, the duration is
+  // changed, this causes a durationchanged event to fire to the media
+  // element.
+  void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
 
   // Set a flag indicating whether seeking is supported
   virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
   virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_FINAL MOZ_OVERRIDE;
   // Returns true if this media supports seeking. False for example for WebM
   // files without an index and chained ogg files.
   virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
   // Returns true if seeking is supported on a transport level (e.g. the server
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -113,16 +113,22 @@ static const uint32_t QUICK_BUFFERING_LO
 // quick buffering in a timely fashion, as the decode pauses when it
 // reaches AMPLE_AUDIO_USECS decoded data, and thus we'll never reach
 // QUICK_BUFFERING_LOW_DATA_USECS.
 PR_STATIC_ASSERT(QUICK_BUFFERING_LOW_DATA_USECS <= AMPLE_AUDIO_USECS);
 
 // This value has been chosen empirically.
 static const uint32_t AUDIOSTREAM_MIN_WRITE_BEFORE_START_USECS = 200000;
 
+// The amount of instability we tollerate in calls to
+// MediaDecoderStateMachine::UpdateEstimatedDuration(); changes of duration
+// less than this are ignored, as they're assumed to be the result of
+// instability in the duration estimation.
+static const int64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
+
 static TimeDuration UsecsToDuration(int64_t aUsecs) {
   return TimeDuration::FromMilliseconds(static_cast<double>(aUsecs) / USECS_PER_MS);
 }
 
 static int64_t DurationToUsecs(TimeDuration aDuration) {
   return static_cast<int64_t>(aDuration.ToSeconds() * USECS_PER_S);
 }
 
@@ -1439,19 +1445,22 @@ void MediaDecoderStateMachine::SetDurati
   if (mStartTime != -1) {
     mEndTime = mStartTime + aDuration;
   } else {
     mStartTime = 0;
     mEndTime = aDuration;
   }
 }
 
-void MediaDecoderStateMachine::UpdateDuration(int64_t aDuration)
+void MediaDecoderStateMachine::UpdateEstimatedDuration(int64_t aDuration)
 {
-  if (aDuration != GetDuration()) {
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+  int64_t duration = GetDuration();
+  if (aDuration != duration &&
+      abs(aDuration - duration) > ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
     SetDuration(aDuration);
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
 }
 
 void MediaDecoderStateMachine::SetMediaEndTime(int64_t aEndTime)
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -155,19 +155,22 @@ public:
   // aDuration is in microseconds.
   void SetDuration(int64_t aDuration);
 
   // Called while decoding metadata to set the end time of the media
   // resource. The decoder monitor must be obtained before calling this.
   // aEndTime is in microseconds.
   void SetMediaEndTime(int64_t aEndTime);
 
-  // Called from decode thread to update the duration. Can result in
-  // a durationchangeevent. aDuration is in microseconds.
-  void UpdateDuration(int64_t aDuration);
+  // Called from main thread to update the duration with an estimated value.
+  // The duration is only changed if its significantly different than the
+  // the current duration, as the incoming duration is an estimate and so
+  // often is unstable as more data is read and the estimate is updated.
+  // Can result in a durationchangeevent. aDuration is in microseconds.
+  void UpdateEstimatedDuration(int64_t aDuration);
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   bool OnDecodeThread() const {
     return IsCurrentThread(mDecodeThread);
   }
   bool OnStateMachineThread() const;
   bool OnAudioThread() const {
--- a/content/media/directshow/DirectShowReader.cpp
+++ b/content/media/directshow/DirectShowReader.cpp
@@ -31,22 +31,24 @@ GetDirectShowLog() {
 #define LOG(...) PR_LOG(GetDirectShowLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 
 #else
 #define LOG(...)
 #endif
 
 DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder),
+    mMP3FrameParser(aDecoder->GetResource()->GetLength()),
 #ifdef DEBUG
     mRotRegister(0),
 #endif
     mNumChannels(0),
     mAudioRate(0),
-    mBytesPerSample(0)
+    mBytesPerSample(0),
+    mDuration(0)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   MOZ_COUNT_CTOR(DirectShowReader);
 }
 
 DirectShowReader::~DirectShowReader()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
@@ -361,9 +363,26 @@ DirectShowReader::OnDecodeThreadStart()
 
 void
 DirectShowReader::OnDecodeThreadFinish()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   CoUninitialize();
 }
 
+void
+DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mMP3FrameParser.IsMP3()) {
+    return;
+  }
+  mMP3FrameParser.Parse(aBuffer, aLength, aOffset);
+  int64_t duration = mMP3FrameParser.GetDuration();
+  if (duration != mDuration) {
+    mDuration = duration;
+    MOZ_ASSERT(mDecoder);
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mDecoder->UpdateEstimatedMediaDuration(mDuration);
+  }
+}
+
 } // namespace mozilla
--- a/content/media/directshow/DirectShowReader.h
+++ b/content/media/directshow/DirectShowReader.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(DirectShowReader_h_)
 #define DirectShowReader_h_
 
 #include "Windows.h" // HRESULT, DWORD
 #include "MediaDecoderReader.h"
 #include "mozilla/RefPtr.h"
+#include "MP3FrameParser.h"
 
 class IGraphBuilder;
 class IMediaControl;
 class IMediaSeeking;
 class IMediaEventEx;
 
 namespace mozilla {
 
@@ -65,16 +66,20 @@ public:
                 int64_t aCurrentTime) MOZ_OVERRIDE;
 
   nsresult GetBuffered(mozilla::dom::TimeRanges* aBuffered,
                        int64_t aStartTime) MOZ_OVERRIDE;
 
   void OnDecodeThreadStart() MOZ_OVERRIDE;
   void OnDecodeThreadFinish() MOZ_OVERRIDE;
 
+  void NotifyDataArrived(const char* aBuffer,
+                         uint32_t aLength,
+                         int64_t aOffset) MOZ_OVERRIDE;
+
 private:
 
   // Calls mAudioQueue.Finish(), and notifies the filter graph that playback
   // is complete. aStatus is the code to send to the filter graph.
   // Always returns false, so that we can just "return Finish()" from
   // DecodeAudioData().
   bool Finish(HRESULT aStatus);
 
@@ -86,28 +91,36 @@ private:
 
   // Wraps the MediaResource, and feeds undecoded data into the filter graph.
   RefPtr<SourceFilter> mSourceFilter;
 
   // Sits at the end of the graph, removing decoded samples from the graph.
   // The graph will block while this is blocked, i.e. it will pause decoding.
   RefPtr<AudioSinkFilter> mAudioSinkFilter;
 
+  // Some MP3s are variable bitrate, so DirectShow's duration estimation
+  // can make its duration estimation based on the wrong bitrate. So we parse
+  // the MP3 frames to get a more accuate estimate of the duration.
+  MP3FrameParser mMP3FrameParser;
+
 #ifdef DEBUG
   // Used to add/remove the filter graph to the Running Object Table. You can
   // connect GraphEdit/GraphStudio to the graph to observe and/or debug its
   // topology and state.
   DWORD mRotRegister;
 #endif
 
   // Number of channels in the audio stream.
   uint32_t mNumChannels;
 
   // Samples per second in the audio stream.
   uint32_t mAudioRate;
 
   // Number of bytes per sample. Can be either 1 or 2.
   uint32_t mBytesPerSample;
+
+  // Duration of the stream, in microseconds.
+  int64_t mDuration;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -627,17 +627,17 @@ void OmxDecoder::NotifyDataArrived(const
 
   int64_t durationUs = mMP3FrameParser.GetDuration();
 
   if (durationUs != mDurationUs) {
     mDurationUs = durationUs;
 
     MOZ_ASSERT(mDecoder);
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->UpdateMediaDuration(mDurationUs);
+    mDecoder->UpdateEstimatedMediaDuration(mDurationUs);
   }
 }
 
 void OmxDecoder::ReleaseVideoBuffer() {
   if (mVideoBuffer) {
     mVideoBuffer->release();
     mVideoBuffer = nullptr;
   }