chiark / gitweb /
8a8f4c63569b76aa96d89ea2f6e06c447031d3aa
[disorder] / clients / playrtp-oss.c
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-oss.c
21  * @brief RTP player - OSS support
22  */
23
24 #include <config.h>
25
26 #if HAVE_SYS_SOUNDCARD_H
27 #include "types.h"
28
29 #include <poll.h>
30 #include <sys/ioctl.h>
31 #include <sys/soundcard.h>
32 #include <assert.h>
33 #include <pthread.h>
34 #include <string.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #if HAVE_LINUX_EMPEG_H
39 # include <linux/empeg.h>
40 #endif
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     int rate = 44100, stereo = 1, format = AFMT_S16_BE;
65     if(!device) {
66       if(access("/dev/dsp", W_OK) == 0)
67         device = "/dev/dsp";
68       else if(access("/dev/audio", W_OK) == 0)
69         device = "/dev/audio";
70       else
71         fatal(0, "cannot determine default audio device");
72     }
73     if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
74       fatal(errno, "error opening %s", device);
75     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
76       fatal(errno, "ioctl SNDCTL_DSP_SETFMT");
77     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
78       fatal(errno, "ioctl SNDCTL_DSP_STEREO");
79     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
80       fatal(errno, "ioctl SNDCTL_DSP_SPEED");
81     if(rate != 44100)
82       error(0, "asking for 44100Hz, got %dHz", rate);
83     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_GETBLKSIZE, &playrtp_oss_bufsize) < 0)
84       fatal(errno, "ioctl SNDCTL_DSP_GETBLKSIZE");
85     playrtp_oss_buffer = xmalloc(playrtp_oss_bufsize);
86     playrtp_oss_bufused = 0;
87     info("OSS buffer size %d", playrtp_oss_bufsize);
88     nonblock(playrtp_oss_fd);
89   }
90 }
91
92 /** @brief Flush the OSS output buffer
93  * @return 0 on success, non-0 on error
94  */
95 static int playrtp_oss_flush(void) {
96   int nbyteswritten;
97
98   if(!playrtp_oss_bufused)
99     return 0;                           /* nothing to do */
100   /* 0 out the unused portion of the buffer */
101   memset(playrtp_oss_buffer + playrtp_oss_bufused, 0,
102          playrtp_oss_bufsize - playrtp_oss_bufused);
103   for(;;) {
104     nbyteswritten = write(playrtp_oss_fd,
105                           playrtp_oss_buffer, playrtp_oss_bufsize);
106     if(nbyteswritten < 0) {
107       switch(errno) {
108       case EINTR:
109         break;                          /* try again */
110       case EAGAIN:
111         return 0;                       /* try later */
112       default:
113         error(errno, "error writing to %s", device);
114         return -1;
115       }
116     } else {
117       if(nbyteswritten < playrtp_oss_bufsize)
118         error(0, "%s: short write (%d/%d)",
119               device, nbyteswritten, playrtp_oss_bufsize);
120       playrtp_oss_bufused = 0;
121       return 0;
122     }
123   }
124 }
125
126 /** @brief Wait until the audio device can accept more data */
127 static void playrtp_oss_wait(void) {
128   struct pollfd fds[1];
129   int n;
130
131   do {
132     fds[0].fd = playrtp_oss_fd;
133     fds[0].events = POLLOUT;
134     while((n = poll(fds, 1, -1)) < 0 && errno == EINTR)
135       ;
136     if(n < 0)
137       fatal(errno, "calling poll");
138   } while(!(fds[0].revents & (POLLOUT|POLLERR)));
139 }
140
141 /** @brief Close the OSS output device
142  * @param hard If nonzero, drop pending data
143  */
144 static void playrtp_oss_disable(int hard) {
145   if(hard) {
146     if(ioctl(playrtp_oss_fd, SNDCTL_DSP_RESET, 0) < 0)
147       error(errno, "ioctl SNDCTL_DSP_RESET");
148   } else
149     playrtp_oss_flush();
150   xclose(playrtp_oss_fd);
151   playrtp_oss_fd = -1;
152   free(playrtp_oss_buffer);
153   playrtp_oss_buffer = 0;
154 }
155
156 /** @brief Write samples to OSS output device
157  * @param data Pointer to sample data
158  * @param nsamples Number of samples
159  * @return 0 on success, non-0 on error
160  */
161 static int playrtp_oss_write(const char *data, size_t samples) {
162   long bytes = samples * sizeof(int16_t);
163   while(bytes > 0) {
164     int n = playrtp_oss_bufsize - playrtp_oss_bufused;
165
166     if(n > bytes)
167       n = bytes;
168     memcpy(playrtp_oss_buffer + playrtp_oss_bufused, data, n);
169     bytes -= n;
170     data += n;
171     playrtp_oss_bufused += n;
172     if(playrtp_oss_bufused == playrtp_oss_bufsize)
173       if(playrtp_oss_flush())
174         return -1;
175   }
176   next_timestamp += samples;
177   return 0;
178 }
179
180 /** @brief Play some data from packet @p p
181  *
182  * @p p is assumed to contain @ref next_timestamp.
183  */
184 static int playrtp_oss_play(const struct packet *p) {
185   return playrtp_oss_write
186     ((const char *)(p->samples_raw + next_timestamp - p->timestamp),
187      (p->timestamp + p->nsamples) - next_timestamp);
188 }
189
190 /** @brief Play some silence before packet @p p
191  *
192  * @p p is assumed to be entirely before @ref next_timestamp.
193  */
194 static int playrtp_oss_infill(const struct packet *p) {
195   static const char zeros[INFILL_SAMPLES * sizeof(int16_t)];
196   size_t samples_available = INFILL_SAMPLES;
197
198   if(p && samples_available > p->timestamp - next_timestamp)
199     samples_available = p->timestamp - next_timestamp;
200   return playrtp_oss_write(zeros, samples_available);
201 }
202
203 /** @brief OSS backend for playrtp */
204 void playrtp_oss(void) {
205   int escape;
206   const struct packet *p;
207
208   pthread_mutex_lock(&lock);
209   for(;;) {
210     /* Wait for the buffer to fill up a bit */
211     playrtp_fill_buffer();
212     playrtp_oss_enable();
213     escape = 0;
214     info("Playing...");
215     /* Keep playing until the buffer empties out, we get an error */
216     while((nsamples >= minbuffer
217            || (nsamples > 0
218                && contains(pheap_first(&packets), next_timestamp)))
219           && !escape) {
220       /* Wait until we can play more */
221       pthread_mutex_unlock(&lock);
222       playrtp_oss_wait();
223       pthread_mutex_lock(&lock);
224       /* Device is ready for more data, find something to play */
225       p = playrtp_next_packet();
226       /* Play it or play some silence */
227       if(contains(p, next_timestamp))
228         escape = playrtp_oss_play(p);
229       else
230         escape = playrtp_oss_infill(p);
231     }
232     active = 0;
233     /* We stop playing for a bit until the buffer re-fills */
234     pthread_mutex_unlock(&lock);
235     playrtp_oss_disable(escape);
236     pthread_mutex_lock(&lock);
237   }
238 }
239
240 #endif
241
242 /*
243 Local Variables:
244 c-basic-offset:2
245 comment-column:40
246 fill-column:79
247 indent-tabs-mode:nil
248 End:
249 */