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