Bug 793274 - Make sure to have enough frames pushed to the AudioStream before starting it. r=kinetik
authorPaul Adenot <paul@paul.cx>
Mon, 26 Nov 2012 15:13:08 +0100
changeset 119268 4317e42ac8078bbaa2a7de0d423973ce84e7aa11
parent 119267 5f0038a234024b016113869402f4124a087c67c3
child 119269 d33e1241da6488ca365e26e5b0a09ed992ab14bf
push id24195
push userMs2ger@gmail.com
push dateSat, 19 Jan 2013 16:10:11 +0000
treeherdermozilla-central@02e12a80aef9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs793274
milestone21.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 793274 - Make sure to have enough frames pushed to the AudioStream before starting it. r=kinetik
content/html/content/src/nsHTMLAudioElement.cpp
content/media/AudioStream.cpp
content/media/AudioStream.h
content/media/MediaDecoderStateMachine.cpp
--- a/content/html/content/src/nsHTMLAudioElement.cpp
+++ b/content/html/content/src/nsHTMLAudioElement.cpp
@@ -171,16 +171,19 @@ nsHTMLAudioElement::MozWriteAudio(const 
 
   float* frames = JS_GetFloat32ArrayData(tsrc);
   // Convert the samples back to integers as we are using fixed point audio in
   // the AudioStream.
   // This could be optimized to avoid allocation and memcpy when
   // AudioDataValue is 'float', but it's not worth it for this deprecated API.
   nsAutoArrayPtr<AudioDataValue> audioData(new AudioDataValue[writeLen * mChannels]);
   ConvertAudioSamples(frames, audioData.get(), writeLen * mChannels);
+  if (!mAudioStream->IsStarted()) {
+    mAudioStream->Start();
+  }
   nsresult rv = mAudioStream->Write(audioData.get(), writeLen);
 
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Return the actual amount written.
   *aRetVal = writeLen * mChannels;
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -54,16 +54,18 @@ class NativeAudioStream : public AudioSt
 
   nsresult Init(int32_t aNumChannels, int32_t aRate,
                 const dom::AudioChannelType aAudioChannelType);
   void Shutdown();
   nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames);
   uint32_t Available();
   void SetVolume(double aVolume);
   void Drain();
+  nsresult Start();
+  bool IsStarted();
   void Pause();
   void Resume();
   int64_t GetPosition();
   int64_t GetPositionInFrames();
   int64_t GetPositionInFramesInternal();
   bool IsPaused();
   int32_t GetMinWriteSize();
 
@@ -176,16 +178,17 @@ static sa_stream_type_t ConvertChannelTo
       return SA_STREAM_TYPE_MAX;
   }
 }
 
 AudioStream::AudioStream()
 : mInRate(0),
   mOutRate(0),
   mChannels(0),
+  mWritten(0),
   mAudioClock(this)
 {}
 
 void AudioStream::InitLibrary()
 {
 #ifdef PR_LOGGING
   gAudioStreamLog = PR_NewLogModule("AudioStream");
 #endif
@@ -271,16 +274,21 @@ nsresult AudioStream::SetPreservesPitch(
     mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
   }
 
   mAudioClock.SetPreservesPitch(aPreservesPitch);
 
   return NS_OK;
 }
 
+int64_t AudioStream::GetWritten()
+{
+  return mWritten;
+}
+
 NativeAudioStream::NativeAudioStream() :
   mVolume(1.0),
   mAudioHandle(0),
   mPaused(false),
   mInError(false)
 {
 }
 
@@ -381,16 +389,18 @@ nsresult NativeAudioStream::Write(const 
       written = WriteToBackend(data, framesAvailable * mChannels);
     } else {
       written = 0;
     }
   } else {
     written = WriteToBackend(aBuf, samples);
   }
 
+  mWritten += aFrames;
+
   if (written == -1) {
     PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_write error"));
     mInError = true;
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
@@ -450,16 +460,29 @@ void NativeAudioStream::Drain()
 
   int r = sa_stream_drain(static_cast<sa_stream_t*>(mAudioHandle));
   if (r != SA_SUCCESS && r != SA_ERROR_INVALID) {
     PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_drain error"));
     mInError = true;
   }
 }
 
+nsresult NativeAudioStream::Start()
+{
+  // Since sydneyaudio is a push API, the playback is started when enough frames
+  // have been written. Hence, Start() is a noop.
+  return NS_OK;
+}
+
+bool NativeAudioStream::IsStarted()
+{
+  // See the comment for the |Start()| method.
+  return true;
+}
+
 void NativeAudioStream::Pause()
 {
   if (mInError)
     return;
   mPaused = true;
   sa_stream_pause(static_cast<sa_stream_t*>(mAudioHandle));
 }
 
@@ -592,16 +615,18 @@ class BufferedAudioStream : public Audio
 
   nsresult Init(int32_t aNumChannels, int32_t aRate,
                 const dom::AudioChannelType aAudioChannelType);
   void Shutdown();
   nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames);
   uint32_t Available();
   void SetVolume(double aVolume);
   void Drain();
+  nsresult Start();
+  bool IsStarted();
   void Pause();
   void Resume();
   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
@@ -788,34 +813,29 @@ BufferedAudioStream::Write(const AudioDa
     uint32_t available = std::min(bytesToCopy, mBuffer.Available());
     NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0,
         "Must copy complete frames.");
 
     mBuffer.AppendElements(src, available);
     src += available;
     bytesToCopy -= available;
 
-    if (mState != STARTED) {
-      int r;
-      {
+    if (bytesToCopy > 0) {
+      // If we are not playing, but our buffer is full, start playing to make
+      // room for soon-to-be-decoded data.
+      if (!IsStarted()) {
         MonitorAutoUnlock mon(mMonitor);
-        r = cubeb_stream_start(mCubebStream);
+        Start();
       }
-      mState = r == CUBEB_OK ? STARTED : ERRORED;
-    }
-
-    if (mState != STARTED) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (bytesToCopy > 0) {
       mon.Wait();
     }
   }
 
+  mWritten += aFrames;
+
   return NS_OK;
 }
 
 uint32_t
 BufferedAudioStream::Available()
 {
   MonitorAutoLock mon(mMonitor);
   NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated.");
@@ -844,16 +864,36 @@ BufferedAudioStream::Drain()
     return;
   }
   mState = DRAINING;
   while (mState == DRAINING) {
     mon.Wait();
   }
 }
 
+nsresult
+BufferedAudioStream::Start()
+{
+  if (!mCubebStream) {
+    return NS_ERROR_FAILURE;
+  }
+  if (mState != STARTED) {
+    int r = cubeb_stream_start(mCubebStream);
+    mState = r == CUBEB_OK ? STARTED : ERRORED;
+    return mState == STARTED ? NS_OK : NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+bool
+BufferedAudioStream::IsStarted()
+{
+  return mState == STARTED ? true : false;
+}
+
 void
 BufferedAudioStream::Pause()
 {
   MonitorAutoLock mon(mMonitor);
   if (!mCubebStream || mState != STARTED) {
     return;
   }
 
--- a/content/media/AudioStream.h
+++ b/content/media/AudioStream.h
@@ -39,16 +39,18 @@ class AudioClock
     // Called on the audio thread.
     double GetPlaybackRate();
     // Set if we are preserving the pitch.
     // Called on the audio thread.
     void SetPreservesPitch(bool aPreservesPitch);
     // Get the current pitch preservation state.
     // Called on the audio thread.
     bool GetPreservesPitch();
+    // Get the number of frames written to the backend.
+    int64_t GetWritten();
   private:
     // This AudioStream holds a strong reference to this AudioClock. This
     // pointer is garanteed to always be valid.
     AudioStream* mAudioStream;
     // The old output rate, to compensate audio latency for the period inbetween
     // the moment resampled buffers are pushed to the hardware and the moment the
     // clock should take the new rate into account for A/V sync.
     int mOldOutRate;
@@ -125,20 +127,30 @@ public:
 
   // Set the current volume of the audio playback. This is a value from
   // 0 (meaning muted) to 1 (meaning full volume).  Thread-safe.
   virtual void SetVolume(double aVolume) = 0;
 
   // Block until buffered audio data has been consumed.
   virtual void Drain() = 0;
 
-  // Pause audio playback
+  // Start the stream.
+  virtual nsresult Start() = 0;
+
+  // Check if the stream is started.
+  virtual bool IsStarted() = 0;
+
+  // Return the number of frames written so far in the stream. This allow the
+  // caller to check if it is safe to start the stream, if needed.
+  virtual int64_t GetWritten();
+
+  // Pause audio playback.
   virtual void Pause() = 0;
 
-  // Resume audio playback
+  // Resume audio playback.
   virtual void Resume() = 0;
 
   // Return the position in microseconds of the audio frame being played by
   // the audio hardware, compensated for playback rate change. Thread-safe.
   virtual int64_t GetPosition() = 0;
 
   // Return the position, measured in audio frames played since the stream
   // was opened, of the audio hardware.  Thread-safe.
@@ -168,15 +180,17 @@ public:
   virtual nsresult SetPreservesPitch(bool aPreservesPitch);
 
 protected:
   // Input rate in Hz (characteristic of the media being played)
   int mInRate;
   // Output rate in Hz (characteristic of the playback rate)
   int mOutRate;
   int mChannels;
+  // Number of frames written to the buffers.
+  int64_t mWritten;
   AudioClock mAudioClock;
   nsAutoPtr<soundtouch::SoundTouch> mTimeStretcher;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -104,16 +104,19 @@ static const uint32_t QUICK_BUFFER_THRES
 static const uint32_t QUICK_BUFFERING_LOW_DATA_USECS = 1000000;
 
 // If QUICK_BUFFERING_LOW_DATA_USECS is > AMPLE_AUDIO_USECS, we won't exit
 // 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;
+
 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);
 }
 
@@ -947,23 +950,38 @@ void MediaDecoderStateMachine::DecodeLoo
 
 bool MediaDecoderStateMachine::IsPlaying()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   return !mPlayStartTime.IsNull();
 }
 
+// If we have already written enough frames to the AudioStream, start the
+// playback.
+static void
+StartAudioStreamPlaybackIfNeeded(AudioStream* aStream)
+{
+  // We want to have enough data in the buffer to start the stream.
+  if (!aStream->IsStarted() &&
+      static_cast<double>(aStream->GetWritten()) / aStream->GetRate() >=
+      static_cast<double>(AUDIOSTREAM_MIN_WRITE_BEFORE_START_USECS) / USECS_PER_S) {
+    aStream->Start();
+  }
+}
+
 static void WriteSilence(AudioStream* aStream, uint32_t aFrames)
 {
   uint32_t numSamples = aFrames * aStream->GetChannels();
   nsAutoTArray<AudioDataValue, 1000> buf;
   buf.SetLength(numSamples);
   memset(buf.Elements(), 0, numSamples * sizeof(AudioDataValue));
   aStream->Write(buf.Elements(), aFrames);
+
+  StartAudioStreamPlaybackIfNeeded(aStream);
 }
 
 void MediaDecoderStateMachine::AudioLoop()
 {
   NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
   LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get()));
   int64_t audioDuration = 0;
   int64_t audioStartTime = -1;
@@ -1103,16 +1121,21 @@ void MediaDecoderStateMachine::AudioLoop
     }
   }
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     if (mReader->AudioQueue().AtEndOfStream() &&
         mState != DECODER_STATE_SHUTDOWN &&
         !mStopAudioThread)
     {
+      // If the media was too short to trigger the start of the audio stream,
+      // start it now.
+      if (!mAudioStream->IsStarted()) {
+        mAudioStream->Start();
+      }
       // Last frame pushed to audio hardware, wait for the audio to finish,
       // before the audio thread terminates.
       bool seeking = false;
       {
         int64_t unplayedFrames = audioDuration % minWriteFrames;
         if (minWriteFrames > 1 && unplayedFrames > 0) {
           // Sound is written by libsydneyaudio to the hardware in blocks of
           // frames of size minWriteFrames. So if the number of frames we've
@@ -1207,16 +1230,18 @@ uint32_t MediaDecoderStateMachine::PlayF
   uint32_t frames = 0;
   if (!PR_GetEnv("MOZ_QUIET")) {
     LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of data to stream for AudioData at %lld",
                        mDecoder.get(), audio->mFrames, audio->mTime));
   }
   mAudioStream->Write(audio->mAudioData,
                       audio->mFrames);
 
+  StartAudioStreamPlaybackIfNeeded(mAudioStream);
+
   offset = audio->mOffset;
   frames = audio->mFrames;
 
   // Dispatch events to the DOM for the audio just written.
   mEventManager.QueueWrittenAudioData(audio->mAudioData.get(),
                                       audio->mFrames * aChannels,
                                       (aFrameOffset + frames) * aChannels);
   if (offset != -1) {