505 lines
13 KiB
C
505 lines
13 KiB
C
/*
|
|
* Copyright (c) 2013, 2017 Ginn Chen <ginnchen@gmail.com>
|
|
*
|
|
* This program is made available under an ISC-style license. See the
|
|
* accompanying file LICENSE for details.
|
|
*/
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/audio.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <sys/stropts.h>
|
|
#include "cubeb/cubeb.h"
|
|
#include "cubeb-internal.h"
|
|
|
|
/* Macros copied from audio_oss.h */
|
|
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
*
|
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
* or http://www.opensolaris.org/os/licensing.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
*
|
|
* When distributing Covered Code, include this CDDL HEADER in each
|
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
* If applicable, add the following below this CDDL HEADER, with the
|
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
*
|
|
* CDDL HEADER END
|
|
*/
|
|
/*
|
|
* Copyright (C) 4Front Technologies 1996-2008.
|
|
*
|
|
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
|
* Use is subject to license terms.
|
|
*/
|
|
#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */
|
|
#define OSSIOC_VOID 0x00000000 /* no parameters */
|
|
#define OSSIOC_OUT 0x20000000 /* copy out parameters */
|
|
#define OSSIOC_IN 0x40000000 /* copy in parameters */
|
|
#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT)
|
|
#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16)
|
|
#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y))
|
|
#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y))
|
|
#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y))
|
|
#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int)
|
|
#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int)
|
|
#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */
|
|
#define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int)
|
|
#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34)
|
|
#define AFMT_S16_LE 0x00000010
|
|
#define AFMT_S16_BE 0x00000020
|
|
|
|
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
|
|
#define AFMT_S16_NE AFMT_S16_BE
|
|
#else
|
|
#define AFMT_S16_NE AFMT_S16_LE
|
|
#endif
|
|
|
|
#define DEFAULT_AUDIO_DEVICE "/dev/audio"
|
|
#define DEFAULT_DSP_DEVICE "/dev/dsp"
|
|
|
|
#define BUF_SIZE_MS 10
|
|
|
|
#if defined(CUBEB_SUNAUDIO_DEBUG)
|
|
#define DPR(...) fprintf(stderr, __VA_ARGS__);
|
|
#else
|
|
#define DPR(...) do {} while(0)
|
|
#endif
|
|
|
|
static struct cubeb_ops const sunaudio_ops;
|
|
|
|
struct cubeb {
|
|
struct cubeb_ops const * ops;
|
|
};
|
|
|
|
struct cubeb_stream {
|
|
cubeb * context;
|
|
pthread_t th; /* to run real-time audio i/o */
|
|
pthread_mutex_t mutex; /* protects fd and frm_played */
|
|
int fd; /* link us to sunaudio */
|
|
int active; /* cubec_start() called */
|
|
int conv; /* need float->s16 conversion */
|
|
int using_oss;
|
|
unsigned char *buf; /* data is prepared here */
|
|
unsigned int rate;
|
|
unsigned int n_channles;
|
|
unsigned int bytes_per_ch;
|
|
unsigned int n_frm;
|
|
unsigned int buffer_size;
|
|
int64_t frm_played;
|
|
cubeb_data_callback data_cb; /* cb to preapare data */
|
|
cubeb_state_callback state_cb; /* cb to notify about state changes */
|
|
void *arg; /* user arg to {data,state}_cb */
|
|
};
|
|
|
|
static void
|
|
float_to_s16(void *ptr, long nsamp)
|
|
{
|
|
int16_t *dst = ptr;
|
|
float *src = ptr;
|
|
|
|
while (nsamp-- > 0)
|
|
*(dst++) = *(src++) * 32767;
|
|
}
|
|
|
|
static void *
|
|
sunaudio_mainloop(void *arg)
|
|
{
|
|
struct cubeb_stream *s = arg;
|
|
int state;
|
|
|
|
DPR("sunaudio_mainloop()\n");
|
|
|
|
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
|
|
|
|
pthread_mutex_lock(&s->mutex);
|
|
DPR("sunaudio_mainloop(), started\n");
|
|
|
|
for (;;) {
|
|
if (!s->active) {
|
|
DPR("sunaudio_mainloop() stopped\n");
|
|
state = CUBEB_STATE_STOPPED;
|
|
break;
|
|
}
|
|
|
|
if (!s->using_oss) {
|
|
audio_info_t info;
|
|
ioctl(s->fd, AUDIO_GETINFO, &info);
|
|
if (s->frm_played > info.play.samples + 3 * s->n_frm) {
|
|
pthread_mutex_unlock(&s->mutex);
|
|
struct timespec ts = {0, 10000}; // 10 ms
|
|
nanosleep(&ts, NULL);
|
|
pthread_mutex_lock(&s->mutex);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&s->mutex);
|
|
unsigned int got = s->data_cb(s, s->arg, NULL, s->buf, s->n_frm);
|
|
DPR("sunaudio_mainloop() ask %d got %d\n", s->n_frm, got);
|
|
pthread_mutex_lock(&s->mutex);
|
|
|
|
if (got < 0) {
|
|
DPR("sunaudio_mainloop() cb err\n");
|
|
state = CUBEB_STATE_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (s->conv) {
|
|
float_to_s16(s->buf, got * s->n_channles);
|
|
}
|
|
|
|
unsigned int avail = got * 2 * s->n_channles; // coverted to s16
|
|
unsigned int pos = 0;
|
|
|
|
while (avail > 0 && s->active) {
|
|
int written = write(s->fd, s->buf + pos, avail);
|
|
if (written == -1) {
|
|
if (errno != EINTR && errno != EWOULDBLOCK) {
|
|
DPR("sunaudio_mainloop() write err\n");
|
|
state = CUBEB_STATE_ERROR;
|
|
break;
|
|
}
|
|
pthread_mutex_unlock(&s->mutex);
|
|
struct timespec ts = {0, 10000}; // 10 ms
|
|
nanosleep(&ts, NULL);
|
|
pthread_mutex_lock(&s->mutex);
|
|
} else {
|
|
pos += written;
|
|
DPR("sunaudio_mainloop() write %d pos %d\n", written, pos);
|
|
s->frm_played += written / 2 / s->n_channles;
|
|
avail -= written;
|
|
}
|
|
}
|
|
|
|
if ((got < s->n_frm)) {
|
|
DPR("sunaudio_mainloop() drained\n");
|
|
state = CUBEB_STATE_DRAINED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&s->mutex);
|
|
s->state_cb(s, s->arg, state);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*static*/ int
|
|
sunaudio_init(cubeb **context, char const *context_name)
|
|
{
|
|
DPR("sunaudio_init(%s)\n", context_name);
|
|
*context = malloc(sizeof(*context));
|
|
(*context)->ops = &sunaudio_ops;
|
|
(void)context_name;
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static char const *
|
|
sunaudio_get_backend_id(cubeb *context)
|
|
{
|
|
return "sunaudio";
|
|
}
|
|
|
|
static void
|
|
sunaudio_destroy(cubeb *context)
|
|
{
|
|
DPR("sunaudio_destroy()\n");
|
|
free(context);
|
|
}
|
|
|
|
static int
|
|
sunaudio_stream_init(cubeb *context,
|
|
cubeb_stream **stream,
|
|
char const *stream_name,
|
|
cubeb_devid input_device,
|
|
cubeb_stream_params * input_stream_params,
|
|
cubeb_devid output_device,
|
|
cubeb_stream_params * output_stream_params,
|
|
unsigned int latency,
|
|
cubeb_data_callback data_callback,
|
|
cubeb_state_callback state_callback,
|
|
void *user_ptr)
|
|
{
|
|
struct cubeb_stream *s;
|
|
DPR("sunaudio_stream_init(%s)\n", stream_name);
|
|
size_t size;
|
|
|
|
s = malloc(sizeof(struct cubeb_stream));
|
|
if (s == NULL)
|
|
return CUBEB_ERROR;
|
|
s->context = context;
|
|
|
|
// If UTAUDIODEV is set, use it with Sun Audio interface
|
|
char * sa_device_name = getenv("UTAUDIODEV");
|
|
char * dsp_device_name = NULL;
|
|
if (!sa_device_name) {
|
|
dsp_device_name = getenv("AUDIODSP");
|
|
if (!dsp_device_name) {
|
|
dsp_device_name = DEFAULT_DSP_DEVICE;
|
|
}
|
|
sa_device_name = getenv("AUDIODEV");
|
|
if (!sa_device_name) {
|
|
sa_device_name = DEFAULT_AUDIO_DEVICE;
|
|
}
|
|
}
|
|
|
|
s->using_oss = 0;
|
|
// Try to use OSS if available
|
|
if (dsp_device_name) {
|
|
s->fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK);
|
|
if (s->fd >= 0) {
|
|
s->using_oss = 1;
|
|
}
|
|
}
|
|
|
|
// Try Sun Audio
|
|
if (!s->using_oss) {
|
|
s->fd = open(sa_device_name, O_WRONLY | O_NONBLOCK);
|
|
}
|
|
|
|
if (s->fd < 0) {
|
|
free(s);
|
|
DPR("sunaudio_stream_init(), open() failed\n");
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
if (s->using_oss) {
|
|
if (ioctl(s->fd, SNDCTL_DSP_SPEED, &output_stream_params->rate) < 0) {
|
|
DPR("ioctl SNDCTL_DSP_SPEED failed.\n");
|
|
close(s->fd);
|
|
free(s);
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
if (ioctl(s->fd, SNDCTL_DSP_CHANNELS, &output_stream_params->channels) < 0) {
|
|
DPR("ioctl SNDCTL_DSP_CHANNELS failed.\n");
|
|
close(s->fd);
|
|
free(s);
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
int format = AFMT_S16_NE;
|
|
if (ioctl(s->fd, SNDCTL_DSP_SETFMT, &format) < 0) {
|
|
DPR("ioctl SNDCTL_DSP_SETFMT failed.\n");
|
|
close(s->fd);
|
|
free(s);
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
} else {
|
|
audio_info_t audio_info;
|
|
AUDIO_INITINFO(&audio_info)
|
|
audio_info.play.sample_rate = output_stream_params->rate;
|
|
audio_info.play.channels = output_stream_params->channels;
|
|
audio_info.play.encoding = AUDIO_ENCODING_LINEAR;
|
|
audio_info.play.precision = 16;
|
|
if (ioctl(s->fd, AUDIO_SETINFO, &audio_info) == -1) {
|
|
DPR("ioctl AUDIO_SETINFO failed.\n");
|
|
close(s->fd);
|
|
free(s);
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
}
|
|
|
|
s->conv = 0;
|
|
switch (output_stream_params->format) {
|
|
case CUBEB_SAMPLE_S16NE:
|
|
s->bytes_per_ch = 2;
|
|
break;
|
|
case CUBEB_SAMPLE_FLOAT32NE:
|
|
s->bytes_per_ch = 4;
|
|
s->conv = 1;
|
|
break;
|
|
default:
|
|
DPR("sunaudio_stream_init() unsupported format\n");
|
|
close(s->fd);
|
|
free(s);
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
s->active = 0;
|
|
s->rate = output_stream_params->rate;
|
|
s->n_channles = output_stream_params->channels;
|
|
s->data_cb = data_callback;
|
|
s->state_cb = state_callback;
|
|
s->arg = user_ptr;
|
|
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
|
free(s);
|
|
return CUBEB_ERROR;
|
|
}
|
|
s->frm_played = 0;
|
|
s->n_frm = s->rate * BUF_SIZE_MS / 1000;
|
|
s->buffer_size = s->bytes_per_ch * s->n_channles * s->n_frm;
|
|
s->buf = malloc(s->buffer_size);
|
|
if (s->buf == NULL) {
|
|
close(s->fd);
|
|
free(s);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
*stream = s;
|
|
DPR("sunaudio_stream_init() end, ok\n");
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static void
|
|
sunaudio_stream_destroy(cubeb_stream *s)
|
|
{
|
|
DPR("sunaudio_stream_destroy()\n");
|
|
if (s->fd > 0) {
|
|
// Flush buffer
|
|
if (s->using_oss) {
|
|
ioctl(s->fd, SNDCTL_DSP_HALT_OUTPUT);
|
|
} else {
|
|
ioctl(s->fd, I_FLUSH);
|
|
}
|
|
close(s->fd);
|
|
}
|
|
free(s->buf);
|
|
free(s);
|
|
}
|
|
|
|
static int
|
|
sunaudio_stream_start(cubeb_stream *s)
|
|
{
|
|
int err;
|
|
|
|
DPR("sunaudio_stream_start()\n");
|
|
s->active = 1;
|
|
err = pthread_create(&s->th, NULL, sunaudio_mainloop, s);
|
|
if (err) {
|
|
s->active = 0;
|
|
return CUBEB_ERROR;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
sunaudio_stream_stop(cubeb_stream *s)
|
|
{
|
|
void *dummy;
|
|
|
|
DPR("sunaudio_stream_stop()\n");
|
|
if (s->active) {
|
|
s->active = 0;
|
|
pthread_join(s->th, &dummy);
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
sunaudio_stream_get_position(cubeb_stream *s, uint64_t *p)
|
|
{
|
|
int rv = CUBEB_OK;
|
|
pthread_mutex_lock(&s->mutex);
|
|
if (s->active && s->fd > 0) {
|
|
if (s->using_oss) {
|
|
int delay;
|
|
ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay);
|
|
int64_t t = s->frm_played - delay / s->n_channles / 2;
|
|
if (t < 0) {
|
|
*p = 0;
|
|
} else {
|
|
*p = t;
|
|
}
|
|
} else {
|
|
audio_info_t info;
|
|
ioctl(s->fd, AUDIO_GETINFO, &info);
|
|
*p = info.play.samples;
|
|
}
|
|
DPR("sunaudio_stream_get_position() %lld\n", *p);
|
|
} else {
|
|
rv = CUBEB_ERROR;
|
|
}
|
|
pthread_mutex_unlock(&s->mutex);
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
sunaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
|
{
|
|
if (!ctx || !max_channels)
|
|
return CUBEB_ERROR;
|
|
|
|
*max_channels = 2;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
sunaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
|
{
|
|
if (!ctx || !rate)
|
|
return CUBEB_ERROR;
|
|
|
|
// XXX Not yet implemented.
|
|
*rate = 44100;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
sunaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
|
|
{
|
|
if (!ctx || !latency_ms)
|
|
return CUBEB_ERROR;
|
|
|
|
// XXX Not yet implemented.
|
|
*latency_ms = 20;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
sunaudio_stream_get_latency(cubeb_stream * s, uint32_t * latency)
|
|
{
|
|
if (!s || !latency)
|
|
return CUBEB_ERROR;
|
|
|
|
int rv = CUBEB_OK;
|
|
pthread_mutex_lock(&s->mutex);
|
|
if (s->active && s->fd > 0) {
|
|
if (s->using_oss) {
|
|
int delay;
|
|
ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay);
|
|
*latency = delay / s->n_channles / 2 / s->rate;
|
|
} else {
|
|
audio_info_t info;
|
|
ioctl(s->fd, AUDIO_GETINFO, &info);
|
|
*latency = (s->frm_played - info.play.samples) / s->rate;
|
|
}
|
|
DPR("sunaudio_stream_get_position() %lld\n", *p);
|
|
} else {
|
|
rv = CUBEB_ERROR;
|
|
}
|
|
pthread_mutex_unlock(&s->mutex);
|
|
return rv;
|
|
}
|
|
|
|
static struct cubeb_ops const sunaudio_ops = {
|
|
.init = sunaudio_init,
|
|
.get_backend_id = sunaudio_get_backend_id,
|
|
.destroy = sunaudio_destroy,
|
|
.get_preferred_sample_rate = sunaudio_get_preferred_sample_rate,
|
|
.stream_init = sunaudio_stream_init,
|
|
.stream_destroy = sunaudio_stream_destroy,
|
|
.stream_start = sunaudio_stream_start,
|
|
.stream_stop = sunaudio_stream_stop,
|
|
.stream_get_position = sunaudio_stream_get_position,
|
|
.get_max_channel_count = sunaudio_get_max_channel_count,
|
|
.get_min_latency = sunaudio_get_min_latency,
|
|
.stream_get_latency = sunaudio_stream_get_latency
|
|
};
|