media/libcubeb/src/cubeb_pulse.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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"
    16 #ifdef DISABLE_LIBPULSE_DLOPEN
    17 #define WRAP(x) x
    18 #else
    19 #define WRAP(x) cubeb_##x
    20 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x
    21 MAKE_TYPEDEF(pa_channel_map_init_auto);
    22 MAKE_TYPEDEF(pa_context_connect);
    23 MAKE_TYPEDEF(pa_context_disconnect);
    24 MAKE_TYPEDEF(pa_context_drain);
    25 MAKE_TYPEDEF(pa_context_get_state);
    26 MAKE_TYPEDEF(pa_context_new);
    27 MAKE_TYPEDEF(pa_context_rttime_new);
    28 MAKE_TYPEDEF(pa_context_set_state_callback);
    29 MAKE_TYPEDEF(pa_context_unref);
    30 MAKE_TYPEDEF(pa_context_get_sink_info_by_name);
    31 MAKE_TYPEDEF(pa_context_get_server_info);
    32 MAKE_TYPEDEF(pa_frame_size);
    33 MAKE_TYPEDEF(pa_operation_get_state);
    34 MAKE_TYPEDEF(pa_operation_unref);
    35 MAKE_TYPEDEF(pa_rtclock_now);
    36 MAKE_TYPEDEF(pa_stream_begin_write);
    37 MAKE_TYPEDEF(pa_stream_cancel_write);
    38 MAKE_TYPEDEF(pa_stream_connect_playback);
    39 MAKE_TYPEDEF(pa_stream_cork);
    40 MAKE_TYPEDEF(pa_stream_disconnect);
    41 MAKE_TYPEDEF(pa_stream_get_latency);
    42 MAKE_TYPEDEF(pa_stream_get_state);
    43 MAKE_TYPEDEF(pa_stream_get_time);
    44 MAKE_TYPEDEF(pa_stream_new);
    45 MAKE_TYPEDEF(pa_stream_set_state_callback);
    46 MAKE_TYPEDEF(pa_stream_set_write_callback);
    47 MAKE_TYPEDEF(pa_stream_unref);
    48 MAKE_TYPEDEF(pa_stream_update_timing_info);
    49 MAKE_TYPEDEF(pa_stream_write);
    50 MAKE_TYPEDEF(pa_threaded_mainloop_free);
    51 MAKE_TYPEDEF(pa_threaded_mainloop_get_api);
    52 MAKE_TYPEDEF(pa_threaded_mainloop_lock);
    53 MAKE_TYPEDEF(pa_threaded_mainloop_in_thread);
    54 MAKE_TYPEDEF(pa_threaded_mainloop_new);
    55 MAKE_TYPEDEF(pa_threaded_mainloop_signal);
    56 MAKE_TYPEDEF(pa_threaded_mainloop_start);
    57 MAKE_TYPEDEF(pa_threaded_mainloop_stop);
    58 MAKE_TYPEDEF(pa_threaded_mainloop_unlock);
    59 MAKE_TYPEDEF(pa_threaded_mainloop_wait);
    60 MAKE_TYPEDEF(pa_usec_to_bytes);
    61 #undef MAKE_TYPEDEF
    62 #endif
    64 static struct cubeb_ops const pulse_ops;
    66 struct cubeb {
    67   struct cubeb_ops const * ops;
    68   void * libpulse;
    69   pa_threaded_mainloop * mainloop;
    70   pa_context * context;
    71   pa_sink_info * default_sink_info;
    72   char * context_name;
    73   int error;
    74 };
    76 struct cubeb_stream {
    77   cubeb * context;
    78   pa_stream * stream;
    79   cubeb_data_callback data_callback;
    80   cubeb_state_callback state_callback;
    81   void * user_ptr;
    82   pa_time_event * drain_timer;
    83   pa_sample_spec sample_spec;
    84   int shutdown;
    85 };
    87 enum cork_state {
    88   UNCORK = 0,
    89   CORK = 1 << 0,
    90   NOTIFY = 1 << 1
    91 };
    93 static void
    94 sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
    95 {
    96   cubeb * ctx = u;
    97   if (!eol) {
    98     ctx->default_sink_info = malloc(sizeof(pa_sink_info));
    99     memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
   100   }
   101   WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
   102 }
   104 static void
   105 server_info_callback(pa_context * context, const pa_server_info * info, void * u)
   106 {
   107   WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
   108 }
   110 static void
   111 context_state_callback(pa_context * c, void * u)
   112 {
   113   cubeb * ctx = u;
   114   if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
   115     ctx->error = 1;
   116   }
   117   WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
   118 }
   120 static void
   121 context_notify_callback(pa_context * c, void * u)
   122 {
   123   cubeb * ctx = u;
   124   WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
   125 }
   127 static void
   128 stream_success_callback(pa_stream * s, int success, void * u)
   129 {
   130   cubeb_stream * stm = u;
   131   WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
   132 }
   134 static void
   135 stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
   136 {
   137   cubeb_stream * stm = u;
   138   /* there's no pa_rttime_free, so use this instead. */
   139   a->time_free(stm->drain_timer);
   140   stm->drain_timer = NULL;
   141   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
   142 }
   144 static void
   145 stream_state_callback(pa_stream * s, void * u)
   146 {
   147   cubeb_stream * stm = u;
   148   if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
   149     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
   150   }
   151   WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
   152 }
   154 static void
   155 stream_request_callback(pa_stream * s, size_t nbytes, void * u)
   156 {
   157   cubeb_stream * stm;
   158   void * buffer;
   159   size_t size;
   160   int r;
   161   long got;
   162   size_t towrite;
   163   size_t frame_size;
   165   stm = u;
   167   if (stm->shutdown)
   168     return;
   170   frame_size = WRAP(pa_frame_size)(&stm->sample_spec);
   172   assert(nbytes % frame_size == 0);
   174   towrite = nbytes;
   176   while (towrite) {
   177     size = towrite;
   178     r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
   179     assert(r == 0);
   180     assert(size > 0);
   181     assert(size % frame_size == 0);
   183     got = stm->data_callback(stm, stm->user_ptr, buffer, size / frame_size);
   184     if (got < 0) {
   185       WRAP(pa_stream_cancel_write)(s);
   186       stm->shutdown = 1;
   187       return;
   188     }
   190     r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
   191     assert(r == 0);
   193     if ((size_t) got < size / frame_size) {
   194       pa_usec_t latency = 0;
   195       r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
   196       if (r == -PA_ERR_NODATA) {
   197         /* this needs a better guess. */
   198         latency = 100 * PA_USEC_PER_MSEC;
   199       }
   200       assert(r == 0 || r == -PA_ERR_NODATA);
   201       /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
   202       /* arbitrary safety margin: double the current latency. */
   203       stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
   204       stm->shutdown = 1;
   205       return;
   206     }
   208     towrite -= size;
   209   }
   211   assert(towrite == 0);
   212 }
   214 static int
   215 wait_until_context_ready(cubeb * ctx)
   216 {
   217   for (;;) {
   218     pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
   219     if (!PA_CONTEXT_IS_GOOD(state))
   220       return -1;
   221     if (state == PA_CONTEXT_READY)
   222       break;
   223     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
   224   }
   225   return 0;
   226 }
   228 static int
   229 wait_until_stream_ready(cubeb_stream * stm)
   230 {
   231   for (;;) {
   232     pa_stream_state_t state = WRAP(pa_stream_get_state)(stm->stream);
   233     if (!PA_STREAM_IS_GOOD(state))
   234       return -1;
   235     if (state == PA_STREAM_READY)
   236       break;
   237     WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
   238   }
   239   return 0;
   240 }
   242 static int
   243 operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
   244 {
   245   while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
   246     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
   247     if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context)))
   248       return -1;
   249     if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream)))
   250       return -1;
   251   }
   252   return 0;
   253 }
   255 static void
   256 stream_cork(cubeb_stream * stm, enum cork_state state)
   257 {
   258   pa_operation * o;
   260   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
   261   o = WRAP(pa_stream_cork)(stm->stream, state & CORK, stream_success_callback, stm);
   262   if (o) {
   263     operation_wait(stm->context, stm->stream, o);
   264     WRAP(pa_operation_unref)(o);
   265   }
   266   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
   268   if (state & NOTIFY) {
   269     stm->state_callback(stm, stm->user_ptr,
   270                         state & CORK ? CUBEB_STATE_STOPPED : CUBEB_STATE_STARTED);
   271   }
   272 }
   274 static void pulse_context_destroy(cubeb * ctx);
   275 static void pulse_destroy(cubeb * ctx);
   277 static int
   278 pulse_context_init(cubeb * ctx)
   279 {
   280   if (ctx->context) {
   281     assert(ctx->error == 1);
   282     pulse_context_destroy(ctx);
   283   }
   285   ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
   286                                 ctx->context_name);
   287   WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
   289   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
   290   WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
   292   if (wait_until_context_ready(ctx) != 0) {
   293     WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
   294     pulse_context_destroy(ctx);
   295     ctx->context = NULL;
   296     return -1;
   297   }
   299   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
   301   ctx->error = 0;
   303   return 0;
   304 }
   306 /*static*/ int
   307 pulse_init(cubeb ** context, char const * context_name)
   308 {
   309   void * libpulse = NULL;
   310   cubeb * ctx;
   312   *context = NULL;
   314 #ifndef DISABLE_LIBPULSE_DLOPEN
   315   libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
   316   if (!libpulse) {
   317     return CUBEB_ERROR;
   318   }
   320 #define LOAD(x) do { \
   321     cubeb_##x = dlsym(libpulse, #x); \
   322     if (!cubeb_##x) { \
   323       dlclose(libpulse); \
   324       return CUBEB_ERROR; \
   325     } \
   326   } while(0)
   327   LOAD(pa_channel_map_init_auto);
   328   LOAD(pa_context_connect);
   329   LOAD(pa_context_disconnect);
   330   LOAD(pa_context_drain);
   331   LOAD(pa_context_get_state);
   332   LOAD(pa_context_new);
   333   LOAD(pa_context_rttime_new);
   334   LOAD(pa_context_set_state_callback);
   335   LOAD(pa_context_get_sink_info_by_name);
   336   LOAD(pa_context_get_server_info);
   337   LOAD(pa_context_unref);
   338   LOAD(pa_frame_size);
   339   LOAD(pa_operation_get_state);
   340   LOAD(pa_operation_unref);
   341   LOAD(pa_rtclock_now);
   342   LOAD(pa_stream_begin_write);
   343   LOAD(pa_stream_cancel_write);
   344   LOAD(pa_stream_connect_playback);
   345   LOAD(pa_stream_cork);
   346   LOAD(pa_stream_disconnect);
   347   LOAD(pa_stream_get_latency);
   348   LOAD(pa_stream_get_state);
   349   LOAD(pa_stream_get_time);
   350   LOAD(pa_stream_new);
   351   LOAD(pa_stream_set_state_callback);
   352   LOAD(pa_stream_set_write_callback);
   353   LOAD(pa_stream_unref);
   354   LOAD(pa_stream_update_timing_info);
   355   LOAD(pa_stream_write);
   356   LOAD(pa_threaded_mainloop_free);
   357   LOAD(pa_threaded_mainloop_get_api);
   358   LOAD(pa_threaded_mainloop_lock);
   359   LOAD(pa_threaded_mainloop_in_thread);
   360   LOAD(pa_threaded_mainloop_new);
   361   LOAD(pa_threaded_mainloop_signal);
   362   LOAD(pa_threaded_mainloop_start);
   363   LOAD(pa_threaded_mainloop_stop);
   364   LOAD(pa_threaded_mainloop_unlock);
   365   LOAD(pa_threaded_mainloop_wait);
   366   LOAD(pa_usec_to_bytes);
   367 #undef LOAD
   368 #endif
   370   ctx = calloc(1, sizeof(*ctx));
   371   assert(ctx);
   373   ctx->ops = &pulse_ops;
   374   ctx->libpulse = libpulse;
   376   ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
   377   ctx->default_sink_info = NULL;
   379   WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
   381   ctx->context_name = context_name ? strdup(context_name) : NULL;
   382   if (pulse_context_init(ctx) != 0) {
   383     pulse_destroy(ctx);
   384     return CUBEB_ERROR;
   385   }
   387   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
   388   WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
   389   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
   391   *context = ctx;
   393   return CUBEB_OK;
   394 }
   396 static char const *
   397 pulse_get_backend_id(cubeb * ctx)
   398 {
   399   return "pulse";
   400 }
   402 static int
   403 pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
   404 {
   405   assert(ctx && max_channels);
   407   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
   408   while (!ctx->default_sink_info) {
   409     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
   410   }
   411   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
   413   *max_channels = ctx->default_sink_info->channel_map.channels;
   415   return CUBEB_OK;
   416 }
   418 static int
   419 pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
   420 {
   421   assert(ctx && rate);
   423   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
   424   while (!ctx->default_sink_info) {
   425     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
   426   }
   427   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
   429   *rate = ctx->default_sink_info->sample_spec.rate;
   431   return CUBEB_OK;
   432 }
   434 static int
   435 pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
   436 {
   437   // According to PulseAudio developers, this is a safe minimum.
   438   *latency_ms = 40;
   440   return CUBEB_OK;
   441 }
   443 static void
   444 pulse_context_destroy(cubeb * ctx)
   445 {
   446   pa_operation * o;
   448   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
   449   o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
   450   if (o) {
   451     operation_wait(ctx, NULL, o);
   452     WRAP(pa_operation_unref)(o);
   453   }
   454   WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
   455   WRAP(pa_context_disconnect)(ctx->context);
   456   WRAP(pa_context_unref)(ctx->context);
   457   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
   458 }
   460 static void
   461 pulse_destroy(cubeb * ctx)
   462 {
   463   pa_operation * o;
   465   if (ctx->context_name) {
   466     free(ctx->context_name);
   467   }
   468   if (ctx->context) {
   469     pulse_context_destroy(ctx);
   470   }
   472   if (ctx->mainloop) {
   473     WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
   474     WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
   475   }
   477   if (ctx->libpulse) {
   478     dlclose(ctx->libpulse);
   479   }
   480   if (ctx->default_sink_info) {
   481     free(ctx->default_sink_info);
   482   }
   483   free(ctx);
   484 }
   486 static void pulse_stream_destroy(cubeb_stream * stm);
   488 static int
   489 pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
   490                   cubeb_stream_params stream_params, unsigned int latency,
   491                   cubeb_data_callback data_callback, cubeb_state_callback state_callback,
   492                   void * user_ptr)
   493 {
   494   pa_sample_spec ss;
   495   cubeb_stream * stm;
   496   pa_operation * o;
   497   pa_buffer_attr battr;
   498   int r;
   500   assert(context);
   502   *stream = NULL;
   504   switch (stream_params.format) {
   505   case CUBEB_SAMPLE_S16LE:
   506     ss.format = PA_SAMPLE_S16LE;
   507     break;
   508   case CUBEB_SAMPLE_S16BE:
   509     ss.format = PA_SAMPLE_S16BE;
   510     break;
   511   case CUBEB_SAMPLE_FLOAT32LE:
   512     ss.format = PA_SAMPLE_FLOAT32LE;
   513     break;
   514   case CUBEB_SAMPLE_FLOAT32BE:
   515     ss.format = PA_SAMPLE_FLOAT32BE;
   516     break;
   517   default:
   518     return CUBEB_ERROR_INVALID_FORMAT;
   519   }
   521   // If the connection failed for some reason, try to reconnect
   522   if (context->error == 1 && pulse_context_init(context) != 0) {
   523     return CUBEB_ERROR;
   524   }
   526   ss.rate = stream_params.rate;
   527   ss.channels = stream_params.channels;
   529   stm = calloc(1, sizeof(*stm));
   530   assert(stm);
   532   stm->context = context;
   534   stm->data_callback = data_callback;
   535   stm->state_callback = state_callback;
   536   stm->user_ptr = user_ptr;
   538   stm->sample_spec = ss;
   540   battr.maxlength = -1;
   541   battr.tlength = WRAP(pa_usec_to_bytes)(latency * PA_USEC_PER_MSEC, &stm->sample_spec);
   542   battr.prebuf = -1;
   543   battr.minreq = battr.tlength / 4;
   544   battr.fragsize = -1;
   546   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
   547   stm->stream = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
   548   if (!stm->stream) {
   549     pulse_stream_destroy(stm);
   550     return CUBEB_ERROR;
   551   }
   552   WRAP(pa_stream_set_state_callback)(stm->stream, stream_state_callback, stm);
   553   WRAP(pa_stream_set_write_callback)(stm->stream, stream_request_callback, stm);
   554   WRAP(pa_stream_connect_playback)(stm->stream, NULL, &battr,
   555                              PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
   556                              PA_STREAM_START_CORKED,
   557                              NULL, NULL);
   559   r = wait_until_stream_ready(stm);
   560   if (r == 0) {
   561     /* force a timing update now, otherwise timing info does not become valid
   562        until some point after initialization has completed. */
   563     o = WRAP(pa_stream_update_timing_info)(stm->stream, stream_success_callback, stm);
   564     if (o) {
   565       r = operation_wait(stm->context, stm->stream, o);
   566       WRAP(pa_operation_unref)(o);
   567     }
   568   }
   569   WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
   571   if (r != 0) {
   572     pulse_stream_destroy(stm);
   573     return CUBEB_ERROR;
   574   }
   576   *stream = stm;
   578   return CUBEB_OK;
   579 }
   581 static void
   582 pulse_stream_destroy(cubeb_stream * stm)
   583 {
   584   if (stm->stream) {
   585     stream_cork(stm, CORK);
   587     WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
   589     if (stm->drain_timer) {
   590       /* there's no pa_rttime_free, so use this instead. */
   591       WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
   592     }
   594     WRAP(pa_stream_set_state_callback)(stm->stream, NULL, NULL);
   595     WRAP(pa_stream_disconnect)(stm->stream);
   596     WRAP(pa_stream_unref)(stm->stream);
   597     WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
   598   }
   600   free(stm);
   601 }
   603 static int
   604 pulse_stream_start(cubeb_stream * stm)
   605 {
   606   stream_cork(stm, UNCORK | NOTIFY);
   607   return CUBEB_OK;
   608 }
   610 static int
   611 pulse_stream_stop(cubeb_stream * stm)
   612 {
   613   stream_cork(stm, CORK | NOTIFY);
   614   return CUBEB_OK;
   615 }
   617 static int
   618 pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
   619 {
   620   int r, in_thread;
   621   pa_usec_t r_usec;
   622   uint64_t bytes;
   624   in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
   626   if (!in_thread) {
   627     WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
   628   }
   629   r = WRAP(pa_stream_get_time)(stm->stream, &r_usec);
   630   if (!in_thread) {
   631     WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
   632   }
   634   if (r != 0) {
   635     return CUBEB_ERROR;
   636   }
   638   bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->sample_spec);
   639   *position = bytes / WRAP(pa_frame_size)(&stm->sample_spec);
   641   return CUBEB_OK;
   642 }
   644 int
   645 pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
   646 {
   647   pa_usec_t r_usec;
   648   int negative, r;
   650   if (!stm) {
   651     return CUBEB_ERROR;
   652   }
   654   r = WRAP(pa_stream_get_latency)(stm->stream, &r_usec, &negative);
   655   assert(!negative);
   656   if (r) {
   657     return CUBEB_ERROR;
   658   }
   660   *latency = r_usec * stm->sample_spec.rate / PA_USEC_PER_SEC;
   661   return CUBEB_OK;
   662 }
   664 static struct cubeb_ops const pulse_ops = {
   665   .init = pulse_init,
   666   .get_backend_id = pulse_get_backend_id,
   667   .get_max_channel_count = pulse_get_max_channel_count,
   668   .get_min_latency = pulse_get_min_latency,
   669   .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
   670   .destroy = pulse_destroy,
   671   .stream_init = pulse_stream_init,
   672   .stream_destroy = pulse_stream_destroy,
   673   .stream_start = pulse_stream_start,
   674   .stream_stop = pulse_stream_stop,
   675   .stream_get_position = pulse_stream_get_position,
   676   .stream_get_latency = pulse_stream_get_latency
   677 };

mercurial