1 /**
2  * @file oss.c  Open Sound System (OSS) driver
3  *
4  * Copyright (C) 2010 Creytiv.com
5  */
6 #include <re.h>
7 #include <rem.h>
8 #include <baresip.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <pthread.h>
12 #include <fcntl.h>
13 #include <sys/ioctl.h>
14 #if defined(NETBSD) || defined(OPENBSD)
15 #include <soundcard.h>
16 #elif defined (LINUX)
17 #include <linux/soundcard.h>
18 #else
19 #include <sys/soundcard.h>
20 #endif
21 #ifdef SOLARIS
22 #include <sys/filio.h>
23 #endif
24 
25 
26 /**
27  * @defgroup oss oss
28  *
29  * Open Sound System (OSS) audio driver module
30  *
31  *
32  * References:
33  *
34  *    http://www.4front-tech.com/linux.html
35  */
36 
37 
38 struct ausrc_st {
39 	const struct ausrc *as;      /* inheritance */
40 	pthread_t thread;
41 	bool run;
42 	int fd;
43 	int16_t *sampv;
44 	size_t sampc;
45 	ausrc_read_h *rh;
46 	ausrc_error_h *errh;
47 	void *arg;
48 };
49 
50 struct auplay_st {
51 	const struct auplay *ap;      /* inheritance */
52 	pthread_t thread;
53 	bool run;
54 	int fd;
55 	int16_t *sampv;
56 	size_t sampc;
57 	auplay_write_h *wh;
58 	void *arg;
59 };
60 
61 
62 static struct ausrc *ausrc;
63 static struct auplay *auplay;
64 static char oss_dev[64] = "/dev/dsp";
65 
66 
67 /*
68  * Automatically calculate the fragment size depending on sampling rate
69  * and number of channels. More entries can be added to the table below.
70  *
71  * NOTE. Powermac 8200 and linux 2.4.18 gives:
72  *       SNDCTL_DSP_SETFRAGMENT: Invalid argument
73  */
set_fragment(int fd,uint32_t sampc)74 static int set_fragment(int fd, uint32_t sampc)
75 {
76 	static const struct {
77 		uint16_t max;
78 		uint16_t size;
79 	} fragv[] = {
80 		{10, 7},  /* 10 x 2^7 = 1280 =  4 x 320 */
81 		{15, 7},  /* 15 x 2^7 = 1920 =  6 x 320 */
82 		{20, 7},  /* 20 x 2^7 = 2560 =  8 x 320 */
83 		{25, 7},  /* 25 x 2^7 = 3200 = 10 x 320 */
84 		{15, 8},  /* 15 x 2^8 = 3840 = 12 x 320 */
85 		{20, 8},  /* 20 x 2^8 = 5120 = 16 x 320 */
86 		{25, 8}   /* 25 x 2^8 = 6400 = 20 x 320 */
87 	};
88 	size_t i;
89 	const uint32_t buf_size = 2 * sampc;
90 
91 	for (i=0; i<ARRAY_SIZE(fragv); i++) {
92 		const uint16_t frag_max  = fragv[i].max;
93 		const uint16_t frag_size = fragv[i].size;
94 		const uint32_t fragment_size = frag_max * (1<<frag_size);
95 
96 		if (0 == (fragment_size%buf_size)) {
97 			int fragment = (frag_max<<16) | frag_size;
98 
99 			if (0 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT,
100 				       &fragment)) {
101 				return 0;
102 			}
103 		}
104 	}
105 
106 	return ENODEV;
107 }
108 
109 
oss_reset(int fd,uint32_t srate,uint8_t ch,int sampc,int nonblock)110 static int oss_reset(int fd, uint32_t srate, uint8_t ch, int sampc,
111 		     int nonblock)
112 {
113 	int format    = AFMT_S16_NE; /* native endian */
114 	int speed     = srate;
115 	int channels  = ch;
116 	int blocksize = 0;
117 	int err;
118 
119 	err = set_fragment(fd, sampc);
120 	if (err)
121 		return err;
122 
123 	if (0 != ioctl(fd, FIONBIO, &nonblock))
124 		return errno;
125 	if (0 != ioctl(fd, SNDCTL_DSP_SETFMT, &format))
126 		return errno;
127 	if (0 != ioctl(fd, SNDCTL_DSP_CHANNELS, &channels))
128 		return errno;
129 	if (2 == channels) {
130 		int stereo = 1;
131 		if (0 != ioctl(fd, SNDCTL_DSP_STEREO, &stereo))
132 			return errno;
133 	}
134 	if (0 != ioctl(fd, SNDCTL_DSP_SPEED, &speed))
135 		return errno;
136 
137 	(void)ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
138 
139 	info("oss: init: %d Hz %d ch, blocksize=%d\n",
140 	     speed, channels, blocksize);
141 
142 	return 0;
143 }
144 
145 
auplay_destructor(void * arg)146 static void auplay_destructor(void *arg)
147 {
148 	struct auplay_st *st = arg;
149 
150 	if (st->run) {
151 		st->run = false;
152 		pthread_join(st->thread, NULL);
153 	}
154 
155 	if (-1 != st->fd) {
156 		(void)close(st->fd);
157 	}
158 
159 	mem_deref(st->sampv);
160 }
161 
162 
ausrc_destructor(void * arg)163 static void ausrc_destructor(void *arg)
164 {
165 	struct ausrc_st *st = arg;
166 
167 	if (st->run) {
168 		st->run = false;
169 		pthread_join(st->thread, NULL);
170 	}
171 
172 	if (-1 != st->fd) {
173 		(void)close(st->fd);
174 	}
175 
176 	mem_deref(st->sampv);
177 }
178 
179 
record_thread(void * arg)180 static void *record_thread(void *arg)
181 {
182 	struct ausrc_st *st = arg;
183 	int n;
184 
185 	while (st->run) {
186 
187 		n = read(st->fd, st->sampv, st->sampc*2);
188 		if (n <= 0)
189 			continue;
190 
191 		st->rh(st->sampv, n/2, st->arg);
192 	}
193 
194 	return NULL;
195 }
196 
197 
play_thread(void * arg)198 static void *play_thread(void *arg)
199 {
200 	struct auplay_st *st = arg;
201 	int n;
202 
203 	while (st->run) {
204 
205 		st->wh(st->sampv, st->sampc, st->arg);
206 
207 		n = write(st->fd, st->sampv, st->sampc*2);
208 		if (n < 0) {
209 			warning("oss: write: %m\n", errno);
210 			break;
211 		}
212 	}
213 
214 	return NULL;
215 }
216 
217 
src_alloc(struct ausrc_st ** stp,const struct ausrc * as,struct media_ctx ** ctx,struct ausrc_prm * prm,const char * device,ausrc_read_h * rh,ausrc_error_h * errh,void * arg)218 static int src_alloc(struct ausrc_st **stp, const struct ausrc *as,
219 		     struct media_ctx **ctx,
220 		     struct ausrc_prm *prm, const char *device,
221 		     ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
222 {
223 	struct ausrc_st *st;
224 	int err;
225 
226 	(void)ctx;
227 	(void)errh;
228 
229 	if (!stp || !as || !prm || prm->fmt != AUFMT_S16LE || !rh)
230 		return EINVAL;
231 
232 	st = mem_zalloc(sizeof(*st), ausrc_destructor);
233 	if (!st)
234 		return ENOMEM;
235 
236 	st->fd   = -1;
237 	st->rh   = rh;
238 	st->errh = errh;
239 	st->arg  = arg;
240 
241 	if (!device)
242 		device = oss_dev;
243 
244 	st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
245 
246 	st->sampv = mem_alloc(2 * st->sampc, NULL);
247 	if (!st->sampv) {
248 		err = ENOMEM;
249 		goto out;
250 	}
251 
252 	st->fd = open(device, O_RDONLY);
253 	if (st->fd < 0) {
254 		err = errno;
255 		goto out;
256 	}
257 
258 	err = oss_reset(st->fd, prm->srate, prm->ch, st->sampc, 0);
259 	if (err)
260 		goto out;
261 
262 	st->as = as;
263 
264 	st->run = true;
265 	err = pthread_create(&st->thread, NULL, record_thread, st);
266 	if (err) {
267 		st->run = false;
268 		goto out;
269 	}
270 
271  out:
272 	if (err)
273 		mem_deref(st);
274 	else
275 		*stp = st;
276 
277 	return err;
278 }
279 
280 
play_alloc(struct auplay_st ** stp,const struct auplay * ap,struct auplay_prm * prm,const char * device,auplay_write_h * wh,void * arg)281 static int play_alloc(struct auplay_st **stp, const struct auplay *ap,
282 		      struct auplay_prm *prm, const char *device,
283 		      auplay_write_h *wh, void *arg)
284 {
285 	struct auplay_st *st;
286 	int err;
287 
288 	if (!stp || !ap || !prm || prm->fmt != AUFMT_S16LE || !wh)
289 		return EINVAL;
290 
291 	st = mem_zalloc(sizeof(*st), auplay_destructor);
292 	if (!st)
293 		return ENOMEM;
294 
295 	st->fd  = -1;
296 	st->wh  = wh;
297 	st->arg = arg;
298 
299 	if (!device)
300 		device = oss_dev;
301 
302 	st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
303 
304 	st->sampv = mem_alloc(st->sampc * 2, NULL);
305 	if (!st->sampv) {
306 		err = ENOMEM;
307 		goto out;
308 	}
309 
310 	st->fd = open(device, O_WRONLY);
311 	if (st->fd < 0) {
312 		err = errno;
313 		goto out;
314 	}
315 
316 	err = oss_reset(st->fd, prm->srate, prm->ch, st->sampc, 0);
317 	if (err)
318 		goto out;
319 
320 	st->ap = ap;
321 
322 	st->run = true;
323 	err = pthread_create(&st->thread, NULL, play_thread, st);
324 	if (err) {
325 		st->run = false;
326 		goto out;
327 	}
328 
329  out:
330 	if (err)
331 		mem_deref(st);
332 	else
333 		*stp = st;
334 
335 	return err;
336 }
337 
338 
module_init(void)339 static int module_init(void)
340 {
341 	int err;
342 
343 	err  = ausrc_register(&ausrc, baresip_ausrcl(), "oss", src_alloc);
344 	err |= auplay_register(&auplay, baresip_auplayl(), "oss", play_alloc);
345 
346 	return err;
347 }
348 
349 
module_close(void)350 static int module_close(void)
351 {
352 	ausrc  = mem_deref(ausrc);
353 	auplay = mem_deref(auplay);
354 
355 	return 0;
356 }
357 
358 
359 EXPORT_SYM const struct mod_export DECL_EXPORTS(oss) = {
360 	"oss",
361 	"audio",
362 	module_init,
363 	module_close,
364 };
365