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