1 /*
2 * ALSA <-> OSS PCM I/O plugin
3 *
4 * Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de>
5 *
6 * This library is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <stdio.h>
22 #include <sys/ioctl.h>
23 #include <alsa/asoundlib.h>
24 #include <alsa/pcm_external.h>
25 #ifdef __linux__
26 #include <linux/soundcard.h>
27 #else
28 #include <sys/soundcard.h>
29 #endif
30
31 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
32
33 #ifdef __FreeBSD__
34 #define FREEBSD_OSS_RATE_MIN 1
35 #define FREEBSD_OSS_RATE_MAX 384000
36
37 #define FREEBSD_OSS_CHANNELS_MIN 1
38 #define FREEBSD_OSS_CHANNELS_MAX 8
39
40 #define FREEBSD_OSS_BUFSZ_MAX 131072
41 #define FREEBSD_OSS_BLKCNT_MIN 2
42 #define FREEBSD_OSS_BLKSZ_MIN 16 /* (FREEBSD_OSS_CHANNELS_MAX * 4) */
43
44 #define FREEBSD_OSS_BUFSZ_MIN (FREEBSD_OSS_BLKCNT_MIN * FREEBSD_OSS_BLKSZ_MIN)
45 #define FREEBSD_OSS_BLKCNT_MAX (FREEBSD_OSS_BUFSZ_MAX / FREEBSD_OSS_BUFSZ_MIN)
46 #define FREEBSD_OSS_BLKSZ_MAX (FREEBSD_OSS_BUFSZ_MAX / FREEBSD_OSS_BLKCNT_MIN)
47 #endif
48
49 typedef struct snd_pcm_oss {
50 snd_pcm_ioplug_t io;
51 char *device;
52 int fd;
53 #ifdef __FreeBSD__
54 int bufsz, ptr, ptr_align, last_bytes;
55 #else
56 int fragment_set;
57 int caps;
58 #endif
59 int format;
60 #ifndef __FreeBSD__
61 unsigned int period_shift;
62 unsigned int periods;
63 #endif
64 unsigned int frame_bytes;
65 } snd_pcm_oss_t;
66
oss_write(snd_pcm_ioplug_t * io,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)67 static snd_pcm_sframes_t oss_write(snd_pcm_ioplug_t *io,
68 const snd_pcm_channel_area_t *areas,
69 snd_pcm_uframes_t offset,
70 snd_pcm_uframes_t size)
71 {
72 snd_pcm_oss_t *oss = io->private_data;
73 const char *buf;
74 ssize_t result;
75
76 /* we handle only an interleaved buffer */
77 buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
78 size *= oss->frame_bytes;
79 result = write(oss->fd, buf, size);
80 #ifdef __FreeBSD__
81 if (result == -1) {
82 if (errno == EAGAIN)
83 return 0;
84 else
85 return -errno;
86 }
87 #else
88 if (result <= 0) {
89 if (result == -EAGAIN)
90 return 0;
91 else
92 return result;
93 }
94 #endif
95 return result / oss->frame_bytes;
96 }
97
oss_read(snd_pcm_ioplug_t * io,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)98 static snd_pcm_sframes_t oss_read(snd_pcm_ioplug_t *io,
99 const snd_pcm_channel_area_t *areas,
100 snd_pcm_uframes_t offset,
101 snd_pcm_uframes_t size)
102 {
103 snd_pcm_oss_t *oss = io->private_data;
104 char *buf;
105 ssize_t result;
106
107 /* we handle only an interleaved buffer */
108 buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
109 size *= oss->frame_bytes;
110 result = read(oss->fd, buf, size);
111 #ifdef __FreeBSD__
112 if (result == -1) {
113 if (errno == EAGAIN)
114 return 0;
115 else
116 return -errno;
117 }
118 #else
119 if (result <= 0) {
120 if (result == -EAGAIN)
121 return 0;
122 else
123 return result;
124 }
125 #endif
126 return result / oss->frame_bytes;
127 }
128
oss_pointer(snd_pcm_ioplug_t * io)129 static snd_pcm_sframes_t oss_pointer(snd_pcm_ioplug_t *io)
130 {
131 #ifdef __FreeBSD__
132 snd_pcm_oss_t *oss = io->private_data;
133 #ifdef FREEBSD_OSS_USE_IO_PTR
134 struct count_info ci;
135 #endif
136 audio_buf_info bi;
137
138 if (io->state != SND_PCM_STATE_RUNNING)
139 return 0;
140
141 if (io->state == SND_PCM_STATE_XRUN)
142 return -EPIPE;
143
144 #ifdef FREEBSD_OSS_USE_IO_PTR
145 if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
146 SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &ci) < 0)
147 return -EINVAL;
148
149 if (ci.ptr == oss->last_bytes &&
150 ((ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
151 SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0) ||
152 bi.bytes == oss->bufsz))
153 return -EPIPE;
154
155 if (ci.ptr < oss->last_bytes)
156 oss->ptr += oss->bufsz;
157
158 oss->ptr += ci.ptr;
159 oss->ptr -= oss->last_bytes;
160 oss->ptr %= oss->ptr_align;
161
162 oss->last_bytes = ci.ptr;
163 #else /* !FREEBSD_OSS_USE_IO_PTR */
164 if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
165 SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0)
166 return -EINVAL;
167
168 if (bi.bytes == oss->bufsz && bi.bytes == oss->last_bytes) {
169 #if 0
170 #ifdef SNDCTL_DSP_GETERROR
171 audio_errinfo ei;
172 if (ioctl(oss->fd, SNDCTL_DSP_GETERROR, &ei) < 0 ||
173 (io->stream == SND_PCM_STREAM_PLAYBACK &&
174 ei.play_underruns != 0) ||
175 (io->stream == SND_PCM_STREAM_CAPTURE &&
176 ei.rec_overruns != 0))
177 #endif
178 #endif
179 return -EPIPE;
180 }
181
182 if (bi.bytes > oss->last_bytes) {
183 oss->ptr += bi.bytes - oss->last_bytes;
184 oss->ptr %= oss->ptr_align;
185 }
186
187 oss->last_bytes = bi.bytes;
188 #endif /* FREEBSD_OSS_USE_IO_PTR */
189
190 return snd_pcm_bytes_to_frames(io->pcm, oss->ptr);
191 #else
192 snd_pcm_oss_t *oss = io->private_data;
193 struct count_info info;
194 int ptr;
195
196 if (ioctl(oss->fd, io->stream == SND_PCM_STREAM_PLAYBACK ?
197 SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &info) < 0) {
198 fprintf(stderr, "*** OSS: oss_pointer error\n");
199 return 0;
200 }
201 ptr = snd_pcm_bytes_to_frames(io->pcm, info.ptr);
202 return ptr;
203 #endif
204 }
205
oss_start(snd_pcm_ioplug_t * io)206 static int oss_start(snd_pcm_ioplug_t *io)
207 {
208 snd_pcm_oss_t *oss = io->private_data;
209 #ifdef __FreeBSD__
210 audio_buf_info bi;
211 #ifdef FREEBSD_OSS_USE_IO_PTR
212 struct count_info ci;
213 #endif
214 #endif
215 int tmp = io->stream == SND_PCM_STREAM_PLAYBACK ?
216 PCM_ENABLE_OUTPUT : PCM_ENABLE_INPUT;
217
218 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
219 fprintf(stderr, "%s()\n", __func__);
220 #endif
221
222 if (ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) {
223 fprintf(stderr, "*** OSS: trigger failed\n");
224 #ifdef __FreeBSD__
225 return -EINVAL;
226 #else
227 if (io->stream == SND_PCM_STREAM_CAPTURE)
228 /* fake read to trigger */
229 read(oss->fd, &tmp, 0);
230 #endif
231 }
232
233 #ifdef __FreeBSD__
234 if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
235 SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0)
236 return -EINVAL;
237
238 if (oss->bufsz != (bi.fragsize * bi.fragstotal)) {
239 fprintf(stderr, "%s(): WARNING - bufsz changed! %d -> %d\n",
240 __func__, oss->bufsz, bi.fragsize * bi.fragstotal);
241 oss->bufsz = bi.fragsize * bi.fragstotal;
242 }
243
244 #ifdef FREEBSD_OSS_USE_IO_PTR
245 if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
246 SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &ci) < 0)
247 return -EINVAL;
248
249 oss->last_bytes = ci.ptr;
250 #else
251 oss->last_bytes = bi.bytes;
252 #endif
253 oss->ptr = 0;
254 #endif
255
256 return 0;
257 }
258
oss_stop(snd_pcm_ioplug_t * io)259 static int oss_stop(snd_pcm_ioplug_t *io)
260 {
261 snd_pcm_oss_t *oss = io->private_data;
262 int tmp = 0;
263
264 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
265 fprintf(stderr, "%s()\n", __func__);
266 #endif
267
268 ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &tmp);
269 return 0;
270 }
271
oss_drain(snd_pcm_ioplug_t * io)272 static int oss_drain(snd_pcm_ioplug_t *io)
273 {
274 snd_pcm_oss_t *oss = io->private_data;
275
276 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
277 fprintf(stderr, "%s()\n", __func__);
278 #endif
279
280 if (io->stream == SND_PCM_STREAM_PLAYBACK)
281 ioctl(oss->fd, SNDCTL_DSP_SYNC, NULL);
282 return 0;
283 }
284
oss_delay(snd_pcm_ioplug_t * io,snd_pcm_sframes_t * delayp)285 static int oss_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
286 {
287 snd_pcm_oss_t *oss = io->private_data;
288 int tmp;
289
290 if (oss->fd < 0)
291 return -EBADFD;
292
293 if (io->stream == SND_PCM_STREAM_PLAYBACK) {
294 if (ioctl(oss->fd, SNDCTL_DSP_GETODELAY, &tmp) < 0 || tmp < 0)
295 tmp = 0;
296 } else {
297 tmp = 0;
298 }
299 *delayp = snd_pcm_bytes_to_frames(io->pcm, tmp);
300
301 return (0);
302 }
303
304 #ifndef __FreeBSD__
oss_prepare(snd_pcm_ioplug_t * io)305 static int oss_prepare(snd_pcm_ioplug_t *io)
306 {
307 snd_pcm_oss_t *oss = io->private_data;
308 int tmp;
309
310 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
311 fprintf(stderr, "%s()\n", __func__);
312 #endif
313
314 ioctl(oss->fd, SNDCTL_DSP_RESET, NULL);
315
316 tmp = io->channels;
317 if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0) {
318 perror("SNDCTL_DSP_CHANNELS");
319 return -EINVAL;
320 }
321 tmp = oss->format;
322 if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &tmp) < 0) {
323 perror("SNDCTL_DSP_SETFMT");
324 return -EINVAL;
325 }
326 tmp = io->rate;
327 if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &tmp) < 0 ||
328 tmp > io->rate * 1.01 || tmp < io->rate * 0.99) {
329 perror("SNDCTL_DSP_SPEED");
330 return -EINVAL;
331 }
332 return 0;
333 }
334 #endif
335
336 #ifdef __FreeBSD__
337 static const struct {
338 int oss_format;
339 snd_pcm_format_t alsa_format;
340 } oss_formats_tab[] = {
341 { AFMT_U8, SND_PCM_FORMAT_U8 },
342 { AFMT_S8, SND_PCM_FORMAT_S8 },
343 { AFMT_MU_LAW, SND_PCM_FORMAT_MU_LAW },
344 { AFMT_A_LAW, SND_PCM_FORMAT_A_LAW },
345 { AFMT_S16_LE, SND_PCM_FORMAT_S16_LE },
346 { AFMT_S16_BE, SND_PCM_FORMAT_S16_BE },
347 { AFMT_U16_LE, SND_PCM_FORMAT_U16_LE },
348 { AFMT_U16_BE, SND_PCM_FORMAT_U16_BE },
349 { AFMT_S24_LE, SND_PCM_FORMAT_S24_3LE },
350 { AFMT_S24_BE, SND_PCM_FORMAT_S24_3BE },
351 { AFMT_U24_LE, SND_PCM_FORMAT_U24_3LE },
352 { AFMT_U24_BE, SND_PCM_FORMAT_U24_3BE },
353 { AFMT_S32_LE, SND_PCM_FORMAT_S32_LE },
354 { AFMT_S32_BE, SND_PCM_FORMAT_S32_BE },
355 { AFMT_U32_LE, SND_PCM_FORMAT_U32_LE },
356 { AFMT_U32_BE, SND_PCM_FORMAT_U32_BE },
357 /* Special */
358 { AFMT_S24_LE, SND_PCM_FORMAT_S20_3LE },
359 { AFMT_S24_BE, SND_PCM_FORMAT_S20_3BE },
360 { AFMT_U24_LE, SND_PCM_FORMAT_U20_3LE },
361 { AFMT_U24_BE, SND_PCM_FORMAT_U20_3BE },
362 { AFMT_S24_LE, SND_PCM_FORMAT_S18_3LE },
363 { AFMT_S24_BE, SND_PCM_FORMAT_S18_3BE },
364 { AFMT_U24_LE, SND_PCM_FORMAT_U18_3LE },
365 { AFMT_U24_BE, SND_PCM_FORMAT_U18_3BE },
366 { AFMT_S32_LE, SND_PCM_FORMAT_S24_LE },
367 { AFMT_S32_BE, SND_PCM_FORMAT_S24_BE },
368 { AFMT_U32_LE, SND_PCM_FORMAT_U24_LE },
369 { AFMT_U32_BE, SND_PCM_FORMAT_U24_BE },
370 };
371 #endif
372
oss_hw_params(snd_pcm_ioplug_t * io,snd_pcm_hw_params_t * params ATTRIBUTE_UNUSED)373 static int oss_hw_params(snd_pcm_ioplug_t *io,
374 snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED)
375 {
376 snd_pcm_oss_t *oss = io->private_data;
377 int i, tmp, err;
378 #ifdef __FreeBSD__
379 int blksz_shift, blkcnt;
380 audio_buf_info bi;
381 #else
382 unsigned int period_bytes;
383 #endif
384 long oflags, flags;
385
386 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
387 fprintf(stderr, "%s()\n", __func__);
388 #endif
389
390 oss->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;
391 #ifdef __FreeBSD__
392 oss->ptr_align = io->buffer_size * oss->frame_bytes;
393
394 oss->format = 0;
395 for (i = 0; i < ARRAY_SIZE(oss_formats_tab); i++) {
396 if (oss_formats_tab[i].alsa_format == io->format) {
397 oss->format = oss_formats_tab[i].oss_format;
398 break;
399 }
400 }
401 if (oss->format == 0) {
402 #else
403 switch (io->format) {
404 case SND_PCM_FORMAT_U8:
405 oss->format = AFMT_U8;
406 break;
407 case SND_PCM_FORMAT_S16_LE:
408 oss->format = AFMT_S16_LE;
409 break;
410 case SND_PCM_FORMAT_S16_BE:
411 oss->format = AFMT_S16_BE;
412 break;
413 default:
414 #endif
415 fprintf(stderr, "*** OSS: unsupported format %s\n", snd_pcm_format_name(io->format));
416 return -EINVAL;
417 }
418 #ifdef __FreeBSD__
419
420 ioctl(oss->fd, SNDCTL_DSP_RESET);
421
422 /* use a 16ms HW buffer by default */
423 tmp = ((16 * io->rate) / 1000) * oss->frame_bytes;
424
425 /* round up to nearest power of two */
426 while (tmp & (tmp - 1))
427 tmp += tmp & ~(tmp - 1);
428
429 /* get logarithmic value */
430 for (blksz_shift = 0; blksz_shift < 24; blksz_shift++) {
431 if (tmp == (1 << blksz_shift))
432 break;
433 }
434
435 tmp = io->buffer_size * oss->frame_bytes;
436
437 /* compute HW buffer big enough to hold SW buffer */
438 for (blkcnt = FREEBSD_OSS_BLKCNT_MIN; blkcnt != FREEBSD_OSS_BLKCNT_MAX; blkcnt *= 2) {
439 if ((blkcnt << blksz_shift) >= tmp)
440 break;
441 }
442
443 tmp = blksz_shift | (blkcnt << 16);
444 if (ioctl(oss->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {
445 perror("SNDCTL_DSP_SETFRAGMENTS");
446 return -EINVAL;
447 }
448
449 tmp = oss->format;
450 if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &tmp) < 0 ||
451 tmp != oss->format) {
452 perror("SNDCTL_DSP_SETFMT");
453 return -EINVAL;
454 }
455
456 tmp = io->channels;
457 if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0 ||
458 tmp != io->channels) {
459 perror("SNDCTL_DSP_CHANNELS");
460 return -EINVAL;
461 }
462
463 tmp = io->rate;
464 if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &tmp) < 0 ||
465 tmp > io->rate * 1.01 || tmp < io->rate * 0.99) {
466 perror("SNDCTL_DSP_SPEED");
467 return -EINVAL;
468 }
469
470 if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
471 SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0) {
472 perror("SNDCTL_DSP_GET[I/O]SPACE");
473 return -EINVAL;
474 }
475
476 oss->bufsz = bi.fragsize * bi.fragstotal;
477
478 #ifdef SNDCTL_DSP_LOW_WATER
479 tmp = ((io->period_size * oss->frame_bytes) * 3) / 4;
480 tmp -= tmp % oss->frame_bytes;
481 if (tmp < oss->frame_bytes)
482 tmp = oss->frame_bytes;
483 if (tmp > bi.fragsize)
484 tmp = bi.fragsize;
485 if (ioctl(oss->fd, SNDCTL_DSP_LOW_WATER, &tmp) < 0)
486 perror("SNDCTL_DSP_LOW_WATER");
487 #endif
488
489 #ifdef FREEBSD_OSS_DEBUG_VERBOSE
490 fprintf(stderr,
491 "\n\n[%lu -> %d] %lu ~ %d -> %d, %lu ~ %d -> %d [d:%ld lw:%d]\n\n",
492 io->buffer_size / io->period_size, bi.fragstotal,
493 io->buffer_size * oss->frame_bytes,
494 (1 << blksz_shift) * blkcnt, oss->bufsz,
495 io->period_size * oss->frame_bytes, 1 << blksz_shift,
496 bi.fragsize,
497 (long)(io->buffer_size * oss->frame_bytes) -
498 oss->bufsz, tmp);
499 #endif
500 #else
501 period_bytes = io->period_size * oss->frame_bytes;
502 oss->period_shift = 0;
503 for (i = 31; i >= 4; i--) {
504 if (period_bytes & (1U << i)) {
505 oss->period_shift = i;
506 break;
507 }
508 }
509 if (! oss->period_shift) {
510 fprintf(stderr, "*** OSS: invalid period size %d\n", (int)io->period_size);
511 return -EINVAL;
512 }
513 oss->periods = io->buffer_size / io->period_size;
514
515 _retry:
516 tmp = oss->period_shift | (oss->periods << 16);
517 if (ioctl(oss->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {
518 if (! oss->fragment_set) {
519 perror("SNDCTL_DSP_SETFRAGMENT");
520 fprintf(stderr, "*** period shift = %d, periods = %d\n", oss->period_shift, oss->periods);
521 return -EINVAL;
522 }
523 /* OSS has no proper way to reinitialize the fragments */
524 /* try to reopen the device */
525 close(oss->fd);
526 oss->fd = open(oss->device, io->stream == SND_PCM_STREAM_PLAYBACK ?
527 O_WRONLY : O_RDONLY);
528 if (oss->fd < 0) {
529 err = -errno;
530 SNDERR("Cannot reopen the device %s", oss->device);
531 return err;
532 }
533 io->poll_fd = oss->fd;
534 io->poll_events = io->stream == SND_PCM_STREAM_PLAYBACK ?
535 POLLOUT : POLLIN;
536 snd_pcm_ioplug_reinit_status(io);
537 oss->fragment_set = 0;
538 goto _retry;
539 }
540 oss->fragment_set = 1;
541 #endif
542
543 if ((flags = fcntl(oss->fd, F_GETFL)) < 0) {
544 err = -errno;
545 perror("F_GETFL");
546 } else {
547 oflags = flags;
548 if (io->nonblock)
549 flags |= O_NONBLOCK;
550 else
551 flags &= ~O_NONBLOCK;
552 if (flags != oflags &&
553 fcntl(oss->fd, F_SETFL, flags) < 0) {
554 err = -errno;
555 perror("F_SETFL");
556 }
557 }
558
559 return 0;
560 }
561
562 static int oss_hw_constraint(snd_pcm_oss_t *oss)
563 {
564 #ifdef __FreeBSD__
565 snd_pcm_ioplug_t *io = &oss->io;
566 static const snd_pcm_access_t access_list[] = {
567 SND_PCM_ACCESS_RW_INTERLEAVED,
568 SND_PCM_ACCESS_MMAP_INTERLEAVED
569 };
570 #ifdef FREEBSD_OSS_BLKCNT_P2
571 unsigned int period_list[30];
572 #endif
573 #ifdef FREEBSD_OSS_BUFSZ_P2
574 unsigned int bufsz_list[30];
575 #endif
576 unsigned int nformats;
577 unsigned int format[ARRAY_SIZE(oss_formats_tab)];
578 #if 0
579 unsigned int nchannels;
580 unsigned int channel[FREEBSD_OSS_CHANNELS_MAX];
581 #endif
582 int i, err, tmp;
583
584 #ifdef FREEBSD_OSS_DEBUG_VERBOSE
585 fprintf(stderr, "%s()\n", __func__);
586 #endif
587
588 /* check trigger */
589 tmp = 0;
590 if (ioctl(oss->fd, SNDCTL_DSP_GETCAPS, &tmp) >= 0) {
591 if (!(tmp & DSP_CAP_TRIGGER))
592 fprintf(stderr, "*** OSS: trigger is not supported!\n");
593 }
594
595 /* access type - interleaved only */
596 if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
597 ARRAY_SIZE(access_list), access_list)) < 0)
598 return err;
599
600 /* supported formats. */
601 tmp = 0;
602 ioctl(oss->fd, SNDCTL_DSP_GETFMTS, &tmp);
603 nformats = 0;
604 for (i = 0; i < ARRAY_SIZE(oss_formats_tab); i++) {
605 if (tmp & oss_formats_tab[i].oss_format)
606 format[nformats++] = oss_formats_tab[i].alsa_format;
607 }
608 if (! nformats)
609 format[nformats++] = SND_PCM_FORMAT_S16;
610 if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
611 nformats, format)) < 0)
612 return err;
613
614 #if 0
615 /* supported channels */
616 nchannels = 0;
617 for (i = 0; i < ARRAY_SIZE(channel); i++) {
618 tmp = i + 1;
619 if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) >= 0 &&
620 1 + i == tmp)
621 channel[nchannels++] = tmp;
622 }
623 if (! nchannels) /* assume 2ch stereo */
624 err = snd_pcm_ioplug_set_param_minmax(io,
625 SND_PCM_IOPLUG_HW_CHANNELS, 2, 2);
626 else
627 err = snd_pcm_ioplug_set_param_list(io,
628 SND_PCM_IOPLUG_HW_CHANNELS, nchannels, channel);
629 if (err < 0)
630 return err;
631 #endif
632 err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
633 FREEBSD_OSS_CHANNELS_MIN, FREEBSD_OSS_CHANNELS_MAX);
634 if (err < 0)
635 return err;
636
637 /* supported rates */
638 err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
639 FREEBSD_OSS_RATE_MIN, FREEBSD_OSS_RATE_MAX);
640 if (err < 0)
641 return err;
642
643 /*
644 * Maximum buffer size on FreeBSD can go up to 131072 bytes without
645 * strict ^2 alignment so that s24le in 3bytes packing can be fed
646 * directly.
647 */
648
649 #ifdef FREEBSD_OSS_BLKCNT_P2
650 tmp = 0;
651 for (i = 1; i < 31 && tmp < ARRAY_SIZE(period_list); i++) {
652 if ((1 << i) > FREEBSD_OSS_BLKCNT_MAX)
653 break;
654 if ((1 << i) < FREEBSD_OSS_BLKCNT_MIN)
655 continue;
656 period_list[tmp++] = 1 << i;
657 }
658
659 if (tmp > 0)
660 err = snd_pcm_ioplug_set_param_list(io,
661 SND_PCM_IOPLUG_HW_PERIODS, tmp, period_list);
662 else
663 #endif
664 /* periods , not strictly ^2 but later on will be refined */
665 err = snd_pcm_ioplug_set_param_minmax(io,
666 SND_PCM_IOPLUG_HW_PERIODS, FREEBSD_OSS_BLKCNT_MIN,
667 FREEBSD_OSS_BLKCNT_MAX);
668 if (err < 0)
669 return err;
670
671 /* period size , not strictly ^2 */
672 err = snd_pcm_ioplug_set_param_minmax(io,
673 SND_PCM_IOPLUG_HW_PERIOD_BYTES, FREEBSD_OSS_BLKSZ_MIN,
674 FREEBSD_OSS_BLKSZ_MAX);
675 if (err < 0)
676 return err;
677
678 #ifdef FREEBSD_OSS_BUFSZ_P2
679 tmp = 0;
680 for (i = 1; i < 31 && tmp < ARRAY_SIZE(bufsz_list); i++) {
681 if ((1 << i) > FREEBSD_OSS_BUFSZ_MAX)
682 break;
683 if ((1 << i) < FREEBSD_OSS_BUFSZ_MIN)
684 continue;
685 bufsz_list[tmp++] = 1 << i;
686 }
687
688 if (tmp > 0)
689 err = snd_pcm_ioplug_set_param_list(io,
690 SND_PCM_IOPLUG_HW_BUFFER_BYTES, tmp, bufsz_list);
691 else
692 #endif
693 /* buffer size , not strictly ^2 */
694 err = snd_pcm_ioplug_set_param_minmax(io,
695 SND_PCM_IOPLUG_HW_BUFFER_BYTES, FREEBSD_OSS_BUFSZ_MIN,
696 FREEBSD_OSS_BUFSZ_MAX);
697 if (err < 0)
698 return err;
699
700 return 0;
701 #else
702 snd_pcm_ioplug_t *io = &oss->io;
703 static const snd_pcm_access_t access_list[] = {
704 SND_PCM_ACCESS_RW_INTERLEAVED,
705 SND_PCM_ACCESS_MMAP_INTERLEAVED
706 };
707 unsigned int nformats;
708 unsigned int format[5];
709 unsigned int nchannels;
710 unsigned int channel[6];
711 /* period and buffer bytes must be power of two */
712 static const unsigned int bytes_list[] = {
713 1U<<8, 1U<<9, 1U<<10, 1U<<11, 1U<<12, 1U<<13, 1U<<14, 1U<<15,
714 1U<<16, 1U<<17, 1U<<18, 1U<<19, 1U<<20, 1U<<21, 1U<<22, 1U<<23
715 };
716 int i, err, tmp;
717
718 /* check trigger */
719 oss->caps = 0;
720 if (ioctl(oss->fd, SNDCTL_DSP_GETCAPS, &oss->caps) >= 0) {
721 if (! (oss->caps & DSP_CAP_TRIGGER))
722 fprintf(stderr, "*** OSS: trigger is not supported!\n");
723 }
724
725 /* access type - interleaved only */
726 if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
727 ARRAY_SIZE(access_list), access_list)) < 0)
728 return err;
729
730 /* supported formats */
731 tmp = 0;
732 ioctl(oss->fd, SNDCTL_DSP_GETFMTS, &tmp);
733 nformats = 0;
734 if (tmp & AFMT_U8)
735 format[nformats++] = SND_PCM_FORMAT_U8;
736 if (tmp & AFMT_S16_LE)
737 format[nformats++] = SND_PCM_FORMAT_S16_LE;
738 if (tmp & AFMT_S16_BE)
739 format[nformats++] = SND_PCM_FORMAT_S16_BE;
740 if (tmp & AFMT_MU_LAW)
741 format[nformats++] = SND_PCM_FORMAT_MU_LAW;
742 if (! nformats)
743 format[nformats++] = SND_PCM_FORMAT_S16;
744 if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
745 nformats, format)) < 0)
746 return err;
747
748 /* supported channels */
749 nchannels = 0;
750 for (i = 0; i < 6; i++) {
751 tmp = i + 1;
752 if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) >= 0)
753 channel[nchannels++] = tmp;
754 }
755 if (! nchannels) /* assume 2ch stereo */
756 err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
757 2, 2);
758 else
759 err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS,
760 nchannels, channel);
761 if (err < 0)
762 return err;
763
764 /* supported rates */
765 /* FIXME: should query? */
766 err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 480000);
767 if (err < 0)
768 return err;
769
770 /* period size (in power of two) */
771 err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
772 ARRAY_SIZE(bytes_list), bytes_list);
773 if (err < 0)
774 return err;
775 /* periods */
776 err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 2, 1024);
777 if (err < 0)
778 return err;
779 /* buffer size (in power of two) */
780 err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
781 ARRAY_SIZE(bytes_list), bytes_list);
782 if (err < 0)
783 return err;
784
785 return 0;
786 #endif
787 }
788
789
790 static int oss_close(snd_pcm_ioplug_t *io)
791 {
792 snd_pcm_oss_t *oss = io->private_data;
793
794 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
795 fprintf(stderr, "%s()\n", __func__);
796 #endif
797
798 close(oss->fd);
799 free(oss->device);
800 free(oss);
801 return 0;
802 }
803
804 static const snd_pcm_ioplug_callback_t oss_playback_callback = {
805 .start = oss_start,
806 .stop = oss_stop,
807 .transfer = oss_write,
808 .pointer = oss_pointer,
809 .close = oss_close,
810 .hw_params = oss_hw_params,
811 #ifndef __FreeBSD__
812 .prepare = oss_prepare,
813 #endif
814 .drain = oss_drain,
815 .delay = oss_delay,
816 };
817
818 static const snd_pcm_ioplug_callback_t oss_capture_callback = {
819 .start = oss_start,
820 .stop = oss_stop,
821 .transfer = oss_read,
822 .pointer = oss_pointer,
823 .close = oss_close,
824 .hw_params = oss_hw_params,
825 #ifndef __FreeBSD__
826 .prepare = oss_prepare,
827 #endif
828 .drain = oss_drain,
829 .delay = oss_delay,
830 };
831
832
833 SND_PCM_PLUGIN_DEFINE_FUNC(oss)
834 {
835 snd_config_iterator_t i, next;
836 const char *device = "/dev/dsp";
837 int err;
838 snd_pcm_oss_t *oss;
839
840 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
841 fprintf(stderr, "%s()\n", __func__);
842 #endif
843
844 snd_config_for_each(i, next, conf) {
845 snd_config_t *n = snd_config_iterator_entry(i);
846 const char *id;
847 if (snd_config_get_id(n, &id) < 0)
848 continue;
849 if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0)
850 continue;
851 if (strcmp(id, "device") == 0) {
852 if (snd_config_get_string(n, &device) < 0) {
853 SNDERR("Invalid type for %s", id);
854 return -EINVAL;
855 }
856 continue;
857 }
858 SNDERR("Unknown field %s", id);
859 return -EINVAL;
860 }
861
862 oss = calloc(1, sizeof(*oss));
863 if (! oss) {
864 SNDERR("cannot allocate");
865 return -ENOMEM;
866 }
867
868 oss->device = strdup(device);
869 if (oss->device == NULL) {
870 SNDERR("cannot allocate");
871 free(oss);
872 return -ENOMEM;
873 }
874 oss->fd = open(device, stream == SND_PCM_STREAM_PLAYBACK ?
875 O_WRONLY : O_RDONLY);
876 if (oss->fd < 0) {
877 err = -errno;
878 SNDERR("Cannot open device %s", device);
879 goto error;
880 }
881
882 oss->io.version = SND_PCM_IOPLUG_VERSION;
883 oss->io.name = "ALSA <-> OSS PCM I/O Plugin";
884 oss->io.poll_fd = oss->fd;
885 oss->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
886 oss->io.mmap_rw = 0;
887 oss->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
888 &oss_playback_callback : &oss_capture_callback;
889 oss->io.private_data = oss;
890
891 err = snd_pcm_ioplug_create(&oss->io, name, stream, mode);
892 if (err < 0)
893 goto error;
894
895 if ((err = oss_hw_constraint(oss)) < 0) {
896 snd_pcm_ioplug_delete(&oss->io);
897 return err;
898 }
899
900 *pcmp = oss->io.pcm;
901 return 0;
902
903 error:
904 if (oss->fd >= 0)
905 close(oss->fd);
906 free(oss->device);
907 free(oss);
908 return err;
909 }
910
911 SND_PCM_PLUGIN_SYMBOL(oss);
912