From f23767ad938f3a652921f5a3d9a3b43c5385c9e4 Mon Sep 17 00:00:00 2001
From: snow <snow@datagirl.xyz>
Date: Sat, 29 Aug 2020 22:40:33 -0700
Subject: [PATCH] Initial commit

---
 README.md        |   7 +
 TODO             |  10 ++
 src/netbsdout.cc | 343 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 360 insertions(+)
 create mode 100644 README.md
 create mode 100644 TODO
 create mode 100644 src/netbsdout.cc

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 <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
+}