1/*
2 * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18
19#ifdef HAVE_CONFIG_H
20# include <config.h>
21#endif
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <unistd.h>
26#include <string.h>
27#include <errno.h>
28#include <stdint.h>
29#include <inttypes.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <fcntl.h>
33#include <sys/ioctl.h>
34
35#include <soundcard.h>
36
37#include "conffile.h"
38#include "logger.h"
39#include "player.h"
40#include "laudio.h"
41
42
43struct pcm_packet
44{
45  uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
46
47  uint64_t rtptime;
48
49  size_t offset;
50
51  struct pcm_packet *next;
52};
53
54static uint64_t pcm_pos;
55static uint64_t pcm_start_pos;
56static int pcm_buf_threshold;
57static int pcm_retry;
58
59static struct pcm_packet *pcm_pkt_head;
60static struct pcm_packet *pcm_pkt_tail;
61
62static char *card_name;
63static int oss_fd;
64
65static enum laudio_state pcm_status;
66static laudio_status_cb status_cb;
67
68
69static void
70update_status(enum laudio_state status)
71{
72  pcm_status = status;
73  status_cb(status);
74}
75
76void
77laudio_write(uint8_t *buf, uint64_t rtptime)
78{
79  struct pcm_packet *pkt;
80  int scratch;
81  int nsamp;
82  int ret;
83
84  pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet));
85  if (!pkt)
86    {
87      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for PCM pkt\n");
88
89      update_status(LAUDIO_FAILED);
90      return;
91    }
92
93  memcpy(pkt->samples, buf, sizeof(pkt->samples));
94
95  pkt->rtptime = rtptime;
96  pkt->offset = 0;
97  pkt->next = NULL;
98
99  if (pcm_pkt_tail)
100    {
101      pcm_pkt_tail->next = pkt;
102      pcm_pkt_tail = pkt;
103    }
104  else
105    {
106      pcm_pkt_head = pkt;
107      pcm_pkt_tail = pkt;
108    }
109
110  if (pcm_pos < pcm_pkt_head->rtptime)
111    {
112      pcm_pos += AIRTUNES_V2_PACKET_SAMPLES;
113
114      return;
115    }
116  else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos >= pcm_start_pos))
117    {
118      /* Start audio output */
119      scratch = PCM_ENABLE_OUTPUT;
120      ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch);
121      if (ret < 0)
122	{
123	  DPRINTF(E_LOG, L_LAUDIO, "Could not enable output: %s\n", strerror(errno));
124
125	  update_status(LAUDIO_FAILED);
126	  return;
127	}
128
129      update_status(LAUDIO_RUNNING);
130    }
131
132  pkt = pcm_pkt_head;
133
134  while (pkt)
135    {
136      nsamp = write(oss_fd, pkt->samples + pkt->offset, sizeof(pkt->samples) - pkt->offset);
137      if (nsamp < 0)
138	{
139	  if (errno == EAGAIN)
140	    {
141	      pcm_retry++;
142
143	      if (pcm_retry < 10)
144		return;
145	    }
146
147	  DPRINTF(E_LOG, L_LAUDIO, "Write error: %s\n", strerror(errno));
148
149	  update_status(LAUDIO_FAILED);
150	  return;
151	}
152
153      pcm_retry = 0;
154
155      pkt->offset += nsamp;
156
157      nsamp = BTOS(nsamp);
158      pcm_pos += nsamp;
159
160      if (pkt->offset == sizeof(pkt->samples))
161	{
162	  pcm_pkt_head = pkt->next;
163
164	  if (pkt == pcm_pkt_tail)
165	    pcm_pkt_tail = NULL;
166
167	  free(pkt);
168
169	  pkt = pcm_pkt_head;
170	}
171
172      /* Don't let the buffer fill up too much */
173      if (nsamp == AIRTUNES_V2_PACKET_SAMPLES)
174	break;
175    }
176}
177
178uint64_t
179laudio_get_pos(void)
180{
181  int delay;
182  int ret;
183
184  ret = ioctl(oss_fd, SNDCTL_DSP_GETODELAY, &delay);
185  if (ret < 0)
186    {
187      DPRINTF(E_LOG, L_LAUDIO, "Could not obtain output delay: %s\n", strerror(errno));
188
189      return pcm_pos;
190    }
191
192  return pcm_pos - BTOS(delay);
193}
194
195void
196laudio_set_volume(int vol)
197{
198  int oss_vol;
199  int ret;
200
201  vol = vol & 0xff;
202  oss_vol = vol | (vol << 8);
203
204  ret = ioctl(oss_fd, SNDCTL_DSP_SETPLAYVOL, &oss_vol);
205  if (ret < 0)
206    {
207      DPRINTF(E_LOG, L_LAUDIO, "Could not set volume: %s\n", strerror(errno));
208
209      return;
210    }
211
212  DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (real: %d)\n", vol, (oss_vol & 0xff));
213}
214
215int
216laudio_start(uint64_t cur_pos, uint64_t next_pkt)
217{
218  int scratch;
219  int ret;
220
221  DPRINTF(E_DBG, L_LAUDIO, "PCM will start after %d samples (%d packets)\n", pcm_buf_threshold, pcm_buf_threshold / AIRTUNES_V2_PACKET_SAMPLES);
222
223  /* Make pcm_pos the rtptime of the packet containing cur_pos */
224  pcm_pos = next_pkt;
225  while (pcm_pos > cur_pos)
226    pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES;
227
228  pcm_start_pos = next_pkt + pcm_buf_threshold;
229
230  /* FIXME check for OSS - Compensate threshold, as it's taken into account by snd_pcm_delay() */
231  pcm_pos += pcm_buf_threshold;
232
233  DPRINTF(E_DBG, L_LAUDIO, "PCM pos %" PRIu64 ", start pos %" PRIu64 "\n", pcm_pos, pcm_start_pos);
234
235  pcm_pkt_head = NULL;
236  pcm_pkt_tail = NULL;
237
238  pcm_retry = 0;
239
240  scratch = 0;
241  ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch);
242  if (ret < 0)
243    {
244      DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno));
245
246      return -1;
247    }
248
249  update_status(LAUDIO_STARTED);
250
251  return 0;
252}
253
254void
255laudio_stop(void)
256{
257  struct pcm_packet *pkt;
258  int ret;
259
260  update_status(LAUDIO_STOPPING);
261
262  ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
263  if (ret < 0)
264    DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno));
265
266  for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head)
267    {
268      pcm_pkt_head = pkt->next;
269
270      free(pkt);
271    }
272
273  pcm_pkt_head = NULL;
274  pcm_pkt_tail = NULL;
275
276  update_status(LAUDIO_OPEN);
277}
278
279int
280laudio_open(void)
281{
282  audio_buf_info bi;
283  int scratch;
284  int ret;
285
286  oss_fd = open(card_name, O_RDWR | O_NONBLOCK);
287  if (oss_fd < 0)
288    {
289      DPRINTF(E_LOG, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno));
290
291      return -1;
292    }
293
294  scratch = 0;
295  ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch);
296  if (ret < 0)
297    {
298      DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno));
299
300      goto out_fail;
301    }
302
303  scratch = AFMT_S16_LE;
304  errno = 0;
305  ret = ioctl(oss_fd, SNDCTL_DSP_SETFMT, &scratch);
306  if ((ret < 0) || (scratch != AFMT_S16_LE))
307    {
308      if (errno)
309	DPRINTF(E_LOG, L_LAUDIO, "Could not set sample format (S16 LE): %s\n", strerror(errno));
310      else
311	DPRINTF(E_LOG, L_LAUDIO, "Sample format S16 LE not supported\n");
312
313      goto out_fail;
314    }
315
316  scratch = 2;
317  errno = 0;
318  ret = ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &scratch);
319  if ((ret < 0) || (scratch != 2))
320    {
321      if (errno)
322	DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo: %s\n", strerror(errno));
323      else
324	DPRINTF(E_LOG, L_LAUDIO, "Stereo not supported\n");
325
326      goto out_fail;
327    }
328
329  scratch = 44100;
330  errno = 0;
331  ret = ioctl(oss_fd, SNDCTL_DSP_SPEED, &scratch);
332  if ((ret < 0) || (scratch != 44100))
333    {
334      if (errno)
335	DPRINTF(E_LOG, L_LAUDIO, "Could not set speed (44100): %s\n", strerror(errno));
336      else
337	DPRINTF(E_LOG, L_LAUDIO, "Sample rate 44100 not supported\n");
338
339      goto out_fail;
340    }
341
342  ret = ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &bi);
343  if (ret < 0)
344    {
345      DPRINTF(E_LOG, L_LAUDIO, "Couldn't get output buffer status: %s\n", strerror(errno));
346
347      goto out_fail;
348    }
349
350  pcm_buf_threshold = (BTOS(bi.bytes) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES;
351
352  update_status(LAUDIO_OPEN);
353
354  return 0;
355
356 out_fail:
357  close(oss_fd);
358  oss_fd = -1;
359
360  return -1;
361}
362
363void
364laudio_close(void)
365{
366  struct pcm_packet *pkt;
367  int ret;
368
369  ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
370  if (ret < 0)
371    DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno));
372
373  close(oss_fd);
374  oss_fd = -1;
375
376  for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head)
377    {
378      pcm_pkt_head = pkt->next;
379
380      free(pkt);
381    }
382
383  pcm_pkt_head = NULL;
384  pcm_pkt_tail = NULL;
385
386  update_status(LAUDIO_CLOSED);
387}
388
389
390int
391laudio_init(laudio_status_cb cb)
392{
393  oss_sysinfo si;
394  int ret;
395
396  status_cb = cb;
397  pcm_status = LAUDIO_CLOSED;
398
399  card_name = cfg_getstr(cfg_getsec(cfg, "audio"), "card");
400
401  oss_fd = open(card_name, O_RDWR);
402  if (oss_fd < 0)
403    {
404      DPRINTF(E_FATAL, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno));
405
406      return -1;
407    }
408
409  ret = ioctl(oss_fd, SNDCTL_SYSINFO, &si);
410
411  close(oss_fd);
412  oss_fd = -1;
413
414  if (ret < 0)
415    {
416      DPRINTF(E_FATAL, L_LAUDIO, "Could not check OSS version: %s\n", strerror(errno));
417
418      return -1;
419    }
420
421  if (si.versionnum < 0x040000)
422    {
423      DPRINTF(E_FATAL, L_LAUDIO, "Your OSS version (%s) is too old; version 4.0.0+ is required\n", si.version);
424
425      return -1;
426    }
427
428  return 0;
429}
430
431void
432laudio_deinit(void)
433{
434  /* EMPTY */
435}
436