chiark / gitweb /
Merge playlist branch against trunk to date.
[disorder] / clients / playrtp-oss.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2007 Richard Kettlewell
4  * Portions copyright (C) 2007 Ross Younger
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  * 
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /** @file clients/playrtp-oss.c
20  * @brief RTP player - OSS and empeg support
21  */
22
23 #include "common.h"
24
25 #if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
26
27 #include <poll.h>
28 #include <sys/ioctl.h>
29 #if !EMPEG_HOST
30 #include <sys/soundcard.h>
31 #endif
32 #include <pthread.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <arpa/inet.h>
37
38 #include "mem.h"
39 #include "log.h"
40 #include "vector.h"
41 #include "heap.h"
42 #include "syscalls.h"
43 #include "playrtp.h"
44
45 /** @brief /dev/dsp (or whatever) */
46 static int playrtp_oss_fd = -1;
47
48 /** @brief Audio buffer */
49 static char *playrtp_oss_buffer;
50
51 /** @brief Size of @ref playrtp_oss_buffer in bytes */
52 static int playrtp_oss_bufsize;
53
54 /** @brief Number of bytes used in @ref playrtp_oss_buffer */
55 static int playrtp_oss_bufused;
56
57 /** @brief Open and configure the OSS audio device */
58 static void playrtp_oss_enable(void) {
59   if(playrtp_oss_fd == -1) {
60 #if EMPEG_HOST
61     /* empeg audio driver only knows /dev/audio, only supports the equivalent
62      * of AFMT_S16_NE, has a fixed buffer size, and does not support the
63      * SNDCTL_ ioctls. */
64     if(!device)
65       device = "/dev/audio";
66     if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
67       fatal(errno, "error opening %s", device);
68     playrtp_oss_bufsize = 4608;
69 #else
70     int rate = 44100, stereo = 1, format = AFMT_S16_BE;
71     if(!device) {
72       if(access("/dev/dsp", W_OK) == 0)
73         device = "/dev/dsp";
74       else if(access("/dev/audio", W_OK) == 0)
75         device = "/dev/audio";
76       else
77         fatal(0, "cannot determine default audio device");
78     }
79     if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
80       fatal(errno, "error opening %s", device);
81     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
82       fatal(errno, "ioctl SNDCTL_DSP_SETFMT");
83     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
84       fatal(errno, "ioctl SNDCTL_DSP_STEREO");
85     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
86       fatal(errno, "ioctl SNDCTL_DSP_SPEED");
87     if(rate != 44100)
88       error(0, "asking for 44100Hz, got %dHz", rate);
89     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_GETBLKSIZE, &playrtp_oss_bufsize) < 0)
90       fatal(errno, "ioctl SNDCTL_DSP_GETBLKSIZE");
91     info("OSS buffer size %d", playrtp_oss_bufsize);
92 #endif
93     playrtp_oss_buffer = xmalloc(playrtp_oss_bufsize);
94     playrtp_oss_bufused = 0;
95     nonblock(playrtp_oss_fd);
96   }
97 }
98
99 /** @brief Flush the OSS output buffer
100  * @return 0 on success, non-0 on error
101  */
102 static int playrtp_oss_flush(void) {
103   int nbyteswritten;
104
105   if(!playrtp_oss_bufused)
106     return 0;                           /* nothing to do */
107   /* 0 out the unused portion of the buffer */
108   memset(playrtp_oss_buffer + playrtp_oss_bufused, 0,
109          playrtp_oss_bufsize - playrtp_oss_bufused);
110 #if EMPEG_HOST 
111   /* empeg audio driver insists on native-endian samples */
112   {
113     uint16_t *ptr,
114       *const limit = (uint16_t *)(playrtp_oss_buffer + playrtp_oss_bufused);
115
116     for(ptr = (uint16_t *)playrtp_oss_buffer; ptr < limit; ++ptr)
117       *ptr = ntohs(*ptr);
118   }
119 #endif
120   for(;;) {
121     nbyteswritten = write(playrtp_oss_fd,
122                           playrtp_oss_buffer, playrtp_oss_bufsize);
123     if(nbyteswritten < 0) {
124       switch(errno) {
125       case EINTR:
126         break;                          /* try again */
127       case EAGAIN:
128         return 0;                       /* try later */
129       default:
130         error(errno, "error writing to %s", device);
131         return -1;
132       }
133     } else {
134       if(nbyteswritten < playrtp_oss_bufsize)
135         error(0, "%s: short write (%d/%d)",
136               device, nbyteswritten, playrtp_oss_bufsize);
137       if(dump_buffer) {
138         int count;
139         const int16_t *sp = (const int16_t *)playrtp_oss_buffer;
140         
141         for(count = 0; count < playrtp_oss_bufsize; count += sizeof(int16_t)) {
142           dump_buffer[dump_index++] = (int16_t)ntohs(*sp++);
143           dump_index %= dump_size;
144         }
145       }
146       playrtp_oss_bufused = 0;
147       return 0;
148     }
149   }
150 }
151
152 /** @brief Wait until the audio device can accept more data */
153 static void playrtp_oss_wait(void) {
154   struct pollfd fds[1];
155   int n;
156
157   do {
158     fds[0].fd = playrtp_oss_fd;
159     fds[0].events = POLLOUT;
160     while((n = poll(fds, 1, -1)) < 0 && errno == EINTR)
161       ;
162     if(n < 0)
163       fatal(errno, "calling poll");
164   } while(!(fds[0].revents & (POLLOUT|POLLERR)));
165 }
166
167 /** @brief Close the OSS output device
168  * @param hard If nonzero, drop pending data
169  */
170 static void playrtp_oss_disable(int hard) {
171   if(hard) {
172 #if !EMPEG_HOST
173     /* No SNDCTL_DSP_ ioctls on empeg */
174     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_RESET, 0) < 0)
175       error(errno, "ioctl SNDCTL_DSP_RESET");
176 #endif
177   } else
178     playrtp_oss_flush();
179   xclose(playrtp_oss_fd);
180   playrtp_oss_fd = -1;
181   free(playrtp_oss_buffer);
182   playrtp_oss_buffer = 0;
183 }
184
185 /** @brief Write samples to OSS output device
186  * @param data Pointer to sample data
187  * @param samples Number of samples
188  * @return 0 on success, non-0 on error
189  */
190 static int playrtp_oss_write(const char *data, size_t samples) {
191   long bytes = samples * sizeof(int16_t);
192   while(bytes > 0) {
193     int n = playrtp_oss_bufsize - playrtp_oss_bufused;
194
195     if(n > bytes)
196       n = bytes;
197     memcpy(playrtp_oss_buffer + playrtp_oss_bufused, data, n);
198     bytes -= n;
199     data += n;
200     playrtp_oss_bufused += n;
201     if(playrtp_oss_bufused == playrtp_oss_bufsize)
202       if(playrtp_oss_flush())
203         return -1;
204   }
205   next_timestamp += samples;
206   return 0;
207 }
208
209 /** @brief Play some data from packet @p p
210  *
211  * @p p is assumed to contain @ref next_timestamp.
212  */
213 static int playrtp_oss_play(const struct packet *p) {
214   return playrtp_oss_write
215     ((const char *)(p->samples_raw + next_timestamp - p->timestamp),
216      (p->timestamp + p->nsamples) - next_timestamp);
217 }
218
219 /** @brief Play some silence before packet @p p
220  *
221  * @p p is assumed to be entirely before @ref next_timestamp.
222  */
223 static int playrtp_oss_infill(const struct packet *p) {
224   static const char zeros[INFILL_SAMPLES * sizeof(int16_t)];
225   size_t samples_available = INFILL_SAMPLES;
226
227   if(p && samples_available > p->timestamp - next_timestamp)
228     samples_available = p->timestamp - next_timestamp;
229   return playrtp_oss_write(zeros, samples_available);
230 }
231
232 /** @brief OSS backend for playrtp */
233 void playrtp_oss(void) {
234   int escape;
235   const struct packet *p;
236
237   pthread_mutex_lock(&lock);
238   for(;;) {
239     /* Wait for the buffer to fill up a bit */
240     playrtp_fill_buffer();
241     playrtp_oss_enable();
242     escape = 0;
243     info("Playing...");
244     /* Keep playing until the buffer empties out, we get an error */
245     while((nsamples >= minbuffer
246            || (nsamples > 0
247                && contains(pheap_first(&packets), next_timestamp)))
248           && !escape) {
249       /* Wait until we can play more */
250       pthread_mutex_unlock(&lock);
251       playrtp_oss_wait();
252       pthread_mutex_lock(&lock);
253       /* Device is ready for more data, find something to play */
254       p = playrtp_next_packet();
255       /* Play it or play some silence */
256       if(contains(p, next_timestamp))
257         escape = playrtp_oss_play(p);
258       else
259         escape = playrtp_oss_infill(p);
260     }
261     active = 0;
262     /* We stop playing for a bit until the buffer re-fills */
263     pthread_mutex_unlock(&lock);
264     playrtp_oss_disable(escape);
265     pthread_mutex_lock(&lock);
266   }
267 }
268
269 #endif
270
271 /*
272 Local Variables:
273 c-basic-offset:2
274 comment-column:40
275 fill-column:79
276 indent-tabs-mode:nil
277 End:
278 */