Bug 1432869 - Update cubeb from upstream to 4c18a84. r?achronop draft
authorBryce Van Dyk <bvandyk@mozilla.com>
Tue, 05 Dec 2017 13:11:01 -0500
changeset 748412 e5ee0bacff143a939da0e4c15243ed102a59af36
parent 748411 6a19a8b442f35671928a69dd3b51ed7af8141358
child 748413 74fd43dc15f49395ef1817e7c66a4bfbda140041
child 748799 fcf42e5c2fb145ff1e60b97307d9377849779684
push id97163
push userbvandyk@mozilla.com
push dateMon, 29 Jan 2018 20:40:12 +0000
reviewersachronop
bugs1432869
milestone60.0a1
Bug 1432869 - Update cubeb from upstream to 4c18a84. r?achronop This brings across the WASAPI loopback stream functionality recently implemented in cubeb. The README_MOZILLA is reverted to its previous format. Previously a couple of patches were highlighted as needing application, however these have now been applied upstream. MozReview-Commit-ID: CV6FHWXZBK0
media/libcubeb/README_MOZILLA
media/libcubeb/gtest/test_audio.cpp
media/libcubeb/gtest/test_devices.cpp
media/libcubeb/gtest/test_duplex.cpp
media/libcubeb/gtest/test_latency.cpp
media/libcubeb/gtest/test_loopback.cpp
media/libcubeb/gtest/test_mixer.cpp
media/libcubeb/gtest/test_overload_callback.cpp
media/libcubeb/gtest/test_record.cpp
media/libcubeb/gtest/test_resampler.cpp
media/libcubeb/gtest/test_sanity.cpp
media/libcubeb/gtest/test_tone.cpp
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb_alsa.c
media/libcubeb/src/cubeb_audiounit.cpp
media/libcubeb/src/cubeb_jack.cpp
media/libcubeb/src/cubeb_opensl.c
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/src/cubeb_sndio.c
media/libcubeb/src/cubeb_wasapi.cpp
media/libcubeb/src/cubeb_winmm.c
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,12 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
-made were those applied by update.sh, the addition of
-Makefile.in build files for the Mozilla build system,
-and the following patches, which may be overwritten when
-included upstream.
-https://github.com/kinetiknz/cubeb/pull/398/commits/c8e66dee61a35e6a6d54e3630e1668bdbd6984b4
-https://github.com/kinetiknz/cubeb/pull/398/commits/2ed979bc891cf1a7822799947a357d4d3b625964
+made were those applied by update.sh and the addition of
+Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was 2b98e3d3279322ce0764ae2b9f28a9ea9000418b (2018-01-19 10:17:28 +1300)
+The git commit ID used was 4c18a84a4573a23a1c39ccf6c70f1a6c328cb110 (2018-01-23 08:50:28 +1300)
--- a/media/libcubeb/gtest/test_audio.cpp
+++ b/media/libcubeb/gtest/test_audio.cpp
@@ -118,16 +118,17 @@ int run_test(int num_channels, layout_in
 
   fprintf(stderr, "Testing %d channel(s), layout: %s, %d Hz, %s (%s)\n", num_channels, layout.name, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
 
   cubeb_stream_params params;
   params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
   params.rate = sampling_rate;
   params.channels = num_channels;
   params.layout = layout.layout;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   synth_state synth(params.channels, params.rate);
 
   cubeb_stream *stream = NULL;
   r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
       4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
   if (r != CUBEB_OK) {
     fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
@@ -166,16 +167,17 @@ int run_panning_volume_test(int is_float
     return CUBEB_OK;
   }
 
   cubeb_stream_params params;
   params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
   params.rate = 44100;
   params.channels = 2;
   params.layout = CUBEB_LAYOUT_STEREO;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   synth_state synth(params.channels, params.rate);
 
   cubeb_stream *stream = NULL;
   r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
       4096, is_float ? &data_cb<float> : &data_cb<short>,
       state_cb_audio, &synth);
   if (r != CUBEB_OK) {
--- a/media/libcubeb/gtest/test_devices.cpp
+++ b/media/libcubeb/gtest/test_devices.cpp
@@ -185,16 +185,17 @@ TEST(cubeb, enumerate_devices)
   cubeb_stream * stream;
   cubeb_stream_params input_params;
   cubeb_stream_params output_params;
 
   input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
   input_params.rate = output_params.rate = 48000;
   input_params.channels = output_params.channels = 1;
   input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
                         NULL, &input_params, NULL, &output_params,
                         1024, data_cb_duplex, state_cb_duplex, nullptr);
 
   ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
 
   r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
--- a/media/libcubeb/gtest/test_duplex.cpp
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -96,20 +96,22 @@ TEST(cubeb, duplex)
     return;
   }
 
   /* typical user-case: mono input, stereo output, low latency. */
   input_params.format = STREAM_FORMAT;
   input_params.rate = 48000;
   input_params.channels = 1;
   input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = CUBEB_STREAM_PREF_NONE;
   output_params.format = STREAM_FORMAT;
   output_params.rate = 48000;
   output_params.channels = 2;
   output_params.layout = CUBEB_LAYOUT_STEREO;
+  output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
                         NULL, &input_params, NULL, &output_params,
                         latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
   ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
--- a/media/libcubeb/gtest/test_latency.cpp
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -29,16 +29,17 @@ TEST(cubeb, latency)
   if (r == CUBEB_OK) {
     ASSERT_GT(preferred_rate, 0u);
   }
 
   cubeb_stream_params params = {
     CUBEB_SAMPLE_FLOAT32NE,
     preferred_rate,
     max_channels,
-    CUBEB_LAYOUT_UNDEFINED
+    CUBEB_LAYOUT_UNDEFINED,
+    CUBEB_STREAM_PREF_NONE
   };
   r = cubeb_get_min_latency(ctx, &params, &latency_frames);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_GT(latency_frames, 0u);
   }
 }
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/gtest/test_loopback.cpp
@@ -0,0 +1,575 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license.  See the
+ * accompanying file LICENSE for details.
+ */
+
+ /* libcubeb api/function test. Requests a loopback device and checks that
+    output is being looped back to input. NOTE: Usage of output devices while
+    performing this test will cause flakey results! */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include "cubeb/cubeb.h"
+#include "common.h"
+
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const uint32_t TONE_FREQUENCY = 440;
+const double OUTPUT_AMPLITUDE = 0.25;
+const uint32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
+
+template<typename T> T ConvertSampleToOutput(double input);
+template<> float ConvertSampleToOutput(double input) { return float(input); }
+template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
+
+template<typename T> double ConvertSampleFromOutput(T sample);
+template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
+template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
+
+/* Simple cross correlation to help find phase shift. Not a performant impl */
+std::vector<double> cross_correlate(std::vector<double> & f,
+                                    std::vector<double> & g,
+                                    size_t signal_length)
+{
+  /* the length we sweep our window through to find the cross correlation */
+  size_t sweep_length = f.size() - signal_length + 1;
+  std::vector<double> correlation;
+  correlation.reserve(sweep_length);
+  for (size_t i = 0; i < sweep_length; i++) {
+    double accumulator = 0.0;
+    for (size_t j = 0; j < signal_length; j++) {
+      accumulator += f.at(j) * g.at(i + j);
+    }
+    correlation.push_back(accumulator);
+  }
+  return correlation;
+}
+
+/* best effort discovery of phase shift between output and (looped) input*/
+size_t find_phase(std::vector<double> & output_frames,
+                  std::vector<double> & input_frames,
+                  size_t signal_length)
+{
+  std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
+  size_t phase = 0;
+  double max_correlation = correlation.at(0);
+  for (size_t i = 1; i < correlation.size(); i++) {
+    if (correlation.at(i) > max_correlation) {
+      max_correlation = correlation.at(i);
+      phase = i;
+    }
+  }
+  return phase;
+}
+
+std::vector<double> normalize_frames(std::vector<double> & frames) {
+  double max = abs(*std::max_element(frames.begin(), frames.end(),
+                                     [](double a, double b) { return abs(a) < abs(b); }));
+  std::vector<double> normalized_frames;
+  normalized_frames.reserve(frames.size());
+  for (const double frame : frames) {
+    normalized_frames.push_back(frame / max);
+  }
+  return normalized_frames;
+}
+
+/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
+void compare_signals(std::vector<double> & output_frames,
+                     std::vector<double> & input_frames)
+{
+  ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
+  size_t num_frames = output_frames.size();
+  std::vector<double> normalized_output_frames = normalize_frames(output_frames);
+  std::vector<double> normalized_input_frames = normalize_frames(input_frames);
+
+  /* calculate mean absolute errors */
+  /* mean absolute errors between output and input */
+  double io_mas = 0.0;
+  /* mean absolute errors between output and silence */
+  double output_silence_mas = 0.0;
+  /* mean absolute errors between input and silence */
+  double input_silence_mas = 0.0;
+  for (size_t i = 0; i < num_frames; i++) {
+    io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
+    output_silence_mas += abs(normalized_output_frames.at(i));
+    input_silence_mas += abs(normalized_input_frames.at(i));
+  }
+  io_mas /= num_frames;
+  output_silence_mas /= num_frames;
+  input_silence_mas /= num_frames;
+
+  ASSERT_LT(io_mas, output_silence_mas)
+    << "Error between output and input should be less than output and silence!";
+  ASSERT_LT(io_mas, input_silence_mas)
+    << "Error between output and input should be less than output and silence!";
+
+  /* make sure extrema are in (roughly) correct location */
+  /* number of maxima + minama expected in the frames*/
+  const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
+  /* expected index of first maxima */
+  const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
+  /* Threshold we expect all maxima and minima to be above or below. Ideally
+     the extrema would be 1 or -1, but particularly at the start of loopback
+     the values seen can be significantly lower. */
+  const double THRESHOLD = 0.5;
+
+  for (size_t i = 0; i < NUM_EXTREMA; i++) {
+    bool is_maximum = i % 2 == 0;
+    /* expected offset to current extreme: i * stide between extrema */
+    size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
+    if (is_maximum) {
+      ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+        << "Output frames have unexpected missing maximum!";
+      ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+        << "Input frames have unexpected missing maximum!";
+    } else {
+      ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+        << "Output frames have unexpected missing minimum!";
+      ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+        << "Input frames have unexpected missing minimum!";
+    }
+  }
+}
+
+struct user_state_loopback {
+  std::mutex user_state_mutex;
+  long position = 0;
+  /* track output */
+  std::vector<double> output_frames;
+  /* track input */
+  std::vector<double> input_frames;
+};
+
+template<typename T>
+long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+  struct user_state_loopback * u = (struct user_state_loopback *) user;
+  T * ib = (T *) inputbuffer;
+  T * ob = (T *) outputbuffer;
+
+  if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+    return CUBEB_ERROR;
+  }
+
+  std::lock_guard<std::mutex> lock(u->user_state_mutex);
+  /* generate our test tone on the fly */
+  for (int i = 0; i < nframes; i++) {
+    double tone = 0.0;
+    if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+      /* generate sine wave */
+      tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+      tone *= OUTPUT_AMPLITUDE;
+    }
+    ob[i] = ConvertSampleToOutput<T>(tone);
+    u->output_frames.push_back(tone);
+    /* store any looped back output, may be silence */
+    u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+  }
+
+  u->position += nframes;
+
+  return nframes;
+}
+
+template<typename T>
+long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+  struct user_state_loopback * u = (struct user_state_loopback *) user;
+  T * ib = (T *) inputbuffer;
+
+  if (outputbuffer != NULL) {
+    // Can't assert as it needs to return, so expect to fail instead
+    EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
+    return CUBEB_ERROR;
+  }
+
+  if (stream == NULL || inputbuffer == NULL) {
+    return CUBEB_ERROR;
+  }
+
+  std::lock_guard<std::mutex> lock(u->user_state_mutex);
+  for (int i = 0; i < nframes; i++) {
+    u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+  }
+
+  return nframes;
+}
+
+template<typename T>
+long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+  struct user_state_loopback * u = (struct user_state_loopback *) user;
+  T * ob = (T *) outputbuffer;
+
+  if (stream == NULL || outputbuffer == NULL) {
+    return CUBEB_ERROR;
+  }
+
+  std::lock_guard<std::mutex> lock(u->user_state_mutex);
+  /* generate our test tone on the fly */
+  for (int i = 0; i < nframes; i++) {
+    double tone = 0.0;
+    if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+      /* generate sine wave */
+      tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+      tone *= OUTPUT_AMPLITUDE;
+    }
+    ob[i] = ConvertSampleToOutput<T>(tone);
+    u->output_frames.push_back(tone);
+  }
+
+  u->position += nframes;
+
+  return nframes;
+}
+
+void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+  if (stream == NULL)
+    return;
+
+  switch (state) {
+  case CUBEB_STATE_STARTED:
+    fprintf(stderr, "stream started\n"); break;
+  case CUBEB_STATE_STOPPED:
+    fprintf(stderr, "stream stopped\n"); break;
+  case CUBEB_STATE_DRAINED:
+    fprintf(stderr, "stream drained\n"); break;
+  default:
+    fprintf(stderr, "unknown stream state %d\n", state);
+  }
+
+  return;
+}
+
+void run_loopback_duplex_test(bool is_float)
+{
+  cubeb * ctx;
+  cubeb_stream * stream;
+  cubeb_stream_params input_params;
+  cubeb_stream_params output_params;
+  int r;
+  uint32_t latency_frames = 0;
+
+  r = common_init(&ctx, "Cubeb loopback example: duplex stream");
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+  std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+    cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+  input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  input_params.rate = SAMPLE_FREQUENCY;
+  input_params.channels = 1;
+  input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+  output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  output_params.rate = SAMPLE_FREQUENCY;
+  output_params.channels = 1;
+  output_params.layout = CUBEB_LAYOUT_MONO;
+  output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+  std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+  ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+  r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+  ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+  /* setup a duplex stream with loopback */
+  r = cubeb_stream_init(ctx, &stream, "Cubeb loopback",
+                        NULL, &input_params, NULL, &output_params, latency_frames,
+                        is_float ? data_cb_loop_duplex<float> : data_cb_loop_duplex<short>,
+                        state_cb_loop, user_data.get());
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+    cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+  cubeb_stream_start(stream);
+  delay(150);
+  cubeb_stream_stop(stream);
+
+  /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+  std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+  std::vector<double> & output_frames = user_data->output_frames;
+  std::vector<double> & input_frames = user_data->input_frames;
+  ASSERT_EQ(output_frames.size(), input_frames.size())
+    << "#Output frames != #input frames";
+
+  size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+  /* extract vectors of just the relevant signal from output and input */
+  auto output_frames_signal_start = output_frames.begin();
+  auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+  std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+  auto input_frames_signal_start = input_frames.begin() + phase;
+  auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+  std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+  compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_duplex)
+{
+  run_loopback_duplex_test(true);
+  run_loopback_duplex_test(false);
+}
+
+void run_loopback_separate_streams_test(bool is_float)
+{
+  cubeb * ctx;
+  cubeb_stream * input_stream;
+  cubeb_stream * output_stream;
+  cubeb_stream_params input_params;
+  cubeb_stream_params output_params;
+  int r;
+  uint32_t latency_frames = 0;
+
+  r = common_init(&ctx, "Cubeb loopback example: separate streams");
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+  std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+    cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+  input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  input_params.rate = SAMPLE_FREQUENCY;
+  input_params.channels = 1;
+  input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+  output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  output_params.rate = SAMPLE_FREQUENCY;
+  output_params.channels = 1;
+  output_params.layout = CUBEB_LAYOUT_MONO;
+
+  std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+  ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+  r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+  ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+  /* setup an input stream with loopback */
+  r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+                        NULL, &input_params, NULL, NULL, latency_frames,
+                        is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+                        state_cb_loop, user_data.get());
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+    cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+  /* setup an output stream */
+  r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+                        NULL, NULL, NULL, &output_params, latency_frames,
+                        is_float ? data_cb_playback<float> : data_cb_playback<short>,
+                        state_cb_loop, user_data.get());
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+    cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+  cubeb_stream_start(input_stream);
+  cubeb_stream_start(output_stream);
+  delay(150);
+  cubeb_stream_stop(output_stream);
+  cubeb_stream_stop(input_stream);
+
+  /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+  std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+  std::vector<double> & output_frames = user_data->output_frames;
+  std::vector<double> & input_frames = user_data->input_frames;
+  ASSERT_LE(output_frames.size(), input_frames.size())
+    << "#Output frames should be less or equal to #input frames";
+
+  size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+  /* extract vectors of just the relevant signal from output and input */
+  auto output_frames_signal_start = output_frames.begin();
+  auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+  std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+  auto input_frames_signal_start = input_frames.begin() + phase;
+  auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+  std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+  compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_separate_streams)
+{
+  run_loopback_separate_streams_test(true);
+  run_loopback_separate_streams_test(false);
+}
+
+void run_loopback_silence_test(bool is_float)
+{
+  cubeb * ctx;
+  cubeb_stream * input_stream;
+  cubeb_stream_params input_params;
+  int r;
+  uint32_t latency_frames = 0;
+
+  r = common_init(&ctx, "Cubeb loopback example: record silence");
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+  std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+    cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+  input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  input_params.rate = SAMPLE_FREQUENCY;
+  input_params.channels = 1;
+  input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+
+  std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+  ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+  r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+  ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+  /* setup an input stream with loopback */
+  r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+                        NULL, &input_params, NULL, NULL, latency_frames,
+                        is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+                        state_cb_loop, user_data.get());
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+    cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+  cubeb_stream_start(input_stream);
+  delay(50);
+  cubeb_stream_stop(input_stream);
+
+  /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+  std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+  std::vector<double> & input_frames = user_data->input_frames;
+
+  /* expect to have at least ~50ms of frames */
+  ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
+  double EPISILON = 0.000001;
+  /* frames should be 0.0, but use epsilon to avoid possible issues with impls
+  that may use ~0.0 silence values. */
+  for (double frame : input_frames) {
+    ASSERT_LT(abs(frame), EPISILON);
+  }
+}
+
+TEST(cubeb, loopback_silence)
+{
+  run_loopback_silence_test(true);
+  run_loopback_silence_test(false);
+}
+
+void run_loopback_device_selection_test(bool is_float)
+{
+  cubeb * ctx;
+  cubeb_device_collection collection;
+  cubeb_stream * input_stream;
+  cubeb_stream * output_stream;
+  cubeb_stream_params input_params;
+  cubeb_stream_params output_params;
+  int r;
+  uint32_t latency_frames = 0;
+
+  r = common_init(&ctx, "Cubeb loopback example: device selection, separate streams");
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+  std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+    cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+  r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+  if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+    fprintf(stderr, "Device enumeration not supported"
+            " for this backend, skipping this test.\n");
+    return;
+  }
+
+  ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+  /* get first preferred output device id */
+  std::string device_id;
+  for (size_t i = 0; i < collection.count; i++) {
+    if (collection.device[i].preferred) {
+      device_id = collection.device[i].device_id;
+      break;
+    }
+  }
+  cubeb_device_collection_destroy(ctx, &collection);
+  if (device_id.empty()) {
+    fprintf(stderr, "Could not find preferred device, aborting test.\n");
+    return;
+  }
+
+  input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  input_params.rate = SAMPLE_FREQUENCY;
+  input_params.channels = 1;
+  input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+  output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+  output_params.rate = SAMPLE_FREQUENCY;
+  output_params.channels = 1;
+  output_params.layout = CUBEB_LAYOUT_MONO;
+
+  std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+  ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+  r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+  ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+  /* setup an input stream with loopback */
+  r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+                        device_id.c_str(), &input_params, NULL, NULL, latency_frames,
+                        is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+                        state_cb_loop, user_data.get());
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+    cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+  /* setup an output stream */
+  r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+                        NULL, NULL, device_id.c_str(), &output_params, latency_frames,
+                        is_float ? data_cb_playback<float> : data_cb_playback<short>,
+                        state_cb_loop, user_data.get());
+  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+    cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+  cubeb_stream_start(input_stream);
+  cubeb_stream_start(output_stream);
+  delay(150);
+  cubeb_stream_stop(output_stream);
+  cubeb_stream_stop(input_stream);
+
+  /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+  std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+  std::vector<double> & output_frames = user_data->output_frames;
+  std::vector<double> & input_frames = user_data->input_frames;
+  ASSERT_LE(output_frames.size(), input_frames.size())
+    << "#Output frames should be less or equal to #input frames";
+
+  size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+  /* extract vectors of just the relevant signal from output and input */
+  auto output_frames_signal_start = output_frames.begin();
+  auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+  std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+  auto input_frames_signal_start = input_frames.begin() + phase;
+  auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+  std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+  compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_device_selection)
+{
+  run_loopback_device_selection_test(true);
+  run_loopback_device_selection_test(false);
+}
--- a/media/libcubeb/gtest/test_mixer.cpp
+++ b/media/libcubeb/gtest/test_mixer.cpp
@@ -109,27 +109,29 @@ downmix_test(float const * data, cubeb_c
   if (in_layout == CUBEB_LAYOUT_UNDEFINED) {
     return; // Only possible output layout would be UNDEFINED.
   }
 
   cubeb_stream_params in_params = {
     STREAM_FORMAT,
     STREAM_FREQUENCY,
     layout_infos[in_layout].channels,
-    in_layout
+    in_layout,
+    CUBEB_STREAM_PREF_NONE
   };
 
   cubeb_stream_params out_params = {
     STREAM_FORMAT,
     STREAM_FREQUENCY,
     // To downmix audio data with undefined layout, its channel number must be
     // smaller than or equal to the input channels.
     (out_layout == CUBEB_LAYOUT_UNDEFINED) ?
       layout_infos[in_layout].channels : layout_infos[out_layout].channels,
-    out_layout
+    out_layout,
+    CUBEB_STREAM_PREF_NONE
    };
 
   if (!cubeb_should_downmix(&in_params, &out_params)) {
     return;
   }
 
   fprintf(stderr, "Downmix from %s to %s\n", layout_infos[in_layout].name, layout_infos[out_layout].name);
 
--- a/media/libcubeb/gtest/test_overload_callback.cpp
+++ b/media/libcubeb/gtest/test_overload_callback.cpp
@@ -63,16 +63,17 @@ TEST(cubeb, overload_callback)
 
   std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
     cleanup_cubeb_at_exit(ctx, cubeb_destroy);
 
   output_params.format = STREAM_FORMAT;
   output_params.rate = 48000;
   output_params.channels = 2;
   output_params.layout = CUBEB_LAYOUT_STEREO;
+  output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK);
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb",
                         NULL, NULL, NULL, &output_params,
                         latency_frames, data_cb, state_cb, NULL);
   ASSERT_EQ(r, CUBEB_OK);
--- a/media/libcubeb/gtest/test_record.cpp
+++ b/media/libcubeb/gtest/test_record.cpp
@@ -89,16 +89,17 @@ TEST(cubeb, record)
   if (!has_available_input_device(ctx)) {
     return;
   }
 
   params.format = STREAM_FORMAT;
   params.rate = SAMPLE_FREQUENCY;
   params.channels = 1;
   params.layout = CUBEB_LAYOUT_UNDEFINED;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
                         4096, data_cb_record, state_cb_record, &stream_state);
   ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
 
   std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
     cleanup_stream_at_exit(stream, cubeb_stream_destroy);
 
--- a/media/libcubeb/gtest/test_resampler.cpp
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -328,16 +328,17 @@ void test_resampler_duplex(uint32_t inpu
   osc_state state;
 
   input_params.format = output_params.format = cubeb_format<T>();
   state.input_channels = input_params.channels = input_channels;
   state.output_channels = output_params.channels = output_channels;
   input_params.rate = input_rate;
   state.output_rate = output_params.rate = output_rate;
   state.target_rate = target_rate;
+  input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
   long got;
 
   cubeb_resampler * resampler =
     cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
                            data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
 
   long latency = cubeb_resampler_latency(resampler);
 
--- a/media/libcubeb/gtest/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -120,16 +120,18 @@ TEST(cubeb, context_variables)
   r = common_init(&ctx, "test_context_variables");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.channels = STREAM_CHANNELS;
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
+
   r = cubeb_get_min_latency(ctx, &params, &value);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_TRUE(value > 0);
   }
 
   r = cubeb_get_preferred_sample_rate(ctx, &value);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
@@ -155,16 +157,17 @@ TEST(cubeb, init_destroy_stream)
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
 
   cubeb_stream_destroy(stream);
   cubeb_destroy(ctx);
@@ -181,16 +184,17 @@ TEST(cubeb, init_destroy_multiple_stream
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
     r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                           test_data_callback, test_state_callback, &dummy);
     ASSERT_EQ(r, CUBEB_OK);
     ASSERT_NE(stream[i], nullptr);
   }
 
@@ -211,16 +215,17 @@ TEST(cubeb, configure_stream)
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = 2; // panning
   params.layout = CUBEB_LAYOUT_STEREO;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
 
   r = cubeb_stream_set_volume(stream, 1.0f);
   ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
@@ -242,16 +247,17 @@ TEST(cubeb, configure_stream_undefined_l
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = 2; // panning
   params.layout = CUBEB_LAYOUT_UNDEFINED;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
 
   r = cubeb_stream_start(stream);
   ASSERT_EQ(r, CUBEB_OK);
@@ -277,16 +283,17 @@ test_init_start_stop_destroy_multiple_st
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
     r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                           test_data_callback, test_state_callback, &dummy);
     ASSERT_EQ(r, CUBEB_OK);
     ASSERT_NE(stream[i], nullptr);
     if (early) {
       r = cubeb_stream_start(stream[i]);
@@ -361,16 +368,17 @@ TEST(cubeb, init_destroy_multiple_contex
    * try to limit the number of streams we create in this test. */
   if (is_windows_7())
     return;
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
     r = common_init(&ctx[i], "test_sanity");
     ASSERT_EQ(r, CUBEB_OK);
     ASSERT_NE(ctx[i], nullptr);
 
     for (j = 0; j < streams_per_ctx; ++j) {
       r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
@@ -399,16 +407,17 @@ TEST(cubeb, basic_stream_operations)
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
 
   /* position and volume before stream has started */
   r = cubeb_stream_get_position(stream, &position);
@@ -447,16 +456,17 @@ TEST(cubeb, stream_position)
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
 
   /* stream position should not advance before starting playback */
   r = cubeb_stream_get_position(stream, &position);
@@ -580,16 +590,17 @@ TEST(cubeb, drain)
   r = common_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
   params.layout = STREAM_LAYOUT;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_drain_data_callback, test_drain_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
 
   r = cubeb_stream_start(stream);
   ASSERT_EQ(r, CUBEB_OK);
--- a/media/libcubeb/gtest/test_tone.cpp
+++ b/media/libcubeb/gtest/test_tone.cpp
@@ -90,16 +90,17 @@ TEST(cubeb, tone)
 
   std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
     cleanup_cubeb_at_exit(ctx, cubeb_destroy);
 
   params.format = STREAM_FORMAT;
   params.rate = SAMPLE_FREQUENCY;
   params.channels = 1;
   params.layout = CUBEB_LAYOUT_MONO;
+  params.prefs = CUBEB_STREAM_PREF_NONE;
 
   std::unique_ptr<cb_user_data> user_data(new cb_user_data());
   ASSERT_TRUE(!!user_data) << "Error allocating user data";
 
   user_data->position = 0;
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, &params,
                         4096, data_cb_tone, state_cb_tone, user_data.get());
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -48,26 +48,28 @@ extern "C" {
       fprintf(stderr, "Could not get preferred sample-rate");
       return rv;
     }
 
     cubeb_stream_params output_params;
     output_params.format = CUBEB_SAMPLE_FLOAT32NE;
     output_params.rate = rate;
     output_params.channels = 2;
+    output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
     cubeb_stream_params input_params;
     input_params.format = CUBEB_SAMPLE_FLOAT32NE;
     input_params.rate = rate;
     input_params.channels = 1;
+    input_params.prefs = CUBEB_STREAM_PREF_NONE;
 
     cubeb_stream * stm;
     rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
-                           NULL, input_params,
-                           NULL, output_params,
+                           NULL, &input_params,
+                           NULL, &output_params,
                            latency_frames,
                            data_cb, state_cb,
                            NULL);
     if (rv != CUBEB_OK) {
       fprintf(stderr, "Could not open the stream");
       return rv;
     }
 
@@ -208,23 +210,33 @@ typedef enum {
   CUBEB_LAYOUT_2F2_LFE,
   CUBEB_LAYOUT_3F2,
   CUBEB_LAYOUT_3F2_LFE,
   CUBEB_LAYOUT_3F3R_LFE,
   CUBEB_LAYOUT_3F4_LFE,
   CUBEB_LAYOUT_MAX
 } cubeb_channel_layout;
 
+/** Miscellaneous stream preferences. */
+typedef enum {
+  CUBEB_STREAM_PREF_NONE     = 0x00, /**< No stream preferences are requested. */
+  CUBEB_STREAM_PREF_LOOPBACK = 0x01 /**< Request a loopback stream. Should be
+                                         specified on the input params and an
+                                         output device to loopback from should
+                                         be passed in place of an input device. */
+} cubeb_stream_prefs;
+
 /** Stream format initialization parameters. */
 typedef struct {
   cubeb_sample_format format;   /**< Requested sample format.  One of
                                      #cubeb_sample_format. */
   uint32_t rate;                /**< Requested sample rate.  Valid range is [1000, 192000]. */
   uint32_t channels;            /**< Requested channel count.  Valid range is [1, 8]. */
   cubeb_channel_layout layout;  /**< Requested channel layout. This must be consistent with the provided channels. */
+  cubeb_stream_prefs prefs;     /**< Requested preferences. */
 } cubeb_stream_params;
 
 /** Audio device description */
 typedef struct {
   char * output_name; /**< The name of the output device */
   char * input_name; /**< The name of the input device */
 } cubeb_device;
 
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -885,16 +885,20 @@ alsa_stream_init_single(cubeb * ctx, cub
   snd_pcm_uframes_t period_size;
   int latency_us = 0;
   char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME;
 
   assert(ctx && stream);
 
   *stream = NULL;
 
+  if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
+
   switch (stream_params->format) {
   case CUBEB_SAMPLE_S16LE:
     format = SND_PCM_FORMAT_S16_LE;
     break;
   case CUBEB_SAMPLE_S16BE:
     format = SND_PCM_FORMAT_S16_BE;
     break;
   case CUBEB_SAMPLE_FLOAT32LE:
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -130,18 +130,18 @@ struct cubeb_stream {
   explicit cubeb_stream(cubeb * context);
 
   cubeb * context;
   cubeb_data_callback data_callback = nullptr;
   cubeb_state_callback state_callback = nullptr;
   cubeb_device_changed_callback device_changed_callback = nullptr;
   owned_critical_section device_changed_callback_lock;
   /* Stream creation parameters */
-  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
-  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
   device_info input_device;
   device_info output_device;
   /* User pointer of data_callback */
   void * user_ptr = nullptr;
   /* Format descriptions */
   AudioStreamBasicDescription input_desc;
   AudioStreamBasicDescription output_desc;
   /* I/O AudioUnits */
@@ -2355,16 +2355,22 @@ audiounit_configure_output(cubeb_stream 
   return CUBEB_OK;
 }
 
 static int
 audiounit_setup_stream(cubeb_stream * stm)
 {
   stm->mutex.assert_current_thread_owns();
 
+  if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||
+      (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {
+    LOG("(%p) Loopback not supported for audiounit.", stm);
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
+
   int r = 0;
 
   device_info in_dev_info = stm->input_device;
   device_info out_dev_info = stm->output_device;
 
   if (has_input(stm) && has_output(stm) &&
       stm->input_device.id != stm->output_device.id) {
     r = audiounit_create_aggregate_device(stm);
--- a/media/libcubeb/src/cubeb_jack.cpp
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -738,16 +738,22 @@ cbjack_stream_init(cubeb * context, cube
          input_stream_params->format != CUBEB_SAMPLE_S16NE)
      ) {
     return CUBEB_ERROR_INVALID_FORMAT;
   }
 
   if (input_device || output_device)
     return CUBEB_ERROR_NOT_SUPPORTED;
 
+  // Loopback is unsupported
+  if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
+      (output_stream_params && (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
+
   *stream = NULL;
 
   // Find a free stream.
   pthread_mutex_lock(&context->mutex);
   cubeb_stream * stm = context_alloc_stream(context, stream_name);
 
   // No free stream?
   if (stm == NULL) {
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -1286,16 +1286,21 @@ opensl_configure_playback(cubeb_stream *
 
 static int
 opensl_validate_stream_param(cubeb_stream_params * stream_params)
 {
   if ((stream_params &&
        (stream_params->channels < 1 || stream_params->channels > 32))) {
     return CUBEB_ERROR_INVALID_FORMAT;
   }
+  if ((stream_params &&
+       (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+    LOG("Loopback is not supported");
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
   return CUBEB_OK;
 }
 
 static int
 opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
                    cubeb_devid input_device,
                    cubeb_stream_params * input_stream_params,
                    cubeb_devid output_device,
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -802,16 +802,19 @@ create_pa_stream(cubeb_stream * stm,
                  cubeb_stream_params * stream_params,
                  char const * stream_name)
 {
   assert(stm && stream_params);
   assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm &&
          (stream_params->layout == CUBEB_LAYOUT_UNDEFINED ||
          (stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
          CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels))));
+  if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
   *pa_stm = NULL;
   pa_sample_spec ss;
   ss.format = to_pulse_format(stream_params->format);
   if (ss.format == PA_SAMPLE_INVALID)
     return CUBEB_ERROR_INVALID_FORMAT;
   ss.rate = stream_params->rate;
   ss.channels = stream_params->channels;
 
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -274,21 +274,29 @@ sndio_stream_init(cubeb * context,
   DPR("sndio_stream_init(%s)\n", stream_name);
 
   s = malloc(sizeof(cubeb_stream));
   if (s == NULL)
     return CUBEB_ERROR;
   memset(s, 0, sizeof(cubeb_stream));
   s->mode = 0;
   if (input_stream_params) {
+    if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+      DPR("sndio_stream_init(), loopback not supported\n");
+      goto err;
+    }
     s->mode |= SIO_REC;
     format = input_stream_params->format;
     rate = input_stream_params->rate;
   }
   if (output_stream_params) {
+    if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+      DPR("sndio_stream_init(), loopback not supported\n");
+      goto err;
+    }
     s->mode |= SIO_PLAY;
     format = output_stream_params->format;
     rate = output_stream_params->rate;
   }
   if (s->mode == 0) {
     DPR("sndio_stream_init(), neither playing nor recording\n");
     goto err;
   }
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -188,30 +188,34 @@ class wasapi_endpoint_notification_clien
  */
 typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
 
 struct cubeb_stream {
   cubeb * context = nullptr;
   /* Mixer pameters. We need to convert the input stream to this
      samplerate/channel layout, as WASAPI does not resample nor upmix
      itself. */
-  cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
-  cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+  cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+  cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
   /* Stream parameters. This is what the client requested,
    * and what will be presented in the callback. */
-  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
-  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
   /* The input and output device, or NULL for default. */
   std::unique_ptr<const wchar_t[]> input_device;
   std::unique_ptr<const wchar_t[]> output_device;
   /* The latency initially requested for this stream, in frames. */
   unsigned latency = 0;
   cubeb_state_callback state_callback = nullptr;
   cubeb_data_callback data_callback = nullptr;
   wasapi_refill_callback refill_callback = nullptr;
+  /* True when a loopback device is requested with no output device. In this
+     case a dummy output device is opened to drive the loopback, but should not
+     be exposed. */
+  bool has_dummy_output = false;
   void * user_ptr = nullptr;
   /* Lifetime considerations:
      - client, render_client, audio_clock and audio_stream_volume are interface
        pointer to the IAudioClient.
      - The lifetime for device_enumerator and notification_client, resampler,
        mix_buffer are the same as the cubeb_stream instance. */
 
   /* Main handle on the WASAPI stream. */
@@ -559,19 +563,19 @@ frames_to_bytes_before_mix(cubeb_stream 
  * converting it to rate and channel layout specified at initialization.
  * It then calls the data callback, via the resampler. */
 long
 refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
        void * output_buffer, long output_frames_needed)
 {
   XASSERT(!stm->draining);
   /* If we need to upmix after resampling, resample into the mix buffer to
-     avoid a copy. */
+     avoid a copy. Avoid exposing output if it is a dummy stream. */
   void * dest = nullptr;
-  if (has_output(stm)) {
+  if (has_output(stm) && !stm->has_dummy_output) {
     if (cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) {
       dest = stm->mix_buffer.data();
     } else {
       dest = output_buffer;
     }
   }
 
   long out_frames = cubeb_resampler_fill(stm->resampler.get(),
@@ -590,79 +594,86 @@ refill(cubeb_stream * stm, void * input_
   /* Go in draining mode if we got fewer frames than requested. */
   if (out_frames < output_frames_needed) {
     LOG("start draining.");
     stm->draining = true;
   }
 
   /* If this is not true, there will be glitches.
      It is alright to have produced less frames if we are draining, though. */
-  XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
+  XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm) || stm->has_dummy_output);
 
-  if (has_output(stm) && cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) {
+  // We don't bother mixing dummy output as it will be silenced, otherwise mix output if needed
+  if (!stm->has_dummy_output && has_output(stm) && cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) {
     XASSERT(dest == stm->mix_buffer.data());
     unsigned long dest_len = out_frames * stm->output_stream_params.channels;
     XASSERT(dest_len <= stm->mix_buffer.size() / stm->bytes_per_sample);
     unsigned long output_buffer_len = out_frames * stm->output_mix_params.channels;
     cubeb_mixer_mix(stm->mixer.get(), out_frames,
                     dest, dest_len, output_buffer, output_buffer_len,
                     &stm->output_stream_params, &stm->output_mix_params);
   }
 
   return out_frames;
 }
 
 /* This helper grabs all the frames available from a capture client, put them in
  * linear_input_buffer. linear_input_buffer should be cleared before the
- * callback exits. */
+ * callback exits. This helper does not work with exclusive mode streams. */
 bool get_input_buffer(cubeb_stream * stm)
 {
-  HRESULT hr;
-  UINT32 padding_in;
-
   XASSERT(has_input(stm));
 
-  hr = stm->input_client->GetCurrentPadding(&padding_in);
-  if (FAILED(hr)) {
-    LOG("Failed to get padding");
-    return false;
-  }
-  XASSERT(padding_in <= stm->input_buffer_frame_count);
-  UINT32 total_available_input = padding_in;
-
+  HRESULT hr;
   BYTE * input_packet = NULL;
   DWORD flags;
   UINT64 dev_pos;
   UINT32 next;
   /* Get input packets until we have captured enough frames, and put them in a
    * contiguous buffer. */
   uint32_t offset = 0;
-  while (offset != total_available_input) {
-    hr = stm->capture_client->GetNextPacketSize(&next);
+  // If the input stream is event driven we should only ever expect to read a
+  // single packet each time. However, if we're pulling from the stream we may
+  // need to grab multiple packets worth of frames that have accumulated (so
+  // need a loop).
+  for (hr = stm->capture_client->GetNextPacketSize(&next);
+       next > 0;
+       hr = stm->capture_client->GetNextPacketSize(&next)) {
+
     if (FAILED(hr)) {
       LOG("cannot get next packet size: %lx", hr);
       return false;
     }
-    /* This can happen if the capture stream has stopped. Just return in this
-     * case. */
-    if (!next) {
-      break;
-    }
 
     UINT32 packet_size;
     hr = stm->capture_client->GetBuffer(&input_packet,
                                         &packet_size,
                                         &flags,
                                         &dev_pos,
                                         NULL);
     if (FAILED(hr)) {
       LOG("GetBuffer failed for capture: %lx", hr);
       return false;
     }
     XASSERT(packet_size == next);
+    // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
+    // flag. There a two primary (non exhaustive) scenarios we anticipate this
+    // flag being set in:
+    //   - The first GetBuffer after Start has this flag undefined. In this
+    //     case the flag may be set but is meaningless and can be ignored.
+    //   - If a glitch is introduced into the input. This should not happen
+    //     for event based inputs, and should be mitigated by using a dummy
+    //     stream to drive input in the case of input only loopback. Without
+    //     a dummy output, input only loopback would glitch on silence. However,
+    //     the dummy input should push silence to the loopback and prevent
+    //     discontinuities. See https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
+    // As the first scenario can be ignored, and we anticipate the second
+    // scenario is mitigated, we ignore the flag.
+    // For more info: https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx,
+    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx
     if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
       LOG("insert silence: ps=%u", packet_size);
       stm->linear_input_buffer->push_silence(packet_size * stm->input_stream_params.channels);
     } else {
       if (cubeb_should_mix(&stm->input_mix_params, &stm->input_stream_params)) {
         bool ok = stm->linear_input_buffer->reserve(stm->linear_input_buffer->length() +
                                                    packet_size * stm->input_stream_params.channels);
         XASSERT(ok);
@@ -682,18 +693,17 @@ bool get_input_buffer(cubeb_stream * stm
     hr = stm->capture_client->ReleaseBuffer(packet_size);
     if (FAILED(hr)) {
       LOG("FAILED to release intput buffer");
       return false;
     }
     offset += packet_size;
   }
 
-  XASSERT(stm->linear_input_buffer->length() >= total_available_input &&
-          offset == total_available_input);
+  XASSERT(stm->linear_input_buffer->length() >= offset);
 
   return true;
 }
 
 /* Get an output buffer from the render_client. It has to be released before
  * exiting the callback. */
 bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
 {
@@ -769,28 +779,46 @@ refill_callback_duplex(cubeb_stream * st
     return true;
   }
 
   /* Wait for draining is not important on duplex. */
   if (stm->draining) {
     return false;
   }
 
-  ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
-        input_frames, output_frames);
+  if (stm->has_dummy_output) {
+    ALOGV("Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
+          input_frames, output_frames);
 
-  refill(stm,
-         stm->linear_input_buffer->data(),
-         input_frames,
-         output_buffer,
-         output_frames);
+    // We don't want to expose the dummy output to the callback so don't pass
+    // the output buffer (it will be released later with silence in it)
+    refill(stm,
+           stm->linear_input_buffer->data(),
+           input_frames,
+           nullptr,
+           0);
+  } else {
+    ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
+          input_frames, output_frames);
+
+    refill(stm,
+           stm->linear_input_buffer->data(),
+           input_frames,
+           output_buffer,
+           output_frames);
+  }
 
   stm->linear_input_buffer->clear();
 
-  hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+  if (stm->has_dummy_output) {
+    // If output is a dummy output, make sure it's silent
+    hr = stm->render_client->ReleaseBuffer(output_frames, AUDCLNT_BUFFERFLAGS_SILENT);
+  } else {
+    hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+  }
   if (FAILED(hr)) {
     LOG("failed to release buffer: %lx", hr);
     return false;
   }
   return true;
 }
 
 bool
@@ -1512,32 +1540,44 @@ int setup_wasapi_stream_one_side(cubeb_s
                                  com_ptr<IAudioClient> & audio_client,
                                  uint32_t * buffer_frame_count,
                                  HANDLE & event,
                                  T & render_or_capture_client,
                                  cubeb_stream_params * mix_params)
 {
   com_ptr<IMMDevice> device;
   HRESULT hr;
+  bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
+  if (is_loopback && direction != eCapture) {
+    LOG("Loopback pref can only be used with capture streams!\n");
+    return CUBEB_ERROR;
+  }
 
   stm->stream_reset_lock.assert_current_thread_owns();
   bool try_again = false;
   // This loops until we find a device that works, or we've exhausted all
   // possibilities.
   do {
     if (devid) {
       hr = get_endpoint(device, devid);
       if (FAILED(hr)) {
         LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
         return CUBEB_ERROR;
       }
     } else {
-      hr = get_default_endpoint(device, direction);
+      // If caller has requested loopback but not specified a device, look for
+      // the default render device. Otherwise look for the default device
+      // appropriate to the direction.
+      hr = get_default_endpoint(device, is_loopback ? eRender : direction);
       if (FAILED(hr)) {
-        LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
+        if (is_loopback) {
+          LOG("Could not get default render endpoint for loopback, error: %lx\n", hr);
+        } else {
+          LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
+        }
         return CUBEB_ERROR;
       }
     }
 
     /* Get a client. We will get all other interfaces we need from
      * this pointer. */
     hr = device->Activate(__uuidof(IAudioClient),
                           CLSCTX_INPROC_SERVER,
@@ -1598,19 +1638,28 @@ int setup_wasapi_stream_one_side(cubeb_s
     LOG("Channel count is different from the layout standard!\n");
   }
   LOG("Setup requested=[f=%d r=%u c=%u l=%s] mix=[f=%d r=%u c=%u l=%s]",
       stream_params->format, stream_params->rate, stream_params->channels,
       CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].name,
       mix_params->format, mix_params->rate, mix_params->channels,
       CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].name);
 
+  DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
+
+  // Check if a loopback device should be requested. Note that event callbacks
+  // do not work with loopback devices, so only request these if not looping.
+  if (is_loopback) {
+    flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+  } else {
+    flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+  }
+
   hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
-                                AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
-                                AUDCLNT_STREAMFLAGS_NOPERSIST,
+                                flags,
                                 frames_to_hns(stm, stm->latency),
                                 0,
                                 mix_format.get(),
                                 NULL);
   if (FAILED(hr)) {
     LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
     return CUBEB_ERROR;
   }
@@ -1621,21 +1670,24 @@ int setup_wasapi_stream_one_side(cubeb_s
         " for %s %lx.", DIRECTION_NAME, hr);
     return CUBEB_ERROR;
   }
   // Input is up/down mixed when depacketized in get_input_buffer.
   if (has_output(stm) && cubeb_should_mix(stream_params, mix_params)) {
     stm->mix_buffer.resize(frames_to_bytes_before_mix(stm, *buffer_frame_count));
   }
 
-  hr = audio_client->SetEventHandle(event);
-  if (FAILED(hr)) {
-    LOG("Could set the event handle for the %s client %lx.",
-        DIRECTION_NAME, hr);
-    return CUBEB_ERROR;
+  // Events are used if not looping back
+  if (!is_loopback) {
+    hr = audio_client->SetEventHandle(event);
+    if (FAILED(hr)) {
+      LOG("Could set the event handle for the %s client %lx.",
+          DIRECTION_NAME, hr);
+      return CUBEB_ERROR;
+    }
   }
 
   hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp());
   if (FAILED(hr)) {
     LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr);
     return CUBEB_ERROR;
   }
 
@@ -1686,16 +1738,37 @@ int setup_wasapi_stream(cubeb_stream * s
 #else
     const int silent_buffer_count = 6;
 #endif
     stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
                                            stm->input_stream_params.channels *
                                            silent_buffer_count);
   }
 
+  // If we don't have an output device but are requesting a loopback device,
+  // we attempt to open that same device in output mode in order to drive the
+  // loopback via the output events.
+  stm->has_dummy_output = false;
+  if (!has_output(stm) && stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+    stm->output_stream_params.rate = stm->input_stream_params.rate;
+    stm->output_stream_params.channels = stm->input_stream_params.channels;
+    stm->output_stream_params.layout = stm->input_stream_params.layout;
+    if (stm->input_device) {
+      size_t len = wcslen(stm->input_device.get());
+      std::unique_ptr<wchar_t[]> tmp(new wchar_t[len + 1]);
+      if (wcsncpy_s(tmp.get(), len + 1, stm->input_device.get(), len) != 0) {
+        LOG("Failed to copy device identifier while copying input stream"
+            " configuration to output stream configuration to drive loopback.");
+        return CUBEB_ERROR;
+      }
+      stm->output_device = move(tmp);
+    }
+    stm->has_dummy_output = true;
+  }
+
   if (has_output(stm)) {
     LOG("(%p) Setup render: device=%p", stm, stm->output_device.get());
     rv = setup_wasapi_stream_one_side(stm,
                                       &stm->output_stream_params,
                                       stm->output_device.get(),
                                       eRender,
                                       __uuidof(IAudioRenderClient),
                                       stm->output_client,
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -409,16 +409,21 @@ winmm_stream_init(cubeb * context, cubeb
     return CUBEB_ERROR_NOT_SUPPORTED;
   }
 
   if (input_device || output_device) {
     /* Device selection not yet implemented. */
     return CUBEB_ERROR_DEVICE_UNAVAILABLE;
   }
 
+  if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+    /* Loopback is not supported */
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
+
   *stream = NULL;
 
   memset(&wfx, 0, sizeof(wfx));
   if (output_stream_params->channels > 2) {
     wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
     wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
   } else {
     wfx.Format.wFormatTag = WAVE_FORMAT_PCM;