Bug 846612 - r=kinetik
authorPaul Adenot <paul@paul.cx>
Mon, 04 Mar 2013 15:48:58 +0100
changeset 124360 0fa7493d1dcfc09ba794f9eb6f5fefa8b89a101a
parent 124359 c80bfb6663b843f73722ac494567e70362380b50
child 124361 2a8cfcbb89714b606a7bcd2d1ebf54fb6e0fb264
push id24417
push userryanvm@gmail.com
push dateMon, 11 Mar 2013 23:58:07 +0000
treeherdermozilla-central@7433bc4545c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs846612
milestone22.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 846612 - r=kinetik
content/html/content/src/nsHTMLMediaElement.cpp
content/media/AudioStream.cpp
content/media/AudioStream.h
content/media/MediaDecoderStateMachine.cpp
content/media/test/crashtests/846612.html
content/media/test/crashtests/crashtests.list
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -3413,17 +3413,19 @@ NS_IMETHODIMP nsHTMLMediaElement::SetDef
 NS_IMETHODIMP nsHTMLMediaElement::GetPlaybackRate(double* aPlaybackRate)
 {
   *aPlaybackRate = mPlaybackRate;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsHTMLMediaElement::SetPlaybackRate(double aPlaybackRate)
 {
-  if (aPlaybackRate < 0) {
+  // Changing the playback rate of a media that has more than two channels is
+  // not supported.
+  if (aPlaybackRate < 0 || (mChannels > 2 && aPlaybackRate != 1.0)) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   mPlaybackRate = ClampPlaybackRate(aPlaybackRate);
 
   if (!mMuted) {
     if (mPlaybackRate < 0 ||
         mPlaybackRate > THRESHOLD_HIGH_PLAYBACKRATE_AUDIO ||
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -218,38 +218,47 @@ void AudioStream::ShutdownLibrary()
   }
 #endif
 }
 
 AudioStream::~AudioStream()
 {
 }
 
-void AudioStream::EnsureTimeStretcherInitialized()
+nsresult AudioStream::EnsureTimeStretcherInitialized()
 {
   if (!mTimeStretcher) {
+    // SoundTouch does not support a number of channels > 2
+    if (mChannels > 2) {
+      return NS_ERROR_FAILURE;
+    }
     mTimeStretcher = new soundtouch::SoundTouch();
     mTimeStretcher->setSampleRate(mInRate);
     mTimeStretcher->setChannels(mChannels);
     mTimeStretcher->setPitch(1.0);
   }
+  return NS_OK;
 }
 
 nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
 {
   NS_ASSERTION(aPlaybackRate > 0.0,
                "Can't handle negative or null playbackrate in the AudioStream.");
   // Avoid instantiating the resampler if we are not changing the playback rate.
   if (aPlaybackRate == mAudioClock.GetPlaybackRate()) {
     return NS_OK;
   }
+
+  if (EnsureTimeStretcherInitialized() != NS_OK) {
+    return NS_ERROR_FAILURE;
+  }
+
   mAudioClock.SetPlaybackRate(aPlaybackRate);
   mOutRate = mInRate / aPlaybackRate;
 
-  EnsureTimeStretcherInitialized();
 
   if (mAudioClock.GetPreservesPitch()) {
     mTimeStretcher->setTempo(aPlaybackRate);
     mTimeStretcher->setRate(1.0f);
   } else {
     mTimeStretcher->setTempo(1.0f);
     mTimeStretcher->setRate(aPlaybackRate);
   }
@@ -258,17 +267,19 @@ nsresult AudioStream::SetPlaybackRate(do
 
 nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch)
 {
   // Avoid instantiating the timestretcher instance if not needed.
   if (aPreservesPitch == mAudioClock.GetPreservesPitch()) {
     return NS_OK;
   }
 
-  EnsureTimeStretcherInitialized();
+  if (EnsureTimeStretcherInitialized() != NS_OK) {
+    return NS_ERROR_FAILURE;
+  }
 
   if (aPreservesPitch == true) {
     mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate());
     mTimeStretcher->setRate(1.0f);
   } else {
     mTimeStretcher->setTempo(1.0f);
     mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
   }
@@ -369,17 +380,19 @@ nsresult NativeAudioStream::Write(const 
 
   if (mInError)
     return NS_ERROR_FAILURE;
 
   uint32_t samples = aFrames * mChannels;
   int32_t written = -1;
 
   if (mInRate != mOutRate) {
-    EnsureTimeStretcherInitialized();
+    if (EnsureTimeStretcherInitialized() != NS_OK) {
+      return NS_ERROR_FAILURE;
+    }
     mTimeStretcher->putSamples(aBuf, aFrames);
     uint32_t numFrames = mTimeStretcher->numSamples();
     uint32_t arraySize = numFrames * mChannels * sizeof(AudioDataValue);
     nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[arraySize]);
     uint32_t framesAvailable = mTimeStretcher->receiveSamples(data, numFrames);
     NS_ASSERTION(mTimeStretcher->numSamples() == 0,
                  "We did not get all the data from the SoundTouch pipeline.");
     // It is possible to have nothing to write: the data are in the processing
@@ -618,17 +631,17 @@ class BufferedAudioStream : public Audio
   int64_t GetPosition();
   int64_t GetPositionInFrames();
   int64_t GetPositionInFramesInternal();
   bool IsPaused();
   int32_t GetMinWriteSize();
   // This method acquires the monitor and forward the call to the base
   // class, to prevent a race on |mTimeStretcher|, in
   // |AudioStream::EnsureTimeStretcherInitialized|.
-  void EnsureTimeStretcherInitialized();
+  nsresult EnsureTimeStretcherInitialized();
 
 private:
   static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
   {
     return static_cast<BufferedAudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
   }
 
   static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
@@ -719,21 +732,21 @@ BufferedAudioStream::BufferedAudioStream
 {
 }
 
 BufferedAudioStream::~BufferedAudioStream()
 {
   Shutdown();
 }
 
-void
+nsresult
 BufferedAudioStream::EnsureTimeStretcherInitialized()
 {
   MonitorAutoLock mon(mMonitor);
-  AudioStream::EnsureTimeStretcherInitialized();
+  return AudioStream::EnsureTimeStretcherInitialized();
 }
 
 nsresult
 BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
                             const dom::AudioChannelType aAudioChannelType)
 {
   cubeb* cubebContext = GetCubebContext();
 
@@ -1011,17 +1024,19 @@ BufferedAudioStream::GetUnprocessed(void
 }
 
 long
 BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames)
 {
   long processedFrames = 0;
 
   // We need to call the non-locking version, because we already have the lock.
-  AudioStream::EnsureTimeStretcherInitialized();
+  if (AudioStream::EnsureTimeStretcherInitialized() != NS_OK) {
+    return 0;
+  }
 
   uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
   double playbackRate = static_cast<double>(mInRate) / mOutRate;
   uint32_t toPopBytes = FramesToBytes(ceil(aFrames / playbackRate));
   uint32_t available = 0;
   bool lowOnBufferedData = false;
   do {
     // Check if we already have enough data in the time stretcher pipeline.
--- a/content/media/AudioStream.h
+++ b/content/media/AudioStream.h
@@ -164,17 +164,17 @@ public:
   // Returns the minimum number of audio frames which must be written before
   // you can be sure that something will be played.
   virtual int32_t GetMinWriteSize() = 0;
 
   int GetRate() { return mOutRate; }
   int GetChannels() { return mChannels; }
 
   // This should be called before attempting to use the time stretcher.
-  virtual void EnsureTimeStretcherInitialized();
+  virtual nsresult EnsureTimeStretcherInitialized();
   // Set playback rate as a multiple of the intrinsic playback rate. This is to
   // be called only with aPlaybackRate > 0.0.
   virtual nsresult SetPlaybackRate(double aPlaybackRate);
   // Switch between resampling (if false) and time stretching (if true, default).
   virtual nsresult SetPreservesPitch(bool aPreservesPitch);
 
 protected:
   // Input rate in Hz (characteristic of the media being played)
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1005,21 +1005,25 @@ void MediaDecoderStateMachine::AudioLoop
 
   {
     // AudioStream initialization can block for extended periods in unusual
     // circumstances, so we take care to drop the decoder monitor while
     // initializing.
     nsAutoPtr<AudioStream> audioStream(AudioStream::AllocateStream());
     audioStream->Init(channels, rate, audioChannelType);
     audioStream->SetVolume(volume);
-    audioStream->SetPreservesPitch(preservesPitch);
+    if (audioStream->SetPreservesPitch(preservesPitch) != NS_OK) {
+      NS_WARNING("Setting the pitch preservation failed at AudioLoop start.");
+    }
     if (playbackRate != 1.0) {
       NS_ASSERTION(playbackRate != 0,
                    "Don't set the playbackRate to 0 on an AudioStream.");
-      audioStream->SetPlaybackRate(playbackRate);
+      if (audioStream->SetPlaybackRate(playbackRate) != NS_OK) {
+        NS_WARNING("Setting the playback rate failed at AudioLoop start.");
+      }
     }
 
     {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       mAudioStream = audioStream;
     }
   }
 
@@ -1071,20 +1075,24 @@ void MediaDecoderStateMachine::AudioLoop
     }
 
     if (setVolume) {
       mAudioStream->SetVolume(volume);
     }
     if (setPlaybackRate) {
       NS_ASSERTION(playbackRate != 0,
                    "Don't set the playbackRate to 0 in the AudioStreams");
-      mAudioStream->SetPlaybackRate(playbackRate);
+      if (mAudioStream->SetPlaybackRate(playbackRate) != NS_OK) {
+        NS_WARNING("Setting the playback rate failed in AudioLoop.");
+      }
     }
     if (setPreservesPitch) {
-      mAudioStream->SetPreservesPitch(preservesPitch);
+      if (mAudioStream->SetPreservesPitch(preservesPitch) != NS_OK) {
+        NS_WARNING("Setting the pitch preservation failed in AudioLoop.");
+      }
     }
     if (minWriteFrames == -1) {
       minWriteFrames = mAudioStream->GetMinWriteSize();
     }
     NS_ASSERTION(mReader->AudioQueue().GetSize() > 0,
                  "Should have data to play");
     // See if there's a gap in the audio. If there is, push silence into the
     // audio hardware, so we can play across the gap.
@@ -2753,16 +2761,22 @@ void MediaDecoderStateMachine::NotifyAud
 
 void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(aPlaybackRate != 0,
       "PlaybackRate == 0 should be handled before this function.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
+  // We don't currently support more than two channels when changing playback
+  // rate.
+  if (mAudioStream && mAudioStream->GetChannels() > 2) {
+    return;
+  }
+
   if (mPlaybackRate == aPlaybackRate) {
     return;
   }
 
   // Get position of the last time we changed the rate.
   if (!HasAudio()) {
     // mBasePosition is a position in the video stream, not an absolute time.
     if (mState == DECODER_STATE_SEEKING) {
new file mode 100644
--- /dev/null
+++ b/content/media/test/crashtests/846612.html
@@ -0,0 +1,8 @@
+<script>
+document.addEventListener("DOMContentLoaded", function() {
+  var a = document.querySelector("audio");
+  a.playbackRate = 2;
+  a.play();
+});
+</script>
+<audio src="495794-1.ogg"></audio>
--- a/content/media/test/crashtests/crashtests.list
+++ b/content/media/test/crashtests/crashtests.list
@@ -8,8 +8,9 @@ load 493915-1.html
 skip-if(Android) load 495794-1.html
 load 492286-1.xhtml
 load 576612-1.html
 skip-if(Android) load 691096-1.html # Android sound API can't handle playing large number of sounds at once.
 load 752784-1.html
 skip-if(Android||B2G) HTTP load 795892-1.html # load failed, bug 833371 for B2G
 skip-if(Android||B2G) load 789075-1.html # load failed, bug 833371 for B2G
 load 844563.html
+load 846612.html