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