aud-netbsd-out/src/netbsdout.cc
2020-09-01 17:45:58 -07:00

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));
}
}