Bug 1103824 - Ensure first initialization of IAudioClient happens on STA thread. r=padenot
authorMatthew Gregan <kinetik@flim.org>
Thu, 14 May 2015 18:33:23 +1200
changeset 249030 aeaa70495f1d0d17413a695bb05ae4a2416e5747
parent 249029 60fc51d40741a6d8ca7348297e972a965993bad5
child 249031 c95ecd760bf3c8509a6f31719e6cd8b001f6a0cc
push idunknown
push userunknown
push dateunknown
reviewerspadenot
bugs1103824
milestone41.0a1
Bug 1103824 - Ensure first initialization of IAudioClient happens on STA thread. r=padenot
dom/media/CubebUtils.cpp
dom/media/MediaDecoder.cpp
dom/media/webaudio/AudioContext.cpp
media/libcubeb/src/cubeb_wasapi.cpp
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -99,22 +99,23 @@ void InitPreferredSampleRate()
     // Query failed, use a sensible default.
     sPreferredSampleRate = 44100;
   }
 }
 
 cubeb* GetCubebContextUnlocked()
 {
   sMutex.AssertCurrentThreadOwns();
-  if (sCubebContext ||
-      cubeb_init(&sCubebContext, "CubebUtils") == CUBEB_OK) {
-    return sCubebContext;
+  if (!sCubebContext) {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (cubeb_init(&sCubebContext, "CubebUtils") != CUBEB_OK) {
+      NS_WARNING("cubeb_init failed");
+    }
   }
-  NS_WARNING("cubeb_init failed");
-  return nullptr;
+  return sCubebContext;
 }
 
 uint32_t GetCubebLatency()
 {
   StaticMutexAutoLock lock(sMutex);
   return sCubebLatency;
 }
 
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaDecoder.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MathAlgorithms.h"
 #include <limits>
 #include "nsIObserver.h"
 #include "nsTArray.h"
+#include "CubebUtils.h"
 #include "VideoUtils.h"
 #include "MediaDecoderStateMachine.h"
 #include "ImageContainer.h"
 #include "MediaResource.h"
 #include "nsError.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "nsIMemoryReporter.h"
@@ -395,16 +396,21 @@ MediaDecoder::MediaDecoder() :
 }
 
 bool MediaDecoder::Init(MediaDecoderOwner* aOwner)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mOwner = aOwner;
   mVideoFrameContainer = aOwner->GetVideoFrameContainer();
   MediaShutdownManager::Instance().Register(this);
+  // We don't use the cubeb context yet, but need to ensure it is created on
+  // the main thread.
+  if (!CubebUtils::GetCubebContext()) {
+    NS_WARNING("Audio backend initialization failed.");
+  }
   return true;
 }
 
 void MediaDecoder::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown)
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -105,16 +105,21 @@ AudioContext::AudioContext(nsPIDOMWindow
   // bound to the window.
   mDestination = new AudioDestinationNode(this, aIsOffline, aChannel,
                                           aNumberOfChannels, aLength, aSampleRate);
   // We skip calling SetIsOnlyNodeForContext and the creation of the
   // audioChannelAgent during mDestination's constructor, because we can only
   // call them after mDestination has been set up.
   mDestination->CreateAudioChannelAgent();
   mDestination->SetIsOnlyNodeForContext(true);
+  // We don't use the cubeb context yet, but need to ensure it is created on
+  // the main thread.
+  if (!aIsOffline && !CubebUtils::GetCubebContext()) {
+    NS_WARNING("Audio backend initialization failed.");
+  }
 }
 
 AudioContext::~AudioContext()
 {
   nsPIDOMWindow* window = GetOwner();
   if (window) {
     window->RemoveAudioContext(this);
   }
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -139,38 +139,27 @@ struct auto_lock {
   {
     lock->leave();
   }
 private:
   owned_critical_section * lock;
 };
 
 struct auto_com {
-  auto_com() {
-    result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+  auto_com(DWORD dwCoInit = COINIT_MULTITHREADED) {
+    result = CoInitializeEx(NULL, dwCoInit);
   }
   ~auto_com() {
-    if (result == RPC_E_CHANGED_MODE) {
-      // This is not an error, COM was not initialized by this function, so it is
-      // not necessary to uninit it.
-      LOG("COM already initialized in STA.\n");
-    } else if (result == S_FALSE) {
-      // This is not an error. We are allowed to call CoInitializeEx more than
-      // once, as long as it is matches by an CoUninitialize call.
-      // We do that in the dtor which is guaranteed to be called.
-      LOG("COM already initialized in MTA\n");
-    }
-    if (SUCCEEDED(result)) {
+    if (ok()) {
       CoUninitialize();
     }
   }
   bool ok() {
-    return result == RPC_E_CHANGED_MODE || SUCCEEDED(result);
+    return SUCCEEDED(result);
   }
-private:
   HRESULT result;
 };
 
 typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)(
                                       const char * TaskName, LPDWORD TaskIndex);
 typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle);
 
 extern cubeb_ops const wasapi_ops;
@@ -487,16 +476,17 @@ wasapi_stream_render_loop(LPVOID stream)
   HANDLE wait_array[3] = {stm->shutdown_event, stm->reconfigure_event, stm->refill_event};
   HANDLE mmcss_handle = NULL;
   HRESULT hr = 0;
   bool first = true;
   DWORD mmcss_task_index = 0;
   auto_com com;
   if (!com.ok()) {
     LOG("COM initialization failed on render_loop thread.\n");
+    stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
     return 0;
   }
 
   /* We could consider using "Pro Audio" here for WebAudio and
    * maybe WebRTC. */
   mmcss_handle =
     stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
   if (!mmcss_handle) {
@@ -683,37 +673,67 @@ HRESULT get_default_endpoint(IMMDevice *
     SafeRelease(enumerator);
     return hr;
   }
 
   SafeRelease(enumerator);
 
   return ERROR_SUCCESS;
 }
+
+owned_critical_section g_first_init_lock;
+bool g_first_init = false;
 } // namespace anonymous
 
 extern "C" {
 int wasapi_init(cubeb ** context, char const * context_name)
 {
-  HRESULT hr;
-  auto_com com;
-  if (!com.ok()) {
-    return CUBEB_ERROR;
-  }
+  auto_lock lock(&g_first_init_lock);
+  if (!g_first_init) {
+    // Per the MSDN documentation for IAudioClient, the first use *must* be made from an STA thread.
+    auto_com com(COINIT_APARTMENTTHREADED);
+    if (FAILED(com.result)) {
+      return CUBEB_ERROR;
+    }
 
-  /* We don't use the device yet, but need to make sure we can initialize one
-     so that this backend is not incorrectly enabled on platforms that don't
-     support WASAPI. */
-  IMMDevice * device;
-  hr = get_default_endpoint(&device);
-  if (FAILED(hr)) {
-    LOG("Could not get device.\n");
-    return CUBEB_ERROR;
+    /* We don't use the device yet, but need to make sure we can initialize one
+       so that this backend is not incorrectly enabled on platforms that don't
+       support WASAPI. */
+    IMMDevice * device;
+    HRESULT hr = get_default_endpoint(&device);
+    if (FAILED(hr)) {
+      LOG("Could not get device: %x\n", hr);
+      return CUBEB_ERROR;
+    }
+    IAudioClient * client;
+    hr = device->Activate(__uuidof(IAudioClient),
+                          CLSCTX_INPROC_SERVER,
+                          NULL, (void **)&client);
+    if (SUCCEEDED(hr)) {
+      WAVEFORMATEX * mix_format;
+      hr = client->GetMixFormat(&mix_format);
+      if (SUCCEEDED(hr)) {
+        hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED,
+                                AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
+                                AUDCLNT_STREAMFLAGS_NOPERSIST,
+                                ms_to_hns(100),
+                                0,
+                                mix_format,
+                                NULL);
+        CoTaskMemFree(mix_format);
+        g_first_init = true;
+      }
+      SafeRelease(client);
+    }
+    SafeRelease(device);
+    if (FAILED(hr)) {
+      LOG("Could not initialize IAudioClient: %x\n", hr);
+      return CUBEB_ERROR;
+    }
   }
-  SafeRelease(device);
 
   cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
 
   ctx->ops = &wasapi_ops;
 
   ctx->mmcss_module = LoadLibraryA("Avrt.dll");
 
   if (ctx->mmcss_module) {
@@ -779,20 +799,18 @@ char const * wasapi_get_backend_id(cubeb
 }
 
 int
 wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
 {
   HRESULT hr;
   IAudioClient * client;
   WAVEFORMATEX * mix_format;
-  auto_com com;
-  if (!com.ok()) {
-    return CUBEB_ERROR;
-  }
+  XASSERT(g_first_init);
+  auto_com com; /* don't care what COM mode we're in here */
 
   XASSERT(ctx && max_channels);
 
   IMMDevice * device;
   hr = get_default_endpoint(&device);
   if (FAILED(hr)) {
     return CUBEB_ERROR;
   }
@@ -820,20 +838,18 @@ wasapi_get_max_channel_count(cubeb * ctx
 }
 
 int
 wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
 {
   HRESULT hr;
   IAudioClient * client;
   REFERENCE_TIME default_period;
-  auto_com com;
-  if (!com.ok()) {
-    return CUBEB_ERROR;
-  }
+  XASSERT(g_first_init);
+  auto_com com; /* don't care what COM mode we're in here */
 
   IMMDevice * device;
   hr = get_default_endpoint(&device);
   if (FAILED(hr)) {
     LOG("Could not get default endpoint:%x.\n", hr);
     return CUBEB_ERROR;
   }
 
@@ -867,20 +883,18 @@ wasapi_get_min_latency(cubeb * ctx, cube
 }
 
 int
 wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
 {
   HRESULT hr;
   IAudioClient * client;
   WAVEFORMATEX * mix_format;
-  auto_com com;
-  if (!com.ok()) {
-    return CUBEB_ERROR;
-  }
+  XASSERT(g_first_init);
+  auto_com com; /* don't care what COM mode we're in here */
 
   IMMDevice * device;
   hr = get_default_endpoint(&device);
   if (FAILED(hr)) {
     return CUBEB_ERROR;
   }
 
   hr = device->Activate(__uuidof(IAudioClient),