tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cubeb_sun.c (19285B)


      1 /*
      2 * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
      3 *
      4 * This program is made available under an ISC-style license.  See the
      5 * accompanying file LICENSE for details.
      6 */
      7 #include "cubeb-internal.h"
      8 #include "cubeb/cubeb.h"
      9 #include "cubeb_tracing.h"
     10 #include <fcntl.h>
     11 #include <limits.h>
     12 #include <pthread.h>
     13 #include <stdbool.h>
     14 #include <stdio.h>
     15 #include <stdlib.h>
     16 #include <string.h>
     17 #include <sys/audioio.h>
     18 #include <sys/ioctl.h>
     19 #include <unistd.h>
     20 
     21 /* Default to 4 + 1 for the default device. */
     22 #ifndef SUN_DEVICE_COUNT
     23 #define SUN_DEVICE_COUNT (5)
     24 #endif
     25 
     26 /* Supported well by most hardware. */
     27 #ifndef SUN_PREFER_RATE
     28 #define SUN_PREFER_RATE (48000)
     29 #endif
     30 
     31 /* Standard acceptable minimum. */
     32 #ifndef SUN_LATENCY_MS
     33 #define SUN_LATENCY_MS (40)
     34 #endif
     35 
     36 #ifndef SUN_DEFAULT_DEVICE
     37 #define SUN_DEFAULT_DEVICE "/dev/audio"
     38 #endif
     39 
     40 #ifndef SUN_BUFFER_FRAMES
     41 #define SUN_BUFFER_FRAMES (32)
     42 #endif
     43 
     44 /*
     45 * Supported on NetBSD regardless of hardware.
     46 */
     47 
     48 #ifndef SUN_MAX_CHANNELS
     49 #ifdef __NetBSD__
     50 #define SUN_MAX_CHANNELS (12)
     51 #else
     52 #define SUN_MAX_CHANNELS (2)
     53 #endif
     54 #endif
     55 
     56 #ifndef SUN_MIN_RATE
     57 #define SUN_MIN_RATE (1000)
     58 #endif
     59 
     60 #ifndef SUN_MAX_RATE
     61 #define SUN_MAX_RATE (192000)
     62 #endif
     63 
     64 static struct cubeb_ops const sun_ops;
     65 
     66 struct cubeb {
     67  struct cubeb_ops const * ops;
     68 };
     69 
     70 struct sun_stream {
     71  char name[32];
     72  int fd;
     73  void * buf;
     74  struct audio_info info;
     75  unsigned frame_size; /* precision in bytes * channels */
     76  bool floating;
     77 };
     78 
     79 struct cubeb_stream {
     80  struct cubeb * context;
     81  void * user_ptr;
     82  pthread_t thread;
     83  pthread_mutex_t mutex; /* protects running, volume, frames_written */
     84  bool running;
     85  float volume;
     86  struct sun_stream play;
     87  struct sun_stream record;
     88  cubeb_data_callback data_cb;
     89  cubeb_state_callback state_cb;
     90  uint64_t frames_written;
     91  uint64_t blocks_written;
     92 };
     93 
     94 int
     95 sun_init(cubeb ** context, char const * context_name)
     96 {
     97  cubeb * c;
     98 
     99  (void)context_name;
    100  if ((c = calloc(1, sizeof(cubeb))) == NULL) {
    101    return CUBEB_ERROR;
    102  }
    103  c->ops = &sun_ops;
    104  *context = c;
    105  return CUBEB_OK;
    106 }
    107 
    108 static void
    109 sun_destroy(cubeb * context)
    110 {
    111  free(context);
    112 }
    113 
    114 static char const *
    115 sun_get_backend_id(cubeb * context)
    116 {
    117  return "sun";
    118 }
    119 
    120 static int
    121 sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
    122 {
    123  (void)context;
    124 
    125  *rate = SUN_PREFER_RATE;
    126  return CUBEB_OK;
    127 }
    128 
    129 static int
    130 sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
    131 {
    132  (void)context;
    133 
    134  *max_channels = SUN_MAX_CHANNELS;
    135  return CUBEB_OK;
    136 }
    137 
    138 static int
    139 sun_get_min_latency(cubeb * context, cubeb_stream_params params,
    140                    uint32_t * latency_frames)
    141 {
    142  (void)context;
    143 
    144  *latency_frames = SUN_LATENCY_MS * params.rate / 1000;
    145  return CUBEB_OK;
    146 }
    147 
    148 static int
    149 sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
    150               struct audio_device * dev)
    151 {
    152  int fd = -1;
    153 
    154  if ((fd = open(device, O_RDONLY)) == -1) {
    155    goto error;
    156  }
    157 #ifdef AUDIO_GETFORMAT
    158  if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
    159    goto error;
    160  }
    161 #endif
    162 #ifdef AUDIO_GETPROPS
    163  if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
    164    goto error;
    165  }
    166 #endif
    167  if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
    168    goto error;
    169  }
    170  close(fd);
    171  return CUBEB_OK;
    172 error:
    173  if (fd != -1) {
    174    close(fd);
    175  }
    176  return CUBEB_ERROR;
    177 }
    178 
    179 /*
    180 * XXX: PR kern/54264
    181 */
    182 static int
    183 sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
    184 {
    185  return prinfo->precision >= 8 && prinfo->precision <= 32 &&
    186         prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
    187         prinfo->sample_rate < SUN_MAX_RATE &&
    188         prinfo->sample_rate > SUN_MIN_RATE;
    189 }
    190 
    191 static int
    192 sun_enumerate_devices(cubeb * context, cubeb_device_type type,
    193                      cubeb_device_collection * collection)
    194 {
    195  unsigned i;
    196  cubeb_device_info device = {0};
    197  char dev[16] = SUN_DEFAULT_DEVICE;
    198  char dev_friendly[64];
    199  struct audio_info hwfmt;
    200  struct audio_device hwname;
    201  struct audio_prinfo * prinfo = NULL;
    202  int hwprops;
    203 
    204  collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
    205  if (collection->device == NULL) {
    206    return CUBEB_ERROR;
    207  }
    208  collection->count = 0;
    209 
    210  for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
    211    if (i > 0) {
    212      (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
    213    }
    214    if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
    215      continue;
    216    }
    217 #ifdef AUDIO_GETPROPS
    218    device.type = 0;
    219    if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
    220        sun_prinfo_verify_sanity(&hwfmt.record)) {
    221      /* the device supports recording, probably */
    222      device.type |= CUBEB_DEVICE_TYPE_INPUT;
    223    }
    224    if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
    225        sun_prinfo_verify_sanity(&hwfmt.play)) {
    226      /* the device supports playback, probably */
    227      device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
    228    }
    229    switch (device.type) {
    230    case 0:
    231      /* device doesn't do input or output, aliens probably involved */
    232      continue;
    233    case CUBEB_DEVICE_TYPE_INPUT:
    234      if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
    235        /* this device is input only, not scanning for those, skip it */
    236        continue;
    237      }
    238      break;
    239    case CUBEB_DEVICE_TYPE_OUTPUT:
    240      if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
    241        /* this device is output only, not scanning for those, skip it */
    242        continue;
    243      }
    244      break;
    245    }
    246    if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
    247      prinfo = &hwfmt.record;
    248    }
    249    if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
    250      prinfo = &hwfmt.play;
    251    }
    252 #endif
    253    if (i > 0) {
    254      (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
    255                     hwname.name, hwname.version, hwname.config, i - 1);
    256    } else {
    257      (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
    258                     hwname.name, hwname.version, hwname.config);
    259    }
    260    device.devid = (void *)(uintptr_t)i;
    261    device.device_id = strdup(dev);
    262    device.friendly_name = strdup(dev_friendly);
    263    device.group_id = strdup(dev);
    264    device.vendor_name = strdup(hwname.name);
    265    device.type = type;
    266    device.state = CUBEB_DEVICE_STATE_ENABLED;
    267    device.preferred =
    268        (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
    269 #ifdef AUDIO_GETFORMAT
    270    device.max_channels = prinfo->channels;
    271    device.default_rate = prinfo->sample_rate;
    272 #else
    273    device.max_channels = 2;
    274    device.default_rate = SUN_PREFER_RATE;
    275 #endif
    276    device.default_format = CUBEB_DEVICE_FMT_S16NE;
    277    device.format = CUBEB_DEVICE_FMT_S16NE;
    278    device.min_rate = SUN_MIN_RATE;
    279    device.max_rate = SUN_MAX_RATE;
    280    device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
    281    device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
    282    collection->device[collection->count++] = device;
    283  }
    284  return CUBEB_OK;
    285 }
    286 
    287 static int
    288 sun_device_collection_destroy(cubeb * context,
    289                              cubeb_device_collection * collection)
    290 {
    291  unsigned i;
    292 
    293  for (i = 0; i < collection->count; ++i) {
    294    free((char *)collection->device[i].device_id);
    295    free((char *)collection->device[i].friendly_name);
    296    free((char *)collection->device[i].group_id);
    297    free((char *)collection->device[i].vendor_name);
    298  }
    299  free(collection->device);
    300  return CUBEB_OK;
    301 }
    302 
    303 static int
    304 sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
    305                struct audio_info * info, struct audio_prinfo * prinfo)
    306 {
    307  prinfo->channels = params->channels;
    308  prinfo->sample_rate = params->rate;
    309 #ifdef AUDIO_ENCODING_SLINEAR_LE
    310  switch (params->format) {
    311  case CUBEB_SAMPLE_S16LE:
    312    prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
    313    prinfo->precision = 16;
    314    break;
    315  case CUBEB_SAMPLE_S16BE:
    316    prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
    317    prinfo->precision = 16;
    318    break;
    319  case CUBEB_SAMPLE_FLOAT32NE:
    320    prinfo->encoding = AUDIO_ENCODING_SLINEAR;
    321    prinfo->precision = 32;
    322    break;
    323  default:
    324    LOG("Unsupported format");
    325    return CUBEB_ERROR_INVALID_FORMAT;
    326  }
    327 #else
    328  switch (params->format) {
    329  case CUBEB_SAMPLE_S16NE:
    330    prinfo->encoding = AUDIO_ENCODING_LINEAR;
    331    prinfo->precision = 16;
    332    break;
    333  case CUBEB_SAMPLE_FLOAT32NE:
    334    prinfo->encoding = AUDIO_ENCODING_LINEAR;
    335    prinfo->precision = 32;
    336    break;
    337  default:
    338    LOG("Unsupported format");
    339    return CUBEB_ERROR_INVALID_FORMAT;
    340  }
    341 #endif
    342  if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
    343    return CUBEB_ERROR;
    344  }
    345  if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
    346    return CUBEB_ERROR;
    347  }
    348  return CUBEB_OK;
    349 }
    350 
    351 static int
    352 sun_stream_stop(cubeb_stream * s)
    353 {
    354  pthread_mutex_lock(&s->mutex);
    355  if (s->running) {
    356    s->running = false;
    357    pthread_mutex_unlock(&s->mutex);
    358    pthread_join(s->thread, NULL);
    359  } else {
    360    pthread_mutex_unlock(&s->mutex);
    361  }
    362  return CUBEB_OK;
    363 }
    364 
    365 static void
    366 sun_stream_destroy(cubeb_stream * s)
    367 {
    368  sun_stream_stop(s);
    369  pthread_mutex_destroy(&s->mutex);
    370  if (s->play.fd != -1) {
    371    close(s->play.fd);
    372  }
    373  if (s->record.fd != -1) {
    374    close(s->record.fd);
    375  }
    376  free(s->play.buf);
    377  free(s->record.buf);
    378  free(s);
    379 }
    380 
    381 static void
    382 sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
    383 {
    384  float * in = buf;
    385  int32_t * out = buf;
    386  int32_t * tail = out + sample_count;
    387 
    388  while (out < tail) {
    389    float f = *(in++) * vol;
    390    if (f < -1.0)
    391      f = -1.0;
    392    else if (f > 1.0)
    393      f = 1.0;
    394    *(out++) = f * (float)INT32_MAX;
    395  }
    396 }
    397 
    398 static void
    399 sun_linear32_to_float(void * buf, unsigned sample_count)
    400 {
    401  int32_t * in = buf;
    402  float * out = buf;
    403  float * tail = out + sample_count;
    404 
    405  while (out < tail) {
    406    *(out++) = (1.0 / 0x80000000) * *(in++);
    407  }
    408 }
    409 
    410 static void
    411 sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
    412 {
    413  unsigned i;
    414  int32_t multiplier = vol * 0x8000;
    415 
    416  for (i = 0; i < sample_count; ++i) {
    417    buf[i] = (buf[i] * multiplier) >> 15;
    418  }
    419 }
    420 
    421 static void *
    422 sun_io_routine(void * arg)
    423 {
    424  cubeb_stream * s = arg;
    425  cubeb_state state = CUBEB_STATE_STARTED;
    426  size_t to_read = 0;
    427  long to_write = 0;
    428  size_t write_ofs = 0;
    429  size_t read_ofs = 0;
    430  int drain = 0;
    431 
    432  CUBEB_REGISTER_THREAD("cubeb rendering thread");
    433 
    434  s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
    435  while (state != CUBEB_STATE_ERROR) {
    436    pthread_mutex_lock(&s->mutex);
    437    if (!s->running) {
    438      pthread_mutex_unlock(&s->mutex);
    439      state = CUBEB_STATE_STOPPED;
    440      break;
    441    }
    442    pthread_mutex_unlock(&s->mutex);
    443    if (s->record.fd != -1 && s->record.floating) {
    444      sun_linear32_to_float(s->record.buf,
    445                            s->record.info.record.channels * SUN_BUFFER_FRAMES);
    446    }
    447    to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
    448                          SUN_BUFFER_FRAMES);
    449    if (to_write == CUBEB_ERROR) {
    450      state = CUBEB_STATE_ERROR;
    451      break;
    452    }
    453    if (s->play.fd != -1) {
    454      float vol;
    455 
    456      pthread_mutex_lock(&s->mutex);
    457      vol = s->volume;
    458      pthread_mutex_unlock(&s->mutex);
    459 
    460      if (s->play.floating) {
    461        sun_float_to_linear32(s->play.buf,
    462                              s->play.info.play.channels * to_write, vol);
    463      } else {
    464        sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
    465                             vol);
    466      }
    467    }
    468    if (to_write < SUN_BUFFER_FRAMES) {
    469      drain = 1;
    470    }
    471    to_write = s->play.fd != -1 ? to_write : 0;
    472    to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
    473    write_ofs = 0;
    474    read_ofs = 0;
    475    while (to_write > 0 || to_read > 0) {
    476      size_t bytes;
    477      ssize_t n, frames;
    478 
    479      if (to_write > 0) {
    480        bytes = to_write * s->play.frame_size;
    481        if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
    482            0) {
    483          state = CUBEB_STATE_ERROR;
    484          break;
    485        }
    486        frames = n / s->play.frame_size;
    487        pthread_mutex_lock(&s->mutex);
    488        s->frames_written += frames;
    489        pthread_mutex_unlock(&s->mutex);
    490        to_write -= frames;
    491        write_ofs += n;
    492      }
    493      if (to_read > 0) {
    494        bytes = to_read * s->record.frame_size;
    495        if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
    496                      bytes)) < 0) {
    497          state = CUBEB_STATE_ERROR;
    498          break;
    499        }
    500        frames = n / s->record.frame_size;
    501        to_read -= frames;
    502        read_ofs += n;
    503      }
    504    }
    505    if (drain && state != CUBEB_STATE_ERROR) {
    506      state = CUBEB_STATE_DRAINED;
    507      break;
    508    }
    509  }
    510  s->state_cb(s, s->user_ptr, state);
    511  CUBEB_UNREGISTER_THREAD();
    512  return NULL;
    513 }
    514 
    515 static int
    516 sun_stream_init(cubeb * context, cubeb_stream ** stream,
    517                char const * stream_name, cubeb_devid input_device,
    518                cubeb_stream_params * input_stream_params,
    519                cubeb_devid output_device,
    520                cubeb_stream_params * output_stream_params,
    521                unsigned latency_frames, cubeb_data_callback data_callback,
    522                cubeb_state_callback state_callback, void * user_ptr)
    523 {
    524  int ret = CUBEB_OK;
    525  cubeb_stream * s = NULL;
    526 
    527  (void)stream_name;
    528  (void)latency_frames;
    529  if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
    530    ret = CUBEB_ERROR;
    531    goto error;
    532  }
    533  s->record.fd = -1;
    534  s->play.fd = -1;
    535  if (input_device != 0) {
    536    snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
    537             (uintptr_t)input_device - 1);
    538  } else {
    539    snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
    540  }
    541  if (output_device != 0) {
    542    snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
    543             (uintptr_t)output_device - 1);
    544  } else {
    545    snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
    546  }
    547  if (input_stream_params != NULL) {
    548    if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
    549      LOG("Loopback not supported");
    550      ret = CUBEB_ERROR_NOT_SUPPORTED;
    551      goto error;
    552    }
    553    if (s->record.fd == -1) {
    554      if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
    555        LOG("Audio device could not be opened as read-only");
    556        ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
    557        goto error;
    558      }
    559    }
    560    AUDIO_INITINFO(&s->record.info);
    561 #ifdef AUMODE_RECORD
    562    s->record.info.mode = AUMODE_RECORD;
    563 #endif
    564    if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
    565                               &s->record.info, &s->record.info.record)) !=
    566        CUBEB_OK) {
    567      LOG("Setting record params failed");
    568      goto error;
    569    }
    570    s->record.floating =
    571        (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
    572  }
    573  if (output_stream_params != NULL) {
    574    if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
    575      LOG("Loopback not supported");
    576      ret = CUBEB_ERROR_NOT_SUPPORTED;
    577      goto error;
    578    }
    579    if (s->play.fd == -1) {
    580      if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
    581        LOG("Audio device could not be opened as write-only");
    582        ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
    583        goto error;
    584      }
    585    }
    586    AUDIO_INITINFO(&s->play.info);
    587 #ifdef AUMODE_PLAY
    588    s->play.info.mode = AUMODE_PLAY;
    589 #endif
    590    if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
    591                               &s->play.info, &s->play.info.play)) !=
    592        CUBEB_OK) {
    593      LOG("Setting play params failed");
    594      goto error;
    595    }
    596    s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
    597  }
    598  s->context = context;
    599  s->volume = 1.0;
    600  s->state_cb = state_callback;
    601  s->data_cb = data_callback;
    602  s->user_ptr = user_ptr;
    603  if (pthread_mutex_init(&s->mutex, NULL) != 0) {
    604    LOG("Failed to create mutex");
    605    goto error;
    606  }
    607  s->play.frame_size =
    608      s->play.info.play.channels * (s->play.info.play.precision / 8);
    609  if (s->play.fd != -1 &&
    610      (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
    611    ret = CUBEB_ERROR;
    612    goto error;
    613  }
    614  s->record.frame_size =
    615      s->record.info.record.channels * (s->record.info.record.precision / 8);
    616  if (s->record.fd != -1 &&
    617      (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
    618          NULL) {
    619    ret = CUBEB_ERROR;
    620    goto error;
    621  }
    622  *stream = s;
    623  return CUBEB_OK;
    624 error:
    625  if (s != NULL) {
    626    sun_stream_destroy(s);
    627  }
    628  return ret;
    629 }
    630 
    631 static int
    632 sun_stream_start(cubeb_stream * s)
    633 {
    634  s->running = true;
    635  if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
    636    LOG("Couldn't create thread");
    637    return CUBEB_ERROR;
    638  }
    639  return CUBEB_OK;
    640 }
    641 
    642 static int
    643 sun_stream_get_position(cubeb_stream * s, uint64_t * position)
    644 {
    645 #ifdef AUDIO_GETOOFFS
    646  struct audio_offset offset;
    647 
    648  if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
    649    return CUBEB_ERROR;
    650  }
    651  s->blocks_written += offset.deltablks;
    652  *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
    653  return CUBEB_OK;
    654 #else
    655  pthread_mutex_lock(&s->mutex);
    656  *position = s->frames_written;
    657  pthread_mutex_unlock(&s->mutex);
    658  return CUBEB_OK;
    659 #endif
    660 }
    661 
    662 static int
    663 sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
    664 {
    665 #ifdef AUDIO_GETBUFINFO
    666  struct audio_info info;
    667 
    668  if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
    669    return CUBEB_ERROR;
    670  }
    671 
    672  *latency = (info.play.seek + info.blocksize) / s->play.frame_size;
    673  return CUBEB_OK;
    674 #else
    675  cubeb_stream_params params;
    676 
    677  params.rate = s->play.info.play.sample_rate;
    678 
    679  return sun_get_min_latency(NULL, params, latency);
    680 #endif
    681 }
    682 
    683 static int
    684 sun_stream_set_volume(cubeb_stream * stream, float volume)
    685 {
    686  pthread_mutex_lock(&stream->mutex);
    687  stream->volume = volume;
    688  pthread_mutex_unlock(&stream->mutex);
    689  return CUBEB_OK;
    690 }
    691 
    692 static int
    693 sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
    694 {
    695  *device = calloc(1, sizeof(cubeb_device));
    696  if (*device == NULL) {
    697    return CUBEB_ERROR;
    698  }
    699  (*device)->input_name =
    700      stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
    701  (*device)->output_name =
    702      stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
    703  return CUBEB_OK;
    704 }
    705 
    706 static int
    707 sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
    708 {
    709  (void)stream;
    710  free(device->input_name);
    711  free(device->output_name);
    712  free(device);
    713  return CUBEB_OK;
    714 }
    715 
    716 static struct cubeb_ops const sun_ops = {
    717    .init = sun_init,
    718    .get_backend_id = sun_get_backend_id,
    719    .get_max_channel_count = sun_get_max_channel_count,
    720    .get_min_latency = sun_get_min_latency,
    721    .get_preferred_sample_rate = sun_get_preferred_sample_rate,
    722    .get_supported_input_processing_params = NULL,
    723    .enumerate_devices = sun_enumerate_devices,
    724    .device_collection_destroy = sun_device_collection_destroy,
    725    .destroy = sun_destroy,
    726    .stream_init = sun_stream_init,
    727    .stream_destroy = sun_stream_destroy,
    728    .stream_start = sun_stream_start,
    729    .stream_stop = sun_stream_stop,
    730    .stream_get_position = sun_stream_get_position,
    731    .stream_get_latency = sun_stream_get_latency,
    732    .stream_get_input_latency = NULL,
    733    .stream_set_volume = sun_stream_set_volume,
    734    .stream_set_name = NULL,
    735    .stream_get_current_device = sun_get_current_device,
    736    .stream_set_input_mute = NULL,
    737    .stream_set_input_processing_params = NULL,
    738    .stream_device_destroy = sun_stream_device_destroy,
    739    .stream_register_device_changed_callback = NULL,
    740    .register_device_collection_changed = NULL};