Bug 1221587: use cubeb devids to select input devices r=padenot
authorRandell Jesup <rjesup@jesup.org>
Thu, 21 Jan 2016 11:51:36 -0500
changeset 281169 cf179eb7e1957aec07dfec289c57199c3ea1ea79
parent 281168 6766d2a6b5a978af0ba95cf27864d2761e926be4
child 281170 ef761a32a969178df81a61bc2d0a376dde2eec03
push id29930
push usercbook@mozilla.com
push dateFri, 22 Jan 2016 11:05:50 +0000
treeherdermozilla-central@7104d650a97d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1221587
milestone46.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 1221587: use cubeb devids to select input devices r=padenot
dom/media/CubebUtils.h
dom/media/GraphDriver.cpp
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
dom/media/MediaStreamGraphImpl.h
dom/media/webrtc/MediaEngineWebRTC.cpp
dom/media/webrtc/MediaEngineWebRTC.h
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -8,16 +8,18 @@
 #define CubebUtils_h_
 
 #include "cubeb/cubeb.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 
 namespace mozilla {
 namespace CubebUtils {
 
+typedef cubeb_devid AudioDeviceID;
+
 // Initialize Audio Library. Some Audio backends require initializing the
 // library before using it.
 void InitLibrary();
 
 // Shutdown Audio Library. Some Audio backends require shutting down the
 // library after using it.
 void ShutdownLibrary();
 
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -554,61 +554,64 @@ AudioCallbackDriver::AudioCallbackDriver
 AudioCallbackDriver::~AudioCallbackDriver()
 {
   MOZ_ASSERT(mPromisesForOperation.IsEmpty());
 }
 
 void
 AudioCallbackDriver::Init()
 {
-  cubeb_stream_params out_params;
-  cubeb_stream_params in_params;
+  cubeb_stream_params output;
+  cubeb_stream_params input;
   uint32_t latency;
 
   MOZ_ASSERT(!NS_IsMainThread(),
       "This is blocking and should never run on the main thread.");
 
-  out_params.devid = nullptr; // XXX take from config for the graph
-  mSampleRate = out_params.rate = CubebUtils::PreferredSampleRate();
+  output.devid = mGraphImpl->mOutputDeviceID;
+  mSampleRate = output.rate = CubebUtils::PreferredSampleRate();
 
 #if defined(__ANDROID__)
 #if defined(MOZ_B2G)
-  out_params.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel);
+  output.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel);
 #else
-  out_params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+  output.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
-  if (out_params.stream_type == CUBEB_STREAM_TYPE_MAX) {
+  if (output.stream_type == CUBEB_STREAM_TYPE_MAX) {
     NS_WARNING("Bad stream type");
     return;
   }
 #else
   (void)mAudioChannel;
 #endif
 
-  out_params.channels = mGraphImpl->AudioChannelCount();
+  output.channels = mGraphImpl->AudioChannelCount();
   if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
-    out_params.format = CUBEB_SAMPLE_S16NE;
+    output.format = CUBEB_SAMPLE_S16NE;
   } else {
-    out_params.format = CUBEB_SAMPLE_FLOAT32NE;
+    output.format = CUBEB_SAMPLE_FLOAT32NE;
   }
 
-  if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), out_params, &latency) != CUBEB_OK) {
+  if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency) != CUBEB_OK) {
     NS_WARNING("Could not get minimal latency from cubeb.");
     return;
   }
 
-  in_params = out_params;
-  in_params.channels = 1; // change to support optional stereo capture
+  input = output;
+  input.channels = 1; // change to support optional stereo capture
+  input.devid = mGraphImpl->mInputDeviceID;
 
   cubeb_stream* stream;
-  // XXX Only pass input in_params if we have an input listener.  Always
+  // XXX Only pass input input if we have an input listener.  Always
   // set up output because it's easier, and it will just get silence.
   // XXX Add support for adding/removing an input listener later.
   if (cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
-                        "AudioCallbackDriver", &out_params, &in_params, latency,
+                        "AudioCallbackDriver",
+                        mGraphImpl->mInputWanted ? &input : nullptr,
+                        mGraphImpl->mOutputWanted ? &output : nullptr, latency,
                         DataCallback_s, StateCallback_s, this) == CUBEB_OK) {
     mAudioStream.own(stream);
   } else {
     NS_WARNING("Could not create a cubeb stream for MediaStreamGraph, falling back to a SystemClockDriver");
     // Fall back to a driver using a normal thread.
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     SetNextDriver(new SystemClockDriver(GraphImpl()));
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -919,56 +919,66 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
   // If the stream has finished and the timestamps of all frames have expired
   // then no more updates are required.
   if (aStream->mFinished && !haveMultipleImages) {
     aStream->mLastPlayedVideoFrame.SetNull();
   }
 }
 
 void
-MediaStreamGraphImpl::OpenAudioInputImpl(char *aName, AudioDataListener *aListener)
+MediaStreamGraphImpl::OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
+                                         AudioDataListener *aListener)
 {
+  MOZ_ASSERT(!mInputWanted);
+  mInputWanted = true;
+  mInputDeviceID = aID;
+  // XXX Switch Drivers
   if (CurrentDriver()->AsAudioCallbackDriver()) {
     CurrentDriver()->SetInputListener(aListener);
   } else {
     // XXX Switch to callback driver
   }
   mAudioInputs.AppendElement(aListener); // always monitor speaker data
 }
 
 nsresult
-MediaStreamGraphImpl::OpenAudioInput(char *aName, AudioDataListener *aListener)
+MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID,
+                                     AudioDataListener *aListener)
 {
   // XXX So, so, so annoying.  Can't AppendMessage except on Mainthread
   if (!NS_IsMainThread()) {
     NS_DispatchToMainThread(WrapRunnable(this,
                                          &MediaStreamGraphImpl::OpenAudioInput,
-                                         aName, aListener)); // XXX Fix! string need to copied
+                                         aID, aListener)); // XXX Fix! string need to copied
     return NS_OK;
   }
   class Message : public ControlMessage {
   public:
-    Message(MediaStreamGraphImpl *aGraph, char *aName, AudioDataListener *aListener) :
-      ControlMessage(nullptr), mGraph(aGraph), mName(aName), mListener(aListener) {}
+    Message(MediaStreamGraphImpl *aGraph, CubebUtils::AudioDeviceID aID,
+            AudioDataListener *aListener) :
+      ControlMessage(nullptr), mGraph(aGraph), mID(aID), mListener(aListener) {}
     virtual void Run()
     {
-      mGraph->OpenAudioInputImpl(mName, mListener);
+      mGraph->OpenAudioInputImpl(mID, mListener);
     }
     MediaStreamGraphImpl *mGraph;
-    char *mName; // XXX needs to copy
+    CubebUtils::AudioDeviceID mID;
     RefPtr<AudioDataListener> mListener;
   };
-  this->AppendMessage(new Message(this, aName, aListener));
+  this->AppendMessage(new Message(this, aID, aListener));
   return NS_OK;
 }
 
 void
 MediaStreamGraphImpl::CloseAudioInputImpl(AudioDataListener *aListener)
 {
+  mInputDeviceID = nullptr;
+  mInputWanted = false;
   CurrentDriver()->RemoveInputListener(aListener);
+  // XXX Switch Drivers
   mAudioInputs.RemoveElement(aListener);
 }
 
 void
 MediaStreamGraphImpl::CloseAudioInput(AudioDataListener *aListener)
 {
   // XXX So, so, so annoying.  Can't AppendMessage except on Mainthread
   if (!NS_IsMainThread()) {
@@ -2706,16 +2716,20 @@ ProcessedMediaStream::DestroyImpl()
   // SetStreamOrderDirty(), for other reasons.
 }
 
 MediaStreamGraphImpl::MediaStreamGraphImpl(GraphDriverType aDriverRequested,
                                            TrackRate aSampleRate,
                                            dom::AudioChannel aChannel)
   : MediaStreamGraph(aSampleRate)
   , mPortCount(0)
+  , mInputWanted(false)
+  , mInputDeviceID(nullptr)
+  , mOutputWanted(true)
+  , mOutputDeviceID(nullptr)
   , mNeedAnotherIteration(false)
   , mGraphDriverAsleep(false)
   , mMonitor("MediaStreamGraphImpl")
   , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
   , mEndTime(GRAPH_TIME_MAX)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
   , mDetectedNotRunning(false)
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -1203,17 +1203,18 @@ public:
   };
   // Main thread only
   static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested,
                                        dom::AudioChannel aChannel);
   static MediaStreamGraph* CreateNonRealtimeInstance(TrackRate aSampleRate);
   // Idempotent
   static void DestroyNonRealtimeInstance(MediaStreamGraph* aGraph);
 
-  virtual nsresult OpenAudioInput(char *aName, AudioDataListener *aListener) {
+  virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+                                  AudioDataListener *aListener) {
     return NS_ERROR_FAILURE;
   }
   virtual void CloseAudioInput(AudioDataListener *aListener) {}
 
   // Control API.
   /**
    * Create a stream that a media decoder (or some other source of
    * media data, such as a camera) can write to.
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -345,18 +345,20 @@ public:
    * Set the correct current video frame for stream aStream.
    */
   void PlayVideo(MediaStream* aStream);
   /**
    * No more data will be forthcoming for aStream. The stream will end
    * at the current buffer end point. The StreamBuffer's tracks must be
    * explicitly set to finished by the caller.
    */
-  void OpenAudioInputImpl(char *aName, AudioDataListener *aListener);
-  virtual nsresult OpenAudioInput(char *aName, AudioDataListener *aListener) override;
+  void OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
+                          AudioDataListener *aListener);
+  virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+                                  AudioDataListener *aListener) override;
   void CloseAudioInputImpl(AudioDataListener *aListener);
   virtual void CloseAudioInput(AudioDataListener *aListener) override;
 
   void FinishStream(MediaStream* aStream);
   /**
    * Compute how much stream data we would like to buffer for aStream.
    */
   StreamTime GetDesiredBufferEnd(MediaStream* aStream);
@@ -577,16 +579,25 @@ public:
    * Date of the last time we updated the main thread with the graph state.
    */
   TimeStamp mLastMainThreadUpdate;
   /**
    * Number of active MediaInputPorts
    */
   int32_t mPortCount;
 
+  /**
+   * Devices to use for cubeb input & output, or NULL for no input (void*),
+   * and boolean to control if we want input/output
+   */
+  bool mInputWanted;
+  CubebUtils::AudioDeviceID mInputDeviceID;
+  bool mOutputWanted;
+  CubebUtils::AudioDeviceID mOutputDeviceID;
+
   // True if the graph needs another iteration after the current iteration.
   Atomic<bool> mNeedAnotherIteration;
   // GraphDriver may need a WakeUp() if something changes
   Atomic<bool> mGraphDriverAsleep;
 
   // mMonitor guards the data below.
   // MediaStreamGraph normally does its work without holding mMonitor, so it is
   // not safe to just grab mMonitor from some thread and start monkeying with
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -39,16 +39,18 @@ GetUserMediaLog()
 #include "MediaEngineGonkVideoSource.h"
 #endif
 
 #undef LOG
 #define LOG(args) MOZ_LOG(GetUserMediaLog(), mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 
+cubeb_device_collection* AudioInputCubeb::mDevices = nullptr;
+
 MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
   : mMutex("mozilla::MediaEngineWebRTC"),
     mVoiceEngine(nullptr),
     mAudioInput(nullptr),
     mAudioEngineInit(false)
 {
 #ifndef MOZ_B2G_CAMERA
   nsCOMPtr<nsIComponentRegistrar> compMgr;
@@ -321,17 +323,24 @@ MediaEngineWebRTC::EnumerateAudioDevices
     }
 
     RefPtr<MediaEngineAudioSource> aSource;
     NS_ConvertUTF8toUTF16 uuid(uniqueId);
     if (mAudioSources.Get(uuid, getter_AddRefs(aSource))) {
       // We've already seen this device, just append.
       aASources->AppendElement(aSource.get());
     } else {
-      aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, mAudioInput,
+      AudioInput* audioinput = mAudioInput;
+      if (true /*platform_supports_full_duplex*/) {
+        // For cubeb, it has state (the selected ID
+        // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this
+        // XXX Small window where the device list/index could change!
+        audioinput = new mozilla::AudioInputCubeb(mVoiceEngine);
+      }
+      aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, audioinput,
                                                       i, deviceName, uniqueId);
       mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
       aASources->AppendElement(aSource);
     }
   }
 }
 
 void
@@ -361,16 +370,17 @@ MediaEngineWebRTC::Shutdown()
   if (mVoiceEngine) {
     mVoiceEngine->SetTraceCallback(nullptr);
     webrtc::VoiceEngine::Delete(mVoiceEngine);
   }
 
   mVoiceEngine = nullptr;
 
   mozilla::camera::Shutdown();
+  AudioInputCubeb::CleanupGlobalData();
 
   if (mThread) {
     mThread->Shutdown();
     mThread = nullptr;
   }
 }
 
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -154,90 +154,123 @@ protected:
 
   webrtc::VoiceEngine* mVoiceEngine;
 };
 
 class AudioInputCubeb final : public AudioInput
 {
 public:
   explicit AudioInputCubeb(webrtc::VoiceEngine* aVoiceEngine) :
-    AudioInput(aVoiceEngine), mDevices(nullptr) {}
+    AudioInput(aVoiceEngine), mSelectedDevice(0)
+  {
+    // Force calculation of the indexes.  We could keep them global
+    // too... cleanup would be annoying
+    int devices;
+    GetNumOfRecordingDevices(devices);
+  }
+
+  static void CleanupGlobalData()
+  {
+    if (mDevices) {
+      // This doesn't require anything more than support for free()
+      cubeb_device_collection_destroy(mDevices);
+      mDevices = nullptr;
+    }
+  }
 
   int GetNumOfRecordingDevices(int& aDevices)
   {
-    // devices = cubeb_get_num_devices(...)
-    if (CUBEB_OK != cubeb_enumerate_devices(CubebUtils::GetCubebContext(),
-                                            CUBEB_DEVICE_TYPE_INPUT,
-                                            &mDevices)) {
-      return 0;
-    }
-    aDevices = 0;
-    for (uint32_t i = 0; i < mDevices->count; i++) {
-      if (mDevices->device[i]->type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
-          mDevices->device[i]->state == CUBEB_DEVICE_STATE_ENABLED)
-      {
-        aDevices++;
-        // XXX to support device changes, we need to identify by name/UUID not index
+    if (!mDevices) {
+      if (CUBEB_OK != cubeb_enumerate_devices(CubebUtils::GetCubebContext(),
+                                              CUBEB_DEVICE_TYPE_INPUT,
+                                              &mDevices)) {
+        return 0;
       }
     }
+    // Calculate translation once (per AudioInput)
+    if (mDeviceIndexes.Length() == 0) {
+      for (uint32_t i = 0; i < mDevices->count; i++) {
+        if (mDevices->device[i]->type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
+            (mDevices->device[i]->state == CUBEB_DEVICE_STATE_ENABLED ||
+             mDevices->device[i]->state == CUBEB_DEVICE_STATE_UNPLUGGED))
+        {
+          mDeviceIndexes.AppendElement(i);
+          // XXX to support device changes, we need to identify by name/devid not index
+        }
+      }
+    }
+    aDevices = mDeviceIndexes.Length();
     return 0;
   }
 
+  int32_t DeviceIndex(int aIndex)
+  {
+    if (aIndex == -1) {
+      aIndex = 0; // -1 = system default
+    }
+    if (aIndex >= (int) mDeviceIndexes.Length()) {
+      return -1;
+    }
+    return mDeviceIndexes[aIndex]; // translate to mDevices index
+  }
+
   int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128],
                              char aStrGuidUTF8[128])
   {
-    if (!mDevices) {
+    int32_t devindex = DeviceIndex(aIndex);
+    if (!mDevices || devindex < 0) {
       return 1;
     }
-    int devindex = aIndex == -1 ? 0 : aIndex;
     PR_snprintf(aStrNameUTF8, 128, "%s%s", aIndex == -1 ? "default: " : "",
                 mDevices->device[devindex]->friendly_name);
     aStrGuidUTF8[0] = '\0';
     return 0;
   }
 
   int GetRecordingDeviceStatus(bool& aIsAvailable)
   {
     // With cubeb, we only expose devices of type CUBEB_DEVICE_TYPE_INPUT
     aIsAvailable = true;
     return 0;
   }
 
   void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener)
   {
+    MOZ_ASSERT(mDevices);
+
     ScopedCustomReleasePtr<webrtc::VoEExternalMedia> ptrVoERender;
     ptrVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
     if (ptrVoERender) {
       ptrVoERender->SetExternalRecordingStatus(true);
     }
-    aGraph->OpenAudioInput(nullptr, aListener);
+    aGraph->OpenAudioInput(mDevices->device[mSelectedDevice]->devid, aListener);
   }
 
   void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener)
   {
     aGraph->CloseAudioInput(aListener);
   }
 
   int SetRecordingDevice(int aIndex)
   {
-    // Relevant with devid support
-    return 1;
+    int32_t devindex = DeviceIndex(aIndex);
+    if (!mDevices || devindex < 0) {
+      return 1;
+    }
+    mSelectedDevice = devindex;
+    return 0;
   }
 
 protected:
-  ~AudioInputCubeb() {
-  {
-    if (mDevices) {
-      cubeb_device_collection_destroy(mDevices);
-      mDevices = nullptr;
-    }
-  }
+  ~AudioInputCubeb() {}
 
 private:
-  cubeb_device_collection* mDevices;
+  nsTArray<int> mDeviceIndexes;
+  int mSelectedDevice;
+  static cubeb_device_collection *mDevices;
 };
 
 class AudioInputWebRTC final : public AudioInput
 {
 public:
   explicit AudioInputWebRTC(webrtc::VoiceEngine* aVoiceEngine) : AudioInput(aVoiceEngine) {}
 
   int GetNumOfRecordingDevices(int& aDevices)