Bug 1545079 - Enhance MockCubeb class to simulate a cubeb stream. r=padenot
authorAlex Chronopoulos <achronop@gmail.com>
Fri, 17 May 2019 16:38:04 +0000
changeset 474371 17935ce513d2c8909992f187edc68e0ae2e5d941
parent 474370 21607f778c71f98666bd5d5520a3839abc615c29
child 474372 70a4ba81a3960e7fdec38063e2608b56b61436b4
push id113152
push userdluca@mozilla.com
push dateSat, 18 May 2019 10:33:03 +0000
treeherdermozilla-inbound@9b2f851979cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1545079
milestone68.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 1545079 - Enhance MockCubeb class to simulate a cubeb stream. r=padenot On MochCubeb add a fake audio thread and the corresponding methods for stream_{init,start,stop,destroy}. Differential Revision: https://phabricator.services.mozilla.com/D30888
dom/media/gtest/MockCubeb.h
--- a/dom/media/gtest/MockCubeb.h
+++ b/dom/media/gtest/MockCubeb.h
@@ -1,13 +1,22 @@
 #ifndef MOCKCUBEB_H_
 #define MOCKCUBEB_H_
 
 #include "AudioDeviceInfo.h"
 
+#include <thread>
+#include <atomic>
+#include <chrono>
+
+using namespace std::chrono_literals;
+
+const long NUM_OF_FRAMES = 512;
+const uint32_t NUM_OF_CHANNELS = 2;
+
 struct cubeb_ops {
   int (*init)(cubeb** context, char const* context_name);
   char const* (*get_backend_id)(cubeb* context);
   int (*get_max_channel_count)(cubeb* context, uint32_t* max_channels);
   int (*get_min_latency)(cubeb* context, cubeb_stream_params params,
                          uint32_t* latency_ms);
   int (*get_preferred_sample_rate)(cubeb* context, uint32_t* rate);
   int (*enumerate_devices)(cubeb* context, cubeb_device_type type,
@@ -49,54 +58,73 @@ static int cubeb_mock_enumerate_devices(
 
 static int cubeb_mock_device_collection_destroy(
     cubeb* context, cubeb_device_collection* collection);
 
 static int cubeb_mock_register_device_collection_changed(
     cubeb* context, cubeb_device_type devtype,
     cubeb_device_collection_changed_callback callback, void* user_ptr);
 
+static int cubeb_mock_stream_init(
+    cubeb* context, cubeb_stream** stream, char const* stream_name,
+    cubeb_devid input_device, cubeb_stream_params* input_stream_params,
+    cubeb_devid output_device, cubeb_stream_params* output_stream_params,
+    unsigned int latency, cubeb_data_callback data_callback,
+    cubeb_state_callback state_callback, void* user_ptr);
+
+static int cubeb_mock_stream_start(cubeb_stream* stream);
+
+static int cubeb_mock_stream_stop(cubeb_stream* stream);
+
+static void cubeb_mock_stream_destroy(cubeb_stream* stream);
+
+static char const* cubeb_mock_get_backend_id(cubeb* context);
+
+static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume);
+
+static int cubeb_mock_get_min_latency(cubeb* context,
+                                      cubeb_stream_params params,
+                                      uint32_t* latency_ms);
+
 // Mock cubeb impl, only supports device enumeration for now.
 cubeb_ops const mock_ops = {
     /*.init =*/NULL,
-    /*.get_backend_id =*/NULL,
+    /*.get_backend_id =*/cubeb_mock_get_backend_id,
     /*.get_max_channel_count =*/NULL,
-    /*.get_min_latency =*/NULL,
+    /*.get_min_latency =*/cubeb_mock_get_min_latency,
     /*.get_preferred_sample_rate =*/NULL,
     /*.enumerate_devices =*/cubeb_mock_enumerate_devices,
     /*.device_collection_destroy =*/cubeb_mock_device_collection_destroy,
     /*.destroy =*/cubeb_mock_destroy,
-    /*.stream_init =*/NULL,
-    /*.stream_destroy =*/NULL,
-    /*.stream_start =*/NULL,
-    /*.stream_stop =*/NULL,
+    /*.stream_init =*/cubeb_mock_stream_init,
+    /*.stream_destroy =*/cubeb_mock_stream_destroy,
+    /*.stream_start =*/cubeb_mock_stream_start,
+    /*.stream_stop =*/cubeb_mock_stream_stop,
     /*.stream_reset_default_device =*/NULL,
     /*.stream_get_position =*/NULL,
     /*.stream_get_latency =*/NULL,
-    /*.stream_set_volume =*/NULL,
+    /*.stream_set_volume =*/cubeb_mock_stream_set_volume,
     /*.stream_set_panning =*/NULL,
     /*.stream_get_current_device =*/NULL,
     /*.stream_device_destroy =*/NULL,
     /*.stream_register_device_changed_callback =*/NULL,
     /*.register_device_collection_changed =*/
     cubeb_mock_register_device_collection_changed};
 
 // This class has two facets: it is both a fake cubeb backend that is intended
 // to be used for testing, and passed to Gecko code that expects a normal
 // backend, but is also controllable by the test code to decide what the backend
 // should do, depending on what is being tested.
 class MockCubeb {
  public:
-  MockCubeb()
-      : ops(&mock_ops),
-        mInputDeviceCollectionChangeCallback(nullptr),
-        mOutputDeviceCollectionChangeCallback(nullptr),
-        mInputDeviceCollectionChangeUserPtr(nullptr),
-        mOutputDeviceCollectionChangeUserPtr(nullptr),
-        mSupportsDeviceCollectionChangedCallback(true) {}
+  MockCubeb() : ops(&mock_ops) {}
+  ~MockCubeb() {
+    assert(!mFakeAudioThread);
+    assert(!mMockStream);
+  }
   // Cubeb backend implementation
   // This allows passing this class as a cubeb* instance.
   cubeb* AsCubebContext() { return reinterpret_cast<cubeb*>(this); }
   // Fill in the collection parameter with all devices of aType.
   int EnumerateDevices(cubeb_device_type aType,
                        cubeb_device_collection* collection) {
 #ifdef ANDROID
     EXPECT_TRUE(false) << "This is not to be called on Android.";
@@ -236,43 +264,119 @@ class MockCubeb {
   }
 
   // This allows simulating a backend that does not support setting a device
   // collection invalidation callback, to be able to test the fallback path.
   void SetSupportDeviceChangeCallback(bool aSupports) {
     mSupportsDeviceCollectionChangedCallback = aSupports;
   }
 
+  // Represents the fake cubeb_stream. The context instance is needed to
+  // provide access on cubeb_ops struct.
+  struct MockCubebStream {
+    cubeb* context = nullptr;
+  };
+
+  // Simulates the audio thread. The thread is created at StreamStart and
+  // destroyed at StreamStop. At next StreamStart a new thread is created.
+  static void ThreadFunction_s(MockCubeb* that) { that->ThreadFunction(); }
+
+  void ThreadFunction() {
+    while (!mStreamStop) {
+      cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(mMockStream.get());
+      long outframes = mDataCallback(stream, mUserPtr, nullptr, mOutputBuffer,
+                                     NUM_OF_FRAMES);
+      if (outframes < NUM_OF_FRAMES) {
+        mStateCallback(stream, mUserPtr, CUBEB_STATE_DRAINED);
+        break;
+      }
+      std::this_thread::sleep_for(
+          std::chrono::milliseconds(NUM_OF_FRAMES * 1000 / mSampleRate));
+    }
+  }
+
+  int StreamInit(cubeb* aContext, cubeb_stream** aStream,
+                 cubeb_stream_params* aInputStreamParams,
+                 cubeb_stream_params* aOutputStreamParams,
+                 cubeb_data_callback aDataCallback,
+                 cubeb_state_callback aStateCallback, void* aUserPtr) {
+    assert(!mFakeAudioThread);
+    mMockStream.reset(new MockCubebStream);
+    mMockStream->context = aContext;
+    *aStream = reinterpret_cast<cubeb_stream*>(mMockStream.get());
+    mDataCallback = aDataCallback;
+    mStateCallback = aStateCallback;
+    mUserPtr = aUserPtr;
+    mSampleRate = aInputStreamParams ? aInputStreamParams->rate
+                                     : aOutputStreamParams->rate;
+    return CUBEB_OK;
+  }
+
+  int StreamStart(cubeb_stream* aStream) {
+    assert(!mFakeAudioThread);
+    mStreamStop = false;
+    mFakeAudioThread.reset(new std::thread(ThreadFunction_s, this));
+    assert(mFakeAudioThread);
+    mStateCallback(aStream, mUserPtr, CUBEB_STATE_STARTED);
+    return CUBEB_OK;
+  }
+
+  int StreamStop(cubeb_stream* aStream) {
+    assert(mFakeAudioThread);
+    mStreamStop = true;
+    mFakeAudioThread->join();
+    mFakeAudioThread.reset();
+    mStateCallback(aStream, mUserPtr, CUBEB_STATE_STOPPED);
+    return CUBEB_OK;
+  }
+
+  void StreamDestroy(cubeb_stream* aStream) { mMockStream.reset(); }
+
  private:
   // This needs to have the exact same memory layout as a real cubeb backend.
   // It's very important for this `ops` member to be the very first member of
   // the class, and to not have any virtual members (to avoid having a vtable).
   const cubeb_ops* ops;
   // The callback to call when the device list has been changed.
-  cubeb_device_collection_changed_callback mInputDeviceCollectionChangeCallback;
+  cubeb_device_collection_changed_callback
+      mInputDeviceCollectionChangeCallback = nullptr;
   cubeb_device_collection_changed_callback
-      mOutputDeviceCollectionChangeCallback;
+      mOutputDeviceCollectionChangeCallback = nullptr;
+  cubeb_data_callback mDataCallback = nullptr;
+  cubeb_state_callback mStateCallback = nullptr;
   // The pointer to pass in the callback.
-  void* mInputDeviceCollectionChangeUserPtr;
-  void* mOutputDeviceCollectionChangeUserPtr;
+  void* mInputDeviceCollectionChangeUserPtr = nullptr;
+  void* mOutputDeviceCollectionChangeUserPtr = nullptr;
+  void* mUserPtr = nullptr;
   // Whether or not this backend supports device collection change notification
   // via a system callback. If not, Gecko is expected to re-query the list every
   // time.
-  bool mSupportsDeviceCollectionChangedCallback;
+  bool mSupportsDeviceCollectionChangedCallback = true;
   // Our input and output devices.
   nsTArray<cubeb_device_info> mInputDevices;
   nsTArray<cubeb_device_info> mOutputDevices;
+
+  // Thread that simulates the audio thread.
+  std::unique_ptr<std::thread> mFakeAudioThread;
+  // Signal to the audio thread that stream is stopped.
+  std::atomic_bool mStreamStop{true};
+  // The fake stream instance.
+  std::unique_ptr<MockCubebStream> mMockStream;
+  // The stream sample rate
+  uint32_t mSampleRate = 0;
+  // The audio buffer used on data callback.
+  float mOutputBuffer[NUM_OF_CHANNELS * NUM_OF_FRAMES];
 };
 
 void cubeb_mock_destroy(cubeb* context) {
   delete reinterpret_cast<MockCubeb*>(context);
 }
 
-static int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
-                                        cubeb_device_collection* out) {
+int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
+                                 cubeb_device_collection* out) {
   MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
   return mock->EnumerateDevices(type, out);
 }
 
 int cubeb_mock_device_collection_destroy(cubeb* context,
                                          cubeb_device_collection* collection) {
   delete[] collection->device;
   return CUBEB_OK;
@@ -281,16 +385,71 @@ int cubeb_mock_device_collection_destroy
 int cubeb_mock_register_device_collection_changed(
     cubeb* context, cubeb_device_type devtype,
     cubeb_device_collection_changed_callback callback, void* user_ptr) {
   MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
   return mock->RegisterDeviceCollectionChangeCallback(devtype, callback,
                                                       user_ptr);
 }
 
+int cubeb_mock_stream_init(
+    cubeb* context, cubeb_stream** stream, char const* stream_name,
+    cubeb_devid input_device, cubeb_stream_params* input_stream_params,
+    cubeb_devid output_device, cubeb_stream_params* output_stream_params,
+    unsigned int latency, cubeb_data_callback data_callback,
+    cubeb_state_callback state_callback, void* user_ptr) {
+  MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
+  return mock->StreamInit(context, stream, input_stream_params,
+                          output_stream_params, data_callback, state_callback,
+                          user_ptr);
+}
+
+int cubeb_mock_stream_start(cubeb_stream* stream) {
+  MockCubeb::MockCubebStream* mockStream =
+      reinterpret_cast<MockCubeb::MockCubebStream*>(stream);
+  MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
+  return mock->StreamStart(stream);
+}
+
+int cubeb_mock_stream_stop(cubeb_stream* stream) {
+  MockCubeb::MockCubebStream* mockStream =
+      reinterpret_cast<MockCubeb::MockCubebStream*>(stream);
+  MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
+  return mock->StreamStop(stream);
+}
+
+void cubeb_mock_stream_destroy(cubeb_stream* stream) {
+  MockCubeb::MockCubebStream* mockStream =
+      reinterpret_cast<MockCubeb::MockCubebStream*>(stream);
+  MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
+  return mock->StreamDestroy(stream);
+}
+
+static char const* cubeb_mock_get_backend_id(cubeb* context) {
+#if defined(XP_LINUX)
+  return "pulse";
+#elif defined(XP_MACOSX)
+  return "audiounit";
+#elif defined(XP_WIN)
+  return "wasapi";
+#elif defined(ANDROID)
+  return "opensl";
+#endif
+}
+
+static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume) {
+  return CUBEB_OK;
+}
+
+int cubeb_mock_get_min_latency(cubeb* context, cubeb_stream_params params,
+                               uint32_t* latency_ms) {
+  *latency_ms = NUM_OF_FRAMES;
+  return CUBEB_OK;
+}
+
 void PrintDevice(cubeb_device_info aInfo) {
   printf(
       "id: %zu\n"
       "device_id: %s\n"
       "friendly_name: %s\n"
       "group_id: %s\n"
       "vendor_name: %s\n"
       "type: %d\n"
@@ -407,9 +566,9 @@ void AddDevices(MockCubeb* mock, uint32_
     // Make it so that the last device is the default input device.
     if (i == device_count - 1) {
       device.preferred = CUBEB_DEVICE_PREF_ALL;
     }
     mock->AddDevice(device);
   }
 }
 
-#endif // MOCKCUBEB_H_
+#endif  // MOCKCUBEB_H_