Bug 612798 - Remoting Audio - Thread per stream to avoid problems with blocking drain/write calls. r=dougt. a=blocking-fennec
--- 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