imported patch cubeb_duplex_wip.patch draft
authorRandell Jesup <rjesup@jesup.org>
Sun, 10 Jan 2016 21:30:58 -0500
changeset 321189 0eb3efbcb9a1fb71b14f47b2a9baacab11d22f2c
parent 320422 b248768c8f5290d001546ddd438ae4453f10bb80
child 321190 531f8e0e659bda142bc78de01dd42dc7cd48347e
push id9349
push userrjesup@wgate.com
push dateWed, 13 Jan 2016 06:48:48 +0000
milestone46.0a1
imported patch cubeb_duplex_wip.patch
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb-internal.h
media/libcubeb/src/cubeb.c
media/libcubeb/src/cubeb_wasapi.cpp
media/libcubeb/src/cubeb_winmm.c
media/libcubeb/tests/test_audio.cpp
media/libcubeb/tests/test_sanity.cpp
media/libcubeb/tests/test_tone.cpp
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -218,25 +218,27 @@ typedef struct {
 typedef struct {
   uint32_t count;                 /**< Device count in collection. */
   cubeb_device_info * device[1];   /**< Array of pointers to device info. */
 } cubeb_device_collection;
 
 /** User supplied data callback.
     @param stream
     @param user_ptr
-    @param buffer
+    @param input_buffer
+    @param output_buffer
     @param nframes
     @retval Number of frames written to buffer, which must equal nframes except
             at end of stream.
     @retval CUBEB_ERROR on error, in which case the data callback will stop
             and the stream will enter a shutdown state. */
 typedef long (* cubeb_data_callback)(cubeb_stream * stream,
                                      void * user_ptr,
-                                     void * buffer,
+                                     void * input_buffer,
+                                     void * output_buffer,
                                      long nframes);
 
 /** User supplied state callback.
     @param stream
     @param user_ptr
     @param state */
 typedef void (* cubeb_state_callback)(cubeb_stream * stream,
                                       void * user_ptr,
@@ -314,17 +316,18 @@ void cubeb_destroy(cubeb * context);
     @param state_callback
     @param user_ptr
     @retval CUBEB_OK
     @retval CUBEB_ERROR
     @retval CUBEB_ERROR_INVALID_FORMAT */
 int cubeb_stream_init(cubeb * context,
                       cubeb_stream ** stream,
                       char const * stream_name,
-                      cubeb_stream_params stream_params,
+                      cubeb_stream_params* input_stream_params,
+                      cubeb_stream_params* output_stream_params,
                       unsigned int latency,
                       cubeb_data_callback data_callback,
                       cubeb_state_callback state_callback,
                       void * user_ptr);
 
 /** Destroy a stream.
     @param stream */
 void cubeb_stream_destroy(cubeb_stream * stream);
--- a/media/libcubeb/src/cubeb-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -18,17 +18,19 @@ struct cubeb_ops {
   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,
                             cubeb_device_collection ** collection);
   void (* destroy)(cubeb * context);
   int (* stream_init)(cubeb * context, cubeb_stream ** stream, char const * stream_name,
-                      cubeb_stream_params stream_params, unsigned int latency,
+                      cubeb_stream_params * input_stream_params,
+                      cubeb_stream_params * output_stream_params,
+                      unsigned int latency,
                       cubeb_data_callback data_callback,
                       cubeb_state_callback state_callback,
                       void * user_ptr);
   void (* stream_destroy)(cubeb_stream * stream);
   int (* stream_start)(cubeb_stream * stream);
   int (* stream_stop)(cubeb_stream * stream);
   int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
   int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -57,24 +57,29 @@ int opensl_init(cubeb ** context, char c
 #if defined(USE_AUDIOTRACK)
 int audiotrack_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_KAI)
 int kai_init(cubeb ** context, char const * context_name);
 #endif
 
 int
-validate_stream_params(cubeb_stream_params stream_params)
+validate_stream_params(cubeb_stream_params * input_stream_params,
+                       cubeb_stream_params * output_stream_params)
 {
-  if (stream_params.rate < 1000 || stream_params.rate > 192000 ||
-      stream_params.channels < 1 || stream_params.channels > 8) {
+  // Rate and sample format must be the same for input and output.
+  if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
+      output_stream_params->channels < 1 || output_stream_params->channels > 8 ||
+	  (input_stream_params && input_stream_params &&
+	  (input_stream_params->rate != output_stream_params->rate  ||
+	  input_stream_params->format != output_stream_params->format))) {
     return CUBEB_ERROR_INVALID_FORMAT;
   }
 
-  switch (stream_params.format) {
+  switch (output_stream_params->format) {
   case CUBEB_SAMPLE_S16LE:
   case CUBEB_SAMPLE_S16BE:
   case CUBEB_SAMPLE_FLOAT32LE:
   case CUBEB_SAMPLE_FLOAT32BE:
     return CUBEB_OK;
   }
 
   return CUBEB_ERROR_INVALID_FORMAT;
@@ -213,34 +218,36 @@ cubeb_destroy(cubeb * context)
     return;
   }
 
   context->ops->destroy(context);
 }
 
 int
 cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
-                  cubeb_stream_params stream_params, unsigned int latency,
+				  cubeb_stream_params * input_stream_params,
+				  cubeb_stream_params * output_stream_params,
+				  unsigned int latency,
                   cubeb_data_callback data_callback,
                   cubeb_state_callback state_callback,
                   void * user_ptr)
 {
   int r;
 
   if (!context || !stream) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
-  if ((r = validate_stream_params(stream_params)) != CUBEB_OK ||
+  if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK ||
       (r = validate_latency(latency)) != CUBEB_OK) {
     return r;
   }
 
   return context->ops->stream_init(context, stream, stream_name,
-                                   stream_params, latency,
+                                   input_stream_params, output_stream_params, latency,
                                    data_callback,
                                    state_callback,
                                    user_ptr);
 }
 
 void
 cubeb_stream_destroy(cubeb_stream * stream)
 {
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -136,16 +136,38 @@ public:
 
 private:
   CRITICAL_SECTION critical_section;
 #ifdef DEBUG
   DWORD owner;
 #endif
 };
 
+template <typename T>
+class auto_ptr {
+public:
+  auto_ptr(T * ptr) 
+    : ptr_(ptr)
+  {
+    // printf("alloc %p\n", ptr);
+  }
+  ~auto_ptr() {
+    // printf("delete %p\n", ptr_);
+    delete ptr_;
+  }
+  T*& operator*() {
+    return ptr_;
+  }
+  T* get() {
+    return ptr_;
+  }
+private:
+  T * ptr_;
+};
+
 struct auto_lock {
   auto_lock(owned_critical_section * lock)
     : lock(lock)
   {
     lock->enter();
   }
   ~auto_lock()
   {
@@ -201,38 +223,50 @@ struct cubeb
      the two function pointers we need. */
   HMODULE mmcss_module;
   set_mm_thread_characteristics_function set_mm_thread_characteristics;
   revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
 };
 
 class wasapi_endpoint_notification_client;
 
+/* We have three possible callbacks we can use with a stream:
+ * - input only
+ * - output only 
+ * - synchronized input and output
+ *
+ * Returns true when we should continue to play, false otherwise.
+ */
+typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
+
 struct cubeb_stream
 {
   cubeb * context;
   /* Mixer pameters. We need to convert the input stream to this
-     samplerate/channel layout, as WASAPI * does not resample nor upmix
+     samplerate/channel layout, as WASAPI does not resample nor upmix
      itself. */
-  cubeb_stream_params mix_params;
-  cubeb_stream_params stream_params;
+  cubeb_stream_params input_mix_params;
+  cubeb_stream_params output_mix_params;
+  cubeb_stream_params input_stream_params;
+  cubeb_stream_params output_stream_params;
   /* The latency initially requested for this stream. */
   unsigned latency;
   cubeb_state_callback state_callback;
   cubeb_data_callback data_callback;
+  wasapi_refill_callback refill_callback;
   void * user_ptr;
 
   /* 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. */
-  IAudioClient * client;
+  IAudioClient * output_client;
   /* Interface pointer to use the event-driven interface. */
   IAudioRenderClient * render_client;
   /* Interface pointer to use the volume facilities. */
   IAudioStreamVolume * audio_stream_volume;
   /* Interface pointer to use the stream audio clock. */
   IAudioClock * audio_clock;
   /* Frames written to the stream since it was opened. Reset on device
      change. Uses mix_params.rate. */
@@ -245,36 +279,45 @@ struct cubeb_stream
   UINT64 prev_position;
   /* Device enumerator to be able to be notified when the default
      device change. */
   IMMDeviceEnumerator * device_enumerator;
   /* Device notification client, to be able to be notified when the default
      audio device changes and route the audio to the new default audio output
      device */
   wasapi_endpoint_notification_client * notification_client;
+  /* Main andle to the WASAPI capture stream. */
+  IAudioClient * input_client;
+  /* Interface to use the event driven capture interface */
+  IAudioCaptureClient * capture_client;
+
   /* This event is set by the stream_stop and stream_destroy
      function, so the render loop can exit properly. */
   HANDLE shutdown_event;
   /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
      The reconfiguration is handled by the render loop thread. */
   HANDLE reconfigure_event;
   /* This is set by WASAPI when we should refill the stream. */
   HANDLE refill_event;
+  /* This is set by WASAPI when we should read from the input stream. In
+   * practice, we read from the input stream in the output callback. */
+  HANDLE input_available_event;
   /* Each cubeb_stream has its own thread. */
   HANDLE thread;
   /* The lock protects all members that are touched by the render thread or
      change during a device reset, including: audio_clock, audio_stream_volume,
      client, frames_written, mix_params, total_frames_written, prev_position. */
   owned_critical_section * stream_reset_lock;
-  /* Maximum number of frames we can be requested in a callback. */
-  uint32_t buffer_frame_count;
+  /* Maximum number of frames that can be requested in a callback. */
+  uint32_t input_buffer_frame_count;
+  uint32_t output_buffer_frame_count;
   /* Resampler instance. Resampling will only happen if necessary. */
   cubeb_resampler * resampler;
   /* Buffer used to downmix or upmix to the number of channels the mixer has.
-     its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */
+     its size is |frames_to_bytes_before_mix(output_buffer_frame_count)|. */
   float * mix_buffer;
   /* Stream volume.  Set via stream_set_volume and used to reset volume on
      device changes. */
   float volume;
   /* True if the stream is draining. */
   bool draining;
 };
 
@@ -372,28 +415,38 @@ private:
   /* refcount for this instance, necessary to implement MSCOM semantics. */
   LONG ref_count;
   HANDLE reconfigure_event;
 };
 
 namespace {
 bool should_upmix(cubeb_stream * stream)
 {
-  return stream->mix_params.channels > stream->stream_params.channels;
+  return stream->output_mix_params.channels > stream->output_stream_params.channels;
 }
 
 bool should_downmix(cubeb_stream * stream)
 {
-  return stream->mix_params.channels < stream->stream_params.channels;
+  return stream->output_mix_params.channels < stream->output_stream_params.channels;
 }
 
 double stream_to_mix_samplerate_ratio(cubeb_stream * stream)
 {
   stream->stream_reset_lock->assert_current_thread_owns();
-  return double(stream->stream_params.rate) / stream->mix_params.rate;
+  return double(stream->output_stream_params.rate) / stream->output_mix_params.rate;
+}
+
+bool has_input(cubeb_stream * stm)
+{
+  return stm->input_stream_params.rate != 0;
+}
+
+bool has_output(cubeb_stream * stm)
+{
+	return stm->output_stream_params.rate != 0;
 }
 
 /* Upmix function, copies a mono channel into L and R */
 template<typename T>
 void
 mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
 {
   for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
@@ -451,33 +504,39 @@ downmix(T * in, long inframes, T * out, 
   }
 }
 
 /* This returns the size of a frame in the stream, before the eventual upmix
    occurs. */
 static size_t
 frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
 {
-  size_t stream_frame_size = stm->stream_params.channels * sizeof(float);
+  size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
   return stream_frame_size * frames;
 }
 
+/* This function handles the processing of the input and output audio,
+ * converting it to rate and channel layout specified at initialization. 
+ * It then calls the data callback. */
 long
-refill(cubeb_stream * stm, float * data, long frames_needed)
+refill(cubeb_stream * stm, float * input_buffer, float * output_buffer, long frames_needed)
 {
   /* If we need to upmix after resampling, resample into the mix buffer to
      avoid a copy. */
   float * dest;
   if (should_upmix(stm) || should_downmix(stm)) {
     dest = stm->mix_buffer;
   } else {
-    dest = data;
+    dest = output_buffer;
   }
 
-  long out_frames = cubeb_resampler_fill(stm->resampler, dest, frames_needed);
+  long out_frames = cubeb_resampler_fill(stm->resampler,
+                                         input_buffer,
+                                         dest,
+                                         frames_needed);
   /* TODO: Report out_frames < 0 as an error via the API. */
   XASSERT(out_frames >= 0);
 
   {
     auto_lock lock(stm->stream_reset_lock);
     stm->frames_written += out_frames;
   }
 
@@ -487,26 +546,217 @@ refill(cubeb_stream * stm, float * data,
     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 == frames_needed || stm->draining);
 
   if (should_upmix(stm)) {
-    upmix(dest, out_frames, data,
-          stm->stream_params.channels, stm->mix_params.channels);
+    upmix(dest, out_frames, output_buffer,
+          stm->output_stream_params.channels, stm->output_mix_params.channels);
   } else if (should_downmix(stm)) {
-    downmix(dest, out_frames, data,
-            stm->stream_params.channels, stm->mix_params.channels);
+    downmix(dest, out_frames, output_buffer,
+            stm->output_stream_params.channels, stm->output_mix_params.channels);
   }
 
   return out_frames;
 }
 
+/**
+ * This function gets input data from a input device, and pass it along with an
+ * output buffer to the callback.  */
+bool
+refill_callback_duplex(cubeb_stream * stm)
+{
+  UINT32 padding_out, padding_in;
+  HRESULT hr;
+
+  hr = stm->output_client->GetCurrentPadding(&padding_out);
+  if (FAILED(hr)) {
+    LOG("Failed to get padding: %x\n", hr);
+    return false;
+  }
+  XASSERT(padding_out <= stm->output_buffer_frame_count);
+  hr = stm->input_client->GetCurrentPadding(&padding_in);
+  if (FAILED(hr)) {
+    LOG("Failed to get padding\n");
+    return false;
+  }
+  XASSERT(padding_in <= stm->input_buffer_frame_count);
+
+  if (stm->draining) {
+    if (padding_out == 0) {
+      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+    }
+    return false;
+  }
+
+  long available_output = stm->output_buffer_frame_count - padding_out;
+  UINT32 total_available_input = padding_in;
+
+  if (available_output == 0) {
+    return 0;
+  }
+
+  BYTE * output_buffer;
+  auto_ptr<BYTE> input_buffer(new BYTE[available_output * sizeof(float) * stm->input_stream_params.channels]);
+
+  BYTE * tmp_input_buffer = NULL;
+  DWORD flags;
+  UINT64 dev_pos;
+  UINT32 next;
+  hr = stm->render_client->GetBuffer(available_output, &output_buffer);
+  if (FAILED(hr)) {
+    LOG("cannot get render buffer\n");
+    return false;
+  }
+  uint32_t offset = 0;
+
+  if (total_available_input < available_output) {
+    uint32_t bytes = (available_output - total_available_input) * sizeof(float) * stm->input_stream_params.channels;
+    memset(input_buffer.get(), 0, bytes);
+    offset += bytes;
+    printf("DROPPING %d.\n", total_available_input);
+    total_available_input = available_output;
+  } else {
+    total_available_input = available_output;
+  }
+
+  while (offset != total_available_input * sizeof(float) * stm->input_stream_params.channels) {
+    hr = stm->capture_client->GetNextPacketSize(&next);
+    if (FAILED(hr)) {
+      LOG("cannot get next packet size: %x\n", hr);
+      return false;
+    }
+    // printf("next packet size:%u\n", next);
+    UINT32 available_input;
+
+    hr = stm->capture_client->GetBuffer(&tmp_input_buffer, &available_input, &flags, &dev_pos, NULL);
+    if (FAILED(hr)) {
+      LOG("GetBuffer failed for capture: %x\n", hr);
+      return false;
+    }
+    XASSERT(available_input == next);
+    if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
+      memset(input_buffer.get() + offset, 0, available_input * sizeof(float) * stm->input_stream_params.channels);
+    } else {
+      memcpy(input_buffer.get() + offset, tmp_input_buffer, available_input * sizeof(float) * stm->input_stream_params.channels);
+    }
+    offset += available_input * sizeof(float) * stm->input_stream_params.channels;
+    hr = stm->capture_client->ReleaseBuffer(available_input);
+    if (FAILED(hr)) {
+      LOG("FAILED to release intput buffer");
+      return false;
+    }
+  }
+  // it's ok to have the first input not being a full buffer, because of input latency.
+  // then, there should be an equal amount of frames.
+  XASSERT(!total_available_input || total_available_input == available_output);
+
+  refill(stm, reinterpret_cast<float *>(input_buffer.get()), reinterpret_cast<float *>(output_buffer), available_output);
+
+  hr = stm->render_client->ReleaseBuffer(available_output, 0);
+
+
+  if (FAILED(hr)) {
+    LOG("failed to release buffer: %x\n", hr);
+    return false;
+  }
+  return true;
+}
+
+bool
+refill_callback_input(cubeb_stream * stm)
+{
+  /* padding here has a meaning inverse than from the output case, it's the
+   * number of frames we can read. */
+  UINT32 padding_input;
+  HRESULT hr;
+  DWORD flags;
+  UINT64 dev_pos;
+
+  hr = stm->input_client->GetCurrentPadding(&padding_input);
+  if (FAILED(hr)) {
+    LOG("Failed to get padding: %x\n", hr);
+    return false;
+  }
+  XASSERT(padding_input <= stm->input_buffer_frame_count);
+
+  /* Maybe that can happen on the first couple callbacks because of latency. */
+  if (padding_input == 0) {
+    printf("no data available.\n");
+    return true;
+  }
+
+  BYTE * data;
+  UINT32 available_input;
+  hr = stm->capture_client->GetBuffer(&data, &available_input, &flags, &dev_pos, NULL);
+  if (SUCCEEDED(hr)) {
+    long read = refill(stm, reinterpret_cast<float*>(data), nullptr, available_input);
+    XASSERT(read == available_input || stm->draining);
+
+    hr = stm->capture_client->ReleaseBuffer(read);
+    if (FAILED(hr)) {
+      LOG("failed to release buffer: %x\n", hr);
+      return false;
+    }
+  } else {
+    LOG("failed to get buffer: %x\n", hr);
+    return false;
+  }
+  return true;
+  return true;
+}
+
+bool
+refill_callback_output(cubeb_stream * stm)
+{
+  UINT32 padding;
+  HRESULT hr;
+
+  hr = stm->output_client->GetCurrentPadding(&padding);
+  if (FAILED(hr)) {
+    LOG("Failed to get padding: %x\n", hr);
+    return false;
+  }
+  XASSERT(padding <= stm->output_buffer_frame_count);
+
+  if (stm->draining) {
+    if (padding == 0) {
+      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+      return false;
+    }
+    return true;
+  }
+
+  long available = stm->output_buffer_frame_count - padding;
+
+  if (available == 0) {
+    return true;
+  }
+
+  BYTE * data;
+  hr = stm->render_client->GetBuffer(available, &data);
+  if (SUCCEEDED(hr)) {
+    long wrote = refill(stm, nullptr, reinterpret_cast<float *>(data), available);
+    XASSERT(wrote == available || stm->draining);
+
+    hr = stm->render_client->ReleaseBuffer(wrote, 0);
+    if (FAILED(hr)) {
+      LOG("failed to release buffer: %x\n", hr);
+      return false;
+    }
+  } else {
+    LOG("failed to get buffer: %x\n", hr);
+    return false;
+  }
+  return true;
+}
+
 static unsigned int __stdcall
 wasapi_stream_render_loop(LPVOID stream)
 {
   cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
 
   bool is_playing = true;
   HANDLE wait_array[3] = {stm->shutdown_event, stm->reconfigure_event, stm->refill_event};
   HANDLE mmcss_handle = NULL;
@@ -550,75 +800,46 @@ wasapi_stream_render_loop(LPVOID stream)
          shutdown. */
       if (stm->draining) {
         stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
       }
       continue;
     }
     case WAIT_OBJECT_0 + 1: { /* reconfigure */
       /* Close the stream */
-      stm->client->Stop();
+      if (stm->output_client) {
+        stm->output_client->Stop();
+      }
+      if (stm->input_client) {
+        stm->input_client->Stop();
+      }
       {
         auto_lock lock(stm->stream_reset_lock);
         close_wasapi_stream(stm);
         /* Reopen a stream and start it immediately. This will automatically pick the
            new default device for this role. */
         int r = setup_wasapi_stream(stm);
         if (r != CUBEB_OK) {
           /* Don't destroy the stream here, since we expect the caller to do
              so after the error has propagated via the state callback. */
           is_playing = false;
           hr = E_FAIL;
           continue;
         }
       }
-      stm->client->Start();
+      if (stm->output_client) {
+        stm->output_client->Start();
+      }
+      if (stm->input_client) {
+        stm->input_client->Start();
+      }
       break;
     }
-    case WAIT_OBJECT_0 + 2: { /* refill */
-      UINT32 padding;
-
-      hr = stm->client->GetCurrentPadding(&padding);
-      if (FAILED(hr)) {
-        LOG("Failed to get padding: %x\n", hr);
-        is_playing = false;
-        continue;
-      }
-      XASSERT(padding <= stm->buffer_frame_count);
-
-      if (stm->draining) {
-        if (padding == 0) {
-          stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
-          is_playing = false;
-        }
-        continue;
-      }
-
-      long available = stm->buffer_frame_count - padding;
-
-      if (available == 0) {
-        continue;
-      }
-
-      BYTE * data;
-      hr = stm->render_client->GetBuffer(available, &data);
-      if (SUCCEEDED(hr)) {
-        long wrote = refill(stm, reinterpret_cast<float *>(data), available);
-        XASSERT(wrote == available || stm->draining);
-
-        hr = stm->render_client->ReleaseBuffer(wrote, 0);
-        if (FAILED(hr)) {
-          LOG("failed to release buffer: %x\n", hr);
-          is_playing = false;
-        }
-      } else {
-        LOG("failed to get buffer: %x\n", hr);
-        is_playing = false;
-      }
-    }
+    case WAIT_OBJECT_0 + 2:  /* refill */
+      is_playing = stm->refill_callback(stm);
       break;
     case WAIT_TIMEOUT:
       XASSERT(stm->shutdown_event == wait_array[0]);
       if (++timeout_count >= timeout_limit) {
         is_playing = false;
         hr = E_FAIL;
       }
       break;
@@ -681,31 +902,31 @@ HRESULT unregister_notification_client(c
   stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client);
 
   SafeRelease(stm->notification_client);
   SafeRelease(stm->device_enumerator);
 
   return S_OK;
 }
 
-HRESULT get_default_endpoint(IMMDevice ** device)
+HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction)
 {
   IMMDeviceEnumerator * enumerator;
   HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                                 NULL, CLSCTX_INPROC_SERVER,
                                 IID_PPV_ARGS(&enumerator));
   if (FAILED(hr)) {
     LOG("Could not get device enumerator: %x\n", hr);
     return hr;
   }
   /* eMultimedia is okay for now ("Music, movies, narration, [...]").
      We will need to change this when we distinguish streams by use-case, other
      possible values being eConsole ("Games, system notification sounds [...]")
      and eCommunication ("Voice communication"). */
-  hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device);
+  hr = enumerator->GetDefaultAudioEndpoint(direction, eMultimedia, device);
   if (FAILED(hr)) {
     LOG("Could not get default audio endpoint: %x\n", hr);
     SafeRelease(enumerator);
     return hr;
   }
 
   SafeRelease(enumerator);
 
@@ -734,17 +955,17 @@ current_stream_delay(cubeb_stream * stm)
   UINT64 pos;
   hr = stm->audio_clock->GetPosition(&pos, NULL);
   if (FAILED(hr)) {
     LOG("GetPosition failed: %x\n", hr);
     return 0;
   }
 
   double cur_pos = static_cast<double>(pos) / freq;
-  double max_pos = static_cast<double>(stm->frames_written)  / stm->mix_params.rate;
+  double max_pos = static_cast<double>(stm->frames_written)  / stm->output_mix_params.rate;
   double delay = max_pos - cur_pos;
   XASSERT(delay >= 0);
 
   return delay;
 }
 
 int
 stream_set_volume(cubeb_stream * stm, float volume)
@@ -790,17 +1011,17 @@ int wasapi_init(cubeb ** context, char c
   if (!com.ok()) {
     return CUBEB_ERROR;
   }
 
   /* We don't use the device yet, but need to make sure we can initialize one
      so that this backend is not incorrectly enabled on platforms that don't
      support WASAPI. */
   IMMDevice * device;
-  hr = get_default_endpoint(&device);
+  hr = get_default_endpoint(&device, eRender);
   if (FAILED(hr)) {
     LOG("Could not get device: %x\n", hr);
     return CUBEB_ERROR;
   }
   SafeRelease(device);
 
   cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
   if (!ctx) {
@@ -882,17 +1103,17 @@ wasapi_get_max_channel_count(cubeb * ctx
   auto_com com;
   if (!com.ok()) {
     return CUBEB_ERROR;
   }
 
   XASSERT(ctx && max_channels);
 
   IMMDevice * device;
-  hr = get_default_endpoint(&device);
+  hr = get_default_endpoint(&device, eRender);
   if (FAILED(hr)) {
     return CUBEB_ERROR;
   }
 
   hr = device->Activate(__uuidof(IAudioClient),
                         CLSCTX_INPROC_SERVER,
                         NULL, (void **)&client);
   SafeRelease(device);
@@ -925,17 +1146,17 @@ wasapi_get_min_latency(cubeb * ctx, cube
     return CUBEB_ERROR;
   }
 
   if (params.format != CUBEB_SAMPLE_FLOAT32NE) {
     return CUBEB_ERROR_INVALID_FORMAT;
   }
 
   IMMDevice * device;
-  hr = get_default_endpoint(&device);
+  hr = get_default_endpoint(&device, eRender);
   if (FAILED(hr)) {
     LOG("Could not get default endpoint: %x\n", hr);
     return CUBEB_ERROR;
   }
 
   hr = device->Activate(__uuidof(IAudioClient),
                         CLSCTX_INPROC_SERVER,
                         NULL, (void **)&client);
@@ -972,17 +1193,17 @@ wasapi_get_preferred_sample_rate(cubeb *
   IAudioClient * client;
   WAVEFORMATEX * mix_format;
   auto_com com;
   if (!com.ok()) {
     return CUBEB_ERROR;
   }
 
   IMMDevice * device;
-  hr = get_default_endpoint(&device);
+  hr = get_default_endpoint(&device, eRender);
   if (FAILED(hr)) {
     return CUBEB_ERROR;
   }
 
   hr = device->Activate(__uuidof(IAudioClient),
                         CLSCTX_INPROC_SERVER,
                         NULL, (void **)&client);
   SafeRelease(device);
@@ -1048,19 +1269,19 @@ handle_channel_layout(cubeb_stream * stm
   (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8;
   (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign;
   format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
   (*mix_format)->wBitsPerSample = 32;
   format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample;
 
   /* Check if wasapi will accept our channel layout request. */
   WAVEFORMATEX * closest;
-  HRESULT hr = stm->client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
-                                              *mix_format,
-                                              &closest);
+  HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
+						     *mix_format,
+						     &closest);
 
   if (hr == S_FALSE) {
     /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
        eventual upmix/downmix ourselves */
     LOG("Using WASAPI suggested format: channels: %d\n", closest->nChannels);
     WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
     XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat);
     CoTaskMemFree(*mix_format);
@@ -1085,154 +1306,249 @@ int setup_wasapi_stream(cubeb_stream * s
 
   stm->stream_reset_lock->assert_current_thread_owns();
 
   auto_com com;
   if (!com.ok()) {
     return CUBEB_ERROR;
   }
 
-  XASSERT(!stm->client && "WASAPI stream already setup, close it first.");
+  XASSERT(!stm->output_client && "WASAPI stream already setup, close it first.");
 
-  hr = get_default_endpoint(&device);
-  if (FAILED(hr)) {
-    LOG("Could not get default endpoint, error: %x\n", hr);
-    return CUBEB_ERROR;
-  }
+  if (has_input(stm)) {
+	  hr = get_default_endpoint(&device, eCapture);
+	  if (FAILED(hr)) {
+		  LOG("Could not get default endpoint, error: %x\n", 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,
-                        NULL, (void **)&stm->client);
-  SafeRelease(device);
-  if (FAILED(hr)) {
-    LOG("Could not activate the device to get an audio client: error: %x\n", 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,
+		  NULL, (void **)&stm->input_client);
+	  SafeRelease(device);
+	  if (FAILED(hr)) {
+		  LOG("Could not activate the device to get an audio client: error: %x\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  /* We have to distinguish between the format the mixer uses,
+	   * and the format the stream we want to play uses. */
+	  hr = stm->input_client->GetMixFormat(&mix_format);
+	  if (FAILED(hr)) {
+		  LOG("Could not fetch current mix format from the audio client: error: %x\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  handle_channel_layout(stm, &mix_format, &stm->input_stream_params);
+
+	  /* Shared mode WASAPI always supports float32 sample format, so this
+	   * is safe. */
+	  stm->input_mix_params.format = CUBEB_SAMPLE_FLOAT32NE;
+	  stm->input_mix_params.rate = mix_format->nSamplesPerSec;
+	  stm->input_mix_params.channels = mix_format->nChannels;
 
-  /* We have to distinguish between the format the mixer uses,
-     and the format the stream we want to play uses. */
-  hr = stm->client->GetMixFormat(&mix_format);
-  if (FAILED(hr)) {
-    LOG("Could not fetch current mix format from the audio client: error: %x\n", hr);
-    return CUBEB_ERROR;
-  }
+	  hr = stm->input_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
+		  AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
+		  AUDCLNT_STREAMFLAGS_NOPERSIST,
+		  ms_to_hns(stm->latency),
+		  0,
+		  mix_format,
+		  NULL);
 
-  handle_channel_layout(stm, &mix_format, &stm->stream_params);
+	  if (FAILED(hr)) {
+		  LOG("Unable to initialize audio client: %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
 
-  /* Shared mode WASAPI always supports float32 sample format, so this
-     is safe. */
-  stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE;
-  stm->mix_params.rate = mix_format->nSamplesPerSec;
-  stm->mix_params.channels = mix_format->nChannels;
+	  hr = stm->input_client->GetBufferSize(&stm->input_buffer_frame_count);
+	  if (FAILED(hr)) {
+		  LOG("Could not get the buffer size from the client %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  if (should_upmix(stm) || should_downmix(stm)) {
+		  stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, stm->input_buffer_frame_count));
+	  }
 
-  hr = stm->client->Initialize(AUDCLNT_SHAREMODE_SHARED,
-                               AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
-                               AUDCLNT_STREAMFLAGS_NOPERSIST,
-                               ms_to_hns(stm->latency),
-                               0,
-                               mix_format,
-                               NULL);
-  CoTaskMemFree(mix_format);
-  if (FAILED(hr)) {
-    LOG("Unable to initialize audio client: %x\n", hr);
-    return CUBEB_ERROR;
+	  hr = stm->input_client->SetEventHandle(stm->input_available_event);
+	  if (FAILED(hr)) {
+		  LOG("Could set the event handle for the input client %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  hr = stm->input_client->GetService(__uuidof(IAudioCaptureClient),
+		  (void **)&stm->capture_client);
+	  if (FAILED(hr)) {
+		  LOG("Could not get the capture client %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
   }
 
-  hr = stm->client->GetBufferSize(&stm->buffer_frame_count);
-  if (FAILED(hr)) {
-    LOG("Could not get the buffer size from the client: %x\n", hr);
-    return CUBEB_ERROR;
-  }
+  if (has_output(stm)) {
+	  hr = get_default_endpoint(&device, eRender);
+	  if (FAILED(hr)) {
+		  LOG("Could not get default endpoint, error: %x\n", 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,
+		  NULL, (void **)&stm->output_client);
+	  SafeRelease(device);
+	  if (FAILED(hr)) {
+		  LOG("Could not activate the device to get an audio client: error: %x\n", hr);
+		  return CUBEB_ERROR;
+	  }
 
-  if (should_upmix(stm) || should_downmix(stm)) {
-    stm->mix_buffer = (float *) malloc(frames_to_bytes_before_mix(stm, stm->buffer_frame_count));
-  }
+     /* We have to distinguish between the format the mixer uses,
+	  * and the format the stream we want to play uses. */
+	  hr = stm->output_client->GetMixFormat(&mix_format);
+	  if (FAILED(hr)) {
+		  LOG("Could not fetch current mix format from the audio client: error: %x\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  handle_channel_layout(stm, &mix_format, &stm->output_stream_params);
+
+	  /* Shared mode WASAPI always supports float32 sample format, so this
+	   * is safe. */
+	  stm->output_mix_params.format = CUBEB_SAMPLE_FLOAT32NE;
+	  stm->output_mix_params.rate = mix_format->nSamplesPerSec;
+	  stm->output_mix_params.channels = mix_format->nChannels;
+
+
+	  hr = stm->output_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
+		  AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
+		  AUDCLNT_STREAMFLAGS_NOPERSIST,
+		  ms_to_hns(stm->latency),
+		  0,
+		  mix_format,
+		  NULL);
+
+	  CoTaskMemFree(mix_format);
+
+	  if (FAILED(hr)) {
+		  LOG("Unable to initialize audio client: %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
 
-  hr = stm->client->SetEventHandle(stm->refill_event);
-  if (FAILED(hr)) {
-    LOG("Could set the event handle for the client: %x\n", hr);
-    return CUBEB_ERROR;
-  }
+	  hr = stm->output_client->GetBufferSize(&stm->output_buffer_frame_count);
+	  if (FAILED(hr)) {
+		  LOG("Could not get the buffer size from the client %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  if (should_upmix(stm) || should_downmix(stm)) {
+		  stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
+	  }
+
+	  hr = stm->output_client->SetEventHandle(stm->refill_event);
+	  if (FAILED(hr)) {
+		  LOG("Could set the event handle for the client %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  hr = stm->output_client->GetService(__uuidof(IAudioRenderClient), (void **)&stm->render_client);
+	  if (FAILED(hr)) {
+		  LOG("Could not get the render client %x.\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
 
-  hr = stm->client->GetService(__uuidof(IAudioRenderClient),
-                               (void **)&stm->render_client);
-  if (FAILED(hr)) {
-    LOG("Could not get the render client: %x\n", hr);
-    return CUBEB_ERROR;
+	  hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
+		  (void **)&stm->audio_stream_volume);
+	  if (FAILED(hr)) {
+		  LOG("Could not get the IAudioStreamVolume: %x\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  XASSERT(stm->frames_written == 0);
+	  hr = stm->output_client->GetService(__uuidof(IAudioClock),
+		  (void **)&stm->audio_clock);
+	  if (FAILED(hr)) {
+		  LOG("Could not get the IAudioClock: %x\n", hr);
+		  return CUBEB_ERROR;
+	  }
+
+	  /* Restore the stream volume over a device change. */
+	  if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
+		  return CUBEB_ERROR;
+	  }
+
+	  /* If we are playing a mono stream, we only resample one channel,
+		 and copy it over, so we are always resampling the number
+		 of channels of the stream, not the number of channels
+		 that WASAPI wants. */
+	  stm->resampler = cubeb_resampler_create(stm, stm->output_stream_params,
+		  stm->output_mix_params.rate,
+		  stm->data_callback,
+		  stm->output_buffer_frame_count,
+		  stm->user_ptr,
+		  CUBEB_RESAMPLER_QUALITY_DESKTOP);
+	  if (!stm->resampler) {
+		  LOG("Could not get a resampler\n");
+		  return CUBEB_ERROR;
+	  }
+
   }
 
-  hr = stm->client->GetService(__uuidof(IAudioStreamVolume),
-                               (void **)&stm->audio_stream_volume);
-  if (FAILED(hr)) {
-    LOG("Could not get the IAudioStreamVolume: %x\n", hr);
-    return CUBEB_ERROR;
-  }
-
-  XASSERT(stm->frames_written == 0);
-  hr = stm->client->GetService(__uuidof(IAudioClock),
-                               (void **)&stm->audio_clock);
-  if (FAILED(hr)) {
-    LOG("Could not get the IAudioClock: %x\n", hr);
-    return CUBEB_ERROR;
-  }
+  XASSERT(has_input(stm) || has_output(stm));
 
-  /* Restore the stream volume over a device change. */
-  if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
-    return CUBEB_ERROR;
-  }
-
-  /* If we are playing a mono stream, we only resample one channel,
-     and copy it over, so we are always resampling the number
-     of channels of the stream, not the number of channels
-     that WASAPI wants. */
-  stm->resampler = cubeb_resampler_create(stm, stm->stream_params,
-                                          stm->mix_params.rate,
-                                          stm->data_callback,
-                                          stm->buffer_frame_count,
-                                          stm->user_ptr,
-                                          CUBEB_RESAMPLER_QUALITY_DESKTOP);
-  if (!stm->resampler) {
-    LOG("Could not get a resampler\n");
-    return CUBEB_ERROR;
+  if (has_input(stm) && has_output(stm)) {
+    stm->refill_callback = refill_callback_duplex;
+  } else if (has_input(stm)) {
+    stm->refill_callback = refill_callback_input;
+  } else if (has_output(stm)) {
+    stm->refill_callback = refill_callback_output;
   }
 
   return CUBEB_OK;
 }
 
 int
 wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
-                   char const * stream_name, cubeb_stream_params stream_params,
+                   char const * stream_name,
+                   cubeb_stream_params * input_stream_params,
+                   cubeb_stream_params * output_stream_params,
                    unsigned int latency, cubeb_data_callback data_callback,
                    cubeb_state_callback state_callback, void * user_ptr)
 {
   HRESULT hr;
   int rv;
   auto_com com;
   if (!com.ok()) {
     return CUBEB_ERROR;
   }
 
   XASSERT(context && stream);
 
-  if (stream_params.format != CUBEB_SAMPLE_FLOAT32NE) {
+  if (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) {
     return CUBEB_ERROR_INVALID_FORMAT;
   }
 
   cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
 
   XASSERT(stm);
 
   stm->context = context;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
-  stm->stream_params = stream_params;
+  stm->output_stream_params = *output_stream_params;
   stm->draining = false;
+  if (input_stream_params) {
+    stm->input_stream_params = *input_stream_params;
+  }
+  if (output_stream_params) {
+    stm->output_stream_params = *output_stream_params;
+  }
   stm->latency = latency;
   stm->volume = 1.0;
 
   stm->stream_reset_lock = new owned_critical_section();
 
   stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
   if (!stm->reconfigure_event) {
     LOG("Can't create the reconfigure event, error: %x\n", GetLastError());
@@ -1242,16 +1558,25 @@ wasapi_stream_init(cubeb * context, cube
 
   stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
   if (!stm->refill_event) {
     LOG("Can't create the refill event, error: %x\n", GetLastError());
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
+  if (input_stream_params) {
+    stm->input_available_event = CreateEvent(NULL, 0, 0, NULL);
+    if (!stm->input_available_event) {
+      LOG("Can't create the input available event , error: %x\n", GetLastError());
+      wasapi_stream_destroy(stm);
+      return CUBEB_ERROR;
+    }
+  }
+
   {
     /* Locking here is not strictly necessary, because we don't have a
        notification client that can reset the stream yet, but it lets us
        assert that the lock is held in the function. */
     auto_lock lock(stm->stream_reset_lock);
     rv = setup_wasapi_stream(stm);
   }
   if (rv != CUBEB_OK) {
@@ -1272,18 +1597,20 @@ wasapi_stream_init(cubeb * context, cube
 }
 
 void close_wasapi_stream(cubeb_stream * stm)
 {
   XASSERT(stm);
 
   stm->stream_reset_lock->assert_current_thread_owns();
 
-  SafeRelease(stm->client);
-  stm->client = NULL;
+  SafeRelease(stm->output_client);
+  stm->output_client = NULL;
+  SafeRelease(stm->input_client);
+  stm->capture_client = NULL;
 
   SafeRelease(stm->render_client);
   stm->render_client = NULL;
 
   SafeRelease(stm->audio_stream_volume);
   stm->audio_stream_volume = NULL;
 
   SafeRelease(stm->audio_clock);
@@ -1305,16 +1632,17 @@ void wasapi_stream_destroy(cubeb_stream 
   XASSERT(stm);
 
   unregister_notification_client(stm);
 
   stop_and_join_render_thread(stm);
 
   SafeRelease(stm->reconfigure_event);
   SafeRelease(stm->refill_event);
+  SafeRelease(stm->input_available_event);
 
   {
     auto_lock lock(stm->stream_reset_lock);
     close_wasapi_stream(stm);
   }
 
   delete stm->stream_reset_lock;
 
@@ -1322,46 +1650,54 @@ void wasapi_stream_destroy(cubeb_stream 
 }
 
 int wasapi_stream_start(cubeb_stream * stm)
 {
   auto_lock lock(stm->stream_reset_lock);
 
   XASSERT(stm && !stm->thread && !stm->shutdown_event);
 
-  if (!stm->client) {
+  if (!stm->output_client) {
     return CUBEB_ERROR;
   }
 
-  HRESULT hr = stm->client->Start();
+  HRESULT hr = stm->output_client->Start();
   if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
     LOG("audioclient invalid device, reconfiguring\n", hr);
 
     BOOL ok = ResetEvent(stm->reconfigure_event);
     if (!ok) {
       LOG("resetting reconfig event failed: %x\n", GetLastError());
     }
 
     close_wasapi_stream(stm);
     int r = setup_wasapi_stream(stm);
     if (r != CUBEB_OK) {
       LOG("reconfigure failed\n");
       return r;
     }
 
-    HRESULT hr = stm->client->Start();
+    HRESULT hr = stm->output_client->Start();
     if (FAILED(hr)) {
       LOG("could not start the stream after reconfig: %x\n", hr);
       return CUBEB_ERROR;
     }
   } else if (FAILED(hr)) {
     LOG("could not start the stream.\n");
     return CUBEB_ERROR;
   }
 
+ if (stm->input_client) {
+	 HRESULT rv = stm->input_client->Start();
+   if (FAILED(rv)) {
+     printf("Could not start capture.\n");
+     return CUBEB_ERROR;
+   }
+ }
+
   stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
   if (!stm->shutdown_event) {
     LOG("Can't create the shutdown event, error: %x\n", GetLastError());
     return CUBEB_ERROR;
   }
 
   stm->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
   if (stm->thread == NULL) {
@@ -1376,39 +1712,44 @@ int wasapi_stream_start(cubeb_stream * s
 
 int wasapi_stream_stop(cubeb_stream * stm)
 {
   XASSERT(stm);
 
   {
     auto_lock lock(stm->stream_reset_lock);
 
-    if (stm->client) {
-      HRESULT hr = stm->client->Stop();
+    if (stm->output_client) {
+      HRESULT hr = stm->output_client->Stop();
       if (FAILED(hr)) {
         LOG("could not stop AudioClient\n");
         return CUBEB_ERROR;
       }
     }
 
+    if (stm->input_client) {
+      stm->input_client->Stop();
+    }
+
+
     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
   }
 
   stop_and_join_render_thread(stm);
 
   return CUBEB_OK;
 }
 
 int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
 {
   XASSERT(stm && position);
   auto_lock lock(stm->stream_reset_lock);
 
   /* Calculate how far behind the current stream head the playback cursor is. */
-  uint64_t stream_delay = current_stream_delay(stm) * stm->stream_params.rate;
+  uint64_t stream_delay = current_stream_delay(stm) * stm->output_stream_params.rate;
 
   /* Calculate the logical stream head in frames at the stream sample rate. */
   uint64_t max_pos = stm->total_frames_written +
                      round(stm->frames_written * stream_to_mix_samplerate_ratio(stm));
 
   *position = max_pos;
   if (stream_delay <= *position) {
     *position -= stream_delay;
@@ -1425,27 +1766,27 @@ int wasapi_stream_get_position(cubeb_str
 int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
 {
   XASSERT(stm && latency);
 
   auto_lock lock(stm->stream_reset_lock);
 
   /* The GetStreamLatency method only works if the
      AudioClient has been initialized. */
-  if (!stm->client) {
+  if (!stm->output_client) {
     return CUBEB_ERROR;
   }
 
   REFERENCE_TIME latency_hns;
-  HRESULT hr = stm->client->GetStreamLatency(&latency_hns);
+  HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
   if (FAILED(hr)) {
     return CUBEB_ERROR;
   }
   double latency_s = hns_to_s(latency_hns);
-  *latency = static_cast<uint32_t>(latency_s * stm->stream_params.rate);
+  *latency = static_cast<uint32_t>(latency_s * stm->output_stream_params.rate);
 
   return CUBEB_OK;
 }
 
 int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
 {
   auto_lock lock(stm->stream_reset_lock);
 
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -174,17 +174,17 @@ winmm_refill_stream(cubeb_stream * stm)
 
   hdr = winmm_get_next_buffer(stm);
 
   wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
 
   /* It is assumed that the caller is holding this lock.  It must be dropped
      during the callback to avoid deadlocks. */
   LeaveCriticalSection(&stm->lock);
-  got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted);
+  got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
   EnterCriticalSection(&stm->lock);
   if (got < 0) {
     LeaveCriticalSection(&stm->lock);
     /* XXX handle this case */
     XASSERT(0);
     return;
   } else if (got < wanted) {
     stm->draining = 1;
--- a/media/libcubeb/tests/test_audio.cpp
+++ b/media/libcubeb/tests/test_audio.cpp
@@ -155,17 +155,17 @@ int run_test(int num_channels, int sampl
   params.channels = num_channels;
 
   synth = synth_create(params.channels, params.rate);
   if (synth == NULL) {
     fprintf(stderr, "Out of memory\n");
     goto cleanup;
   }
 
-  r = cubeb_stream_init(ctx, &stream, "test tone", params,
+  r = cubeb_stream_init(ctx, &stream, "test tone", nullptr, &params,
                         100, is_float ? data_cb_float : data_cb_short, state_cb, synth);
   if (r != CUBEB_OK) {
     fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
     goto cleanup;
   }
 
   cubeb_stream_start(stream);
   delay(200);
@@ -207,17 +207,17 @@ int run_panning_volume_test(int is_float
   params.channels = 2;
 
   synth = synth_create(params.channels, params.rate);
   if (synth == NULL) {
     fprintf(stderr, "Out of memory\n");
     goto cleanup;
   }
 
-  r = cubeb_stream_init(ctx, &stream, "test tone", params,
+  r = cubeb_stream_init(ctx, &stream, "test tone", nullptr, &params,
                         100, is_float ? data_cb_float : data_cb_short, state_cb, synth);
   if (r != CUBEB_OK) {
     fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
     goto cleanup;
   }
 
   fprintf(stderr, "Testing: volume\n");
   for(int i=0;i <= 4; ++i)
--- a/media/libcubeb/tests/test_sanity.cpp
+++ b/media/libcubeb/tests/test_sanity.cpp
@@ -35,23 +35,23 @@
 #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
 #endif
 
 static int dummy;
 static uint64_t total_frames_written;
 static int delay_callback;
 
 static long
-test_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes)
+test_data_callback(cubeb_stream * stm, void * user_ptr, void * inputbuffer, void * outputbuffer, long nframes)
 {
-  assert(stm && user_ptr == &dummy && p && nframes > 0);
+  assert(stm && user_ptr == &dummy && nframes > 0);
 #if (defined(_WIN32) || defined(__WIN32__))
-  memset(p, 0, nframes * sizeof(float));
+  memset(outputbuffer, 0, nframes * sizeof(float));
 #else
-  memset(p, 0, nframes * sizeof(short));
+  memset(outputbuffer, 0, nframes * sizeof(short));
 #endif
 
   total_frames_written += nframes;
   if (delay_callback) {
     delay(10);
   }
   return nframes;
 }
@@ -153,17 +153,17 @@ test_init_destroy_stream(void)
 
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
-  r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY,
+  r = cubeb_stream_init(ctx, &stream, "test", nullptr, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   assert(r == 0 && stream);
 
   cubeb_stream_destroy(stream);
   cubeb_destroy(ctx);
 
   END_TEST;
 }
@@ -182,17 +182,17 @@ test_init_destroy_multiple_streams(void)
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
-    r = cubeb_stream_init(ctx, &stream[i], "test", params, STREAM_LATENCY,
+    r = cubeb_stream_init(ctx, &stream[i], "test", NULL, &params, STREAM_LATENCY,
                           test_data_callback, test_state_callback, &dummy);
     assert(r == 0);
     assert(stream[i]);
   }
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
     cubeb_stream_destroy(stream[i]);
   }
@@ -214,17 +214,17 @@ test_configure_stream(void)
 
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = 2; // panning
 
-  r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY,
+  r = cubeb_stream_init(ctx, &stream, "test", NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   assert(r == 0 && stream);
 
   r = cubeb_stream_set_volume(stream, 1.0f);
   assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
 
   r = cubeb_stream_set_panning(stream, 0.0f);
   assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
@@ -248,17 +248,17 @@ test_init_start_stop_destroy_multiple_st
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
-    r = cubeb_stream_init(ctx, &stream[i], "test", params, STREAM_LATENCY,
+    r = cubeb_stream_init(ctx, &stream[i], "test", NULL, &params, STREAM_LATENCY,
                           test_data_callback, test_state_callback, &dummy);
     assert(r == 0);
     assert(stream[i]);
     if (early) {
       r = cubeb_stream_start(stream[i]);
       assert(r == 0);
     }
   }
@@ -312,17 +312,17 @@ test_init_destroy_multiple_contexts_and_
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
   for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
     r = cubeb_init(&ctx[i], "test_sanity");
     assert(r == 0 && ctx[i]);
 
     for (j = 0; j < streams_per_ctx; ++j) {
-      r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", params, STREAM_LATENCY,
+      r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, &params, STREAM_LATENCY,
                             test_data_callback, test_state_callback, &dummy);
       assert(r == 0);
       assert(stream[i * streams_per_ctx + j]);
     }
   }
 
   for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
     for (j = 0; j < streams_per_ctx; ++j) {
@@ -347,17 +347,17 @@ test_basic_stream_operations(void)
 
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
-  r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY,
+  r = cubeb_stream_init(ctx, &stream, "test", NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   assert(r == 0 && stream);
 
   /* position and volume before stream has started */
   r = cubeb_stream_get_position(stream, &position);
   assert(r == 0 && position == 0);
 
   r = cubeb_stream_start(stream);
@@ -396,17 +396,17 @@ test_stream_position(void)
 
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
-  r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY,
+  r = cubeb_stream_init(ctx, &stream, "test", NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   assert(r == 0 && stream);
 
   /* stream position should not advance before starting playback */
   r = cubeb_stream_get_position(stream, &position);
   assert(r == 0 && position == 0);
 
   delay(500);
@@ -469,26 +469,26 @@ test_stream_position(void)
 
   END_TEST;
 }
 
 static int do_drain;
 static int got_drain;
 
 static long
-test_drain_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes)
+test_drain_data_callback(cubeb_stream * stm, void * user_ptr, void * inputbuffer, void * outputbuffer, long nframes)
 {
-  assert(stm && user_ptr == &dummy && p && nframes > 0);
+  assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
   if (do_drain == 1) {
     do_drain = 2;
     return 0;
   }
   /* once drain has started, callback must never be called again */
   assert(do_drain != 2);
-  memset(p, 0, nframes * sizeof(short));
+  memset(outputbuffer, 0, nframes * sizeof(short));
   total_frames_written += nframes;
   return nframes;
 }
 
 void
 test_drain_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
 {
   if (state == CUBEB_STATE_DRAINED) {
@@ -512,17 +512,17 @@ test_drain(void)
 
   r = cubeb_init(&ctx, "test_sanity");
   assert(r == 0 && ctx);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
 
-  r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY,
+  r = cubeb_stream_init(ctx, &stream, "test", NULL, &params, STREAM_LATENCY,
                         test_drain_data_callback, test_drain_state_callback, &dummy);
   assert(r == 0 && stream);
 
   r = cubeb_stream_start(stream);
   assert(r == 0);
 
   delay(500);
 
--- a/media/libcubeb/tests/test_tone.cpp
+++ b/media/libcubeb/tests/test_tone.cpp
@@ -29,23 +29,23 @@
 #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
 #endif
 
 /* store the phase of the generated waveform */
 struct cb_user_data {
   long position;
 };
 
-long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes)
+long data_cb(cubeb_stream *stream, void *user, void *inputbuffer, void * outputbuffer, long nframes)
 {
   struct cb_user_data *u = (struct cb_user_data *)user;
 #if (defined(_WIN32) || defined(__WIN32__))
-  float *b = (float *)buffer;
+  float *b = (float *)outputbuffer;
 #else
-  short *b = (short *)buffer;
+  short *b = (short *)outputbuffer;
 #endif
   float t1, t2;
   int i;
 
   if (stream == NULL || u == NULL)
     return CUBEB_ERROR;
 
   /* generate our test tone on the fly */
@@ -122,17 +122,17 @@ int main(int argc, char *argv[])
 
   user_data = (struct cb_user_data *) malloc(sizeof(*user_data));
   if (user_data == NULL) {
     fprintf(stderr, "Error allocating user data\n");
     return CUBEB_ERROR;
   }
   user_data->position = 0;
 
-  r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", params,
+  r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", nullptr, &params,
                         250, data_cb, state_cb, user_data);
   if (r != CUBEB_OK) {
     fprintf(stderr, "Error initializing cubeb stream\n");
     return r;
   }
 
   cubeb_stream_start(stream);
   delay(500);