Commit | Line | Data |
---|---|---|
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 */ | |
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; | |
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 | */ | |
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 | */ |