chiark / gitweb /
make client.c return status code on error
[disorder] / clients / playrtp-alsa.c
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
26 #if HAVE_ALSA_ASOUNDLIB_H 
27 #include "types.h"
28
29 #include <poll.h>
30 #include <alsa/asoundlib.h>
31 #include <assert.h>
32 #include <pthread.h>
33 #include <arpa/inet.h>
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 */
42 static snd_pcm_t *pcm;
43
44 /** @brief True when @ref pcm is up and running */
45 static int playrtp_alsa_prepared = 1;
46
47 static 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)
87     fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
88           MAXSAMPLES * samplesize * 3, err);
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);
98   if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
99     fatal(0, "error calling snd_pcm_sw_params: %d", err);
100 }
101
102 /** @brief Wait until ALSA wants some audio */
103 static void wait_alsa(void) {
104   struct pollfd fds[64];
105   int nfds, err;
106   unsigned short events;
107
108   for(;;) {
109     do {
110       if((nfds = snd_pcm_poll_descriptors(pcm,
111                                           fds, sizeof fds / sizeof *fds)) < 0)
112         fatal(0, "error calling snd_pcm_poll_descriptors: %d", nfds);
113     } while(poll(fds, nfds, -1) < 0 && errno == EINTR);
114     if((err = snd_pcm_poll_descriptors_revents(pcm, fds, nfds, &events)))
115       fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
116     if(events & POLLOUT)
117       return;
118   }
119 }
120
121 /** @brief Play some sound via ALSA
122  * @param s Pointer to sample data
123  * @param n Number of samples
124  * @return 0 on success, -1 on non-fatal error
125  */
126 static int playrtp_alsa_writei(const void *s, size_t n) {
127   /* Do the write */
128   const snd_pcm_sframes_t frames_written = snd_pcm_writei(pcm, s, n / 2);
129   if(frames_written < 0) {
130     /* Something went wrong */
131     switch(frames_written) {
132     case -EAGAIN:
133       return 0;
134     case -EPIPE:
135       error(0, "error calling snd_pcm_writei: %ld",
136             (long)frames_written);
137       return -1;
138     default:
139       fatal(0, "error calling snd_pcm_writei: %ld",
140             (long)frames_written);
141     }
142   } else {
143     /* Success */
144     next_timestamp += frames_written * 2;
145     if(dump_buffer) {
146       snd_pcm_sframes_t count;
147       const int16_t *sp = s;
148       
149       for(count = 0; count < frames_written * 2; ++count) {
150         dump_buffer[dump_index++] = (int16_t)ntohs(*sp++);
151         dump_index %= dump_size;
152       }
153     }
154     return 0;
155   }
156 }
157
158 /** @brief Play the relevant part of a packet
159  * @param p Packet to play
160  * @return 0 on success, -1 on non-fatal error
161  */
162 static int playrtp_alsa_play(const struct packet *p) {
163   return playrtp_alsa_writei(p->samples_raw + next_timestamp - p->timestamp,
164                              (p->timestamp + p->nsamples) - next_timestamp);
165 }
166
167 /** @brief Play some silence
168  * @param p Next packet or NULL
169  * @return 0 on success, -1 on non-fatal error
170  */
171 static int playrtp_alsa_infill(const struct packet *p) {
172   static const uint16_t zeros[INFILL_SAMPLES];
173   size_t samples_available = INFILL_SAMPLES;
174
175   if(p && samples_available > p->timestamp - next_timestamp)
176     samples_available = p->timestamp - next_timestamp;
177   return playrtp_alsa_writei(zeros, samples_available);
178 }
179
180 static void playrtp_alsa_enable(void){
181   int err;
182
183   if(!playrtp_alsa_prepared) {
184     if((err = snd_pcm_prepare(pcm)))
185       fatal(0, "error calling snd_pcm_prepare: %d", err);
186     playrtp_alsa_prepared = 1;
187   }
188 }
189
190 /** @brief Reset ALSA state after we lost synchronization */
191 static void playrtp_alsa_disable(int hard_reset) {
192   int err;
193
194   if((err = snd_pcm_nonblock(pcm, 0)))
195     fatal(0, "error calling snd_pcm_nonblock: %d", err);
196   if(hard_reset) {
197     if((err = snd_pcm_drop(pcm)))
198       fatal(0, "error calling snd_pcm_drop: %d", err);
199   } else
200     if((err = snd_pcm_drain(pcm)))
201       fatal(0, "error calling snd_pcm_drain: %d", err);
202   if((err = snd_pcm_nonblock(pcm, 1)))
203     fatal(0, "error calling snd_pcm_nonblock: %d", err);
204   playrtp_alsa_prepared = 0;
205 }
206
207 void playrtp_alsa(void) {
208   int escape;
209   const struct packet *p;
210
211   playrtp_alsa_init();
212   pthread_mutex_lock(&lock);
213   for(;;) {
214     /* Wait for the buffer to fill up a bit */
215     playrtp_fill_buffer();
216     playrtp_alsa_enable();
217     escape = 0;
218     info("Playing...");
219     /* Keep playing until the buffer empties out, or ALSA tells us to get
220      * lost */
221     while((nsamples >= minbuffer
222            || (nsamples > 0
223                && contains(pheap_first(&packets), next_timestamp)))
224           && !escape) {
225       /* Wait for ALSA to ask us for more data */
226       pthread_mutex_unlock(&lock);
227       wait_alsa();
228       pthread_mutex_lock(&lock);
229       /* ALSA is ready for more data, find something to play */
230       p = playrtp_next_packet();
231       /* Play it or play some silence */
232       if(contains(p, next_timestamp))
233         escape = playrtp_alsa_play(p);
234       else
235         escape = playrtp_alsa_infill(p);
236     }
237     active = 0;
238     /* We stop playing for a bit until the buffer re-fills */
239     pthread_mutex_unlock(&lock);
240     playrtp_alsa_disable(escape);
241     pthread_mutex_lock(&lock);
242   }
243 }
244
245 #endif
246
247 /*
248 Local Variables:
249 c-basic-offset:2
250 comment-column:40
251 fill-column:79
252 indent-tabs-mode:nil
253 End:
254 */