chiark / gitweb /
playrtp: cope better with spurious ALSA underruns
[disorder] / clients / playrtp-alsa.c
CommitLineData
c593cf7c 1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2007 Richard Kettlewell
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 * USA
19 */
20/** @file clients/playrtp-alsa.c
21 * @brief RTP player - ALSA support
22 */
23
24#include <config.h>
25
146e86fb 26#if HAVE_ALSA_ASOUNDLIB_H
c593cf7c 27#include "types.h"
28
29#include <poll.h>
30#include <alsa/asoundlib.h>
31#include <assert.h>
32#include <pthread.h>
b28bddbb 33#include <arpa/inet.h>
c593cf7c 34
35#include "mem.h"
36#include "log.h"
37#include "vector.h"
38#include "heap.h"
39#include "playrtp.h"
40
41/** @brief PCM handle */
42static snd_pcm_t *pcm;
43
44/** @brief True when @ref pcm is up and running */
45static int playrtp_alsa_prepared = 1;
46
47static void playrtp_alsa_init(void) {
48 snd_pcm_hw_params_t *hwparams;
49 snd_pcm_sw_params_t *swparams;
50 /* Only support one format for now */
51 const int sample_format = SND_PCM_FORMAT_S16_BE;
52 unsigned rate = 44100;
53 const int channels = 2;
54 const int samplesize = channels * sizeof(uint16_t);
55 snd_pcm_uframes_t pcm_bufsize = MAXSAMPLES * samplesize * 3;
56 /* If we can write more than this many samples we'll get a wakeup */
57 const int avail_min = 256;
58 int err;
59
60 /* Open ALSA */
61 if((err = snd_pcm_open(&pcm,
62 device ? device : "default",
63 SND_PCM_STREAM_PLAYBACK,
64 SND_PCM_NONBLOCK)))
65 fatal(0, "error from snd_pcm_open: %d", err);
66 /* Set up 'hardware' parameters */
67 snd_pcm_hw_params_alloca(&hwparams);
68 if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
69 fatal(0, "error from snd_pcm_hw_params_any: %d", err);
70 if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
71 SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
72 fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
73 if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
74 sample_format)) < 0)
75
76 fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d",
77 sample_format, err);
78 if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0)
79 fatal(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
80 rate, err);
81 if((err = snd_pcm_hw_params_set_channels(pcm, hwparams,
82 channels)) < 0)
83 fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
84 channels, err);
85 if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
86 &pcm_bufsize)) < 0)
fb94c294
RK
87 fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%ld): %d",
88 (long)pcm_bufsize, err);
c593cf7c 89 if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
90 fatal(0, "error calling snd_pcm_hw_params: %d", err);
91 /* Set up 'software' parameters */
92 snd_pcm_sw_params_alloca(&swparams);
93 if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
94 fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
95 if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0)
96 fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
97 avail_min, err);
fb94c294
RK
98 /* Default start threshold is 1, which means that PCM starts as soon as we've
99 * written anything. Setting it to pcm_bufsize (around 15000) produces
100 * -EINVAL. 1024 is a guess... */
101 if((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, 1024)) < 0)
102 fatal(0, "error calling snd_pcm_sw_params_set_start_threshold %d: %d",
103 1024, err);
c593cf7c 104 if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
105 fatal(0, "error calling snd_pcm_sw_params: %d", err);
106}
107
108/** @brief Wait until ALSA wants some audio */
109static void wait_alsa(void) {
110 struct pollfd fds[64];
111 int nfds, err;
112 unsigned short events;
113
114 for(;;) {
115 do {
116 if((nfds = snd_pcm_poll_descriptors(pcm,
117 fds, sizeof fds / sizeof *fds)) < 0)
118 fatal(0, "error calling snd_pcm_poll_descriptors: %d", nfds);
119 } while(poll(fds, nfds, -1) < 0 && errno == EINTR);
120 if((err = snd_pcm_poll_descriptors_revents(pcm, fds, nfds, &events)))
121 fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
122 if(events & POLLOUT)
123 return;
124 }
125}
126
127/** @brief Play some sound via ALSA
128 * @param s Pointer to sample data
129 * @param n Number of samples
130 * @return 0 on success, -1 on non-fatal error
131 */
132static int playrtp_alsa_writei(const void *s, size_t n) {
fb94c294
RK
133 int err;
134 snd_pcm_sframes_t frames_written;
135
c593cf7c 136 /* Do the write */
fb94c294 137 frames_written = snd_pcm_writei(pcm, s, n / 2);
c593cf7c 138 if(frames_written < 0) {
139 /* Something went wrong */
140 switch(frames_written) {
141 case -EAGAIN:
142 return 0;
143 case -EPIPE:
144 error(0, "error calling snd_pcm_writei: %ld",
145 (long)frames_written);
fb94c294
RK
146 if((err = snd_pcm_prepare(pcm)) < 0) {
147 error(0, "error calling snd_pcm_prepare: %d", err);
148 return -1;
149 }
150 frames_written = snd_pcm_writei(pcm, s, n / 2);
151 if(frames_written == -EAGAIN)
152 return 0;
153 else if(frames_written < 0) {
154 error(0, "error calling snd_pcm_writei: %ld",
155 (long)frames_written);
156 return -1;
157 }
158 break;
c593cf7c 159 default:
160 fatal(0, "error calling snd_pcm_writei: %ld",
161 (long)frames_written);
162 }
fb94c294
RK
163 }
164 /* Success */
165 next_timestamp += frames_written * 2;
166 if(dump_buffer) {
167 snd_pcm_sframes_t count;
168 const int16_t *sp = s;
169
170 for(count = 0; count < frames_written * 2; ++count) {
171 dump_buffer[dump_index++] = (int16_t)ntohs(*sp++);
172 dump_index %= dump_size;
b28bddbb 173 }
c593cf7c 174 }
fb94c294 175 return 0;
c593cf7c 176}
177
178/** @brief Play the relevant part of a packet
179 * @param p Packet to play
180 * @return 0 on success, -1 on non-fatal error
181 */
182static int playrtp_alsa_play(const struct packet *p) {
183 return playrtp_alsa_writei(p->samples_raw + next_timestamp - p->timestamp,
184 (p->timestamp + p->nsamples) - next_timestamp);
185}
186
187/** @brief Play some silence
188 * @param p Next packet or NULL
189 * @return 0 on success, -1 on non-fatal error
190 */
191static int playrtp_alsa_infill(const struct packet *p) {
192 static const uint16_t zeros[INFILL_SAMPLES];
193 size_t samples_available = INFILL_SAMPLES;
194
195 if(p && samples_available > p->timestamp - next_timestamp)
196 samples_available = p->timestamp - next_timestamp;
197 return playrtp_alsa_writei(zeros, samples_available);
198}
199
200static void playrtp_alsa_enable(void){
201 int err;
202
203 if(!playrtp_alsa_prepared) {
204 if((err = snd_pcm_prepare(pcm)))
205 fatal(0, "error calling snd_pcm_prepare: %d", err);
206 playrtp_alsa_prepared = 1;
207 }
208}
209
210/** @brief Reset ALSA state after we lost synchronization */
211static void playrtp_alsa_disable(int hard_reset) {
212 int err;
213
214 if((err = snd_pcm_nonblock(pcm, 0)))
215 fatal(0, "error calling snd_pcm_nonblock: %d", err);
216 if(hard_reset) {
217 if((err = snd_pcm_drop(pcm)))
218 fatal(0, "error calling snd_pcm_drop: %d", err);
219 } else
220 if((err = snd_pcm_drain(pcm)))
221 fatal(0, "error calling snd_pcm_drain: %d", err);
222 if((err = snd_pcm_nonblock(pcm, 1)))
223 fatal(0, "error calling snd_pcm_nonblock: %d", err);
224 playrtp_alsa_prepared = 0;
225}
226
227void playrtp_alsa(void) {
228 int escape;
229 const struct packet *p;
230
231 playrtp_alsa_init();
232 pthread_mutex_lock(&lock);
233 for(;;) {
234 /* Wait for the buffer to fill up a bit */
235 playrtp_fill_buffer();
236 playrtp_alsa_enable();
237 escape = 0;
238 info("Playing...");
239 /* Keep playing until the buffer empties out, or ALSA tells us to get
240 * lost */
241 while((nsamples >= minbuffer
242 || (nsamples > 0
243 && contains(pheap_first(&packets), next_timestamp)))
244 && !escape) {
245 /* Wait for ALSA to ask us for more data */
246 pthread_mutex_unlock(&lock);
247 wait_alsa();
248 pthread_mutex_lock(&lock);
249 /* ALSA is ready for more data, find something to play */
250 p = playrtp_next_packet();
251 /* Play it or play some silence */
252 if(contains(p, next_timestamp))
253 escape = playrtp_alsa_play(p);
254 else
255 escape = playrtp_alsa_infill(p);
256 }
257 active = 0;
258 /* We stop playing for a bit until the buffer re-fills */
259 pthread_mutex_unlock(&lock);
260 playrtp_alsa_disable(escape);
261 pthread_mutex_lock(&lock);
262 }
263}
264
265#endif
266
267/*
268Local Variables:
269c-basic-offset:2
270comment-column:40
271fill-column:79
272indent-tabs-mode:nil
273End:
274*/