#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 = { "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)); } }