1 /*
2  * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
3  * Copyright (c) 2013 Christian Neukirchen <chneukirchen@gmail.com>
4  * Copyright (c) 2020 Rozhuk Ivan <rozhuk.im@gmail.com>
5  * Copyright (c) 2021 Andrew Krasavin <noiseless-ak@yandex.ru>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include "config.h"
21 
22 #include <sys/types.h>
23 #include <poll.h>
24 #include <errno.h>
25 #include <sndio.h>
26 
27 #include "options/m_option.h"
28 #include "common/msg.h"
29 
30 #include "audio/format.h"
31 #include "ao.h"
32 #include "internal.h"
33 
34 struct priv {
35     struct sio_hdl *hdl;
36     struct sio_par par;
37     int delay;
38     bool playing;
39     int vol;
40     int havevol;
41     struct pollfd *pfd;
42 };
43 
44 
45 static const struct mp_chmap sndio_layouts[] = {
46     {0},                                        /* empty */
47     {1, {MP_SPEAKER_ID_FL}},                    /* mono */
48     MP_CHMAP2(FL, FR),                          /* stereo */
49     {0},                                        /* 2.1 */
50     MP_CHMAP4(FL, FR, BL, BR),                  /* 4.0 */
51     {0},                                        /* 5.0 */
52     MP_CHMAP6(FL, FR, BL, BR, FC, LFE),         /* 5.1 */
53     {0},                                        /* 6.1 */
54     MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), /* 7.1 */
55     /* Above is the fixed channel assignment for sndio, since we need to
56      * fill all channels and cannot insert silence, not all layouts are
57      * supported.
58      * NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */
59 };
60 
61 static void uninit(struct ao *ao);
62 
63 
64 /* Make libsndio call movecb(). */
process_events(struct ao * ao)65 static void process_events(struct ao *ao)
66 {
67     struct priv *p = ao->priv;
68 
69     int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
70     while (poll(p->pfd, n, 0) < 0 && errno == EINTR);
71 
72     sio_revents(p->hdl, p->pfd);
73 }
74 
75 /* Call-back invoked to notify of the hardware position. */
movecb(void * addr,int delta)76 static void movecb(void *addr, int delta)
77 {
78     struct ao *ao = addr;
79     struct priv *p = ao->priv;
80 
81     p->delay -= delta;
82 }
83 
84 /* Call-back invoked to notify about volume changes. */
volcb(void * addr,unsigned newvol)85 static void volcb(void *addr, unsigned newvol)
86 {
87     struct ao *ao = addr;
88     struct priv *p = ao->priv;
89 
90     p->vol = newvol;
91 }
92 
init(struct ao * ao)93 static int init(struct ao *ao)
94 {
95     struct priv *p = ao->priv;
96     struct mp_chmap_sel sel = {0};
97     size_t i;
98     struct af_to_par {
99         int format, bits, sig;
100     };
101     static const struct af_to_par af_to_par[] = {
102         {AF_FORMAT_U8,   8, 0},
103         {AF_FORMAT_S16, 16, 1},
104         {AF_FORMAT_S32, 32, 1},
105     };
106     const struct af_to_par *ap;
107     const char *device = ((ao->device) ? ao->device : SIO_DEVANY);
108 
109     /* Opening device. */
110     MP_VERBOSE(ao, "Using '%s' audio device.\n", device);
111     p->hdl = sio_open(device, SIO_PLAY, 0);
112     if (p->hdl == NULL) {
113         MP_ERR(ao, "Can't open audio device %s.\n", device);
114         goto err_out;
115     }
116 
117     sio_initpar(&p->par);
118 
119     /* Selecting sound format. */
120     ao->format = af_fmt_from_planar(ao->format);
121 
122     p->par.bits = 16;
123     p->par.sig = 1;
124     p->par.le = SIO_LE_NATIVE;
125     for (i = 0; i < MP_ARRAY_SIZE(af_to_par); i++) {
126         ap = &af_to_par[i];
127         if (ap->format == ao->format) {
128             p->par.bits = ap->bits;
129             p->par.sig = ap->sig;
130             break;
131         }
132     }
133 
134     p->par.rate = ao->samplerate;
135 
136     /* Channels count. */
137     for (i = 0; i < MP_ARRAY_SIZE(sndio_layouts); i++) {
138         mp_chmap_sel_add_map(&sel, &sndio_layouts[i]);
139     }
140     if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
141         goto err_out;
142 
143     p->par.pchan = ao->channels.num;
144     p->par.appbufsz = p->par.rate * 250 / 1000;    /* 250ms buffer */
145     p->par.round = p->par.rate * 10 / 1000;    /*  10ms block size */
146 
147     if (!sio_setpar(p->hdl, &p->par)) {
148         MP_ERR(ao, "couldn't set params\n");
149         goto err_out;
150     }
151 
152     /* Get current sound params. */
153     if (!sio_getpar(p->hdl, &p->par)) {
154         MP_ERR(ao, "couldn't get params\n");
155         goto err_out;
156     }
157     if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) {
158         MP_ERR(ao, "swapped endian output not supported\n");
159         goto err_out;
160     }
161 
162     /* Update sound params. */
163     if (p->par.bits == 8 && p->par.bps == 1 && !p->par.sig) {
164         ao->format = AF_FORMAT_U8;
165     } else if (p->par.bits == 16 && p->par.bps == 2 && p->par.sig) {
166         ao->format = AF_FORMAT_S16;
167     } else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4 && p->par.sig) {
168         ao->format = AF_FORMAT_S32;
169     } else {
170         MP_ERR(ao, "couldn't set format\n");
171         goto err_out;
172     }
173 
174     p->havevol = sio_onvol(p->hdl, volcb, ao);
175     sio_onmove(p->hdl, movecb, ao);
176 
177     p->pfd = talloc_array_ptrtype(p, p->pfd, sio_nfds(p->hdl));
178     if (!p->pfd)
179         goto err_out;
180 
181     ao->device_buffer = p->par.bufsz;
182     MP_VERBOSE(ao, "bufsz = %i, appbufsz = %i, round = %i\n",
183         p->par.bufsz, p->par.appbufsz, p->par.round);
184 
185     p->delay = 0;
186     p->playing = false;
187     if (!sio_start(p->hdl)) {
188         MP_ERR(ao, "start: sio_start() fail.\n");
189         goto err_out;
190     }
191 
192     return 0;
193 
194 err_out:
195     uninit(ao);
196     return -1;
197 }
198 
uninit(struct ao * ao)199 static void uninit(struct ao *ao)
200 {
201     struct priv *p = ao->priv;
202 
203     if (p->hdl) {
204         sio_close(p->hdl);
205         p->hdl = NULL;
206     }
207     p->pfd = NULL;
208     p->playing = false;
209 }
210 
control(struct ao * ao,enum aocontrol cmd,void * arg)211 static int control(struct ao *ao, enum aocontrol cmd, void *arg)
212 {
213     struct priv *p = ao->priv;
214     ao_control_vol_t *vol = arg;
215 
216     switch (cmd) {
217     case AOCONTROL_GET_VOLUME:
218         if (!p->havevol)
219             return CONTROL_FALSE;
220         vol->left = vol->right = p->vol * 100 / SIO_MAXVOL;
221         break;
222     case AOCONTROL_SET_VOLUME:
223         if (!p->havevol)
224             return CONTROL_FALSE;
225         sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
226         break;
227     default:
228         return CONTROL_UNKNOWN;
229     }
230     return CONTROL_OK;
231 }
232 
reset(struct ao * ao)233 static void reset(struct ao *ao)
234 {
235     struct priv *p = ao->priv;
236 
237     if (p->playing) {
238         p->playing = false;
239 
240         if (!sio_stop(p->hdl)) {
241             MP_ERR(ao, "reset: couldn't sio_stop()\n");
242         }
243         p->delay = 0;
244         if (!sio_start(p->hdl)) {
245             MP_ERR(ao, "reset: sio_start() fail.\n");
246         }
247     }
248 }
249 
start(struct ao * ao)250 static void start(struct ao *ao)
251 {
252     struct priv *p = ao->priv;
253 
254     p->playing = true;
255     process_events(ao);
256 }
257 
audio_write(struct ao * ao,void ** data,int samples)258 static bool audio_write(struct ao *ao, void **data, int samples)
259 {
260     struct priv *p = ao->priv;
261     const size_t size = (samples * ao->sstride);
262     size_t rc;
263 
264     rc = sio_write(p->hdl, data[0], size);
265     if (rc != size) {
266         MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n",
267             size, rc);
268         reset(ao);
269         p->playing = false;
270         return false;
271     }
272     p->delay += samples;
273 
274     return true;
275 }
276 
get_state(struct ao * ao,struct mp_pcm_state * state)277 static void get_state(struct ao *ao, struct mp_pcm_state *state)
278 {
279     struct priv *p = ao->priv;
280 
281     process_events(ao);
282 
283     /* how many samples we can play without blocking */
284     state->free_samples = ao->device_buffer - p->delay;
285     state->free_samples = state->free_samples / p->par.round * p->par.round;
286     /* how many samples are already in the buffer to be played */
287     state->queued_samples = p->delay;
288     /* delay in seconds between first and last sample in buffer */
289     state->delay = p->delay / (double)p->par.rate;
290 
291     /* report unexpected EOF / underrun */
292     if (state->queued_samples && state->queued_samples &&
293         state->queued_samples < state->free_samples &&
294         p->playing || sio_eof(p->hdl))
295     {
296         MP_VERBOSE(ao, "get_state: EOF/underrun detected.\n");
297         MP_VERBOSE(ao, "get_state: free: %d, queued: %d, delay: %lf\n", \
298                 state->free_samples, state->queued_samples, state->delay);
299         p->playing = false;
300         state->playing = p->playing;
301         ao_wakeup_playthread(ao);
302     } else {
303         state->playing = p->playing;
304     }
305 }
306 
307 const struct ao_driver audio_out_sndio = {
308     .name      = "sndio",
309     .description = "sndio audio output",
310     .init      = init,
311     .uninit    = uninit,
312     .control   = control,
313     .reset     = reset,
314     .start     = start,
315     .write     = audio_write,
316     .get_state = get_state,
317     .priv_size = sizeof(struct priv),
318 };
319