Bug 612798 - Remoting Audio - Thread per stream to avoid problems with blocking drain/write calls. r=dougt. a=blocking-fennec
authorMatthew Gregan <kinetik@flim.org>
Tue, 30 Nov 2010 18:37:32 +1300
changeset 58403 9bbfdb7bc9a86b654c4e5079f8bf6023c386963f
parent 58402 c334590653876d3b63c8a2b1a8cd196a937da08e
child 58404 17cecf2c18244b09dadd870e3aeede1f036e269c
push idunknown
push userunknown
push dateunknown
reviewersdougt, blocking-fennec
bugs612798
milestone2.0b8pre
Bug 612798 - Remoting Audio - Thread per stream to avoid problems with blocking drain/write calls. r=dougt. a=blocking-fennec
content/media/nsAudioStream.cpp
content/media/nsAudioStream.h
dom/ipc/AudioChild.cpp
dom/ipc/AudioChild.h
dom/ipc/AudioParent.cpp
dom/ipc/PAudio.ipdl
--- a/content/media/nsAudioStream.cpp
+++ b/content/media/nsAudioStream.cpp
@@ -35,16 +35,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifdef MOZ_IPC
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PAudioChild.h"
 #include "mozilla/dom/AudioChild.h"
+#include "mozilla/Monitor.h"
 #include "nsXULAppAPI.h"
 using namespace mozilla::dom;
 #endif
 
 #include <stdio.h>
 #include <math.h>
 #include "prlog.h"
 #include "prmem.h"
@@ -62,20 +63,16 @@ extern "C" {
 #endif
 
 using mozilla::TimeStamp;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gAudioStreamLog = nsnull;
 #endif
 
-#ifdef MOZ_IPC
-static nsIThread *gAudioPlaybackThread = nsnull;
-#endif
-
 #define FAKE_BUFFER_SIZE 176400
 #define MILLISECONDS_PER_SECOND 1000
 
 class nsAudioStreamLocal : public nsAudioStream
 {
  public:
   NS_DECL_ISUPPORTS
 
@@ -138,21 +135,22 @@ class nsAudioStreamRemote : public nsAud
   PRInt64 GetSampleOffset();
   PRBool IsPaused();
 
   nsRefPtr<AudioChild> mAudioChild;
 
   SampleFormat mFormat;
   int mRate;
   int mChannels;
+
+  PRInt32 mBytesPerSample;
+
   // PR_TRUE if this audio stream is paused.
   PRPackedBool mPaused;
 
-  PRInt32 mBytesPerSample;
-
   friend class AudioInitEvent;
 };
 
 class AudioInitEvent : public nsRunnable
 {
  public:
   AudioInitEvent(nsAudioStreamRemote* owner)
   {
@@ -290,42 +288,27 @@ class AudioShutdownEvent : public nsRunn
 #endif // MOZ_IPC
 
 
 void nsAudioStream::InitLibrary()
 {
 #ifdef PR_LOGGING
   gAudioStreamLog = PR_NewLogModule("nsAudioStream");
 #endif
-
-#ifdef MOZ_IPC
-  // We only need this thread in the main process.
-  if (XRE_GetProcessType() == GeckoProcessType_Default) {
-      NS_NewThread(&gAudioPlaybackThread);
-  }
-#endif
 }
 
 void nsAudioStream::ShutdownLibrary()
 {
-#ifdef MOZ_IPC
-  NS_IF_RELEASE(gAudioPlaybackThread);
-#endif
 }
 
 
 nsIThread *
-nsAudioStream::GetGlobalThread()
+nsAudioStream::GetThread()
 {
-#ifdef MOZ_IPC
-  NS_IF_ADDREF(gAudioPlaybackThread);
-  return gAudioPlaybackThread;
-#else
-  return nsnull;
-#endif
+  return mAudioPlaybackThread;
 }
 
 nsAudioStream* nsAudioStream::AllocateStream()
 {
 #ifdef MOZ_IPC
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     return new nsAudioStreamRemote();
   }
@@ -337,16 +320,22 @@ nsAudioStreamLocal::nsAudioStreamLocal()
   mVolume(1.0),
   mAudioHandle(0),
   mRate(0),
   mChannels(0),
   mFormat(FORMAT_S16_LE),
   mPaused(PR_FALSE),
   mInError(PR_FALSE)
 {
+#ifdef MOZ_IPC
+  // We only need this thread in the main process.
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    NS_NewThread(getter_AddRefs(mAudioPlaybackThread));
+  }
+#endif
 }
 
 nsAudioStreamLocal::~nsAudioStreamLocal()
 {
   Shutdown();
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS0(nsAudioStreamLocal)
@@ -658,17 +647,18 @@ nsAudioStreamRemote::SetVolume(float aVo
 }
 
 void
 nsAudioStreamRemote::Drain()
 {
   if (!mAudioChild)
     return;
   nsCOMPtr<nsIRunnable> event = new AudioDrainEvent(mAudioChild);
-  NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+  NS_DispatchToMainThread(event);
+  mAudioChild->WaitForDrain();
 }
  
 void
 nsAudioStreamRemote::Pause()
 {
   mPaused = PR_TRUE;
   if (!mAudioChild)
     return;
--- a/content/media/nsAudioStream.h
+++ b/content/media/nsAudioStream.h
@@ -36,16 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 #if !defined(nsAudioStream_h_)
 #define nsAudioStream_h_
 
 #include "nscore.h"
 #include "nsISupportsImpl.h"
 #include "nsIThread.h"
+#include "nsAutoPtr.h"
 
 class nsAudioStream : public nsISupports
 {
 public:
 
   enum SampleFormat
   {
     FORMAT_U8,
@@ -58,17 +59,17 @@ public:
   static void InitLibrary();
 
   // Shutdown Audio Library. Some Audio backends require shutting down the
   // library after using it.
   static void ShutdownLibrary();
 
   // Thread, usually for MOZ_IPC handling, that is shared between audio streams.
   // This may return null in the child process
-  static nsIThread *GetGlobalThread();
+  virtual nsIThread *GetThread();
 
   // AllocateStream will return either a local stream or a remoted stream
   // depending on where you call it from.  If MOZ_IPC is enabled, and you
   // call this from a child process, you may recieve an implementation which
   // forwards to a compositing process.
   static nsAudioStream* AllocateStream();
 
   // Initialize the audio stream. aNumChannels is the number of audio channels 
@@ -109,11 +110,14 @@ public:
   virtual PRInt64 GetPosition() = 0;
 
   // Return the position, measured in samples played since the start, by
   // the audio hardware.
   virtual PRInt64 GetSampleOffset() = 0;
 
   // Returns PR_TRUE when the audio stream is paused.
   virtual PRBool IsPaused() = 0;
+
+protected:
+  nsCOMPtr<nsIThread> mAudioPlaybackThread;
 };
 
 #endif
--- a/dom/ipc/AudioChild.cpp
+++ b/dom/ipc/AudioChild.cpp
@@ -43,17 +43,19 @@ namespace mozilla {
 namespace dom {
 
 NS_IMPL_THREADSAFE_ADDREF(AudioChild);
 NS_IMPL_THREADSAFE_RELEASE(AudioChild);
 
 AudioChild::AudioChild()
   : mLastSampleOffset(-1),
     mLastSampleOffsetTime(0),
-    mIPCOpen(PR_TRUE)
+    mAudioMonitor("media.audiochild.monitor"),
+    mIPCOpen(PR_TRUE),
+    mDrained(PR_FALSE)
 {
   MOZ_COUNT_CTOR(AudioChild);
 }
 
 AudioChild::~AudioChild()
 {
   MOZ_COUNT_DTOR(AudioChild);
 }
@@ -68,16 +70,34 @@ bool
 AudioChild::RecvSampleOffsetUpdate(const PRInt64& offset,
                                    const PRInt64& time)
 {
   mLastSampleOffset = offset;
   mLastSampleOffsetTime = time;
   return true;
 }
 
+bool
+AudioChild::RecvDrainDone()
+{
+  mozilla::MonitorAutoEnter mon(mAudioMonitor);
+  mDrained = PR_TRUE;
+  mAudioMonitor.NotifyAll();
+  return true;
+}
+
+void
+AudioChild::WaitForDrain()
+{
+  mozilla::MonitorAutoEnter mon(mAudioMonitor);
+  while (!mDrained && mIPCOpen) {
+    mAudioMonitor.Wait();
+  }
+}
+
 PRInt64
 AudioChild::GetLastKnownSampleOffset()
 {
   return mLastSampleOffset;
 }
 
 PRInt64
 AudioChild::GetLastKnownSampleOffsetTime()
--- a/dom/ipc/AudioChild.h
+++ b/dom/ipc/AudioChild.h
@@ -36,38 +36,43 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_dom_AudioChild_h
 #define mozilla_dom_AudioChild_h
 
 #include "mozilla/dom/PAudioChild.h"
+#include "mozilla/Monitor.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioChild : public PAudioChild
 {
  public:
     NS_IMETHOD_(nsrefcnt) AddRef();
     NS_IMETHOD_(nsrefcnt) Release();
 
     AudioChild();
     virtual ~AudioChild();
     virtual bool RecvSampleOffsetUpdate(const PRInt64&, const PRInt64&);
+    virtual bool RecvDrainDone();
+    virtual void WaitForDrain();
     virtual void ActorDestroy(ActorDestroyReason);
     
     PRInt64 GetLastKnownSampleOffset();
     PRInt64 GetLastKnownSampleOffsetTime();
 
     PRBool IsIPCOpen() { return mIPCOpen; };
  private:
     nsAutoRefCnt mRefCnt;
     NS_DECL_OWNINGTHREAD
     PRInt64 mLastSampleOffset, mLastSampleOffsetTime;
+    mozilla::Monitor mAudioMonitor;
     PRPackedBool mIPCOpen;
+    PRPackedBool mDrained;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/ipc/AudioParent.cpp
+++ b/dom/ipc/AudioParent.cpp
@@ -84,16 +84,56 @@ class AudioPauseEvent : public nsRunnabl
     return NS_OK;
   }
 
  private:
     nsRefPtr<nsAudioStream> mOwner;
     PRBool mPause;
 };
 
+class AudioDrainDoneEvent : public nsRunnable
+{
+ public:
+  AudioDrainDoneEvent(AudioParent* owner)
+  {
+    mOwner = owner;
+  }
+
+  NS_IMETHOD Run()
+  {
+    mOwner->SendDrainDone();
+    return NS_OK;
+  }
+
+ private:
+    nsRefPtr<AudioParent> mOwner;
+};
+
+class AudioDrainEvent : public nsRunnable
+{
+ public:
+  AudioDrainEvent(AudioParent* parent, nsAudioStream* owner)
+  {
+    mParent = parent;
+    mOwner = owner;
+  }
+
+  NS_IMETHOD Run()
+  {
+    mOwner->Drain();
+    nsCOMPtr<nsIRunnable> event = new AudioDrainDoneEvent(mParent);
+    NS_DispatchToMainThread(event);
+    return NS_OK;
+  }
+
+ private:
+    nsRefPtr<nsAudioStream> mOwner;
+    nsRefPtr<AudioParent> mParent;
+};
+
 NS_IMPL_THREADSAFE_ISUPPORTS1(AudioParent, nsITimerCallback)
 
 nsresult
 AudioParent::Notify(nsITimer* timer)
 {
   if (!mIPCOpen || !mStream) {
     timer->Cancel();
     return NS_ERROR_FAILURE;
@@ -104,51 +144,52 @@ AudioParent::Notify(nsITimer* timer)
   return NS_OK;
 }
 bool
 AudioParent::RecvWrite(
         const nsCString& data,
         const PRUint32& count)
 {
   nsCOMPtr<nsIRunnable> event = new AudioWriteEvent(mStream, data, count);
-  nsCOMPtr<nsIThread> thread = nsAudioStream::GetGlobalThread();
+  nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
-    
+
 bool
 AudioParent::RecvSetVolume(const float& aVolume)
 {
   if (mStream)
     mStream->SetVolume(aVolume);
   return true;
 }
 
 bool
 AudioParent::RecvDrain()
 {
-  if (mStream)
-    mStream->Drain();
+  nsCOMPtr<nsIRunnable> event = new AudioDrainEvent(this, mStream);
+  nsCOMPtr<nsIThread> thread = mStream->GetThread();
+  thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::RecvPause()
 {
   nsCOMPtr<nsIRunnable> event = new AudioPauseEvent(mStream, PR_TRUE);
-  nsCOMPtr<nsIThread> thread = nsAudioStream::GetGlobalThread();
+  nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::RecvResume()
 {
   nsCOMPtr<nsIRunnable> event = new AudioPauseEvent(mStream, PR_FALSE);
-  nsCOMPtr<nsIThread> thread = nsAudioStream::GetGlobalThread();
+  nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::Recv__delete__()
 {
   if (mStream) {
--- a/dom/ipc/PAudio.ipdl
+++ b/dom/ipc/PAudio.ipdl
@@ -37,34 +37,35 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 include protocol PContent;
 
 namespace mozilla {
 namespace dom {
 
-sync protocol PAudio
+protocol PAudio
 {
   manager PContent;
 
 parent:
 
   __delete__();
 
   Write(nsCString data, PRUint32 count);
 
   SetVolume(float aVolume);
 
-  sync Drain();
+  Drain();
 
   Pause();
   Resume();
 
  child:
 
   SampleOffsetUpdate(PRInt64 offset, PRInt64 time);
+  DrainDone();
 
 
 };
 
 } // namespace dom
 } // namespace mozilla