commit f23767ad938f3a652921f5a3d9a3b43c5385c9e4 Author: snow Date: Sat Aug 29 22:40:33 2020 -0700 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..097bb42 --- /dev/null +++ b/README.md @@ -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. diff --git a/TODO b/TODO new file mode 100644 index 0000000..6c388a6 --- /dev/null +++ b/TODO @@ -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 diff --git a/src/netbsdout.cc b/src/netbsdout.cc new file mode 100644 index 0000000..29a74a0 --- /dev/null +++ b/src/netbsdout.cc @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + + +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 +}