$NetBSD: patch-audio__sun.c,v 1.1 2022/07/01 18:36:28 nia Exp $ Add support for Sun/NetBSD audio. --- audio_sun.c.orig 2021-04-04 06:45:52.830430162 +0000 +++ audio_sun.c @@ -0,0 +1,258 @@ +/* + * sun output driver. This file is part of Shairport Sync. + * Copyright (c) 2013 Dimitri Sokolyuk + * Copyright (c) 2017 Tobias Kortkamp + * Copyright (c) 2021 Nia Alarie + * + * Modifications for audio synchronisation + * and related work, copyright (c) Mike Brady 2014 -- 2017 + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "audio.h" +#include "common.h" +#include +#include +#include +#include +#ifdef __sun +#include +#include +#endif +#include +#include +#include +#include +#include + +static void help(void); +static int init(int, char **); +static void deinit(void); +static void start(int, int); +static int play(void *, int); +static void stop(void); +static int delay(long *); +static void flush(void); + +audio_output audio_sun = {.name = "sun", + .help = &help, + .init = &init, + .deinit = &deinit, + .prepare = NULL, + .start = &start, + .stop = &stop, + .is_running = NULL, + .flush = &flush, + .delay = &delay, + .play = &play, + .volume = NULL, + .parameters = NULL, + .mute = NULL}; + +static pthread_mutex_t sun_mutex = PTHREAD_MUTEX_INITIALIZER; +static int framesize; +static int fd; + +struct sun_format { + const char *name; + sps_format_t fmt; + unsigned int encoding; + unsigned int precision; +}; + +/* SunOS compatibility defines for NetBSD extensions. */ + +#ifndef AUDIO_ENCODING_SLINEAR +#define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_LINEAR +#endif + +#ifndef AUDIO_ENCODING_ULINEAR +#define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_LINEAR8 +#endif + +#ifndef AUDIO_ENCODING_SLINEAR_LE +#define AUDIO_ENCODING_SLINEAR_LE AUDIO_ENCODING_LINEAR +#endif + +#ifndef AUDIO_ENCODING_SLINEAR_BE +#define AUDIO_ENCODING_SLINEAR_BE AUDIO_ENCODING_LINEAR +#endif + +#ifndef AUDIO_GETBUFINFO +#define AUDIO_GETBUFINFO AUDIO_GETINFO +#endif + +#ifndef DEFAULT_DEV +#define DEFAULT_DEV "/dev/audio" +#endif + +static struct sun_format formats[] = {{"U8", SPS_FORMAT_U8, AUDIO_ENCODING_ULINEAR, 8}, + {"S8", SPS_FORMAT_S8, AUDIO_ENCODING_SLINEAR, 16}, + {"S16", SPS_FORMAT_S16, AUDIO_ENCODING_SLINEAR, 16}, + {"S16_LE", SPS_FORMAT_S16_LE, AUDIO_ENCODING_SLINEAR_LE, 16}, + {"S16_BE", SPS_FORMAT_S16_BE, AUDIO_ENCODING_SLINEAR_BE, 16}, + {"S32", SPS_FORMAT_S16, AUDIO_ENCODING_SLINEAR, 32}, + {"S32_LE", SPS_FORMAT_S32_LE, AUDIO_ENCODING_SLINEAR_LE, 32}, + {"S32_BE", SPS_FORMAT_S32_BE, AUDIO_ENCODING_SLINEAR_BE, 32}}; + +static void help() { printf(" -d output-device set the output device [default*|...]\n"); } + +static int init(int argc, char **argv) { + struct audio_info info; + int found, opt; + unsigned int i; + const char *devname, *tmp; + + // set up default values first + + AUDIO_INITINFO(&info); + info.play.encoding = AUDIO_ENCODING_SLINEAR_LE; + info.play.sample_rate = 44100; + info.play.channels = 2; + info.play.precision = 16; + + devname = DEFAULT_DEV; + + config.audio_backend_buffer_desired_length = 1.0; + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. + config.audio_backend_latency_offset = 0; + + // get settings from settings file + + // do the "general" audio options. Note, these options are in the "general" stanza! + parse_general_audio_options(); + + // get the specific settings + + if (config.cfg != NULL) { + if (!config_lookup_string(config.cfg, "sun.device", &devname)) + devname = DEFAULT_DEV; + if (config_lookup_string(config.cfg, "sun.format", &tmp)) { + for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { + if (strcasecmp(formats[i].name, tmp) == 0) { + config.output_format = formats[i].fmt; + info.play.encoding = formats[i].encoding; + info.play.precision = formats[i].precision; + found = 1; + break; + } + } + if (!found) + die("Invalid output format \"%s\". Should be one of: U8, S8, S16, S16_LE, S16_BE, S32, S32_LE, S32_BE", + tmp); + } + } + optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour + argv--; // so we shift the arguments to satisfy getopt() + argc++; + while ((opt = getopt(argc, argv, "d:")) > 0) { + switch (opt) { + case 'd': + devname = optarg; + break; + default: + help(); + die("Invalid audio option -%c specified", opt); + } + } + if (optind < argc) + die("Invalid audio argument: %s", argv[optind]); + + pthread_mutex_lock(&sun_mutex); + debug(1, "Output device name is \"%s\".", devname); + fd = open(devname, O_WRONLY); + if (fd < 0) + die("sun: cannot open audio device"); + + if (ioctl(fd, AUDIO_SETINFO, &info) < 0) + die("sun: failed to set audio parameters"); + if (ioctl(fd, AUDIO_GETINFO, &info) < 0) + die("sun: failed to get audio parameters"); + + framesize = (info.play.precision / 8) * info.play.channels; + config.output_rate = info.play.sample_rate; + config.audio_backend_buffer_desired_length = 1.0 * info.play.buffer_size / info.play.sample_rate; + config.audio_backend_latency_offset = 0; + + pthread_mutex_unlock(&sun_mutex); + return 0; +} + +static void deinit() { + pthread_mutex_lock(&sun_mutex); + close(fd); + pthread_mutex_unlock(&sun_mutex); +} + +static void start(__attribute__((unused)) int sample_rate, + __attribute__((unused)) int sample_format) { + struct audio_info tmpinfo; + + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.pause = false; + + pthread_mutex_lock(&sun_mutex); + if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) + die("sun: unable to start"); + pthread_mutex_unlock(&sun_mutex); +} + +static int play(void *buf, int frames) { + if (frames > 0) { + pthread_mutex_lock(&sun_mutex); + if (write(fd, buf, frames * framesize) < 0) + die("sun: unable to write"); + pthread_mutex_unlock(&sun_mutex); + } + return 0; +} + +static void stop() { + struct audio_info tmpinfo; + + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.pause = true; + + pthread_mutex_lock(&sun_mutex); + (void)ioctl(fd, AUDIO_DRAIN, NULL); + if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) + die("sun: unable to stop"); + pthread_mutex_unlock(&sun_mutex); +} + +static int delay(long *_delay) { + struct audio_info tmpinfo; + + pthread_mutex_lock(&sun_mutex); + if (ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo) < 0) + die("sun: unable to get audio buffer info"); + *_delay = tmpinfo.play.seek / framesize; + pthread_mutex_unlock(&sun_mutex); + return 0; +} + +static void flush() { + pthread_mutex_lock(&sun_mutex); +#ifdef AUDIO_FLUSH + if (ioctl(fd, AUDIO_FLUSH, NULL) < 0) + die("sun: unable to flush"); +#else + if (ioctl(fd, I_FLUSH, FLUSHW) < 0) + die("sun: unable to flush"); +#endif + pthread_mutex_unlock(&sun_mutex); +}