chiark / gitweb /
init script now uses start-stop-daemon if available (i.e. on Debian
[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)
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 */
103static 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 */
126static 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;
b28bddbb
RK
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 }
c593cf7c 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 */
162static 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 */
171static 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
180static 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 */
191static 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
207void 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/*
248Local Variables:
249c-basic-offset:2
250comment-column:40
251fill-column:79
252indent-tabs-mode:nil
253End:
254*/