зеркало из https://github.com/mozilla/cubeb.git
sunaudio (NetBSD/illumos) backend (#510)
* Initial support. Device scanning and sanity checks work. Playback and recording does not. * stream_init fixes. Audio test now runs, though doesn't do anything. * Set the prinfo type to the device type we'll later be limited to. * Playback support. Also implement stream_get_current_device and stream_device_destroy. * AUDIO_SETINFO properly. * Don't poll forever when recording. Use the recommended number of latency_frames as our BUFSIZE. * Fix typo. * Don't allocate devid. Required for a sanity test. * Fix some tests * Initial attempt to support Solaris * stdlib.h for calloc. * undo local change * Should probably be using "events" here, and the state change isn't necessary now that it's done elsewhere. * Avoid format truncation warning. * more illumos fixes * Performance improvements. It seems polling for input is not a viable strategy. * Protect volume and frames_written with a mutex. * Protect running with a mutex.
This commit is contained in:
Родитель
03eb237c3b
Коммит
9f39687e7f
|
@ -225,6 +225,14 @@ if(USE_SNDIO)
|
|||
target_link_libraries(cubeb PRIVATE sndio)
|
||||
endif()
|
||||
|
||||
check_include_files(sys/audioio.h USE_SUN)
|
||||
if(USE_SUN)
|
||||
target_sources(cubeb PRIVATE
|
||||
src/cubeb_sun.c)
|
||||
target_compile_definitions(cubeb PRIVATE USE_SUN)
|
||||
target_link_libraries(cubeb PRIVATE pthread)
|
||||
endif()
|
||||
|
||||
check_include_files(kai.h USE_KAI)
|
||||
if(USE_KAI)
|
||||
target_sources(cubeb PRIVATE
|
||||
|
|
10
src/cubeb.c
10
src/cubeb.c
|
@ -54,6 +54,9 @@ int wasapi_init(cubeb ** context, char const * context_name);
|
|||
#if defined(USE_SNDIO)
|
||||
int sndio_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
int sun_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_OPENSL)
|
||||
int opensl_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
|
@ -154,6 +157,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
|
|||
} else if (!strcmp(backend_name, "sndio")) {
|
||||
#if defined(USE_SNDIO)
|
||||
init_oneshot = sndio_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "sun")) {
|
||||
#if defined(USE_SUN)
|
||||
init_oneshot = sun_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "opensl")) {
|
||||
#if defined(USE_OPENSL)
|
||||
|
@ -205,6 +212,9 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
|
|||
#if defined(USE_SNDIO)
|
||||
sndio_init,
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
sun_init,
|
||||
#endif
|
||||
#if defined(USE_OPENSL)
|
||||
opensl_init,
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
* Copyright © 2019 Nia Alarie
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#include <sys/audioio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
|
||||
#define BYTES_TO_FRAMES(bytes, channels) \
|
||||
(bytes / (channels * sizeof(int16_t)))
|
||||
|
||||
#define FRAMES_TO_BYTES(frames, channels) \
|
||||
(frames * (channels * sizeof(int16_t)))
|
||||
|
||||
/* Default to 4 + 1 for the default device. */
|
||||
#ifndef SUN_DEVICE_COUNT
|
||||
#define SUN_DEVICE_COUNT (5)
|
||||
#endif
|
||||
|
||||
/* Supported well by most hardware. */
|
||||
#ifndef SUN_PREFER_RATE
|
||||
#define SUN_PREFER_RATE (48000)
|
||||
#endif
|
||||
|
||||
/* Standard acceptable minimum. */
|
||||
#ifndef SUN_LATENCY_MS
|
||||
#define SUN_LATENCY_MS (40)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_DEFAULT_DEVICE
|
||||
#define SUN_DEFAULT_DEVICE "/dev/audio"
|
||||
#endif
|
||||
|
||||
#ifndef SUN_POLL_TIMEOUT
|
||||
#define SUN_POLL_TIMEOUT (1000)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_BUFFER_FRAMES
|
||||
#define SUN_BUFFER_FRAMES (32)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Supported on NetBSD regardless of hardware.
|
||||
*/
|
||||
|
||||
#ifndef SUN_MAX_CHANNELS
|
||||
# ifdef __NetBSD__
|
||||
# define SUN_MAX_CHANNELS (12)
|
||||
# else
|
||||
# define SUN_MAX_CHANNELS (2)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef SUN_MIN_RATE
|
||||
#define SUN_MIN_RATE (1000)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_MAX_RATE
|
||||
#define SUN_MAX_RATE (192000)
|
||||
#endif
|
||||
|
||||
static struct cubeb_ops const sun_ops;
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
struct cubeb * context;
|
||||
void * user_ptr;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex; /* protects running, volume, frames_written */
|
||||
int floating;
|
||||
int running;
|
||||
int play_fd;
|
||||
int record_fd;
|
||||
float volume;
|
||||
struct audio_info p_info; /* info for the play fd */
|
||||
struct audio_info r_info; /* info for the record fd */
|
||||
cubeb_data_callback data_cb;
|
||||
cubeb_state_callback state_cb;
|
||||
int16_t * play_buf;
|
||||
int16_t * record_buf;
|
||||
float * f_play_buf;
|
||||
float * f_record_buf;
|
||||
char input_name[32];
|
||||
char output_name[32];
|
||||
uint64_t frames_written;
|
||||
};
|
||||
|
||||
int
|
||||
sun_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
cubeb * c;
|
||||
|
||||
(void)context_name;
|
||||
if ((c = calloc(1, sizeof(cubeb))) == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
c->ops = &sun_ops;
|
||||
*context = c;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sun_destroy(cubeb * context)
|
||||
{
|
||||
free(context);
|
||||
}
|
||||
|
||||
static char const *
|
||||
sun_get_backend_id(cubeb * context)
|
||||
{
|
||||
return "sun";
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*rate = SUN_PREFER_RATE;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*max_channels = SUN_MAX_CHANNELS;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_min_latency(cubeb * context, cubeb_stream_params params,
|
||||
uint32_t * latency_frames)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*latency_frames = SUN_LATENCY_MS * params.rate / 1000;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_hwinfo(const char * device, struct audio_info * format,
|
||||
int * props, struct audio_device * dev)
|
||||
{
|
||||
int fd = -1;
|
||||
|
||||
if ((fd = open(device, O_RDONLY)) == -1) {
|
||||
goto error;
|
||||
}
|
||||
#ifdef AUDIO_GETFORMAT
|
||||
if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
#ifdef AUDIO_GETPROPS
|
||||
if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: PR kern/54264
|
||||
*/
|
||||
static int
|
||||
sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
|
||||
{
|
||||
return prinfo->precision >= 8 && prinfo->precision <= 32 &&
|
||||
prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
|
||||
prinfo->sample_rate < SUN_MAX_RATE && prinfo->sample_rate > SUN_MIN_RATE;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
unsigned i;
|
||||
cubeb_device_info device = {0};
|
||||
char dev[16] = SUN_DEFAULT_DEVICE;
|
||||
char dev_friendly[64];
|
||||
struct audio_info hwfmt;
|
||||
struct audio_device hwname;
|
||||
struct audio_prinfo *prinfo = NULL;
|
||||
int hwprops;
|
||||
|
||||
collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
|
||||
if (collection->device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
collection->count = 0;
|
||||
|
||||
for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
|
||||
if (i > 0) {
|
||||
(void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
|
||||
}
|
||||
if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
|
||||
continue;
|
||||
}
|
||||
#ifdef AUDIO_GETPROPS
|
||||
device.type = 0;
|
||||
if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
|
||||
sun_prinfo_verify_sanity(&hwfmt.record)) {
|
||||
/* the device supports recording, probably */
|
||||
device.type |= CUBEB_DEVICE_TYPE_INPUT;
|
||||
}
|
||||
if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
|
||||
sun_prinfo_verify_sanity(&hwfmt.play)) {
|
||||
/* the device supports playback, probably */
|
||||
device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
|
||||
}
|
||||
switch (device.type) {
|
||||
case 0:
|
||||
/* device doesn't do input or output, aliens probably involved */
|
||||
continue;
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
|
||||
/* this device is input only, not scanning for those, skip it */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
|
||||
/* this device is output only, not scanning for those, skip it */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
|
||||
prinfo = &hwfmt.record;
|
||||
}
|
||||
if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
|
||||
prinfo = &hwfmt.play;
|
||||
}
|
||||
#endif
|
||||
if (i > 0) {
|
||||
(void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
|
||||
hwname.name, hwname.version, hwname.config, i - 1);
|
||||
} else {
|
||||
(void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
|
||||
hwname.name, hwname.version, hwname.config);
|
||||
}
|
||||
device.devid = (void *)(uintptr_t)i;
|
||||
device.device_id = strdup(dev);
|
||||
device.friendly_name = strdup(dev_friendly);
|
||||
device.group_id = strdup(dev);
|
||||
device.vendor_name = strdup(hwname.name);
|
||||
device.type = type;
|
||||
device.state = CUBEB_DEVICE_STATE_ENABLED;
|
||||
device.preferred = (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
|
||||
#ifdef AUDIO_GETFORMAT
|
||||
device.max_channels = prinfo->channels;
|
||||
device.default_rate = prinfo->sample_rate;
|
||||
#else
|
||||
device.max_channels = 2;
|
||||
device.default_rate = SUN_PREFER_RATE;
|
||||
#endif
|
||||
device.default_format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device.format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device.min_rate = SUN_MIN_RATE;
|
||||
device.max_rate = SUN_MAX_RATE;
|
||||
device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
|
||||
device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
|
||||
collection->device[collection->count++] = device;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_device_collection_destroy(cubeb * context,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < collection->count; ++i) {
|
||||
free((char *)collection->device[i].device_id);
|
||||
free((char *)collection->device[i].friendly_name);
|
||||
free((char *)collection->device[i].group_id);
|
||||
free((char *)collection->device[i].vendor_name);
|
||||
}
|
||||
free(collection->device);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
|
||||
struct audio_info * info, struct audio_prinfo * prinfo)
|
||||
{
|
||||
prinfo->channels = params->channels;
|
||||
prinfo->sample_rate = params->rate;
|
||||
prinfo->precision = 16;
|
||||
#ifdef AUDIO_ENCODING_SLINEAR_LE
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
stream->floating = 1;
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
#else
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
stream->floating = 1;
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
#endif
|
||||
if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_stop(cubeb_stream * s)
|
||||
{
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (s->running) {
|
||||
s->running = 0;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
pthread_join(s->thread, NULL);
|
||||
} else {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sun_stream_destroy(cubeb_stream * s)
|
||||
{
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
sun_stream_stop(s);
|
||||
if (s->play_fd != -1) {
|
||||
close(s->play_fd);
|
||||
}
|
||||
if (s->record_fd != -1) {
|
||||
close(s->record_fd);
|
||||
}
|
||||
free(s->f_play_buf);
|
||||
free(s->f_record_buf);
|
||||
free(s->play_buf);
|
||||
free(s->record_buf);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void
|
||||
sun_float_to_linear(float * in, int16_t * out,
|
||||
unsigned channels, long frames, float vol)
|
||||
{
|
||||
unsigned i, sample_count = frames * channels;
|
||||
float multiplier = vol * 0x8000;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
int32_t sample = lrintf(in[i] * multiplier);
|
||||
if (sample < -0x8000) {
|
||||
out[i] = -0x8000;
|
||||
} else if (sample > 0x7fff) {
|
||||
out[i] = 0x7fff;
|
||||
} else {
|
||||
out[i] = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear_to_float(int16_t * in, float * out,
|
||||
unsigned channels, long frames)
|
||||
{
|
||||
unsigned i, sample_count = frames * channels;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
out[i] = (1.0 / 0x8000) * in[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol)
|
||||
{
|
||||
unsigned i, sample_count = frames * channels;
|
||||
int32_t multiplier = vol * 0x8000;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
buf[i] = (buf[i] * multiplier) >> 15;
|
||||
}
|
||||
}
|
||||
|
||||
static void *
|
||||
sun_io_routine(void * arg)
|
||||
{
|
||||
cubeb_stream *s = arg;
|
||||
cubeb_state state = CUBEB_STATE_STARTED;
|
||||
size_t to_read = 0;
|
||||
long to_write = 0;
|
||||
size_t write_ofs = 0;
|
||||
size_t read_ofs = 0;
|
||||
struct pollfd pfds[1];
|
||||
int drain = 0;
|
||||
|
||||
s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
|
||||
while (state != CUBEB_STATE_ERROR) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->running) {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
state = CUBEB_STATE_STOPPED;
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
if (s->floating) {
|
||||
if (s->record_fd != -1) {
|
||||
sun_linear_to_float(s->record_buf, s->f_record_buf,
|
||||
s->r_info.record.channels, SUN_BUFFER_FRAMES);
|
||||
}
|
||||
to_write = s->data_cb(s, s->user_ptr,
|
||||
s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play_fd != -1) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
sun_float_to_linear(s->f_play_buf, s->play_buf,
|
||||
s->p_info.play.channels, to_write, s->volume);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
} else {
|
||||
to_write = s->data_cb(s, s->user_ptr,
|
||||
s->record_buf, s->play_buf, SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play_fd != -1) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
}
|
||||
if (to_write < SUN_BUFFER_FRAMES) {
|
||||
drain = 1;
|
||||
}
|
||||
pfds[0].fd = s->play_fd;
|
||||
if (s->play_fd != -1) {
|
||||
if (to_write > 0) {
|
||||
pfds[0].events = POLLOUT;
|
||||
}
|
||||
} else {
|
||||
pfds[0].events = 0;
|
||||
to_write = 0;
|
||||
}
|
||||
to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0;
|
||||
write_ofs = 0;
|
||||
read_ofs = 0;
|
||||
while (to_write > 0 || to_read > 0) {
|
||||
size_t bytes;
|
||||
ssize_t n, frames;
|
||||
|
||||
if (pfds[0].events != 0 && poll(pfds, 1, SUN_POLL_TIMEOUT) == -1) {
|
||||
LOG("poll failed");
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if ((pfds[0].revents & POLLHUP) || (pfds[0].revents & POLLERR)) {
|
||||
LOG("audio device disconnected");
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (to_write > 0 && (pfds[0].revents & POLLOUT)) {
|
||||
bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels);
|
||||
if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = BYTES_TO_FRAMES(n, s->p_info.play.channels);
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
s->frames_written += frames;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
to_write -= frames;
|
||||
write_ofs += frames;
|
||||
if (to_write == 0) {
|
||||
pfds[0].events = 0;
|
||||
}
|
||||
}
|
||||
if (to_read > 0) {
|
||||
bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels);
|
||||
if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = BYTES_TO_FRAMES(n, s->r_info.record.channels);
|
||||
to_read -= frames;
|
||||
read_ofs += frames;
|
||||
}
|
||||
}
|
||||
if (drain && state != CUBEB_STATE_ERROR) {
|
||||
state = CUBEB_STATE_DRAINED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->state_cb(s, s->user_ptr, state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_init(cubeb * context,
|
||||
cubeb_stream ** stream,
|
||||
char const * stream_name,
|
||||
cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned latency_frames,
|
||||
cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback,
|
||||
void * user_ptr)
|
||||
{
|
||||
int ret = CUBEB_OK;
|
||||
cubeb_stream *s = NULL;
|
||||
|
||||
(void)stream_name;
|
||||
(void)latency_frames;
|
||||
if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->record_fd = -1;
|
||||
s->play_fd = -1;
|
||||
if (input_device != 0) {
|
||||
snprintf(s->input_name, sizeof(s->input_name),
|
||||
"/dev/audio%zu", (uintptr_t)input_device - 1);
|
||||
} else {
|
||||
snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (output_device != 0) {
|
||||
snprintf(s->output_name, sizeof(s->output_name),
|
||||
"/dev/audio%zu", (uintptr_t)output_device - 1);
|
||||
} else {
|
||||
snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (input_stream_params != NULL) {
|
||||
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
LOG("Loopback not supported");
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->record_fd == -1) {
|
||||
if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) {
|
||||
LOG("Audio device cannot be opened as read-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->r_info);
|
||||
#ifdef AUMODE_RECORD
|
||||
s->r_info.mode = AUMODE_RECORD;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->record_fd, s, input_stream_params,
|
||||
&s->r_info, &s->r_info.record)) != CUBEB_OK) {
|
||||
LOG("Setting record params failed");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (output_stream_params != NULL) {
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
LOG("Loopback not supported");
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->play_fd == -1) {
|
||||
if ((s->play_fd = open(s->output_name, O_WRONLY | O_NONBLOCK)) == -1) {
|
||||
LOG("Audio device cannot be opened as write-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->p_info);
|
||||
#ifdef AUMODE_PLAY
|
||||
s->p_info.mode = AUMODE_PLAY;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->play_fd, s, output_stream_params,
|
||||
&s->p_info, &s->p_info.play)) != CUBEB_OK) {
|
||||
LOG("Setting play params failed");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
s->context = context;
|
||||
s->volume = 1.0;
|
||||
s->state_cb = state_callback;
|
||||
s->data_cb = data_callback;
|
||||
s->user_ptr = user_ptr;
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
LOG("Failed to create mutex");
|
||||
goto error;
|
||||
}
|
||||
if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->p_info.play.channels * sizeof(int16_t))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->r_info.record.channels * sizeof(int16_t))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
if (s->floating) {
|
||||
if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->p_info.play.channels * sizeof(float))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->r_info.record.channels * sizeof(float))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
*stream = s;
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
if (s != NULL) {
|
||||
sun_stream_destroy(s);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_start(cubeb_stream * s)
|
||||
{
|
||||
s->running = 1;
|
||||
if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
|
||||
LOG("Couldn't create thread");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_position(cubeb_stream * s, uint64_t * position)
|
||||
{
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
*position = s->frames_written;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||
{
|
||||
#ifdef AUDIO_GETBUFINFO
|
||||
struct audio_info info;
|
||||
|
||||
if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize,
|
||||
info.play.channels);
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
cubeb_params params;
|
||||
|
||||
params.rate = stream->p_info.play.sample_rate;
|
||||
|
||||
return sun_get_min_latency(NULL, params, latency);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
{
|
||||
pthread_mutex_lock(&stream->mutex);
|
||||
stream->volume = volume;
|
||||
pthread_mutex_unlock(&stream->mutex);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
|
||||
{
|
||||
*device = calloc(1, sizeof(cubeb_device));
|
||||
if (*device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
(*device)->input_name = stream->record_fd != -1 ?
|
||||
strdup(stream->input_name) : NULL;
|
||||
(*device)->output_name = stream->play_fd != -1 ?
|
||||
strdup(stream->output_name) : NULL;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
|
||||
{
|
||||
(void)stream;
|
||||
free(device->input_name);
|
||||
free(device->output_name);
|
||||
free(device);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const sun_ops = {
|
||||
.init = sun_init,
|
||||
.get_backend_id = sun_get_backend_id,
|
||||
.get_max_channel_count = sun_get_max_channel_count,
|
||||
.get_min_latency = sun_get_min_latency,
|
||||
.get_preferred_sample_rate = sun_get_preferred_sample_rate,
|
||||
.enumerate_devices = sun_enumerate_devices,
|
||||
.device_collection_destroy = sun_device_collection_destroy,
|
||||
.destroy = sun_destroy,
|
||||
.stream_init = sun_stream_init,
|
||||
.stream_destroy = sun_stream_destroy,
|
||||
.stream_start = sun_stream_start,
|
||||
.stream_stop = sun_stream_stop,
|
||||
.stream_reset_default_device = NULL,
|
||||
.stream_get_position = sun_stream_get_position,
|
||||
.stream_get_latency = sun_stream_get_latency,
|
||||
.stream_set_volume = sun_stream_set_volume,
|
||||
.stream_set_panning = NULL,
|
||||
.stream_get_current_device = sun_get_current_device,
|
||||
.stream_device_destroy = sun_stream_device_destroy,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL
|
||||
};
|
Загрузка…
Ссылка в новой задаче