364 lines
8.3 KiB
C++
364 lines
8.3 KiB
C++
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/audioio.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <libaudcore/audstrings.h>
|
|
#include <libaudcore/i18n.h>
|
|
#include <libaudcore/plugin.h>
|
|
#include <libaudcore/ringbuf.h>
|
|
#include <libaudcore/runtime.h>
|
|
|
|
class NetBSDOutput : public OutputPlugin
|
|
{
|
|
public:
|
|
static const char about[];
|
|
static const char * const defaults[];
|
|
static constexpr PluginInfo info = {
|
|
"NetBSD Audio Output",
|
|
"netbsdout",
|
|
about
|
|
};
|
|
|
|
constexpr NetBSDOutput () : OutputPlugin (info, 10) {}
|
|
|
|
bool init ();
|
|
void cleanup ();
|
|
|
|
bool open_audio (int format, int rate, int chans, String & error);
|
|
void close_audio ();
|
|
|
|
int write_audio (const void * data, int size);
|
|
void pause (bool pause);
|
|
|
|
void period_wait ();
|
|
int get_delay ();
|
|
|
|
void set_volume(StereoVolume volume);
|
|
StereoVolume get_volume();
|
|
|
|
void flush ();
|
|
void drain ();
|
|
};
|
|
|
|
__attribute__((visibility("default"))) NetBSDOutput aud_plugin_instance;
|
|
|
|
static String audio_path;
|
|
static int audio_fd;
|
|
|
|
static bool audio_paused, audio_flushed;
|
|
|
|
static pthread_mutex_t nbout_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t nbout_cond = PTHREAD_COND_INITIALIZER;
|
|
|
|
struct fmt_conv {
|
|
int aud_format;
|
|
unsigned int encoding;
|
|
unsigned int bits;
|
|
};
|
|
|
|
const char
|
|
NetBSDOutput::about[] =
|
|
"NetBSD Audio Plugin";
|
|
|
|
const char * const
|
|
NetBSDOutput::defaults[] = {
|
|
"audio_dev", "/dev/audio",
|
|
nullptr
|
|
};
|
|
|
|
static bool
|
|
pause_audio (bool pause)
|
|
{
|
|
audio_info_t info;
|
|
|
|
AUDIO_INITINFO (& info);
|
|
|
|
info.play.pause = pause;
|
|
|
|
if (ioctl(audio_fd, AUDIO_SETINFO, & info) != 0) {
|
|
AUDERR ("Unable to set pause: %s.\n", strerror (errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
NetBSDOutput::init ()
|
|
{
|
|
aud_config_set_defaults ("netbsd", defaults);
|
|
audio_path = aud_get_str ("netbsd", "audio_dev");
|
|
|
|
audio_fd = open (audio_path, O_WRONLY);
|
|
|
|
if (audio_fd == -1) {
|
|
AUDERR ("Failed to open %s: %s.\n", (const char *) audio_path, strerror (errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::cleanup ()
|
|
{
|
|
close (audio_fd);
|
|
}
|
|
|
|
/*
|
|
* Converts the format libaudcore provides to something usable
|
|
* in audio_info_t, then passes along the information to the
|
|
* audio device.
|
|
*/
|
|
bool
|
|
NetBSDOutput::open_audio (int format,
|
|
int rate,
|
|
int chans,
|
|
String & error)
|
|
{
|
|
/* TODO: Use FMT_S24_* or FMT_S24_3* */
|
|
audio_info_t info;
|
|
int props;
|
|
/* Audacious->NetBSD format table */
|
|
static const struct fmt_conv fmt_table[] = {
|
|
{FMT_S8, AUDIO_ENCODING_SLINEAR, 8},
|
|
{FMT_U8, AUDIO_ENCODING_ULINEAR, 8},
|
|
{FMT_S16_LE, AUDIO_ENCODING_SLINEAR_LE, 16},
|
|
{FMT_S16_BE, AUDIO_ENCODING_SLINEAR_BE, 16},
|
|
{FMT_U16_LE, AUDIO_ENCODING_ULINEAR_LE, 16},
|
|
{FMT_U16_BE, AUDIO_ENCODING_ULINEAR_BE, 16},
|
|
{FMT_S32_LE, AUDIO_ENCODING_SLINEAR_LE, 32},
|
|
{FMT_S32_BE, AUDIO_ENCODING_SLINEAR_BE, 32},
|
|
{FMT_U32_LE, AUDIO_ENCODING_ULINEAR_LE, 32},
|
|
{FMT_U32_BE, AUDIO_ENCODING_ULINEAR_BE, 32}
|
|
};
|
|
struct fmt_conv cur_format = { -1, 0, 0 };
|
|
|
|
if (ioctl (audio_fd, AUDIO_GETPROPS, & props) == -1) {
|
|
error = String (str_printf
|
|
("Failed to get audio properties on %s: %s.",
|
|
(const char *) audio_path, strerror (errno)));
|
|
return false;
|
|
}
|
|
|
|
if ((props & AUDIO_PROP_PLAYBACK) != AUDIO_PROP_PLAYBACK) {
|
|
error = String (str_printf
|
|
("Device %s does not support playback.",
|
|
(const char *) audio_path));
|
|
}
|
|
|
|
AUDIO_INITINFO (&info);
|
|
|
|
/* Loop to find the right format, if it's supported */
|
|
for (auto & conv : fmt_table) {
|
|
if (conv.aud_format == format) {
|
|
cur_format = conv;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cur_format.aud_format == -1) {
|
|
error = String ("Audio format not supported.\n");
|
|
return false;
|
|
}
|
|
|
|
info.play.encoding = cur_format.encoding;
|
|
info.play.precision = cur_format.bits;
|
|
|
|
/* Channels and sample rate are straightforward, at least */
|
|
info.play.channels = chans;
|
|
info.play.sample_rate = rate;
|
|
info.mode = AUMODE_PLAY;
|
|
|
|
if (ioctl (audio_fd, AUDIO_SETINFO, & info) == -1) {
|
|
error = String (str_printf
|
|
("Failed to set track info on %s: %s.\n",
|
|
(const char *) audio_path, strerror (errno)));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::close_audio ()
|
|
{
|
|
AUDDBG ("close_audio is a stub!\n"); /* XXX */
|
|
}
|
|
|
|
int
|
|
NetBSDOutput::write_audio (const void * data,
|
|
int size)
|
|
{
|
|
int res, len;
|
|
|
|
if ((res = pthread_mutex_lock (& nbout_mutex)) != 0) {
|
|
AUDERR ("Couldn't lock mutex: %s.\n", strerror (res));
|
|
return 0;
|
|
}
|
|
|
|
len = write(audio_fd, data, size);
|
|
|
|
pthread_mutex_unlock (& nbout_mutex);
|
|
return len;
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::period_wait ()
|
|
{
|
|
struct pollfd fds = {
|
|
audio_fd,
|
|
POLLOUT,
|
|
0
|
|
};
|
|
int res;
|
|
|
|
if ((res = pthread_mutex_lock (& nbout_mutex)) != 0) {
|
|
AUDERR ("Couldn't lock mutex: %s.\n", strerror (res));
|
|
return;
|
|
}
|
|
|
|
while ((res = poll(& fds, 1, 0)) == 0) {
|
|
if (audio_flushed) {
|
|
AUDDBG ("Audio flushed, exiting period_wait loop...\n");
|
|
break;
|
|
}
|
|
|
|
pthread_cond_wait (& nbout_cond, & nbout_mutex);
|
|
}
|
|
|
|
pthread_mutex_unlock (& nbout_mutex);
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::pause (bool pause)
|
|
{
|
|
int res;
|
|
|
|
AUDDBG ("Setting pause = %s.\n", pause ? "true" : "false");
|
|
|
|
if ((res = pthread_mutex_lock (& nbout_mutex)) != 0) {
|
|
AUDERR ("Couldn't lock mutex: %s.\n", strerror (res));
|
|
return;
|
|
}
|
|
|
|
if (pause_audio (pause)) {
|
|
audio_paused = pause;
|
|
}
|
|
|
|
pthread_cond_broadcast (& nbout_cond);
|
|
pthread_mutex_unlock (& nbout_mutex);
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::drain ()
|
|
{
|
|
int res;
|
|
|
|
AUDDBG ("Draining...\n");
|
|
|
|
if ((res = pthread_mutex_lock (& nbout_mutex)) != 0) {
|
|
AUDERR ("Couldn't lock mutex: %s.\n", strerror (res));
|
|
return;
|
|
}
|
|
|
|
if (ioctl (audio_fd, AUDIO_DRAIN) != 0) {
|
|
AUDERR ("Couldn't drain audio: %s\n.", strerror (errno));
|
|
}
|
|
|
|
pthread_mutex_unlock (& nbout_mutex);
|
|
}
|
|
|
|
int
|
|
NetBSDOutput::get_delay ()
|
|
{
|
|
audio_info_t info;
|
|
unsigned int delay_time;
|
|
int res;
|
|
|
|
if ((res = pthread_mutex_lock (& nbout_mutex)) != 0) {
|
|
AUDERR ("Couldn't lock mutex: %s.\n", strerror (res));
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl (audio_fd, AUDIO_GETBUFINFO, & info) != 0) {
|
|
AUDERR ("Couldn't get audio info: %s.\n", strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Calculate the time remaining by dividing the bytes left to read
|
|
* by (sample rate * bits of precision).
|
|
*/
|
|
if (info.play.sample_rate > 0 && info.play.precision > 0) {
|
|
delay_time = aud::rdiv (info.play.seek,
|
|
(info.play.sample_rate * info.play.precision));
|
|
} else {
|
|
AUDERR ("Sample rate and precision are both 0\n");
|
|
return -1;
|
|
}
|
|
|
|
pthread_mutex_unlock (& nbout_mutex);
|
|
return delay_time;
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::flush ()
|
|
{
|
|
int res;
|
|
|
|
AUDDBG ("Flushing!\n");
|
|
|
|
if ((res = pthread_mutex_lock (& nbout_mutex)) != 0) {
|
|
AUDERR ("Couldn't lock mutex: %s.\n", strerror (res));
|
|
return;
|
|
}
|
|
|
|
if (ioctl (audio_fd, AUDIO_FLUSH) != 0) {
|
|
AUDERR ("Couldn't flush audio: %s\n.", strerror (errno));
|
|
}
|
|
|
|
audio_flushed = true;
|
|
|
|
pthread_cond_broadcast (& nbout_cond);
|
|
pthread_mutex_unlock (& nbout_mutex);
|
|
}
|
|
|
|
StereoVolume
|
|
NetBSDOutput::get_volume ()
|
|
{
|
|
audio_info_t info;
|
|
int aud_vol;
|
|
|
|
if (ioctl (audio_fd, AUDIO_GETINFO, & info) != 0) {
|
|
AUDERR ("Unable to get volume: %s.\n", strerror (errno));
|
|
return { 0, 0 };
|
|
}
|
|
|
|
aud_vol = aud::rescale ((int)info.play.gain - AUDIO_MIN_GAIN,
|
|
AUDIO_MAX_GAIN - AUDIO_MIN_GAIN,
|
|
100);
|
|
|
|
return { aud_vol, aud_vol };
|
|
}
|
|
|
|
void
|
|
NetBSDOutput::set_volume (StereoVolume volume)
|
|
{
|
|
audio_info_t info;
|
|
|
|
AUDIO_INITINFO (& info);
|
|
info.play.gain = aud::rescale (aud::max
|
|
(volume.left, volume.right), 100,
|
|
AUDIO_MAX_GAIN) + AUDIO_MIN_GAIN;
|
|
|
|
if (ioctl(audio_fd, AUDIO_SETINFO, & info) != 0) {
|
|
AUDERR ("Unable to set volume: %s.\n", strerror (errno));
|
|
}
|
|
}
|