Bug 848954 - Part 22 - Port the osx panning code to live in the AudioCallbackDriver. r=jesup
authorPaul Adenot <paul@paul.cx>
Tue, 26 Aug 2014 17:02:31 +0200
changeset 223512 c6cc3eebcd72ab004839c3f7ffced282ac6672ad
parent 223511 792b2a9e647ee528237487376991c65b88a46cfb
child 223513 511915d63977504d393f88ec8df058cecd0bfe8d
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs848954
milestone34.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 848954 - Part 22 - Port the osx panning code to live in the AudioCallbackDriver. r=jesup
content/media/GraphDriver.cpp
content/media/GraphDriver.h
--- a/content/media/GraphDriver.cpp
+++ b/content/media/GraphDriver.cpp
@@ -1,16 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <MediaStreamGraphImpl.h>
 #include "CubebUtils.h"
 
+#ifdef XP_MACOSX
+#include <sys/sysctl.h>
+#endif
+
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaStreamGraphLog;
 #define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
 #else
 #define STREAM_LOG(type, msg)
 #endif
 
 namespace mozilla {
@@ -510,16 +514,19 @@ AudioCallbackDriver::Init()
                         "AudioCallbackDriver", params, latency,
                         DataCallback_s, StateCallback_s, this) == CUBEB_OK) {
     mAudioStream.own(stream);
   } else {
     NS_WARNING("Could not create a cubeb stream for MediaStreamGraph.");
     return;
   }
 
+  cubeb_stream_register_device_changed_callback(mAudioStream,
+                                                AudioCallbackDriver::DeviceChangedCallback_s);
+
   StartStream();
 
   STREAM_LOG(PR_LOG_DEBUG, ("AudioCallbackDriver started."));
 }
 
 
 void
 AudioCallbackDriver::Destroy()
@@ -654,16 +661,23 @@ AudioCallbackDriver::DataCallback_s(cube
 /* static */ void
 AudioCallbackDriver::StateCallback_s(cubeb_stream* aStream, void * aUser,
                                      cubeb_state aState)
 {
   AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
   driver->StateCallback(aState);
 }
 
+/* static */ void
+AudioCallbackDriver::DeviceChangedCallback_s(void* aUser)
+{
+  AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
+  driver->DeviceChangedCallback();
+}
+
 bool AudioCallbackDriver::InCallback() {
   MonitorAutoLock mon(mGraphImpl->GetMonitor());
   return mInCallback;
 }
 
 AudioCallbackDriver::AutoInCallback::AutoInCallback(AudioCallbackDriver* aDriver)
   : mDriver(aDriver)
 {
@@ -808,16 +822,69 @@ AudioCallbackDriver::MixerCallback(Audio
 
   mBuffer.WriteFrames(aMixedBuffer, mBuffer.Available());
   MOZ_ASSERT(mBuffer.Available() == 0, "Missing frames to fill audio callback's buffer.");
 
   DebugOnly<uint32_t> written = mScratchBuffer.Fill(aMixedBuffer + toWrite * aChannels, aFrames - toWrite);
   NS_WARN_IF_FALSE(written == aFrames - toWrite, "Dropping frames.");
 };
 
+void AudioCallbackDriver::PanOutputIfNeeded(bool aMicrophoneActive)
+{
+#ifdef XP_MACOSX
+  cubeb_device* out;
+  int rv;
+  char name[128];
+  size_t length = sizeof(name);
+
+  rv = sysctlbyname("hw.model", name, &length, NULL, 0);
+  if (rv) {
+    return;
+  }
+
+  if (!strncmp(name, "MacBookPro", 10)) {
+    if (cubeb_stream_get_current_device(mAudioStream, &out) == CUBEB_OK) {
+      // Check if we are currently outputing sound on external speakers.
+      if (!strcmp(out->output_name, "ispk")) {
+        // Pan everything to the right speaker.
+        if (aMicrophoneActive) {
+          if (cubeb_stream_set_panning(mAudioStream, 1.0) != CUBEB_OK) {
+            NS_WARNING("Could not pan audio output to the right.");
+          }
+        } else {
+          if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) {
+            NS_WARNING("Could not pan audio output to the center.");
+          }
+        }
+      } else {
+        if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) {
+          NS_WARNING("Could not pan audio output to the center.");
+        }
+      }
+      cubeb_stream_device_destroy(mAudioStream, out);
+    }
+  }
+#endif
+}
+
+void
+AudioCallbackDriver::DeviceChangedCallback() {
+  MonitorAutoLock mon(mGraphImpl->GetMonitor());
+  PanOutputIfNeeded(mMicrophoneActive);
+}
+
+void
+AudioCallbackDriver::SetMicrophoneActive(bool aActive)
+{
+  MonitorAutoLock mon(mGraphImpl->GetMonitor());
+
+  mMicrophoneActive = aActive;
+
+  PanOutputIfNeeded(mMicrophoneActive);
+}
 
 uint32_t
 AudioCallbackDriver::IterationDuration()
 {
   // The real fix would be to have an API in cubeb to give us the number. Short
   // of that, we approximate it here. bug 1019507
   return mIterationDurationMS;
 }
--- a/content/media/GraphDriver.h
+++ b/content/media/GraphDriver.h
@@ -337,16 +337,17 @@ public:
   virtual void WakeUp() MOZ_OVERRIDE;
 
   /* Static wrapper function cubeb calls back. */
   static long DataCallback_s(cubeb_stream * aStream,
                              void * aUser, void * aBuffer,
                              long aFrames);
   static void StateCallback_s(cubeb_stream* aStream, void * aUser,
                               cubeb_state aState);
+  static void DeviceChangedCallback_s(void * aUser);
   /* This function is called by the underlying audio backend when a refill is
    * needed. This is what drives the whole graph when it is used to output
    * audio. If the return value is exactly aFrames, this function will get
    * called again. If it is less than aFrames, the stream will go in draining
    * mode, and this function will not be called again. */
   long DataCallback(AudioDataValue* aBuffer, long aFrames);
   /* This function is called by the underlying audio backend, but is only used
    * for informational purposes at the moment. */
@@ -365,17 +366,29 @@ public:
 
   virtual AudioCallbackDriver* AsAudioCallbackDriver() {
     return this;
   }
 
   bool InCallback();
 
   bool IsStarted();
+
+  /* Tell the driver whether this process is using a microphone or not. This is
+   * thread safe. */
+  void SetMicrophoneActive(bool aActive);
 private:
+  /**
+   * On certain MacBookPro, the microphone is located near the left speaker.
+   * We need to pan the sound output to the right speaker if we are using the
+   * mic and the built-in speaker, or we will have terrible echo.  */
+  void PanOutputIfNeeded(bool aMicrophoneActive);
+  /**
+   * This is called when the output device used by the cubeb stream changes. */
+  void DeviceChangedCallback();
   /* Start the cubeb stream */
   void StartStream();
   friend class AsyncCubebTask;
   void Init();
   /* MediaStreamGraph are always down/up mixed to stereo for now. */
   static const uint32_t ChannelCount = 2;
   /* The size of this buffer comes from the fact that some audio backends can
    * call back with a number of frames lower than one block (128 frames), so we
@@ -421,16 +434,20 @@ private:
   nsCOMPtr<nsIThread> mInitShutdownThread;
   dom::AudioChannel mAudioChannel;
   /* This can only be accessed with the graph's monitor held. */
   bool mInCallback;
   /* A thread has been created to be able to pause and restart the audio thread,
    * but has not done so yet. This indicates tha the callback should return
    * early */
   bool mPauseRequested;
+  /**
+   * True if microphone is being used by this process. This is synchronized by
+   * the graph's monitor. */
+  bool mMicrophoneActive;
 };
 
 class AsyncCubebTask : public nsRunnable
 {
 public:
   enum AsyncCubebOperation {
     INIT,
     SHUTDOWN,