LCOV - code coverage report
Current view: top level - media/libcubeb/src - cubeb_pulse.c (source / functions) Hit Total Coverage
Test: output.info Lines: 0 681 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 59 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright © 2011 Mozilla Foundation
       3             :  *
       4             :  * This program is made available under an ISC-style license.  See the
       5             :  * accompanying file LICENSE for details.
       6             :  */
       7             : #undef NDEBUG
       8             : #include <assert.h>
       9             : #include <dlfcn.h>
      10             : #include <stdlib.h>
      11             : #include <pulse/pulseaudio.h>
      12             : #include <string.h>
      13             : #include "cubeb/cubeb.h"
      14             : #include "cubeb-internal.h"
      15             : #include "cubeb_mixer.h"
      16             : #include "cubeb_utils.h"
      17             : #include <stdio.h>
      18             : 
      19             : #ifdef DISABLE_LIBPULSE_DLOPEN
      20             : #define WRAP(x) x
      21             : #else
      22             : #define WRAP(x) cubeb_##x
      23             : #define LIBPULSE_API_VISIT(X)                   \
      24             :   X(pa_channel_map_can_balance)                 \
      25             :   X(pa_channel_map_init)                        \
      26             :   X(pa_context_connect)                         \
      27             :   X(pa_context_disconnect)                      \
      28             :   X(pa_context_drain)                           \
      29             :   X(pa_context_get_server_info)                 \
      30             :   X(pa_context_get_sink_info_by_name)           \
      31             :   X(pa_context_get_sink_info_list)              \
      32             :   X(pa_context_get_sink_input_info)             \
      33             :   X(pa_context_get_source_info_list)            \
      34             :   X(pa_context_get_state)                       \
      35             :   X(pa_context_new)                             \
      36             :   X(pa_context_rttime_new)                      \
      37             :   X(pa_context_set_sink_input_volume)           \
      38             :   X(pa_context_set_state_callback)              \
      39             :   X(pa_context_unref)                           \
      40             :   X(pa_cvolume_set)                             \
      41             :   X(pa_cvolume_set_balance)                     \
      42             :   X(pa_frame_size)                              \
      43             :   X(pa_operation_get_state)                     \
      44             :   X(pa_operation_unref)                         \
      45             :   X(pa_proplist_gets)                           \
      46             :   X(pa_rtclock_now)                             \
      47             :   X(pa_stream_begin_write)                      \
      48             :   X(pa_stream_cancel_write)                     \
      49             :   X(pa_stream_connect_playback)                 \
      50             :   X(pa_stream_cork)                             \
      51             :   X(pa_stream_disconnect)                       \
      52             :   X(pa_stream_get_channel_map)                  \
      53             :   X(pa_stream_get_index)                        \
      54             :   X(pa_stream_get_latency)                      \
      55             :   X(pa_stream_get_sample_spec)                  \
      56             :   X(pa_stream_get_state)                        \
      57             :   X(pa_stream_get_time)                         \
      58             :   X(pa_stream_new)                              \
      59             :   X(pa_stream_set_state_callback)               \
      60             :   X(pa_stream_set_write_callback)               \
      61             :   X(pa_stream_unref)                            \
      62             :   X(pa_stream_update_timing_info)               \
      63             :   X(pa_stream_write)                            \
      64             :   X(pa_sw_volume_from_linear)                   \
      65             :   X(pa_threaded_mainloop_free)                  \
      66             :   X(pa_threaded_mainloop_get_api)               \
      67             :   X(pa_threaded_mainloop_in_thread)             \
      68             :   X(pa_threaded_mainloop_lock)                  \
      69             :   X(pa_threaded_mainloop_new)                   \
      70             :   X(pa_threaded_mainloop_signal)                \
      71             :   X(pa_threaded_mainloop_start)                 \
      72             :   X(pa_threaded_mainloop_stop)                  \
      73             :   X(pa_threaded_mainloop_unlock)                \
      74             :   X(pa_threaded_mainloop_wait)                  \
      75             :   X(pa_usec_to_bytes)                           \
      76             :   X(pa_stream_set_read_callback)                \
      77             :   X(pa_stream_connect_record)                   \
      78             :   X(pa_stream_readable_size)                    \
      79             :   X(pa_stream_writable_size)                    \
      80             :   X(pa_stream_peek)                             \
      81             :   X(pa_stream_drop)                             \
      82             :   X(pa_stream_get_buffer_attr)                  \
      83             :   X(pa_stream_get_device_name)                  \
      84             :   X(pa_context_set_subscribe_callback)          \
      85             :   X(pa_context_subscribe)                       \
      86             :   X(pa_mainloop_api_once)                       \
      87             : 
      88             : #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
      89             : LIBPULSE_API_VISIT(MAKE_TYPEDEF);
      90             : #undef MAKE_TYPEDEF
      91             : #endif
      92             : 
      93             : static struct cubeb_ops const pulse_ops;
      94             : 
      95             : struct cubeb {
      96             :   struct cubeb_ops const * ops;
      97             :   void * libpulse;
      98             :   pa_threaded_mainloop * mainloop;
      99             :   pa_context * context;
     100             :   pa_sink_info * default_sink_info;
     101             :   char * context_name;
     102             :   int error;
     103             :   cubeb_device_collection_changed_callback collection_changed_callback;
     104             :   void * collection_changed_user_ptr;
     105             : };
     106             : 
     107             : struct cubeb_stream {
     108             :   cubeb * context;
     109             :   pa_stream * output_stream;
     110             :   pa_stream * input_stream;
     111             :   cubeb_data_callback data_callback;
     112             :   cubeb_state_callback state_callback;
     113             :   void * user_ptr;
     114             :   pa_time_event * drain_timer;
     115             :   pa_sample_spec output_sample_spec;
     116             :   pa_sample_spec input_sample_spec;
     117             :   int shutdown;
     118             :   float volume;
     119             :   cubeb_state state;
     120             : };
     121             : 
     122             : static const float PULSE_NO_GAIN = -1.0;
     123             : 
     124             : enum cork_state {
     125             :   UNCORK = 0,
     126             :   CORK = 1 << 0,
     127             :   NOTIFY = 1 << 1
     128             : };
     129             : 
     130             : static void
     131           0 : sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
     132             : {
     133             :   (void)context;
     134           0 :   cubeb * ctx = u;
     135           0 :   if (!eol) {
     136           0 :     free(ctx->default_sink_info);
     137           0 :     ctx->default_sink_info = malloc(sizeof(pa_sink_info));
     138           0 :     memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
     139             :   }
     140           0 :   WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
     141           0 : }
     142             : 
     143             : static void
     144           0 : server_info_callback(pa_context * context, const pa_server_info * info, void * u)
     145             : {
     146             :   pa_operation * o;
     147           0 :   o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
     148           0 :   if (o) {
     149           0 :     WRAP(pa_operation_unref)(o);
     150             :   }
     151           0 : }
     152             : 
     153             : static void
     154           0 : context_state_callback(pa_context * c, void * u)
     155             : {
     156           0 :   cubeb * ctx = u;
     157           0 :   if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
     158           0 :     ctx->error = 1;
     159             :   }
     160           0 :   WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
     161           0 : }
     162             : 
     163             : static void
     164           0 : context_notify_callback(pa_context * c, void * u)
     165             : {
     166             :   (void)c;
     167           0 :   cubeb * ctx = u;
     168           0 :   WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
     169           0 : }
     170             : 
     171             : static void
     172           0 : stream_success_callback(pa_stream * s, int success, void * u)
     173             : {
     174             :   (void)s;
     175             :   (void)success;
     176           0 :   cubeb_stream * stm = u;
     177           0 :   WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
     178           0 : }
     179             : 
     180             : static void
     181           0 : stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
     182             : {
     183           0 :   stm->state = s;
     184           0 :   stm->state_callback(stm, stm->user_ptr, s);
     185           0 : }
     186             : 
     187             : static void
     188           0 : stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
     189             : {
     190             :   (void)a;
     191             :   (void)tv;
     192           0 :   cubeb_stream * stm = u;
     193           0 :   assert(stm->drain_timer == e);
     194           0 :   stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
     195             :   /* there's no pa_rttime_free, so use this instead. */
     196           0 :   a->time_free(stm->drain_timer);
     197           0 :   stm->drain_timer = NULL;
     198           0 :   WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
     199           0 : }
     200             : 
     201             : static void
     202           0 : stream_state_callback(pa_stream * s, void * u)
     203             : {
     204           0 :   cubeb_stream * stm = u;
     205           0 :   if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
     206           0 :     stream_state_change_callback(stm, CUBEB_STATE_ERROR);
     207             :   }
     208           0 :   WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
     209           0 : }
     210             : 
     211             : static void
     212           0 : trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cubeb_stream * stm)
     213             : {
     214             :   void * buffer;
     215             :   size_t size;
     216             :   int r;
     217             :   long got;
     218             :   size_t towrite, read_offset;
     219             :   size_t frame_size;
     220             : 
     221           0 :   frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
     222           0 :   assert(nbytes % frame_size == 0);
     223             : 
     224           0 :   towrite = nbytes;
     225           0 :   read_offset = 0;
     226           0 :   while (towrite) {
     227           0 :     size = towrite;
     228           0 :     r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
     229             :     // Note: this has failed running under rr on occassion - needs investigation.
     230           0 :     assert(r == 0);
     231           0 :     assert(size > 0);
     232           0 :     assert(size % frame_size == 0);
     233             : 
     234           0 :     LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd", size, read_offset);
     235           0 :     got = stm->data_callback(stm, stm->user_ptr, (uint8_t const *)input_data + read_offset, buffer, size / frame_size);
     236           0 :     if (got < 0) {
     237           0 :       WRAP(pa_stream_cancel_write)(s);
     238           0 :       stm->shutdown = 1;
     239           0 :       return;
     240             :     }
     241             :     // If more iterations move offset of read buffer
     242           0 :     if (input_data) {
     243           0 :       size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
     244           0 :       read_offset += (size / frame_size) * in_frame_size;
     245             :     }
     246             : 
     247           0 :     if (stm->volume != PULSE_NO_GAIN) {
     248           0 :       uint32_t samples =  size * stm->output_sample_spec.channels / frame_size ;
     249             : 
     250           0 :       if (stm->output_sample_spec.format == PA_SAMPLE_S16BE ||
     251           0 :           stm->output_sample_spec.format == PA_SAMPLE_S16LE) {
     252           0 :         short * b = buffer;
     253           0 :         for (uint32_t i = 0; i < samples; i++) {
     254           0 :           b[i] *= stm->volume;
     255             :         }
     256             :       } else {
     257           0 :         float * b = buffer;
     258           0 :         for (uint32_t i = 0; i < samples; i++) {
     259           0 :           b[i] *= stm->volume;
     260             :         }
     261             :       }
     262             :     }
     263             : 
     264           0 :     r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
     265           0 :     assert(r == 0);
     266             : 
     267           0 :     if ((size_t) got < size / frame_size) {
     268           0 :       pa_usec_t latency = 0;
     269           0 :       r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
     270           0 :       if (r == -PA_ERR_NODATA) {
     271             :         /* this needs a better guess. */
     272           0 :         latency = 100 * PA_USEC_PER_MSEC;
     273             :       }
     274           0 :       assert(r == 0 || r == -PA_ERR_NODATA);
     275             :       /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
     276             :       /* arbitrary safety margin: double the current latency. */
     277           0 :       assert(!stm->drain_timer);
     278           0 :       stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
     279           0 :       stm->shutdown = 1;
     280           0 :       return;
     281             :     }
     282             : 
     283           0 :     towrite -= size;
     284             :   }
     285             : 
     286           0 :   assert(towrite == 0);
     287             : }
     288             : 
     289             : static int
     290           0 : read_from_input(pa_stream * s, void const ** buffer, size_t * size)
     291             : {
     292           0 :   size_t readable_size = WRAP(pa_stream_readable_size)(s);
     293           0 :   if (readable_size > 0) {
     294           0 :     if (WRAP(pa_stream_peek)(s, buffer, size) < 0) {
     295           0 :       return -1;
     296             :     }
     297             :   }
     298           0 :   return readable_size;
     299             : }
     300             : 
     301             : static void
     302           0 : stream_write_callback(pa_stream * s, size_t nbytes, void * u)
     303             : {
     304           0 :   LOGV("Output callback to be written buffer size %zd", nbytes);
     305           0 :   cubeb_stream * stm = u;
     306           0 :   if (stm->shutdown ||
     307           0 :       stm->state != CUBEB_STATE_STARTED) {
     308           0 :     return;
     309             :   }
     310             : 
     311           0 :   if (!stm->input_stream){
     312             :     // Output/playback only operation.
     313             :     // Write directly to output
     314           0 :     assert(!stm->input_stream && stm->output_stream);
     315           0 :     trigger_user_callback(s, NULL, nbytes, stm);
     316             :   }
     317             : }
     318             : 
     319             : static void
     320           0 : stream_read_callback(pa_stream * s, size_t nbytes, void * u)
     321             : {
     322           0 :   LOGV("Input callback buffer size %zd", nbytes);
     323           0 :   cubeb_stream * stm = u;
     324           0 :   if (stm->shutdown) {
     325           0 :     return;
     326             :   }
     327             : 
     328           0 :   void const * read_data = NULL;
     329             :   size_t read_size;
     330           0 :   while (read_from_input(s, &read_data, &read_size) > 0) {
     331             :     /* read_data can be NULL in case of a hole. */
     332           0 :     if (read_data) {
     333           0 :       size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
     334           0 :       size_t read_frames = read_size / in_frame_size;
     335             : 
     336           0 :       if (stm->output_stream) {
     337             :         // input/capture + output/playback operation
     338           0 :         size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
     339           0 :         size_t write_size = read_frames * out_frame_size;
     340             :         // Offer full duplex data for writing
     341           0 :         trigger_user_callback(stm->output_stream, read_data, write_size, stm);
     342             :       } else {
     343             :         // input/capture only operation. Call callback directly
     344           0 :         long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL, read_frames);
     345           0 :         if (got < 0 || (size_t) got != read_frames) {
     346           0 :           WRAP(pa_stream_cancel_write)(s);
     347           0 :           stm->shutdown = 1;
     348           0 :           break;
     349             :         }
     350             :       }
     351             :     }
     352           0 :     if (read_size > 0) {
     353           0 :       WRAP(pa_stream_drop)(s);
     354             :     }
     355             : 
     356           0 :     if (stm->shutdown) {
     357           0 :       return;
     358             :     }
     359             :   }
     360             : }
     361             : 
     362             : static int
     363           0 : wait_until_context_ready(cubeb * ctx)
     364             : {
     365           0 :   for (;;) {
     366           0 :     pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
     367           0 :     if (!PA_CONTEXT_IS_GOOD(state))
     368           0 :       return -1;
     369           0 :     if (state == PA_CONTEXT_READY)
     370           0 :       break;
     371           0 :     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
     372             :   }
     373           0 :   return 0;
     374             : }
     375             : 
     376             : static int
     377           0 : wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop)
     378             : {
     379           0 :   if (!stream || !mainloop){
     380           0 :     return -1;
     381             :   }
     382           0 :   for (;;) {
     383           0 :     pa_stream_state_t state = WRAP(pa_stream_get_state)(stream);
     384           0 :     if (!PA_STREAM_IS_GOOD(state))
     385           0 :       return -1;
     386           0 :     if (state == PA_STREAM_READY)
     387           0 :       break;
     388           0 :     WRAP(pa_threaded_mainloop_wait)(mainloop);
     389             :   }
     390           0 :   return 0;
     391             : }
     392             : 
     393             : static int
     394           0 : wait_until_stream_ready(cubeb_stream * stm)
     395             : {
     396           0 :   if (stm->output_stream &&
     397           0 :       wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) == -1) {
     398           0 :     return -1;
     399             :   }
     400           0 :   if(stm->input_stream &&
     401           0 :      wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) == -1) {
     402           0 :     return -1;
     403             :   }
     404           0 :   return 0;
     405             : }
     406             : 
     407             : static int
     408           0 : operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
     409             : {
     410           0 :   while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
     411           0 :     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
     412           0 :     if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
     413           0 :       return -1;
     414             :     }
     415           0 :     if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
     416           0 :       return -1;
     417             :     }
     418             :   }
     419           0 :   return 0;
     420             : }
     421             : 
     422             : static void
     423           0 : cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
     424             : {
     425             :   pa_operation * o;
     426           0 :   if (!io_stream) {
     427           0 :     return;
     428             :   }
     429           0 :   o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback, stm);
     430           0 :   if (o) {
     431           0 :     operation_wait(stm->context, io_stream, o);
     432           0 :     WRAP(pa_operation_unref)(o);
     433             :   }
     434             : }
     435             : 
     436             : static void
     437           0 : stream_cork(cubeb_stream * stm, enum cork_state state)
     438             : {
     439           0 :   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
     440           0 :   cork_io_stream(stm, stm->output_stream, state);
     441           0 :   cork_io_stream(stm, stm->input_stream, state);
     442           0 :   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     443             : 
     444           0 :   if (state & NOTIFY) {
     445           0 :     stream_state_change_callback(stm, state & CORK ? CUBEB_STATE_STOPPED
     446             :                                                    : CUBEB_STATE_STARTED);
     447             :   }
     448           0 : }
     449             : 
     450             : static int
     451           0 : stream_update_timing_info(cubeb_stream * stm)
     452             : {
     453           0 :   int r = -1;
     454           0 :   pa_operation * o = NULL;
     455           0 :   if (stm->output_stream) {
     456           0 :     o = WRAP(pa_stream_update_timing_info)(stm->output_stream, stream_success_callback, stm);
     457           0 :     if (o) {
     458           0 :       r = operation_wait(stm->context, stm->output_stream, o);
     459           0 :       WRAP(pa_operation_unref)(o);
     460             :     }
     461           0 :     if (r != 0) {
     462           0 :       return r;
     463             :     }
     464             :   }
     465             : 
     466           0 :   if (stm->input_stream) {
     467           0 :     o = WRAP(pa_stream_update_timing_info)(stm->input_stream, stream_success_callback, stm);
     468           0 :     if (o) {
     469           0 :       r = operation_wait(stm->context, stm->input_stream, o);
     470           0 :       WRAP(pa_operation_unref)(o);
     471             :     }
     472             :   }
     473             : 
     474           0 :   return r;
     475             : }
     476             : 
     477             : static pa_channel_position_t
     478           0 : cubeb_channel_to_pa_channel(cubeb_channel channel)
     479             : {
     480           0 :   assert(channel != CHANNEL_INVALID);
     481             : 
     482             :   // This variable may be used for multiple times, so we should avoid to
     483             :   // allocate it in stack, or it will be created and removed repeatedly.
     484             :   // Use static to allocate this local variable in data space instead of stack.
     485             :   static pa_channel_position_t map[CHANNEL_MAX] = {
     486             :     // PA_CHANNEL_POSITION_INVALID,      // CHANNEL_INVALID
     487             :     PA_CHANNEL_POSITION_MONO,         // CHANNEL_MONO
     488             :     PA_CHANNEL_POSITION_FRONT_LEFT,   // CHANNEL_LEFT
     489             :     PA_CHANNEL_POSITION_FRONT_RIGHT,  // CHANNEL_RIGHT
     490             :     PA_CHANNEL_POSITION_FRONT_CENTER, // CHANNEL_CENTER
     491             :     PA_CHANNEL_POSITION_SIDE_LEFT,    // CHANNEL_LS
     492             :     PA_CHANNEL_POSITION_SIDE_RIGHT,   // CHANNEL_RS
     493             :     PA_CHANNEL_POSITION_REAR_LEFT,    // CHANNEL_RLS
     494             :     PA_CHANNEL_POSITION_REAR_CENTER,  // CHANNEL_RCENTER
     495             :     PA_CHANNEL_POSITION_REAR_RIGHT,   // CHANNEL_RRS
     496             :     PA_CHANNEL_POSITION_LFE           // CHANNEL_LFE
     497             :   };
     498             : 
     499           0 :   return map[channel];
     500             : }
     501             : 
     502             : static cubeb_channel
     503           0 : pa_channel_to_cubeb_channel(pa_channel_position_t channel)
     504             : {
     505           0 :   assert(channel != PA_CHANNEL_POSITION_INVALID);
     506           0 :   switch(channel) {
     507           0 :     case PA_CHANNEL_POSITION_MONO: return CHANNEL_MONO;
     508           0 :     case PA_CHANNEL_POSITION_FRONT_LEFT: return CHANNEL_LEFT;
     509           0 :     case PA_CHANNEL_POSITION_FRONT_RIGHT: return CHANNEL_RIGHT;
     510           0 :     case PA_CHANNEL_POSITION_FRONT_CENTER: return CHANNEL_CENTER;
     511           0 :     case PA_CHANNEL_POSITION_SIDE_LEFT: return CHANNEL_LS;
     512           0 :     case PA_CHANNEL_POSITION_SIDE_RIGHT: return CHANNEL_RS;
     513           0 :     case PA_CHANNEL_POSITION_REAR_LEFT: return CHANNEL_RLS;
     514           0 :     case PA_CHANNEL_POSITION_REAR_CENTER: return CHANNEL_RCENTER;
     515           0 :     case PA_CHANNEL_POSITION_REAR_RIGHT: return CHANNEL_RRS;
     516           0 :     case PA_CHANNEL_POSITION_LFE: return CHANNEL_LFE;
     517           0 :     default: return CHANNEL_INVALID;
     518             :   }
     519             : }
     520             : 
     521             : static void
     522           0 : layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
     523             : {
     524           0 :   assert(cm && layout != CUBEB_LAYOUT_UNDEFINED);
     525             : 
     526           0 :   WRAP(pa_channel_map_init)(cm);
     527           0 :   cm->channels = CUBEB_CHANNEL_LAYOUT_MAPS[layout].channels;
     528           0 :   for (uint8_t i = 0 ; i < cm->channels ; ++i) {
     529           0 :     cm->map[i] = cubeb_channel_to_pa_channel(CHANNEL_INDEX_TO_ORDER[layout][i]);
     530             :   }
     531           0 : }
     532             : 
     533             : static cubeb_channel_layout
     534           0 : channel_map_to_layout(pa_channel_map * cm)
     535             : {
     536             :   cubeb_channel_map cubeb_map;
     537           0 :   cubeb_map.channels = cm->channels;
     538           0 :   for (uint32_t i = 0 ; i < cm->channels ; ++i) {
     539           0 :     cubeb_map.map[i] = pa_channel_to_cubeb_channel(cm->map[i]);
     540             :   }
     541           0 :   return cubeb_channel_map_to_layout(&cubeb_map);
     542             : }
     543             : 
     544             : static void pulse_context_destroy(cubeb * ctx);
     545             : static void pulse_destroy(cubeb * ctx);
     546             : 
     547             : static int
     548           0 : pulse_context_init(cubeb * ctx)
     549             : {
     550           0 :   if (ctx->context) {
     551           0 :     assert(ctx->error == 1);
     552           0 :     pulse_context_destroy(ctx);
     553             :   }
     554             : 
     555           0 :   ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
     556           0 :                                       ctx->context_name);
     557           0 :   if (!ctx->context) {
     558           0 :     return -1;
     559             :   }
     560           0 :   WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
     561             : 
     562           0 :   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
     563           0 :   WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
     564             : 
     565           0 :   if (wait_until_context_ready(ctx) != 0) {
     566           0 :     WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
     567           0 :     pulse_context_destroy(ctx);
     568           0 :     ctx->context = NULL;
     569           0 :     return -1;
     570             :   }
     571             : 
     572           0 :   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
     573             : 
     574           0 :   ctx->error = 0;
     575             : 
     576           0 :   return 0;
     577             : }
     578             : 
     579             : /*static*/ int
     580           0 : pulse_init(cubeb ** context, char const * context_name)
     581             : {
     582           0 :   void * libpulse = NULL;
     583             :   cubeb * ctx;
     584             :   pa_operation * o;
     585             : 
     586           0 :   *context = NULL;
     587             : 
     588             : #ifndef DISABLE_LIBPULSE_DLOPEN
     589           0 :   libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
     590           0 :   if (!libpulse) {
     591           0 :     return CUBEB_ERROR;
     592             :   }
     593             : 
     594             : #define LOAD(x) {                               \
     595             :     cubeb_##x = dlsym(libpulse, #x);            \
     596             :     if (!cubeb_##x) {                           \
     597             :       dlclose(libpulse);                        \
     598             :       return CUBEB_ERROR;                       \
     599             :     }                                           \
     600             :   }
     601             : 
     602           0 :   LIBPULSE_API_VISIT(LOAD);
     603             : #undef LOAD
     604             : #endif
     605             : 
     606           0 :   ctx = calloc(1, sizeof(*ctx));
     607           0 :   assert(ctx);
     608             : 
     609           0 :   ctx->ops = &pulse_ops;
     610           0 :   ctx->libpulse = libpulse;
     611             : 
     612           0 :   ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
     613           0 :   ctx->default_sink_info = NULL;
     614             : 
     615           0 :   WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
     616             : 
     617           0 :   ctx->context_name = context_name ? strdup(context_name) : NULL;
     618           0 :   if (pulse_context_init(ctx) != 0) {
     619           0 :     pulse_destroy(ctx);
     620           0 :     return CUBEB_ERROR;
     621             :   }
     622             : 
     623             :   /* server_info_callback performs a second async query, which is
     624             :      responsible for initializing default_sink_info and signalling the
     625             :      mainloop to end the wait. */
     626           0 :   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
     627           0 :   o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
     628           0 :   if (o) {
     629           0 :     operation_wait(ctx, NULL, o);
     630           0 :     WRAP(pa_operation_unref)(o);
     631             :   }
     632           0 :   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
     633           0 :   assert(ctx->default_sink_info);
     634             : 
     635           0 :   *context = ctx;
     636             : 
     637           0 :   return CUBEB_OK;
     638             : }
     639             : 
     640             : static char const *
     641           0 : pulse_get_backend_id(cubeb * ctx)
     642             : {
     643             :   (void)ctx;
     644           0 :   return "pulse";
     645             : }
     646             : 
     647             : static int
     648           0 : pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
     649             : {
     650             :   (void)ctx;
     651           0 :   assert(ctx && max_channels);
     652             : 
     653           0 :   *max_channels = ctx->default_sink_info->channel_map.channels;
     654             : 
     655           0 :   return CUBEB_OK;
     656             : }
     657             : 
     658             : static int
     659           0 : pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
     660             : {
     661           0 :   assert(ctx && rate);
     662             :   (void)ctx;
     663             : 
     664           0 :   *rate = ctx->default_sink_info->sample_spec.rate;
     665             : 
     666           0 :   return CUBEB_OK;
     667             : }
     668             : 
     669             : static int
     670           0 : pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout)
     671             : {
     672           0 :   assert(ctx && layout);
     673             :   (void)ctx;
     674             : 
     675           0 :   *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map);
     676             : 
     677           0 :   return CUBEB_OK;
     678             : }
     679             : 
     680             : static int
     681           0 : pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
     682             : {
     683             :   (void)ctx;
     684             :   // According to PulseAudio developers, this is a safe minimum.
     685           0 :   *latency_frames = 25 * params.rate / 1000;
     686             : 
     687           0 :   return CUBEB_OK;
     688             : }
     689             : 
     690             : static void
     691           0 : pulse_context_destroy(cubeb * ctx)
     692             : {
     693             :   pa_operation * o;
     694             : 
     695           0 :   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
     696           0 :   o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
     697           0 :   if (o) {
     698           0 :     operation_wait(ctx, NULL, o);
     699           0 :     WRAP(pa_operation_unref)(o);
     700             :   }
     701           0 :   WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
     702           0 :   WRAP(pa_context_disconnect)(ctx->context);
     703           0 :   WRAP(pa_context_unref)(ctx->context);
     704           0 :   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
     705           0 : }
     706             : 
     707             : static void
     708           0 : pulse_destroy(cubeb * ctx)
     709             : {
     710           0 :   free(ctx->context_name);
     711           0 :   if (ctx->context) {
     712           0 :     pulse_context_destroy(ctx);
     713             :   }
     714             : 
     715           0 :   if (ctx->mainloop) {
     716           0 :     WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
     717           0 :     WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
     718             :   }
     719             : 
     720           0 :   if (ctx->libpulse) {
     721           0 :     dlclose(ctx->libpulse);
     722             :   }
     723           0 :   free(ctx->default_sink_info);
     724           0 :   free(ctx);
     725           0 : }
     726             : 
     727             : static void pulse_stream_destroy(cubeb_stream * stm);
     728             : 
     729             : static pa_sample_format_t
     730           0 : to_pulse_format(cubeb_sample_format format)
     731             : {
     732           0 :   switch (format) {
     733             :   case CUBEB_SAMPLE_S16LE:
     734           0 :     return PA_SAMPLE_S16LE;
     735             :   case CUBEB_SAMPLE_S16BE:
     736           0 :     return PA_SAMPLE_S16BE;
     737             :   case CUBEB_SAMPLE_FLOAT32LE:
     738           0 :     return PA_SAMPLE_FLOAT32LE;
     739             :   case CUBEB_SAMPLE_FLOAT32BE:
     740           0 :     return PA_SAMPLE_FLOAT32BE;
     741             :   default:
     742           0 :     return PA_SAMPLE_INVALID;
     743             :   }
     744             : }
     745             : 
     746             : static int
     747           0 : create_pa_stream(cubeb_stream * stm,
     748             :                  pa_stream ** pa_stm,
     749             :                  cubeb_stream_params * stream_params,
     750             :                  char const * stream_name)
     751             : {
     752           0 :   assert(stm && stream_params);
     753           0 :   assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm &&
     754             :          stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
     755             :          CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels));
     756           0 :   *pa_stm = NULL;
     757             :   pa_sample_spec ss;
     758           0 :   ss.format = to_pulse_format(stream_params->format);
     759           0 :   if (ss.format == PA_SAMPLE_INVALID)
     760           0 :     return CUBEB_ERROR_INVALID_FORMAT;
     761           0 :   ss.rate = stream_params->rate;
     762           0 :   ss.channels = stream_params->channels;
     763             : 
     764           0 :   if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) {
     765           0 :     *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
     766             :   } else {
     767             :     pa_channel_map cm;
     768           0 :     layout_to_channel_map(stream_params->layout, &cm);
     769           0 :     *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
     770             :   }
     771           0 :   return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
     772             : }
     773             : 
     774             : static pa_buffer_attr
     775           0 : set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spec)
     776             : {
     777             :   pa_buffer_attr battr;
     778           0 :   battr.maxlength = -1;
     779           0 :   battr.prebuf    = -1;
     780           0 :   battr.tlength   = latency_frames * WRAP(pa_frame_size)(sample_spec);
     781           0 :   battr.minreq    = battr.tlength / 4;
     782           0 :   battr.fragsize  = battr.minreq;
     783             : 
     784           0 :   LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",
     785             :       battr.maxlength, battr.tlength, battr.prebuf, battr.minreq, battr.fragsize);
     786             : 
     787           0 :   return battr;
     788             : }
     789             : 
     790             : static int
     791           0 : pulse_stream_init(cubeb * context,
     792             :                   cubeb_stream ** stream,
     793             :                   char const * stream_name,
     794             :                   cubeb_devid input_device,
     795             :                   cubeb_stream_params * input_stream_params,
     796             :                   cubeb_devid output_device,
     797             :                   cubeb_stream_params * output_stream_params,
     798             :                   unsigned int latency_frames,
     799             :                   cubeb_data_callback data_callback,
     800             :                   cubeb_state_callback state_callback,
     801             :                   void * user_ptr)
     802             : {
     803             :   cubeb_stream * stm;
     804             :   pa_buffer_attr battr;
     805             :   int r;
     806             : 
     807           0 :   assert(context);
     808             : 
     809             :   // If the connection failed for some reason, try to reconnect
     810           0 :   if (context->error == 1 && pulse_context_init(context) != 0) {
     811           0 :     return CUBEB_ERROR;
     812             :   }
     813             : 
     814           0 :   *stream = NULL;
     815             : 
     816           0 :   stm = calloc(1, sizeof(*stm));
     817           0 :   assert(stm);
     818             : 
     819           0 :   stm->context = context;
     820           0 :   stm->data_callback = data_callback;
     821           0 :   stm->state_callback = state_callback;
     822           0 :   stm->user_ptr = user_ptr;
     823           0 :   stm->volume = PULSE_NO_GAIN;
     824           0 :   stm->state = -1;
     825           0 :   assert(stm->shutdown == 0);
     826             : 
     827           0 :   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
     828           0 :   if (output_stream_params) {
     829           0 :     r = create_pa_stream(stm, &stm->output_stream, output_stream_params, stream_name);
     830           0 :     if (r != CUBEB_OK) {
     831           0 :       WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     832           0 :       pulse_stream_destroy(stm);
     833           0 :       return r;
     834             :     }
     835             : 
     836           0 :     stm->output_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->output_stream));
     837             : 
     838           0 :     WRAP(pa_stream_set_state_callback)(stm->output_stream, stream_state_callback, stm);
     839           0 :     WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm);
     840             : 
     841           0 :     battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
     842           0 :     WRAP(pa_stream_connect_playback)(stm->output_stream,
     843             :                                      (char const *) output_device,
     844             :                                      &battr,
     845             :                                      PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
     846             :                                      PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
     847             :                                      NULL, NULL);
     848             :   }
     849             : 
     850             :   // Set up input stream
     851           0 :   if (input_stream_params) {
     852           0 :     r = create_pa_stream(stm, &stm->input_stream, input_stream_params, stream_name);
     853           0 :     if (r != CUBEB_OK) {
     854           0 :       WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     855           0 :       pulse_stream_destroy(stm);
     856           0 :       return r;
     857             :     }
     858             : 
     859           0 :     stm->input_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->input_stream));
     860             : 
     861           0 :     WRAP(pa_stream_set_state_callback)(stm->input_stream, stream_state_callback, stm);
     862           0 :     WRAP(pa_stream_set_read_callback)(stm->input_stream, stream_read_callback, stm);
     863             : 
     864           0 :     battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
     865           0 :     WRAP(pa_stream_connect_record)(stm->input_stream,
     866             :                                    (char const *) input_device,
     867             :                                    &battr,
     868             :                                    PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
     869             :                                    PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
     870             :   }
     871             : 
     872           0 :   r = wait_until_stream_ready(stm);
     873           0 :   if (r == 0) {
     874             :     /* force a timing update now, otherwise timing info does not become valid
     875             :        until some point after initialization has completed. */
     876           0 :     r = stream_update_timing_info(stm);
     877             :   }
     878             : 
     879           0 :   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     880             : 
     881           0 :   if (r != 0) {
     882           0 :     pulse_stream_destroy(stm);
     883           0 :     return CUBEB_ERROR;
     884             :   }
     885             : 
     886           0 :   if (g_cubeb_log_level) {
     887           0 :     if (output_stream_params){
     888             :       const pa_buffer_attr * output_att;
     889           0 :       output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
     890           0 :       LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",output_att->maxlength, output_att->tlength,
     891             :           output_att->prebuf, output_att->minreq, output_att->fragsize);
     892             :     }
     893             : 
     894           0 :     if (input_stream_params){
     895             :       const pa_buffer_attr * input_att;
     896           0 :       input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
     897           0 :       LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",input_att->maxlength, input_att->tlength,
     898             :           input_att->prebuf, input_att->minreq, input_att->fragsize);
     899             :     }
     900             :   }
     901             : 
     902           0 :   *stream = stm;
     903             : 
     904           0 :   return CUBEB_OK;
     905             : }
     906             : 
     907             : static void
     908           0 : pulse_stream_destroy(cubeb_stream * stm)
     909             : {
     910           0 :   stream_cork(stm, CORK);
     911             : 
     912           0 :   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
     913           0 :   if (stm->output_stream) {
     914             : 
     915           0 :     if (stm->drain_timer) {
     916             :       /* there's no pa_rttime_free, so use this instead. */
     917           0 :       WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
     918             :     }
     919             : 
     920           0 :     WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
     921           0 :     WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL);
     922           0 :     WRAP(pa_stream_disconnect)(stm->output_stream);
     923           0 :     WRAP(pa_stream_unref)(stm->output_stream);
     924             :   }
     925             : 
     926           0 :   if (stm->input_stream) {
     927           0 :     WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL);
     928           0 :     WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL);
     929           0 :     WRAP(pa_stream_disconnect)(stm->input_stream);
     930           0 :     WRAP(pa_stream_unref)(stm->input_stream);
     931             :   }
     932           0 :   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     933             : 
     934           0 :   free(stm);
     935           0 : }
     936             : 
     937             : static void
     938           0 : pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
     939             : {
     940             :   (void)a;
     941           0 :   cubeb_stream * stm = userdata;
     942           0 :   if (stm->shutdown) {
     943           0 :     return;
     944             :   }
     945           0 :   size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
     946           0 :   trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
     947             : }
     948             : 
     949             : static int
     950           0 : pulse_stream_start(cubeb_stream * stm)
     951             : {
     952           0 :   stm->shutdown = 0;
     953           0 :   stream_cork(stm, UNCORK | NOTIFY);
     954             : 
     955           0 :   if (stm->output_stream && !stm->input_stream) {
     956             :     /* On output only case need to manually call user cb once in order to make
     957             :      * things roll. This is done via a defer event in order to execute it
     958             :      * from PA server thread. */
     959           0 :     WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
     960           0 :     WRAP(pa_mainloop_api_once)(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
     961             :                                pulse_defer_event_cb, stm);
     962           0 :     WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     963             :   }
     964             : 
     965           0 :   return CUBEB_OK;
     966             : }
     967             : 
     968             : static int
     969           0 : pulse_stream_stop(cubeb_stream * stm)
     970             : {
     971           0 :   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
     972           0 :   stm->shutdown = 1;
     973             :   // If draining is taking place wait to finish
     974           0 :   while (stm->drain_timer) {
     975           0 :     WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
     976             :   }
     977           0 :   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
     978             : 
     979           0 :   stream_cork(stm, CORK | NOTIFY);
     980           0 :   return CUBEB_OK;
     981             : }
     982             : 
     983             : static int
     984           0 : pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
     985             : {
     986             :   int r, in_thread;
     987             :   pa_usec_t r_usec;
     988             :   uint64_t bytes;
     989             : 
     990           0 :   if (!stm || !stm->output_stream) {
     991           0 :     return CUBEB_ERROR;
     992             :   }
     993             : 
     994           0 :   in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
     995             : 
     996           0 :   if (!in_thread) {
     997           0 :     WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
     998             :   }
     999           0 :   r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec);
    1000           0 :   if (!in_thread) {
    1001           0 :     WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
    1002             :   }
    1003             : 
    1004           0 :   if (r != 0) {
    1005           0 :     return CUBEB_ERROR;
    1006             :   }
    1007             : 
    1008           0 :   bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec);
    1009           0 :   *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec);
    1010             : 
    1011           0 :   return CUBEB_OK;
    1012             : }
    1013             : 
    1014             : static int
    1015           0 : pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
    1016             : {
    1017             :   pa_usec_t r_usec;
    1018             :   int negative, r;
    1019             : 
    1020           0 :   if (!stm || !stm->output_stream) {
    1021           0 :     return CUBEB_ERROR;
    1022             :   }
    1023             : 
    1024           0 :   r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative);
    1025           0 :   assert(!negative);
    1026           0 :   if (r) {
    1027           0 :     return CUBEB_ERROR;
    1028             :   }
    1029             : 
    1030           0 :   *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC;
    1031           0 :   return CUBEB_OK;
    1032             : }
    1033             : 
    1034             : static void
    1035           0 : volume_success(pa_context *c, int success, void *userdata)
    1036             : {
    1037             :   (void)success;
    1038             :   (void)c;
    1039           0 :   cubeb_stream * stream = userdata;
    1040           0 :   assert(success);
    1041           0 :   WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
    1042           0 : }
    1043             : 
    1044             : static int
    1045           0 : pulse_stream_set_volume(cubeb_stream * stm, float volume)
    1046             : {
    1047             :   uint32_t index;
    1048             :   pa_operation * op;
    1049             :   pa_volume_t vol;
    1050             :   pa_cvolume cvol;
    1051             :   const pa_sample_spec * ss;
    1052             : 
    1053           0 :   if (!stm->output_stream) {
    1054           0 :     return CUBEB_ERROR;
    1055             :   }
    1056             : 
    1057           0 :   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
    1058             : 
    1059             :   /* if the pulse daemon is configured to use flat volumes,
    1060             :    * apply our own gain instead of changing the input volume on the sink. */
    1061           0 :   if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) {
    1062           0 :     stm->volume = volume;
    1063             :   } else {
    1064           0 :     ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
    1065             : 
    1066           0 :     vol = WRAP(pa_sw_volume_from_linear)(volume);
    1067           0 :     WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
    1068             : 
    1069           0 :     index = WRAP(pa_stream_get_index)(stm->output_stream);
    1070             : 
    1071           0 :     op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
    1072             :                                                 index, &cvol, volume_success,
    1073             :                                                 stm);
    1074           0 :     if (op) {
    1075           0 :       operation_wait(stm->context, stm->output_stream, op);
    1076           0 :       WRAP(pa_operation_unref)(op);
    1077             :     }
    1078             :   }
    1079             : 
    1080           0 :   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
    1081             : 
    1082           0 :   return CUBEB_OK;
    1083             : }
    1084             : 
    1085             : struct sink_input_info_result {
    1086             :   pa_cvolume * cvol;
    1087             :   pa_threaded_mainloop * mainloop;
    1088             : };
    1089             : 
    1090             : static void
    1091           0 : sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u)
    1092             : {
    1093           0 :   struct sink_input_info_result * r = u;
    1094           0 :   if (!eol) {
    1095           0 :     *r->cvol = i->volume;
    1096             :   }
    1097           0 :   WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0);
    1098           0 : }
    1099             : 
    1100             : static int
    1101           0 : pulse_stream_set_panning(cubeb_stream * stm, float panning)
    1102             : {
    1103             :   const pa_channel_map * map;
    1104             :   pa_cvolume cvol;
    1105             :   uint32_t index;
    1106             :   pa_operation * op;
    1107             : 
    1108           0 :   if (!stm->output_stream) {
    1109           0 :     return CUBEB_ERROR;
    1110             :   }
    1111             : 
    1112           0 :   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
    1113             : 
    1114           0 :   map = WRAP(pa_stream_get_channel_map)(stm->output_stream);
    1115           0 :   if (!WRAP(pa_channel_map_can_balance)(map)) {
    1116           0 :     WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
    1117           0 :     return CUBEB_ERROR;
    1118             :   }
    1119             : 
    1120           0 :   index = WRAP(pa_stream_get_index)(stm->output_stream);
    1121             : 
    1122           0 :   struct sink_input_info_result r = { &cvol, stm->context->mainloop };
    1123           0 :   op = WRAP(pa_context_get_sink_input_info)(stm->context->context,
    1124             :                                             index,
    1125             :                                             sink_input_info_cb,
    1126             :                                             &r);
    1127           0 :   if (op) {
    1128           0 :     operation_wait(stm->context, stm->output_stream, op);
    1129           0 :     WRAP(pa_operation_unref)(op);
    1130             :   }
    1131             : 
    1132           0 :   WRAP(pa_cvolume_set_balance)(&cvol, map, panning);
    1133             : 
    1134           0 :   op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
    1135             :                                               index, &cvol, volume_success,
    1136             :                                               stm);
    1137           0 :   if (op) {
    1138           0 :     operation_wait(stm->context, stm->output_stream, op);
    1139           0 :     WRAP(pa_operation_unref)(op);
    1140             :   }
    1141             : 
    1142           0 :   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
    1143             : 
    1144           0 :   return CUBEB_OK;
    1145             : }
    1146             : 
    1147             : typedef struct {
    1148             :   char * default_sink_name;
    1149             :   char * default_source_name;
    1150             : 
    1151             :   cubeb_device_info * devinfo;
    1152             :   uint32_t max;
    1153             :   uint32_t count;
    1154             :   cubeb * context;
    1155             : } pulse_dev_list_data;
    1156             : 
    1157             : static cubeb_device_fmt
    1158           0 : pulse_format_to_cubeb_format(pa_sample_format_t format)
    1159             : {
    1160           0 :   switch (format) {
    1161             :   case PA_SAMPLE_S16LE:
    1162           0 :     return CUBEB_DEVICE_FMT_S16LE;
    1163             :   case PA_SAMPLE_S16BE:
    1164           0 :     return CUBEB_DEVICE_FMT_S16BE;
    1165             :   case PA_SAMPLE_FLOAT32LE:
    1166           0 :     return CUBEB_DEVICE_FMT_F32LE;
    1167             :   case PA_SAMPLE_FLOAT32BE:
    1168           0 :     return CUBEB_DEVICE_FMT_F32BE;
    1169             :   default:
    1170           0 :     return CUBEB_DEVICE_FMT_F32NE;
    1171             :   }
    1172             : }
    1173             : 
    1174             : static void
    1175           0 : pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data)
    1176             : {
    1177           0 :   if (list_data->count == list_data->max) {
    1178           0 :     list_data->max += 8;
    1179           0 :     list_data->devinfo = realloc(list_data->devinfo,
    1180           0 :         sizeof(cubeb_device_info) * list_data->max);
    1181             :   }
    1182           0 : }
    1183             : 
    1184             : static cubeb_device_state
    1185           0 : pulse_get_state_from_sink_port(pa_sink_port_info * info)
    1186             : {
    1187           0 :   if (info != NULL) {
    1188             : #if PA_CHECK_VERSION(2, 0, 0)
    1189           0 :     if (info->available == PA_PORT_AVAILABLE_NO)
    1190           0 :       return CUBEB_DEVICE_STATE_UNPLUGGED;
    1191             :     else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
    1192             : #endif
    1193           0 :       return CUBEB_DEVICE_STATE_ENABLED;
    1194             :   }
    1195             : 
    1196           0 :   return CUBEB_DEVICE_STATE_ENABLED;
    1197             : }
    1198             : 
    1199             : static void
    1200           0 : pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
    1201             :                    int eol, void * user_data)
    1202             : {
    1203           0 :   pulse_dev_list_data * list_data = user_data;
    1204             :   cubeb_device_info * devinfo;
    1205             :   const char * prop;
    1206             : 
    1207             :   (void)context;
    1208             : 
    1209           0 :   if (eol || info == NULL)
    1210           0 :     return;
    1211             : 
    1212           0 :   pulse_ensure_dev_list_data_list_size(list_data);
    1213           0 :   devinfo = &list_data->devinfo[list_data->count];
    1214           0 :   memset(devinfo, 0, sizeof(cubeb_device_info));
    1215             : 
    1216           0 :   devinfo->device_id = strdup(info->name);
    1217           0 :   devinfo->devid = (cubeb_devid) devinfo->device_id;
    1218           0 :   devinfo->friendly_name = strdup(info->description);
    1219           0 :   prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
    1220           0 :   if (prop)
    1221           0 :     devinfo->group_id = strdup(prop);
    1222           0 :   prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
    1223           0 :   if (prop)
    1224           0 :     devinfo->vendor_name = strdup(prop);
    1225             : 
    1226           0 :   devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
    1227           0 :   devinfo->state = pulse_get_state_from_sink_port(info->active_port);
    1228           0 :   devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ?
    1229           0 :     CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
    1230             : 
    1231           0 :   devinfo->format = CUBEB_DEVICE_FMT_ALL;
    1232           0 :   devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
    1233           0 :   devinfo->max_channels = info->channel_map.channels;
    1234           0 :   devinfo->min_rate = 1;
    1235           0 :   devinfo->max_rate = PA_RATE_MAX;
    1236           0 :   devinfo->default_rate = info->sample_spec.rate;
    1237             : 
    1238           0 :   devinfo->latency_lo = 0;
    1239           0 :   devinfo->latency_hi = 0;
    1240             : 
    1241           0 :   list_data->count += 1;
    1242             : 
    1243           0 :   WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
    1244             : }
    1245             : 
    1246             : static cubeb_device_state
    1247           0 : pulse_get_state_from_source_port(pa_source_port_info * info)
    1248             : {
    1249           0 :   if (info != NULL) {
    1250             : #if PA_CHECK_VERSION(2, 0, 0)
    1251           0 :     if (info->available == PA_PORT_AVAILABLE_NO)
    1252           0 :       return CUBEB_DEVICE_STATE_UNPLUGGED;
    1253             :     else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
    1254             : #endif
    1255           0 :       return CUBEB_DEVICE_STATE_ENABLED;
    1256             :   }
    1257             : 
    1258           0 :   return CUBEB_DEVICE_STATE_ENABLED;
    1259             : }
    1260             : 
    1261             : static void
    1262           0 : pulse_source_info_cb(pa_context * context, const pa_source_info * info,
    1263             :     int eol, void * user_data)
    1264             : {
    1265           0 :   pulse_dev_list_data * list_data = user_data;
    1266             :   cubeb_device_info * devinfo;
    1267             :   const char * prop;
    1268             : 
    1269             :   (void)context;
    1270             : 
    1271           0 :   if (eol)
    1272           0 :     return;
    1273             : 
    1274           0 :   pulse_ensure_dev_list_data_list_size(list_data);
    1275           0 :   devinfo = &list_data->devinfo[list_data->count];
    1276           0 :   memset(devinfo, 0, sizeof(cubeb_device_info));
    1277             : 
    1278           0 :   devinfo->device_id = strdup(info->name);
    1279           0 :   devinfo->devid = (cubeb_devid) devinfo->device_id;
    1280           0 :   devinfo->friendly_name = strdup(info->description);
    1281           0 :   prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
    1282           0 :   if (prop)
    1283           0 :     devinfo->group_id = strdup(prop);
    1284           0 :   prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
    1285           0 :   if (prop)
    1286           0 :     devinfo->vendor_name = strdup(prop);
    1287             : 
    1288           0 :   devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
    1289           0 :   devinfo->state = pulse_get_state_from_source_port(info->active_port);
    1290           0 :   devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ?
    1291           0 :     CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
    1292             : 
    1293           0 :   devinfo->format = CUBEB_DEVICE_FMT_ALL;
    1294           0 :   devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
    1295           0 :   devinfo->max_channels = info->channel_map.channels;
    1296           0 :   devinfo->min_rate = 1;
    1297           0 :   devinfo->max_rate = PA_RATE_MAX;
    1298           0 :   devinfo->default_rate = info->sample_spec.rate;
    1299             : 
    1300           0 :   devinfo->latency_lo = 0;
    1301           0 :   devinfo->latency_hi = 0;
    1302             : 
    1303           0 :   list_data->count += 1;
    1304           0 :   WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
    1305             : }
    1306             : 
    1307             : static void
    1308           0 : pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
    1309             : {
    1310           0 :   pulse_dev_list_data * list_data = userdata;
    1311             : 
    1312             :   (void)c;
    1313             : 
    1314           0 :   free(list_data->default_sink_name);
    1315           0 :   free(list_data->default_source_name);
    1316           0 :   list_data->default_sink_name = strdup(i->default_sink_name);
    1317           0 :   list_data->default_source_name = strdup(i->default_source_name);
    1318             : 
    1319           0 :   WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
    1320           0 : }
    1321             : 
    1322             : static int
    1323           0 : pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
    1324             :                         cubeb_device_collection * collection)
    1325             : {
    1326           0 :   pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context };
    1327             :   pa_operation * o;
    1328             : 
    1329           0 :   WRAP(pa_threaded_mainloop_lock)(context->mainloop);
    1330             : 
    1331           0 :   o = WRAP(pa_context_get_server_info)(context->context,
    1332             :       pulse_server_info_cb, &user_data);
    1333           0 :   if (o) {
    1334           0 :     operation_wait(context, NULL, o);
    1335           0 :     WRAP(pa_operation_unref)(o);
    1336             :   }
    1337             : 
    1338           0 :   if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
    1339           0 :     o = WRAP(pa_context_get_sink_info_list)(context->context,
    1340             :         pulse_sink_info_cb, &user_data);
    1341           0 :     if (o) {
    1342           0 :       operation_wait(context, NULL, o);
    1343           0 :       WRAP(pa_operation_unref)(o);
    1344             :     }
    1345             :   }
    1346             : 
    1347           0 :   if (type & CUBEB_DEVICE_TYPE_INPUT) {
    1348           0 :     o = WRAP(pa_context_get_source_info_list)(context->context,
    1349             :         pulse_source_info_cb, &user_data);
    1350           0 :     if (o) {
    1351           0 :       operation_wait(context, NULL, o);
    1352           0 :       WRAP(pa_operation_unref)(o);
    1353             :     }
    1354             :   }
    1355             : 
    1356           0 :   WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
    1357             : 
    1358           0 :   collection->device = user_data.devinfo;
    1359           0 :   collection->count = user_data.count;
    1360             : 
    1361           0 :   free(user_data.default_sink_name);
    1362           0 :   free(user_data.default_source_name);
    1363           0 :   return CUBEB_OK;
    1364             : }
    1365             : 
    1366             : static int
    1367           0 : pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
    1368             : {
    1369             : #if PA_CHECK_VERSION(0, 9, 8)
    1370           0 :   *device = calloc(1, sizeof(cubeb_device));
    1371           0 :   if (*device == NULL)
    1372           0 :     return CUBEB_ERROR;
    1373             : 
    1374           0 :   if (stm->input_stream) {
    1375           0 :     const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream);
    1376           0 :     (*device)->input_name = (name == NULL) ? NULL : strdup(name);
    1377             :   }
    1378             : 
    1379           0 :   if (stm->output_stream) {
    1380           0 :     const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream);
    1381           0 :     (*device)->output_name = (name == NULL) ? NULL : strdup(name);
    1382             :   }
    1383             : 
    1384           0 :   return CUBEB_OK;
    1385             : #else
    1386             :   return CUBEB_ERROR_NOT_SUPPORTED;
    1387             : #endif
    1388             : }
    1389             : 
    1390             : static int
    1391           0 : pulse_stream_device_destroy(cubeb_stream * stream,
    1392             :                             cubeb_device * device)
    1393             : {
    1394             :   (void)stream;
    1395           0 :   free(device->input_name);
    1396           0 :   free(device->output_name);
    1397           0 :   free(device);
    1398           0 :   return CUBEB_OK;
    1399             : }
    1400             : 
    1401             : static void
    1402           0 : pulse_subscribe_callback(pa_context * ctx,
    1403             :                          pa_subscription_event_type_t t,
    1404             :                          uint32_t index, void * userdata)
    1405             : {
    1406             :   (void)ctx;
    1407           0 :   cubeb * context = userdata;
    1408             : 
    1409           0 :   switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
    1410             :   case PA_SUBSCRIPTION_EVENT_SOURCE:
    1411             :   case PA_SUBSCRIPTION_EVENT_SINK:
    1412             : 
    1413           0 :     if (g_cubeb_log_level) {
    1414           0 :       if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
    1415           0 :           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
    1416           0 :         LOG("Removing sink index %d", index);
    1417           0 :       } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
    1418           0 :           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
    1419           0 :         LOG("Adding sink index %d", index);
    1420             :       }
    1421           0 :       if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
    1422           0 :           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
    1423           0 :         LOG("Removing source index %d", index);
    1424           0 :       } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
    1425           0 :           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
    1426           0 :         LOG("Adding source index %d", index);
    1427             :       }
    1428             :     }
    1429             : 
    1430           0 :     if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
    1431           0 :         (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
    1432           0 :       context->collection_changed_callback(context, context->collection_changed_user_ptr);
    1433             :     }
    1434           0 :     break;
    1435             :   }
    1436           0 : }
    1437             : 
    1438             : static void
    1439           0 : subscribe_success(pa_context *c, int success, void *userdata)
    1440             : {
    1441             :   (void)c;
    1442           0 :   cubeb * context = userdata;
    1443           0 :   assert(success);
    1444           0 :   WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0);
    1445           0 : }
    1446             : 
    1447             : static int
    1448           0 : pulse_register_device_collection_changed(cubeb * context,
    1449             :                                          cubeb_device_type devtype,
    1450             :                                          cubeb_device_collection_changed_callback collection_changed_callback,
    1451             :                                          void * user_ptr)
    1452             : {
    1453           0 :   context->collection_changed_callback = collection_changed_callback;
    1454           0 :   context->collection_changed_user_ptr = user_ptr;
    1455             : 
    1456           0 :   WRAP(pa_threaded_mainloop_lock)(context->mainloop);
    1457             : 
    1458             :   pa_subscription_mask_t mask;
    1459           0 :   if (context->collection_changed_callback == NULL) {
    1460             :     // Unregister subscription
    1461           0 :     WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
    1462           0 :     mask = PA_SUBSCRIPTION_MASK_NULL;
    1463             :   } else {
    1464           0 :     WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
    1465           0 :     if (devtype == CUBEB_DEVICE_TYPE_INPUT)
    1466           0 :       mask = PA_SUBSCRIPTION_MASK_SOURCE;
    1467           0 :     else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT)
    1468           0 :       mask = PA_SUBSCRIPTION_MASK_SINK;
    1469             :     else
    1470           0 :       mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
    1471             :   }
    1472             : 
    1473             :   pa_operation * o;
    1474           0 :   o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
    1475           0 :   if (o == NULL) {
    1476           0 :     LOG("Context subscribe failed");
    1477           0 :     return CUBEB_ERROR;
    1478             :   }
    1479           0 :   operation_wait(context, NULL, o);
    1480           0 :   WRAP(pa_operation_unref)(o);
    1481             : 
    1482           0 :   WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
    1483             : 
    1484           0 :   return CUBEB_OK;
    1485             : }
    1486             : 
    1487             : static struct cubeb_ops const pulse_ops = {
    1488             :   .init = pulse_init,
    1489             :   .get_backend_id = pulse_get_backend_id,
    1490             :   .get_max_channel_count = pulse_get_max_channel_count,
    1491             :   .get_min_latency = pulse_get_min_latency,
    1492             :   .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
    1493             :   .get_preferred_channel_layout = pulse_get_preferred_channel_layout,
    1494             :   .enumerate_devices = pulse_enumerate_devices,
    1495             :   .device_collection_destroy = cubeb_utils_default_device_collection_destroy,
    1496             :   .destroy = pulse_destroy,
    1497             :   .stream_init = pulse_stream_init,
    1498             :   .stream_destroy = pulse_stream_destroy,
    1499             :   .stream_start = pulse_stream_start,
    1500             :   .stream_stop = pulse_stream_stop,
    1501             :   .stream_get_position = pulse_stream_get_position,
    1502             :   .stream_get_latency = pulse_stream_get_latency,
    1503             :   .stream_set_volume = pulse_stream_set_volume,
    1504             :   .stream_set_panning = pulse_stream_set_panning,
    1505             :   .stream_get_current_device = pulse_stream_get_current_device,
    1506             :   .stream_device_destroy = pulse_stream_device_destroy,
    1507             :   .stream_register_device_changed_callback = NULL,
    1508             :   .register_device_collection_changed = pulse_register_device_collection_changed
    1509             : };

Generated by: LCOV version 1.13