Initial commit
This commit is contained in:
commit
f23767ad93
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Audacious /dev/audio Output
|
||||||
|
|
||||||
|
Provides direct output to /dev/audio as an alternative to the SDL2
|
||||||
|
output plugin.
|
||||||
|
|
||||||
|
**Please note this is still a heavy work in progress!** There will
|
||||||
|
likely be bugs, and TODO probably needs a TODO.
|
10
TODO
Normal file
10
TODO
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
* Figure out how to manage get_volume and set_volume
|
||||||
|
* Should it be global? Does (audio_info_t)info.play.gain
|
||||||
|
refer to the global mixer or just the track (i'm guessing
|
||||||
|
from the manpage that it's global?)
|
||||||
|
* Find out if/how audio(4) supports 24-bit linear PCM
|
||||||
|
* Audacious supports two methods-- one is 3-byte, the other
|
||||||
|
is padded 4-byte. I'm guessing audio(4) doesn't support
|
||||||
|
both, but who knows
|
||||||
|
* Clean up the code in terms of formatting/layout
|
||||||
|
* Figure out a license
|
343
src/netbsdout.cc
Normal file
343
src/netbsdout.cc
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
#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 = {
|
||||||
|
"/dev/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 period_wait ();
|
||||||
|
|
||||||
|
void drain ();
|
||||||
|
|
||||||
|
void set_volume(StereoVolume volume);
|
||||||
|
StereoVolume get_volume();
|
||||||
|
|
||||||
|
int get_delay ();
|
||||||
|
|
||||||
|
void pause (bool pause);
|
||||||
|
|
||||||
|
void flush ();
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((visibility("default"))) NetBSDOutput aud_plugin_instance;
|
||||||
|
|
||||||
|
static String audio_path;
|
||||||
|
static int audio_fd;
|
||||||
|
|
||||||
|
static bool audio_paused, audio_prebuffer, 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", audio_path, strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NetBSDOutput::cleanup ()
|
||||||
|
{
|
||||||
|
close (audio_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
NetBSDOutput::open_audio (int format,
|
||||||
|
int rate,
|
||||||
|
int chans,
|
||||||
|
String & error)
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
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.",
|
||||||
|
audio_path, strerror (errno)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props | AUDIO_PROP_PLAYBACK != AUDIO_PROP_PLAYBACK) {
|
||||||
|
error = String (str_printf
|
||||||
|
("Device %s does not support playback.",
|
||||||
|
audio_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
AUDIO_INITINFO (&info);
|
||||||
|
|
||||||
|
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.channels = chans;
|
||||||
|
info.play.encoding = cur_format.encoding;
|
||||||
|
info.play.precision = cur_format.bits;
|
||||||
|
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",
|
||||||
|
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, len;
|
||||||
|
audio_info_t info;
|
||||||
|
|
||||||
|
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 ()
|
||||||
|
{
|
||||||
|
// too noisy, even for debug...
|
||||||
|
// AUDDBG ("get_volume: Stub!\n");
|
||||||
|
|
||||||
|
return {100, 100}; // XXX
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NetBSDOutput::set_volume (StereoVolume volume)
|
||||||
|
{
|
||||||
|
AUDDBG ("set_volume: Stub!\n"); // XXX
|
||||||
|
}
|
Loading…
Reference in a new issue