Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/third_party/rust/cubeb-sys/libcubeb/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 51 kB image not shown  

Quelle  cubeb_pulse.c   Sprache: C

 
/*
 * Copyright © 2011 Mozilla Foundation
 *
 * This program is made available under an ISC-style license.  See the
 * accompanying file LICENSE for details.
 */

#undef NDEBUG
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
#include "cubeb_strings.h"
#include <assert.h>
#include <dlfcn.h>
#include <pulse/pulseaudio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef DISABLE_LIBPULSE_DLOPEN
#define WRAP(x) x
#else
#define WRAP(x) (*cubeb_##x)
#define LIBPULSE_API_VISIT(X)                                                  \
  X(pa_channel_map_can_balance)                                                \
  X(pa_channel_map_init)                                                       \
  X(pa_context_connect)                                                        \
  X(pa_context_disconnect)                                                     \
  X(pa_context_drain)                                                          \
  X(pa_context_get_server_info)                                                \
  X(pa_context_get_sink_info_by_name)                                          \
  X(pa_context_get_sink_info_list)                                             \
  X(pa_context_get_sink_input_info)                                            \
  X(pa_context_get_source_info_list)                                           \
  X(pa_context_get_state)                                                      \
  X(pa_context_new)                                                            \
  X(pa_context_rttime_new)                                                     \
  X(pa_context_set_sink_input_volume)                                          \
  X(pa_context_set_state_callback)                                             \
  X(pa_context_unref)                                                          \
  X(pa_cvolume_set)                                                            \
  X(pa_cvolume_set_balance)                                                    \
  X(pa_frame_size)                                                             \
  X(pa_operation_get_state)                                                    \
  X(pa_operation_unref)                                                        \
  X(pa_proplist_gets)                                                          \
  X(pa_rtclock_now)                                                            \
  X(pa_stream_begin_write)                                                     \
  X(pa_stream_cancel_write)                                                    \
  X(pa_stream_connect_playback)                                                \
  X(pa_stream_cork)                                                            \
  X(pa_stream_disconnect)                                                      \
  X(pa_stream_get_channel_map)                                                 \
  X(pa_stream_get_index)                                                       \
  X(pa_stream_get_latency)                                                     \
  X(pa_stream_get_sample_spec)                                                 \
  X(pa_stream_get_state)                                                       \
  X(pa_stream_get_time)                                                        \
  X(pa_stream_new)                                                             \
  X(pa_stream_set_state_callback)                                              \
  X(pa_stream_set_write_callback)                                              \
  X(pa_stream_unref)                                                           \
  X(pa_stream_update_timing_info)                                              \
  X(pa_stream_write)                                                           \
  X(pa_sw_volume_from_linear)                                                  \
  X(pa_threaded_mainloop_free)                                                 \
  X(pa_threaded_mainloop_get_api)                                              \
  X(pa_threaded_mainloop_in_thread)                                            \
  X(pa_threaded_mainloop_lock)                                                 \
  X(pa_threaded_mainloop_new)                                                  \
  X(pa_threaded_mainloop_signal)                                               \
  X(pa_threaded_mainloop_start)                                                \
  X(pa_threaded_mainloop_stop)                                                 \
  X(pa_threaded_mainloop_unlock)                                               \
  X(pa_threaded_mainloop_wait)                                                 \
  X(pa_usec_to_bytes)                                                          \
  X(pa_stream_set_read_callback)                                               \
  X(pa_stream_connect_record)                                                  \
  X(pa_stream_readable_size)                                                   \
  X(pa_stream_writable_size)                                                   \
  X(pa_stream_peek)                                                            \
  X(pa_stream_drop)                                                            \
  X(pa_stream_get_buffer_attr)                                                 \
  X(pa_stream_get_device_name)                                                 \
  X(pa_context_set_subscribe_callback)                                         \
  X(pa_context_subscribe)                                                      \
  X(pa_mainloop_api_once)                                                      \
  X(pa_get_library_version)                                                    \
  X(pa_channel_map_init_auto)                                                  \
  X(pa_stream_set_name)

#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBPULSE_API_VISIT(MAKE_TYPEDEF);
#undef MAKE_TYPEDEF
#endif

#if PA_CHECK_VERSION(2, 0, 0)
static int has_pulse_v2 = 0;
#endif

static struct cubeb_ops const pulse_ops;

struct cubeb_default_sink_info {
  pa_channel_map channel_map;
  uint32_t sample_spec_rate;
  pa_sink_flags_t flags;
};

struct cubeb {
  struct cubeb_ops const * ops;
  void * libpulse;
  pa_threaded_mainloop * mainloop;
  pa_context * context;
  struct cubeb_default_sink_info * default_sink_info;
  char * context_name;
  int error;
  cubeb_device_collection_changed_callback output_collection_changed_callback;
  void * output_collection_changed_user_ptr;
  cubeb_device_collection_changed_callback input_collection_changed_callback;
  void * input_collection_changed_user_ptr;
  cubeb_strings * device_ids;
};

struct cubeb_stream {
  /* Note: Must match cubeb_stream layout in cubeb.c. */
  cubeb * context;
  void * user_ptr;
  /**/
  pa_stream * output_stream;
  pa_stream * input_stream;
  cubeb_data_callback data_callback;
  cubeb_state_callback state_callback;
  pa_time_event * drain_timer;
  pa_sample_spec output_sample_spec;
  pa_sample_spec input_sample_spec;
  int shutdown;
  float volume;
  cubeb_state state;
};

static const float PULSE_NO_GAIN = -1.0;

enum cork_state { UNCORK = 0, CORK = 1 << 0, NOTIFY = 1 << 1 };

static int
intern_device_id(cubeb * ctx, char const ** id)
{
  char const * interned;

  assert(ctx);
  assert(id);

  interned = cubeb_strings_intern(ctx->device_ids, *id);
  if (!interned) {
    return CUBEB_ERROR;
  }

  *id = interned;

  return CUBEB_OK;
}

static void
sink_info_callback(pa_context * context, const pa_sink_info * info, int eol,
                   void * u)
{
  (void)context;
  cubeb * ctx = u;
  if (!eol) {
    free(ctx->default_sink_info);
    ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info));
    memcpy(&ctx->default_sink_info->channel_map, &info->channel_map,
           sizeof(pa_channel_map));
    ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate;
    ctx->default_sink_info->flags = info->flags;
  }
  WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
}

static void
server_info_callback(pa_context * context, const pa_server_info * info,
                     void * u)
{
  pa_operation * o;
  o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name,
                                             sink_info_callback, u);
  if (o) {
    WRAP(pa_operation_unref)(o);
  }
}

static void
context_state_callback(pa_context * c, void * u)
{
  cubeb * ctx = u;
  if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
    ctx->error = 1;
  }
  WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
}

static void
context_notify_callback(pa_context * c, void * u)
{
  (void)c;
  cubeb * ctx = u;
  WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
}

static void
stream_success_callback(pa_stream * s, int success, void * u)
{
  (void)s;
  (void)success;
  cubeb_stream * stm = u;
  WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
}

static void
stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
{
  stm->state = s;
  stm->state_callback(stm, stm->user_ptr, s);
}

static void
stream_drain_callback(pa_mainloop_api * a, pa_time_event * e,
                      struct timeval const * tv, void * u)
{
  (void)a;
  (void)tv;
  cubeb_stream * stm = u;
  assert(stm->drain_timer == e);
  stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
  /* there's no pa_rttime_free, so use this instead. */
  a->time_free(stm->drain_timer);
  stm->drain_timer = NULL;
  WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
}

static void
stream_state_callback(pa_stream * s, void * u)
{
  cubeb_stream * stm = u;
  if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
    stream_state_change_callback(stm, CUBEB_STATE_ERROR);
  }
  WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
}

static void
trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes,
                      cubeb_stream * stm)
{
  void * buffer;
  size_t size;
  int r;
  long got;
  size_t towrite, read_offset;
  size_t frame_size;

  frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
  assert(nbytes % frame_size == 0);

  towrite = nbytes;
  read_offset = 0;
  while (towrite) {
    size = towrite;
    r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
    // Note: this has failed running under rr on occassion - needs
    // investigation.
    assert(r == 0);
    assert(size > 0);
    assert(size % frame_size == 0);

    LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd",
         size, read_offset);
    got = stm->data_callback(stm, stm->user_ptr,
                             (uint8_t const *)input_data + read_offset, buffer,
                             size / frame_size);
    if (got < 0) {
      WRAP(pa_stream_cancel_write)(s);
      stm->shutdown = 1;
      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
      return;
    }
    // If more iterations move offset of read buffer
    if (input_data) {
      size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
      read_offset += (size / frame_size) * in_frame_size;
    }

    if (stm->volume != PULSE_NO_GAIN) {
      uint32_t samples = size * stm->output_sample_spec.channels / frame_size;

      if (stm->output_sample_spec.format == PA_SAMPLE_S16BE ||
          stm->output_sample_spec.format == PA_SAMPLE_S16LE) {
        short * b = buffer;
        for (uint32_t i = 0; i < samples; i++) {
          b[i] *= stm->volume;
        }
      } else {
        float * b = buffer;
        for (uint32_t i = 0; i < samples; i++) {
          b[i] *= stm->volume;
        }
      }
    }

    r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0,
                              PA_SEEK_RELATIVE);
    assert(r == 0);

    if ((size_t)got < size / frame_size) {
      pa_usec_t latency = 0;
      r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
      if (r == -PA_ERR_NODATA) {
        /* this needs a better guess. */
        latency = 100 * PA_USEC_PER_MSEC;
      }
      assert(r == 0 || r == -PA_ERR_NODATA);
      /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
      /* arbitrary safety margin: double the current latency. */
      assert(!stm->drain_timer);
      stm->drain_timer = WRAP(pa_context_rttime_new)(
          stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency,
          stream_drain_callback, stm);
      stm->shutdown = 1;
      return;
    }

    towrite -= size;
  }

  assert(towrite == 0);
}

static int
read_from_input(pa_stream * s, void const ** buffer, size_t * size)
{
  size_t readable_size = WRAP(pa_stream_readable_size)(s);
  if (readable_size > 0) {
    if (WRAP(pa_stream_peek)(s, buffer, size) < 0) {
      return -1;
    }
  }
  return readable_size;
}

static void
stream_write_callback(pa_stream * s, size_t nbytes, void * u)
{
  LOGV("Output callback to be written buffer size %zd", nbytes);
  cubeb_stream * stm = u;
  if (stm->shutdown || stm->state != CUBEB_STATE_STARTED) {
    return;
  }

  if (!stm->input_stream) {
    // Output/playback only operation.
    // Write directly to output
    assert(!stm->input_stream && stm->output_stream);
    trigger_user_callback(s, NULL, nbytes, stm);
  }
}

static void
stream_read_callback(pa_stream * s, size_t nbytes, void * u)
{
  LOGV("Input callback buffer size %zd", nbytes);
  cubeb_stream * stm = u;
  if (stm->shutdown) {
    return;
  }

  void const * read_data = NULL;
  size_t read_size;
  while (read_from_input(s, &read_data, &read_size) > 0) {
    /* read_data can be NULL in case of a hole. */
    if (read_data) {
      size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
      size_t read_frames = read_size / in_frame_size;

      if (stm->output_stream) {
        // input/capture + output/playback operation
        size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
        size_t write_size = read_frames * out_frame_size;
        // Offer full duplex data for writing
        trigger_user_callback(stm->output_stream, read_data, write_size, stm);
      } else {
        // input/capture only operation. Call callback directly
        long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL,
                                      read_frames);
        if (got < 0 || (size_t)got != read_frames) {
          WRAP(pa_stream_cancel_write)(s);
          stm->shutdown = 1;
          if (got < 0) {
            stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
          }
          break;
        }
      }
    }
    if (read_size > 0) {
      WRAP(pa_stream_drop)(s);
    }

    if (stm->shutdown) {
      return;
    }
  }
}

static int
wait_until_context_ready(cubeb * ctx)
{
  for (;;) {
    pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
    if (!PA_CONTEXT_IS_GOOD(state))
      return -1;
    if (state == PA_CONTEXT_READY)
      break;
    WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
  }
  return 0;
}

static int
wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop)
{
  if (!stream || !mainloop) {
    return -1;
  }
  for (;;) {
    pa_stream_state_t state = WRAP(pa_stream_get_state)(stream);
    if (!PA_STREAM_IS_GOOD(state))
      return -1;
    if (state == PA_STREAM_READY)
      break;
    WRAP(pa_threaded_mainloop_wait)(mainloop);
  }
  return 0;
}

static int
wait_until_stream_ready(cubeb_stream * stm)
{
  if (stm->output_stream &&
      wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) ==
          -1) {
    return -1;
  }
  if (stm->input_stream &&
      wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) ==
          -1) {
    return -1;
  }
  return 0;
}

static int
operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
{
  while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
    WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
    if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
      return -1;
    }
    if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
      return -1;
    }
  }
  return 0;
}

static void
cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
{
  pa_operation * o;
  if (!io_stream) {
    return;
  }
  o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback,
                           stm);
  if (o) {
    operation_wait(stm->context, io_stream, o);
    WRAP(pa_operation_unref)(o);
  }
}

static void
stream_cork(cubeb_stream * stm, enum cork_state state)
{
  WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
  cork_io_stream(stm, stm->output_stream, state);
  cork_io_stream(stm, stm->input_stream, state);
  WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);

  if (state & NOTIFY) {
    stream_state_change_callback(stm, state & CORK ? CUBEB_STATE_STOPPED
                                                   : CUBEB_STATE_STARTED);
  }
}

static int
stream_update_timing_info(cubeb_stream * stm)
{
  int r = -1;
  pa_operation * o = NULL;
  if (stm->output_stream) {
    o = WRAP(pa_stream_update_timing_info)(stm->output_stream,
                                           stream_success_callback, stm);
    if (o) {
      r = operation_wait(stm->context, stm->output_stream, o);
      WRAP(pa_operation_unref)(o);
    }
    if (r != 0) {
      return r;
    }
  }

  if (stm->input_stream) {
    o = WRAP(pa_stream_update_timing_info)(stm->input_stream,
                                           stream_success_callback, stm);
    if (o) {
      r = operation_wait(stm->context, stm->input_stream, o);
      WRAP(pa_operation_unref)(o);
    }
  }

  return r;
}

static pa_channel_position_t
cubeb_channel_to_pa_channel(cubeb_channel channel)
{
  switch (channel) {
  case CHANNEL_FRONT_LEFT:
    return PA_CHANNEL_POSITION_FRONT_LEFT;
  case CHANNEL_FRONT_RIGHT:
    return PA_CHANNEL_POSITION_FRONT_RIGHT;
  case CHANNEL_FRONT_CENTER:
    return PA_CHANNEL_POSITION_FRONT_CENTER;
  case CHANNEL_LOW_FREQUENCY:
    return PA_CHANNEL_POSITION_LFE;
  case CHANNEL_BACK_LEFT:
    return PA_CHANNEL_POSITION_REAR_LEFT;
  case CHANNEL_BACK_RIGHT:
    return PA_CHANNEL_POSITION_REAR_RIGHT;
  case CHANNEL_FRONT_LEFT_OF_CENTER:
    return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
  case CHANNEL_FRONT_RIGHT_OF_CENTER:
    return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
  case CHANNEL_BACK_CENTER:
    return PA_CHANNEL_POSITION_REAR_CENTER;
  case CHANNEL_SIDE_LEFT:
    return PA_CHANNEL_POSITION_SIDE_LEFT;
  case CHANNEL_SIDE_RIGHT:
    return PA_CHANNEL_POSITION_SIDE_RIGHT;
  case CHANNEL_TOP_CENTER:
    return PA_CHANNEL_POSITION_TOP_CENTER;
  case CHANNEL_TOP_FRONT_LEFT:
    return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
  case CHANNEL_TOP_FRONT_CENTER:
    return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
  case CHANNEL_TOP_FRONT_RIGHT:
    return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
  case CHANNEL_TOP_BACK_LEFT:
    return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
  case CHANNEL_TOP_BACK_CENTER:
    return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
  case CHANNEL_TOP_BACK_RIGHT:
    return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
  default:
    return PA_CHANNEL_POSITION_INVALID;
  }
}

static void
layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
{
  assert(cm && layout != CUBEB_LAYOUT_UNDEFINED);

  WRAP(pa_channel_map_init)(cm);

  uint32_t channels = 0;
  cubeb_channel_layout channelMap = layout;
  for (uint32_t i = 0; channelMap != 0; ++i) {
    uint32_t channel = (channelMap & 1) << i;
    if (channel != 0) {
      cm->map[channels] = cubeb_channel_to_pa_channel(channel);
      channels++;
    }
    channelMap = channelMap >> 1;
  }
  unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout);
  assert(channels_from_layout <= UINT8_MAX);
  cm->channels = (uint8_t)channels_from_layout;

  // Special case single channel center mapping as mono.
  if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_FRONT_CENTER) {
    cm->map[0] = PA_CHANNEL_POSITION_MONO;
  }
}

static void
pulse_context_destroy(cubeb * ctx);
static void
pulse_destroy(cubeb * ctx);

static int
pulse_context_init(cubeb * ctx)
{
  int r;

  if (ctx->context) {
    assert(ctx->error == 1);
    pulse_context_destroy(ctx);
  }

  ctx->context = WRAP(pa_context_new)(
      WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop), ctx->context_name);
  if (!ctx->context) {
    return -1;
  }
  WRAP(pa_context_set_state_callback)
  (ctx->context, context_state_callback, ctx);

  WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
  r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);

  if (r < 0 || wait_until_context_ready(ctx) != 0) {
    WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
    pulse_context_destroy(ctx);
    ctx->context = NULL;
    return -1;
  }

  WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);

  ctx->error = 0;

  return 0;
}

static int
pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask);

/*static*/ int
pulse_init(cubeb ** context, char const * context_name)
{
  void * libpulse = NULL;
  cubeb * ctx;
  pa_operation * o;

  *context = NULL;

#ifndef DISABLE_LIBPULSE_DLOPEN
  libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
  if (!libpulse) {
    libpulse = dlopen("libpulse.so", RTLD_LAZY);
    if (!libpulse) {
      return CUBEB_ERROR;
    }
  }

#define LOAD(x)                                                                \
  {                                                                            \
    cubeb_##x = dlsym(libpulse, #x);                                           \
    if (!cubeb_##x) {                                                          \
      dlclose(libpulse);                                                       \
      return CUBEB_ERROR;                                                      \
    }                                                                          \
  }

  LIBPULSE_API_VISIT(LOAD);
#undef LOAD
#endif

#if PA_CHECK_VERSION(2, 0, 0)
  const char * version = WRAP(pa_get_library_version)();
  has_pulse_v2 = strtol(version, NULL, 10) >= 2;
#endif

  ctx = calloc(1, sizeof(*ctx));
  assert(ctx);

  ctx->ops = &pulse_ops;
  ctx->libpulse = libpulse;
  if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
    pulse_destroy(ctx);
    return CUBEB_ERROR;
  }

  ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
  ctx->default_sink_info = NULL;

  WRAP(pa_threaded_mainloop_start)(ctx->mainloop);

  ctx->context_name = context_name ? strdup(context_name) : NULL;
  if (pulse_context_init(ctx) != 0) {
    pulse_destroy(ctx);
    return CUBEB_ERROR;
  }

  /* server_info_callback performs a second async query, which is
     responsible for initializing default_sink_info and signalling the
     mainloop to end the wait. */

  WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
  o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
  if (o) {
    operation_wait(ctx, NULL, o);
    WRAP(pa_operation_unref)(o);
  }
  WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);

  /* Update `default_sink_info` when the default device changes. */
  pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER);

  *context = ctx;

  return CUBEB_OK;
}

static char const *
pulse_get_backend_id(cubeb * ctx)
{
  (void)ctx;
  return "pulse";
}

static int
pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
  (void)ctx;
  assert(ctx && max_channels);

  if (!ctx->default_sink_info)
    return CUBEB_ERROR;

  *max_channels = ctx->default_sink_info->channel_map.channels;

  return CUBEB_OK;
}

static int
pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
  assert(ctx && rate);
  (void)ctx;

  if (!ctx->default_sink_info)
    return CUBEB_ERROR;

  *rate = ctx->default_sink_info->sample_spec_rate;

  return CUBEB_OK;
}

static int
pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params,
                      uint32_t * latency_frames)
{
  (void)ctx;
  // According to PulseAudio developers, this is a safe minimum.
  *latency_frames = 25 * params.rate / 1000;

  return CUBEB_OK;
}

static void
pulse_context_destroy(cubeb * ctx)
{
  pa_operation * o;

  WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
  o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
  if (o) {
    operation_wait(ctx, NULL, o);
    WRAP(pa_operation_unref)(o);
  }
  WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
  WRAP(pa_context_disconnect)(ctx->context);
  WRAP(pa_context_unref)(ctx->context);
  WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
}

static void
pulse_destroy(cubeb * ctx)
{
  assert(!ctx->input_collection_changed_callback &&
         !ctx->input_collection_changed_user_ptr &&
         !ctx->output_collection_changed_callback &&
         !ctx->output_collection_changed_user_ptr);
  free(ctx->context_name);
  if (ctx->context) {
    pulse_context_destroy(ctx);
  }

  if (ctx->mainloop) {
    WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
    WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
  }

  if (ctx->device_ids) {
    cubeb_strings_destroy(ctx->device_ids);
  }
#ifndef DISABLE_LIBPULSE_DLOPEN
  if (ctx->libpulse) {
    dlclose(ctx->libpulse);
  }
#endif
  free(ctx->default_sink_info);
  free(ctx);
}

static void
pulse_stream_destroy(cubeb_stream * stm);

static pa_sample_format_t
to_pulse_format(cubeb_sample_format format)
{
  switch (format) {
  case CUBEB_SAMPLE_S16LE:
    return PA_SAMPLE_S16LE;
  case CUBEB_SAMPLE_S16BE:
    return PA_SAMPLE_S16BE;
  case CUBEB_SAMPLE_FLOAT32LE:
    return PA_SAMPLE_FLOAT32LE;
  case CUBEB_SAMPLE_FLOAT32BE:
    return PA_SAMPLE_FLOAT32BE;
  default:
    return PA_SAMPLE_INVALID;
  }
}

static cubeb_channel_layout
pulse_default_layout_for_channels(uint32_t ch)
{
  assert(ch > 0 && ch <= 8);
  switch (ch) {
  case 1:
    return CUBEB_LAYOUT_MONO;
  case 2:
    return CUBEB_LAYOUT_STEREO;
  case 3:
    return CUBEB_LAYOUT_3F;
  case 4:
    return CUBEB_LAYOUT_QUAD;
  case 5:
    return CUBEB_LAYOUT_3F2;
  case 6:
    return CUBEB_LAYOUT_3F_LFE | CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT;
  case 7:
    return CUBEB_LAYOUT_3F3R_LFE;
  case 8:
    return CUBEB_LAYOUT_3F4_LFE;
  }
  // Never get here!
  return CUBEB_LAYOUT_UNDEFINED;
}

static int
create_pa_stream(cubeb_stream * stm, pa_stream ** pa_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_nb_channels(stream_params->layout) ==
                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;
  if (stream_params->channels > UINT8_MAX)
    return CUBEB_ERROR_INVALID_FORMAT;
  ss.channels = (uint8_t)stream_params->channels;

  if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) {
    pa_channel_map cm;
    if (stream_params->channels <= 8 &&
        !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels,
                                        PA_CHANNEL_MAP_DEFAULT)) {
      LOG("Layout undefined and PulseAudio's default layout has not been "
          "configured, guess one.");
      layout_to_channel_map(
          pulse_default_layout_for_channels(stream_params->channels), &cm);
      *pa_stm =
          WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
    } else {
      LOG("Layout undefined, PulseAudio will use its default.");
      *pa_stm =
          WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
    }
  } else {
    pa_channel_map cm;
    layout_to_channel_map(stream_params->layout, &cm);
    *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
  }
  return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
}

static pa_buffer_attr
set_buffering_attribute(unsigned int latency_frames,
                        pa_sample_spec * sample_spec)
{
  pa_buffer_attr battr;
  battr.maxlength = -1;
  battr.prebuf = -1;
  battr.tlength = latency_frames * WRAP(pa_frame_size)(sample_spec);
  battr.minreq = battr.tlength / 4;
  battr.fragsize = battr.minreq;

  LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq "
      "%u, fragsize %u",
      battr.maxlength, battr.tlength, battr.prebuf, battr.minreq,
      battr.fragsize);

  return battr;
}

static int
pulse_stream_init(cubeb * context, cubeb_stream ** stream,
                  char const * stream_name, cubeb_devid input_device,
                  cubeb_stream_params * input_stream_params,
                  cubeb_devid output_device,
                  cubeb_stream_params * output_stream_params,
                  unsigned int latency_frames,
                  cubeb_data_callback data_callback,
                  cubeb_state_callback state_callback, void * user_ptr)
{
  cubeb_stream * stm;
  pa_buffer_attr battr;
  int r;

  assert(context);

  // If the connection failed for some reason, try to reconnect
  if (context->error == 1 && pulse_context_init(context) != 0) {
    return CUBEB_ERROR;
  }

  *stream = NULL;

  stm = calloc(1, sizeof(*stm));
  assert(stm);

  stm->context = context;
  stm->data_callback = data_callback;
  stm->state_callback = state_callback;
  stm->user_ptr = user_ptr;
  stm->volume = PULSE_NO_GAIN;
  stm->state = -1;
  assert(stm->shutdown == 0);

  WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
  if (output_stream_params) {
    r = create_pa_stream(stm, &stm->output_stream, output_stream_params,
                         stream_name);
    if (r != CUBEB_OK) {
      WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
      pulse_stream_destroy(stm);
      return r;
    }

    stm->output_sample_spec =
        *(WRAP(pa_stream_get_sample_spec)(stm->output_stream));

    WRAP(pa_stream_set_state_callback)
    (stm->output_stream, stream_state_callback, stm);
    WRAP(pa_stream_set_write_callback)
    (stm->output_stream, stream_write_callback, stm);

    battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
    WRAP(pa_stream_connect_playback)
    (stm->output_stream, (char const *)output_device, &battr,
     PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
         PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
     NULL, NULL);
  }

  // Set up input stream
  if (input_stream_params) {
    r = create_pa_stream(stm, &stm->input_stream, input_stream_params,
                         stream_name);
    if (r != CUBEB_OK) {
      WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
      pulse_stream_destroy(stm);
      return r;
    }

    stm->input_sample_spec =
        *(WRAP(pa_stream_get_sample_spec)(stm->input_stream));

    WRAP(pa_stream_set_state_callback)
    (stm->input_stream, stream_state_callback, stm);
    WRAP(pa_stream_set_read_callback)
    (stm->input_stream, stream_read_callback, stm);

    battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
    WRAP(pa_stream_connect_record)
    (stm->input_stream, (char const *)input_device, &battr,
     PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
         PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
  }

  r = wait_until_stream_ready(stm);
  if (r == 0) {
    /* force a timing update now, otherwise timing info does not become valid
       until some point after initialization has completed. */

    r = stream_update_timing_info(stm);
  }

  WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);

  if (r != 0) {
    pulse_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  if (cubeb_log_get_level()) {
    if (output_stream_params) {
      const pa_buffer_attr * output_att;
      output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
      LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, "
          "minreq %u, fragsize %u",
          output_att->maxlength, output_att->tlength, output_att->prebuf,
          output_att->minreq, output_att->fragsize);
    }

    if (input_stream_params) {
      const pa_buffer_attr * input_att;
      input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
      LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq "
          "%u, fragsize %u",
          input_att->maxlength, input_att->tlength, input_att->prebuf,
          input_att->minreq, input_att->fragsize);
    }
  }

  *stream = stm;
  LOG("Cubeb stream (%p) init successful.", *stream);

  return CUBEB_OK;
}

static void
pulse_stream_destroy(cubeb_stream * stm)
{
  stream_cork(stm, CORK);

  WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
  if (stm->output_stream) {

    if (stm->drain_timer) {
      /* there's no pa_rttime_free, so use this instead. */
      WRAP(pa_threaded_mainloop_get_api)
      (stm->context->mainloop)->time_free(stm->drain_timer);
    }

    WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
    WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL);
    WRAP(pa_stream_disconnect)(stm->output_stream);
    WRAP(pa_stream_unref)(stm->output_stream);
  }

  if (stm->input_stream) {
    WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL);
    WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL);
    WRAP(pa_stream_disconnect)(stm->input_stream);
    WRAP(pa_stream_unref)(stm->input_stream);
  }
  WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);

  LOG("Cubeb stream (%p) destroyed successfully.", stm);
  free(stm);
}

static void
pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
{
  (void)a;
  cubeb_stream * stm = userdata;
  if (stm->shutdown) {
    return;
  }
  size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
  trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
}

static int
pulse_stream_start(cubeb_stream * stm)
{
  stm->shutdown = 0;
  stream_cork(stm, UNCORK | NOTIFY);

  if (stm->output_stream && !stm->input_stream) {
    /* On output only case need to manually call user cb once in order to make
     * things roll. This is done via a defer event in order to execute it
     * from PA server thread. */

    WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
    WRAP(pa_mainloop_api_once)
    (WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
     pulse_defer_event_cb, stm);
    WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
  }

  LOG("Cubeb stream (%p) started successfully.", stm);
  return CUBEB_OK;
}

static int
pulse_stream_stop(cubeb_stream * stm)
{
  WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
  stm->shutdown = 1;
  // If draining is taking place wait to finish
  while (stm->drain_timer) {
    WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
  }
  WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);

  stream_cork(stm, CORK | NOTIFY);
  LOG("Cubeb stream (%p) stopped successfully.", stm);
  return CUBEB_OK;
}

static int
pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
  int r, in_thread;
  pa_usec_t r_usec;
  uint64_t bytes;

  if (!stm || !stm->output_stream) {
    return CUBEB_ERROR;
  }

  in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);

  if (!in_thread) {
    WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
  }
  r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec);
  if (!in_thread) {
    WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
  }

  if (r != 0) {
    return CUBEB_ERROR;
  }

  bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec);
  *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec);

  return CUBEB_OK;
}

static int
pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
  pa_usec_t r_usec;
  int negative, r;

  if (!stm || !stm->output_stream) {
    return CUBEB_ERROR;
  }

  r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative);
  assert(!negative);
  if (r) {
    return CUBEB_ERROR;
  }

  *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC;
  return CUBEB_OK;
}

static void
volume_success(pa_context * c, int success, void * userdata)
{
  (void)success;
  (void)c;
  cubeb_stream * stream = userdata;
  assert(success);
  WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
}

static void
rename_success(pa_stream * s, int success, void * userdata)
{
  cubeb_stream * stream = userdata;
  assert(success);
  WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
}

static int
pulse_stream_set_volume(cubeb_stream * stm, float volume)
{
  uint32_t index;
  pa_operation * op;
  pa_volume_t vol;
  pa_cvolume cvol;
  const pa_sample_spec * ss;
  cubeb * ctx;

  if (!stm->output_stream) {
    return CUBEB_ERROR;
  }

  WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);

  /* if the pulse daemon is configured to use flat volumes,
   * apply our own gain instead of changing the input volume on the sink. */

  ctx = stm->context;
  if (ctx->default_sink_info &&
      (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) {
    stm->volume = volume;
  } else {
    ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);

    vol = WRAP(pa_sw_volume_from_linear)(volume);
    WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);

    index = WRAP(pa_stream_get_index)(stm->output_stream);

    op = WRAP(pa_context_set_sink_input_volume)(ctx->context, index, &cvol,
                                                volume_success, stm);
    if (op) {
      operation_wait(ctx, stm->output_stream, op);
      WRAP(pa_operation_unref)(op);
    }
  }

  WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);

  return CUBEB_OK;
}

static int
pulse_stream_set_name(cubeb_stream * stm, char const * stream_name)
{
  if (!stm || !stm->output_stream) {
    return CUBEB_ERROR;
  }

  WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);

  pa_operation * op = WRAP(pa_stream_set_name)(stm->output_stream, stream_name,
                                               rename_success, stm);

  if (op) {
    operation_wait(stm->context, stm->output_stream, op);
    WRAP(pa_operation_unref)(op);
  }

  WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);

  return CUBEB_OK;
}

typedef struct {
  char * default_sink_name;
  char * default_source_name;

  cubeb_device_info * devinfo;
  uint32_t max;
  uint32_t count;
  cubeb * context;
} pulse_dev_list_data;

static cubeb_device_fmt
pulse_format_to_cubeb_format(pa_sample_format_t format)
{
  switch (format) {
  case PA_SAMPLE_S16LE:
    return CUBEB_DEVICE_FMT_S16LE;
  case PA_SAMPLE_S16BE:
    return CUBEB_DEVICE_FMT_S16BE;
  case PA_SAMPLE_FLOAT32LE:
    return CUBEB_DEVICE_FMT_F32LE;
  case PA_SAMPLE_FLOAT32BE:
    return CUBEB_DEVICE_FMT_F32BE;
  default:
    return CUBEB_DEVICE_FMT_F32NE;
  }
}

static void
pulse_ensure_dev_list_data_list_size(pulse_dev_list_data * list_data)
{
  if (list_data->count == list_data->max) {
    list_data->max += 8;
    list_data->devinfo =
        realloc(list_data->devinfo, sizeof(cubeb_device_info) * list_data->max);
  }
}

static cubeb_device_state
pulse_get_state_from_sink_port(pa_sink_port_info * info)
{
  if (info != NULL) {
#if PA_CHECK_VERSION(2, 0, 0)
    if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO)
      return CUBEB_DEVICE_STATE_UNPLUGGED;
    else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
#endif
      return CUBEB_DEVICE_STATE_ENABLED;
  }

  return CUBEB_DEVICE_STATE_ENABLED;
}

static void
pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, int eol,
                   void * user_data)
{
  pulse_dev_list_data * list_data = user_data;
  cubeb_device_info * devinfo;
  char const * prop = NULL;
  char const * device_id = NULL;

  (void)context;

  if (eol) {
    WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
    return;
  }

  if (info == NULL)
    return;

  device_id = info->name;
  if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
    assert(NULL);
    return;
  }

  pulse_ensure_dev_list_data_list_size(list_data);
  devinfo = &list_data->devinfo[list_data->count];
  memset(devinfo, 0, sizeof(cubeb_device_info));

  devinfo->device_id = device_id;
  devinfo->devid = (cubeb_devid)devinfo->device_id;
  devinfo->friendly_name = strdup(info->description);
  prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
  if (prop)
    devinfo->group_id = strdup(prop);
  prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
  if (prop)
    devinfo->vendor_name = strdup(prop);

  devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
  devinfo->state = pulse_get_state_from_sink_port(info->active_port);
  devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0)
                           ? CUBEB_DEVICE_PREF_ALL
                           : CUBEB_DEVICE_PREF_NONE;

  devinfo->format = CUBEB_DEVICE_FMT_ALL;
  devinfo->default_format =
      pulse_format_to_cubeb_format(info->sample_spec.format);
  devinfo->max_channels = info->channel_map.channels;
  devinfo->min_rate = 1;
  devinfo->max_rate = PA_RATE_MAX;
  devinfo->default_rate = info->sample_spec.rate;

  devinfo->latency_lo = 0;
  devinfo->latency_hi = 0;

  list_data->count += 1;
}

static cubeb_device_state
pulse_get_state_from_source_port(pa_source_port_info * info)
{
  if (info != NULL) {
#if PA_CHECK_VERSION(2, 0, 0)
    if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO)
      return CUBEB_DEVICE_STATE_UNPLUGGED;
    else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
#endif
      return CUBEB_DEVICE_STATE_ENABLED;
  }

  return CUBEB_DEVICE_STATE_ENABLED;
}

static void
pulse_source_info_cb(pa_context * context, const pa_source_info * info, int eol,
                     void * user_data)
{
  pulse_dev_list_data * list_data = user_data;
  cubeb_device_info * devinfo;
  char const * prop = NULL;
  char const * device_id = NULL;

  (void)context;

  if (eol) {
    WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
    return;
  }

  device_id = info->name;
  if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
    assert(NULL);
    return;
  }

  pulse_ensure_dev_list_data_list_size(list_data);
  devinfo = &list_data->devinfo[list_data->count];
  memset(devinfo, 0, sizeof(cubeb_device_info));

  devinfo->device_id = device_id;
  devinfo->devid = (cubeb_devid)devinfo->device_id;
  devinfo->friendly_name = strdup(info->description);
  prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
  if (prop)
    devinfo->group_id = strdup(prop);
  prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
  if (prop)
    devinfo->vendor_name = strdup(prop);

  devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
  devinfo->state = pulse_get_state_from_source_port(info->active_port);
  devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0)
                           ? CUBEB_DEVICE_PREF_ALL
                           : CUBEB_DEVICE_PREF_NONE;

  devinfo->format = CUBEB_DEVICE_FMT_ALL;
  devinfo->default_format =
      pulse_format_to_cubeb_format(info->sample_spec.format);
  devinfo->max_channels = info->channel_map.channels;
  devinfo->min_rate = 1;
  devinfo->max_rate = PA_RATE_MAX;
  devinfo->default_rate = info->sample_spec.rate;

  devinfo->latency_lo = 0;
  devinfo->latency_hi = 0;

  list_data->count += 1;
}

static void
pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
{
  pulse_dev_list_data * list_data = userdata;

  (void)c;

  free(list_data->default_sink_name);
  free(list_data->default_source_name);
  list_data->default_sink_name =
      i->default_sink_name ? strdup(i->default_sink_name) : NULL;
  list_data->default_source_name =
      i->default_source_name ? strdup(i->default_source_name) : NULL;

  WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
}

static int
pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
                        cubeb_device_collection * collection)
{
  pulse_dev_list_data user_data = {NULL, NULL, NULL, 0, 0, context};
  pa_operation * o;

  WRAP(pa_threaded_mainloop_lock)(context->mainloop);

  o = WRAP(pa_context_get_server_info)(context->context, pulse_server_info_cb,
                                       &user_data);
  if (o) {
    operation_wait(context, NULL, o);
    WRAP(pa_operation_unref)(o);
  }

  if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
    o = WRAP(pa_context_get_sink_info_list)(context->context,
                                            pulse_sink_info_cb, &user_data);
    if (o) {
      operation_wait(context, NULL, o);
      WRAP(pa_operation_unref)(o);
    }
  }

  if (type & CUBEB_DEVICE_TYPE_INPUT) {
    o = WRAP(pa_context_get_source_info_list)(context->context,
                                              pulse_source_info_cb, &user_data);
    if (o) {
      operation_wait(context, NULL, o);
      WRAP(pa_operation_unref)(o);
    }
  }

  WRAP(pa_threaded_mainloop_unlock)(context->mainloop);

  collection->device = user_data.devinfo;
  collection->count = user_data.count;

  free(user_data.default_sink_name);
  free(user_data.default_source_name);
  return CUBEB_OK;
}

static int
pulse_device_collection_destroy(cubeb * ctx,
                                cubeb_device_collection * collection)
{
  size_t n;

  for (n = 0; n < collection->count; n++) {
    free((void *)collection->device[n].friendly_name);
    free((void *)collection->device[n].vendor_name);
    free((void *)collection->device[n].group_id);
  }

  free(collection->device);
  return CUBEB_OK;
}

static int
pulse_stream_get_current_device(cubeb_stream * stm,
                                cubeb_device ** const device)
{
#if PA_CHECK_VERSION(0, 9, 8)
  *device = calloc(1, sizeof(cubeb_device));
  if (*device == NULL)
    return CUBEB_ERROR;

  if (stm->input_stream) {
    const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream);
    (*device)->input_name = (name == NULL) ? NULL : strdup(name);
  }

  if (stm->output_stream) {
    const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream);
    (*device)->output_name = (name == NULL) ? NULL : strdup(name);
  }

  return CUBEB_OK;
#else
  return CUBEB_ERROR_NOT_SUPPORTED;
#endif
}

static int
pulse_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
{
  (void)stream;
  free(device->input_name);
  free(device->output_name);
  free(device);
  return CUBEB_OK;
}

static void
pulse_subscribe_callback(pa_context * ctx, pa_subscription_event_type_t t,
                         uint32_t index, void * userdata)
{
  (void)ctx;
  cubeb * context = userdata;

  switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
  case PA_SUBSCRIPTION_EVENT_SERVER:
    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
      LOG("Server changed %d", index);
      WRAP(pa_context_get_server_info)
      (context->context, server_info_callback, context);
    }
    break;
  case PA_SUBSCRIPTION_EVENT_SOURCE:
  case PA_SUBSCRIPTION_EVENT_SINK:

    if (cubeb_log_get_level()) {
      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
              PA_SUBSCRIPTION_EVENT_SOURCE &&
          (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
              PA_SUBSCRIPTION_EVENT_REMOVE) {
        LOG("Removing source index %d", index);
      } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
                     PA_SUBSCRIPTION_EVENT_SOURCE &&
                 (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
                     PA_SUBSCRIPTION_EVENT_NEW) {
        LOG("Adding source index %d", index);
      }
      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
              PA_SUBSCRIPTION_EVENT_SINK &&
          (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
              PA_SUBSCRIPTION_EVENT_REMOVE) {
        LOG("Removing sink index %d", index);
      } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
                     PA_SUBSCRIPTION_EVENT_SINK &&
                 (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
                     PA_SUBSCRIPTION_EVENT_NEW) {
        LOG("Adding sink index %d", index);
      }
    }

    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
        (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
          PA_SUBSCRIPTION_EVENT_SOURCE) {
        context->input_collection_changed_callback(
            context, context->input_collection_changed_user_ptr);
      }
      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
          PA_SUBSCRIPTION_EVENT_SINK) {
        context->output_collection_changed_callback(
            context, context->output_collection_changed_user_ptr);
      }
    }
    break;
  }
}

static void
subscribe_success(pa_context * c, int success, void * userdata)
{
  (void)c;
  cubeb * context = userdata;
  assert(success);
  WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0);
}

static int
pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask)
{
  WRAP(pa_threaded_mainloop_lock)(context->mainloop);

  WRAP(pa_context_set_subscribe_callback)
  (context->context, pulse_subscribe_callback, context);

  pa_operation * o;
  o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success,
                                 context);
  if (o == NULL) {
    WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
    LOG("Context subscribe failed");
    return CUBEB_ERROR;
  }
  operation_wait(context, NULL, o);
  WRAP(pa_operation_unref)(o);

  WRAP(pa_threaded_mainloop_unlock)(context->mainloop);

  return CUBEB_OK;
}

static int
pulse_register_device_collection_changed(
    cubeb * context, cubeb_device_type devtype,
    cubeb_device_collection_changed_callback collection_changed_callback,
    void * user_ptr)
{
  if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
    context->input_collection_changed_callback = collection_changed_callback;
    context->input_collection_changed_user_ptr = user_ptr;
  }
  if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
    context->output_collection_changed_callback = collection_changed_callback;
    context->output_collection_changed_user_ptr = user_ptr;
  }

  pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL;
  if (context->input_collection_changed_callback) {
    /* Input added or removed */
    mask |= PA_SUBSCRIPTION_MASK_SOURCE;
  }
  if (context->output_collection_changed_callback) {
    /* Output added or removed */
    mask |= PA_SUBSCRIPTION_MASK_SINK;
  }
  /* Default device changed, this is always registered in order to update the
   * `default_sink_info` when the default device changes. */

  mask |= PA_SUBSCRIPTION_MASK_SERVER;

  return pulse_subscribe_notifications(context, mask);
}

static struct cubeb_ops const pulse_ops = {
    .init = pulse_init,
    .get_backend_id = pulse_get_backend_id,
    .get_max_channel_count = pulse_get_max_channel_count,
    .get_min_latency = pulse_get_min_latency,
    .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
    .get_supported_input_processing_params = NULL,
    .enumerate_devices = pulse_enumerate_devices,
    .device_collection_destroy = pulse_device_collection_destroy,
    .destroy = pulse_destroy,
    .stream_init = pulse_stream_init,
    .stream_destroy = pulse_stream_destroy,
    .stream_start = pulse_stream_start,
    .stream_stop = pulse_stream_stop,
    .stream_get_position = pulse_stream_get_position,
    .stream_get_latency = pulse_stream_get_latency,
    .stream_get_input_latency = NULL,
    .stream_set_volume = pulse_stream_set_volume,
    .stream_set_name = pulse_stream_set_name,
    .stream_get_current_device = pulse_stream_get_current_device,
    .stream_set_input_mute = NULL,
    .stream_set_input_processing_params = NULL,
    .stream_device_destroy = pulse_stream_device_destroy,
    .stream_register_device_changed_callback = NULL,
    .register_device_collection_changed =
        pulse_register_device_collection_changed};

Messung V0.5
C=93 H=99 G=95

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.