361 lines
7.8 KiB
C
361 lines
7.8 KiB
C
|
|
/*
|
||
|
|
* sndio: sndio audio output
|
||
|
|
*
|
||
|
|
* Copyright (c) 2008 Christian Weisgerber <naddy@openbsd.org>,
|
||
|
|
* Alexandre Ratchov <alex@caoua.org>
|
||
|
|
*
|
||
|
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
|
* copyright notice and this permission notice appear in all copies.
|
||
|
|
*
|
||
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "out123_int.h"
|
||
|
|
|
||
|
|
#include <sndio.h>
|
||
|
|
|
||
|
|
#include "debug.h"
|
||
|
|
|
||
|
|
static int sndio_to_mpg123_enc(int sign, int bits)
|
||
|
|
{
|
||
|
|
switch(bits)
|
||
|
|
{
|
||
|
|
case 8:
|
||
|
|
return sign ? MPG123_ENC_SIGNED_8 : MPG123_ENC_UNSIGNED_8;
|
||
|
|
case 16:
|
||
|
|
return sign ? MPG123_ENC_SIGNED_16 : MPG123_ENC_UNSIGNED_16;
|
||
|
|
case 24:
|
||
|
|
return sign ? MPG123_ENC_SIGNED_24 : MPG123_ENC_UNSIGNED_24;
|
||
|
|
case 32:
|
||
|
|
return sign ? MPG123_ENC_SIGNED_32 : MPG123_ENC_UNSIGNED_32;
|
||
|
|
}
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static double rate_diff(long a, unsigned int b)
|
||
|
|
{
|
||
|
|
double ar = (double)a;
|
||
|
|
double br = (double)b;
|
||
|
|
return ( a>b ? (ar-br)/ar : ( a <= 0 ? 1. : (br-ar)/ar ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
static int mpg123_to_sndio_enc(int enc, unsigned int *sig, unsigned int *bits)
|
||
|
|
{
|
||
|
|
switch(enc) {
|
||
|
|
case -1:
|
||
|
|
// Query default format only.
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_SIGNED_32:
|
||
|
|
*sig = 1;
|
||
|
|
*bits = 32;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_UNSIGNED_32:
|
||
|
|
*sig = 0;
|
||
|
|
*bits = 32;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_SIGNED_24:
|
||
|
|
*sig = 1;
|
||
|
|
*bits = 24;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_UNSIGNED_24:
|
||
|
|
*sig = 0;
|
||
|
|
*bits = 24;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_SIGNED_16:
|
||
|
|
*sig = 1;
|
||
|
|
*bits = 16;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_UNSIGNED_16:
|
||
|
|
*sig = 0;
|
||
|
|
*bits = 16;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_UNSIGNED_8:
|
||
|
|
*sig = 0;
|
||
|
|
*bits = 8;
|
||
|
|
break;
|
||
|
|
case MPG123_ENC_SIGNED_8:
|
||
|
|
*sig = 1;
|
||
|
|
*bits = 8;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Just check if there seems some support for stereo audio,
|
||
|
|
// take maximum channel count otherwise.
|
||
|
|
static unsigned int guess_channels(struct sio_hdl *hdl)
|
||
|
|
{
|
||
|
|
struct sio_cap cap;
|
||
|
|
unsigned int maxchan = 0;
|
||
|
|
unsigned int stereo_mask = 0;
|
||
|
|
unsigned int all_conf = 0;
|
||
|
|
if(!sio_getcap(hdl, &cap))
|
||
|
|
return 0; // Zero is as good as nothing.
|
||
|
|
// There is no specification of order in pchan[], So no guessing
|
||
|
|
// about which index for stereo.
|
||
|
|
for(int ci=0; ci<SIO_NCHAN; ++ci)
|
||
|
|
{
|
||
|
|
if(2 == cap.pchan[ci])
|
||
|
|
stereo_mask |= 1 << ci; // Maybe even multiple entries, eh?
|
||
|
|
}
|
||
|
|
for(unsigned int i=0; i<cap.nconf; ++i)
|
||
|
|
{
|
||
|
|
all_conf |= cap.confs[i].pchan;
|
||
|
|
}
|
||
|
|
for(int ci=0; ci<SIO_NCHAN; ++ci)
|
||
|
|
{
|
||
|
|
if(all_conf & (1 << ci) && cap.pchan[ci] > maxchan)
|
||
|
|
maxchan = cap.pchan[ci];
|
||
|
|
}
|
||
|
|
mdebug("maximum device channels: %u\n", maxchan);
|
||
|
|
return all_conf & stereo_mask ? 2 : maxchan;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int open_sndio(out123_handle *ao)
|
||
|
|
{
|
||
|
|
struct sio_hdl *hdl;
|
||
|
|
struct sio_par par;
|
||
|
|
|
||
|
|
hdl = sio_open(ao->device /* NULL is fine */, SIO_PLAY, 0);
|
||
|
|
if (hdl == NULL)
|
||
|
|
{
|
||
|
|
error("Got nothing from sio_open(). ");
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sio_initpar(&par);
|
||
|
|
|
||
|
|
par.le = SIO_LE_NATIVE;
|
||
|
|
if(ao->format != -1)
|
||
|
|
{
|
||
|
|
mdebug("Actually opening with %d channels, rate %ld.", ao->channels, ao->rate);
|
||
|
|
par.rate = ao->rate;
|
||
|
|
par.pchan = ao->channels;
|
||
|
|
} else
|
||
|
|
{
|
||
|
|
// Hack around buggy sndio versions up to 1.8.0 that fail to
|
||
|
|
// neuter the default value before handing to OSS driver.
|
||
|
|
// Also need to re-open, as sndio likes errors to be fatal.
|
||
|
|
if(!sio_setpar(hdl, &par))
|
||
|
|
{
|
||
|
|
sio_close(hdl);
|
||
|
|
hdl = sio_open(ao->device, SIO_PLAY, 0);
|
||
|
|
if(hdl == NULL)
|
||
|
|
{
|
||
|
|
error("Re-opening of device for channel guessing failed.");
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
par.pchan = guess_channels(hdl);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(mpg123_to_sndio_enc(ao->format, &par.sig, &par.bits))
|
||
|
|
{
|
||
|
|
if (!AOQUIET)
|
||
|
|
error1("invalid sample format %d",
|
||
|
|
ao->format);
|
||
|
|
sio_close(hdl);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)
|
||
|
|
|| par.le != SIO_LE_NATIVE )
|
||
|
|
{
|
||
|
|
if(!AOQUIET)
|
||
|
|
error("parameter setup failure");
|
||
|
|
sio_close(hdl);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
if(ao->format == -1) // Store default format.
|
||
|
|
{
|
||
|
|
ao->format = sndio_to_mpg123_enc(par.sig, par.bits);
|
||
|
|
ao->rate = par.rate;
|
||
|
|
ao->channels = par.pchan;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if( ao->format != sndio_to_mpg123_enc(par.sig, par.bits)
|
||
|
|
|| ao->channels != par.pchan
|
||
|
|
|| rate_diff(ao->rate, par.rate) > 0.005
|
||
|
|
)
|
||
|
|
{
|
||
|
|
if(!AOQUIET)
|
||
|
|
error("format not accepted as given");
|
||
|
|
sio_close(hdl);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
if(!sio_start(hdl))
|
||
|
|
{
|
||
|
|
if(!AOQUIET)
|
||
|
|
error("cannot start");
|
||
|
|
sio_close(hdl);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ao->userptr = hdl;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int get_formats_sndio(out123_handle *ao)
|
||
|
|
{
|
||
|
|
struct sio_hdl *hdl = (struct sio_hdl *)ao->userptr;
|
||
|
|
struct sio_cap cap;
|
||
|
|
int fmt = 0;
|
||
|
|
|
||
|
|
// Direct querying with sio_setpar()/sio_getpar is too slow, so let's
|
||
|
|
// learn about the funky bitmask indexing of the capability stuff.
|
||
|
|
if(!sio_getcap(hdl, &cap))
|
||
|
|
{
|
||
|
|
if(!AOQUIET)
|
||
|
|
error("failure getting caps");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
unsigned int rmask = 0;
|
||
|
|
for(int ri=0; ri<SIO_NRATE; ++ri)
|
||
|
|
{
|
||
|
|
if(ao->rate == cap.rate[ri])
|
||
|
|
{
|
||
|
|
rmask |= 1 << ri;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if(!rmask)
|
||
|
|
{
|
||
|
|
for(int ri=0; ri<SIO_NRATE; ++ri)
|
||
|
|
{
|
||
|
|
if(rate_diff(ao->rate, cap.rate[ri]) <= 0.005)
|
||
|
|
{
|
||
|
|
rmask |= 1 << ri;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
unsigned int cmask = 0;
|
||
|
|
for(int ci=0; ci<SIO_NCHAN; ++ci)
|
||
|
|
{
|
||
|
|
if(ao->channels == cap.pchan[ci])
|
||
|
|
{
|
||
|
|
cmask |= 1 << ci;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!rmask || !cmask)
|
||
|
|
{
|
||
|
|
// no rate match, do the elaborate check.
|
||
|
|
debug("cap table does not help, doing elaborate format check");
|
||
|
|
static int fmts[] =
|
||
|
|
{
|
||
|
|
MPG123_ENC_SIGNED_8, MPG123_ENC_UNSIGNED_8
|
||
|
|
, MPG123_ENC_SIGNED_16, MPG123_ENC_UNSIGNED_16
|
||
|
|
, MPG123_ENC_SIGNED_24, MPG123_ENC_UNSIGNED_24
|
||
|
|
, MPG123_ENC_SIGNED_32, MPG123_ENC_UNSIGNED_32
|
||
|
|
};
|
||
|
|
|
||
|
|
for(int i=0;i<sizeof(fmts)/sizeof(int);i++)
|
||
|
|
{
|
||
|
|
struct sio_par par;
|
||
|
|
sio_initpar(&par);
|
||
|
|
par.le = SIO_LE_NATIVE;
|
||
|
|
mpg123_to_sndio_enc(fmts[i], &par.sig, &par.bits);
|
||
|
|
par.rate = ao->rate;
|
||
|
|
par.pchan = ao->channels;
|
||
|
|
if(sio_setpar(hdl, &par) && sio_getpar(hdl, &par))
|
||
|
|
{
|
||
|
|
if( par.le == SIO_LE_NATIVE
|
||
|
|
&& fmts[i] == sndio_to_mpg123_enc(par.sig, par.bits)
|
||
|
|
&& ao->channels == par.pchan
|
||
|
|
&& rate_diff(ao->rate, par.rate) <= 0.005
|
||
|
|
)
|
||
|
|
fmt |= fmts[i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
{
|
||
|
|
int menc[SIO_NENC];
|
||
|
|
for(int ei=0; ei<SIO_NENC; ++ei)
|
||
|
|
{
|
||
|
|
if(cap.enc[ei].le != SIO_LE_NATIVE)
|
||
|
|
menc[ei] = 0;
|
||
|
|
else
|
||
|
|
menc[ei] = sndio_to_mpg123_enc(cap.enc[ei].sig, cap.enc[ei].bits);
|
||
|
|
if(menc[ei] < 0)
|
||
|
|
menc[ei] = 0;
|
||
|
|
}
|
||
|
|
for(unsigned int i=0; i<cap.nconf; ++i)
|
||
|
|
{
|
||
|
|
if(cap.confs[i].pchan & cmask && cap.confs[i].rate & rmask)
|
||
|
|
for(int ei=0; ei<SIO_NENC; ++ei)
|
||
|
|
if(cap.confs[i].enc & (1<<ei))
|
||
|
|
fmt |= menc[ei];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return fmt;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int write_sndio(out123_handle *ao, unsigned char *buf, int len)
|
||
|
|
{
|
||
|
|
struct sio_hdl *hdl = (struct sio_hdl *)ao->userptr;
|
||
|
|
int count;
|
||
|
|
|
||
|
|
count = (int)sio_write(hdl, buf, len);
|
||
|
|
if (count == 0 && sio_eof(hdl))
|
||
|
|
return -1;
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void flush_sndio(out123_handle *ao)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int close_sndio(out123_handle *ao)
|
||
|
|
{
|
||
|
|
struct sio_hdl *hdl = (struct sio_hdl *)ao->userptr;
|
||
|
|
|
||
|
|
if(hdl)
|
||
|
|
sio_close(hdl);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int init_sndio(out123_handle* ao)
|
||
|
|
{
|
||
|
|
if (ao == NULL)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
/* Set callbacks */
|
||
|
|
ao->open = open_sndio;
|
||
|
|
ao->flush = flush_sndio; /* required */
|
||
|
|
ao->write = write_sndio;
|
||
|
|
ao->get_formats = get_formats_sndio;
|
||
|
|
ao->close = close_sndio;
|
||
|
|
|
||
|
|
/* Success */
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Module information data structure
|
||
|
|
*/
|
||
|
|
mpg123_module_t mpg123_output_module_info = {
|
||
|
|
/* api_version */ MPG123_MODULE_API_VERSION,
|
||
|
|
/* name */ "sndio",
|
||
|
|
/* description */ "Output audio using sndio library",
|
||
|
|
/* revision */ "$Rev:$",
|
||
|
|
/* handle */ NULL,
|
||
|
|
|
||
|
|
/* init_output */ init_sndio,
|
||
|
|
};
|