source: roaraudio/plugins/alsavs/pcm_roar.c @ 5823:f9f70dbaa376

Last change on this file since 5823:f9f70dbaa376 was 5823:f9f70dbaa376, checked in by phi, 11 years ago

updated copyright

File size: 15.2 KB
Line 
1//pcm_roar.c:
2
3/*
4 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2010-2013
5 *      Copyright (C) Hans-Kristian 'maister' Arntzen - 2010-2011
6 *
7 *  This file is part of libroar a part of RoarAudio,
8 *  a cross-platform sound system for both, home and professional use.
9 *  See README for details.
10 *
11 *  This file is free software; you can redistribute it and/or modify
12 *  it under the terms of the GNU General Public License version 3
13 *  as published by the Free Software Foundation.
14 *
15 *  libroar is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 *  GNU General Public License for more details.
19 *
20 *  You should have received a copy of the GNU General Public License
21 *  along with this software; see the file COPYING.  If not, write to
22 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 *  Boston, MA 02110-1301, USA.
24 *
25 *  NOTE for everyone want's to change something and send patches:
26 *  read README and HACKING! There a addition information on
27 *  the license of this document you need to read before you send
28 *  any patches.
29 *
30 *  NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc
31 *  or libpulse*:
32 *  The libs libroaresd, libroararts and libroarpulse link this lib
33 *  and are therefore GPL. Because of this it may be illigal to use
34 *  them with any software that uses libesd, libartsc or libpulse*.
35 */
36
37//#define DEBUG
38#include "roar.h"
39
40// Equvivalent to prepare(). Starts a stream. Might/will be called several times during a program!
41/////////////////////////////////////////////////
42// Status: Should be mostly complete.
43// Condition for invalid fh is not solved here.
44////////////////////////////////////////////////
45static int roar_pcm_start (snd_pcm_ioplug_t * io) {
46 struct roar_alsa_pcm * self = io->private_data;
47 int fh;
48 int val;
49 int err;
50
51 ROAR_DBG("roar_pcm_start(*) = ?");
52
53 // If start is called several times in a row, just ignore it.
54 if (self->stream_opened)
55  return 0;
56
57 if ( (self->vss = roar_vs_new_from_con(&(self->roar.con), &err)) == NULL ) {
58  roar_err_set(err);
59  roar_err_update();
60  return -errno;
61 }
62
63 if ( roar_vs_stream(self->vss, &(self->info),
64                     io->stream == SND_PCM_STREAM_PLAYBACK ? ROAR_DIR_PLAY : ROAR_DIR_MONITOR,
65                     &err) == -1 ) {
66  roar_vs_close(self->vss, ROAR_VS_TRUE, NULL);
67  self->vss = NULL;
68  roar_err_set(err);
69  roar_err_update();
70  return -errno;
71 }
72
73 // ALSA really expects there to be self->bufsize writable bytes.
74 if ( roar_vs_buffer(self->vss, self->bufsize + 1, &err) == -1 ) {
75  roar_vs_close(self->vss, ROAR_VS_TRUE, NULL);
76  self->vss = NULL;
77  roar_err_set(err);
78  roar_err_update();
79  return -errno;
80 }
81
82 val = ROAR_VS_ASYNCLEVEL_AUTO;
83 if ( roar_vs_ctl(self->vss, ROAR_VS_CMD_SET_ASYNC, &val, &err) == -1 ) {
84  roar_vs_close(self->vss, ROAR_VS_TRUE, NULL);
85  self->vss = NULL;
86  roar_err_set(err);
87  roar_err_update();
88  return -errno;
89 }
90
91 if ( self->stream_role != ROAR_ROLE_UNKNOWN ) {
92  if ( roar_vs_role(self->vss, self->stream_role, &err) == -1 ) {
93   ROAR_WARN("roar_pcm_start(*): Can not set stream role: %s(%i): %s",
94             roar_role2str(self->stream_role), self->stream_role, roar_error2str(roar_error));
95  }
96 }
97
98 if ( roar_vio_ctl(roar_vs_vio_obj(self->vss, NULL),
99    io->stream == SND_PCM_STREAM_PLAYBACK ? ROAR_VIO_CTL_GET_SELECT_WRITE_FH :
100    ROAR_VIO_CTL_GET_SELECT_READ_FH, &fh) != 1 ) {
101  io->poll_fd = fh;
102  io->poll_events = io->stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
103 }
104
105 // Oh, no, what should we do here if roar_vio_ctl() fails to grab a valid fh to poll?
106 // In any case, ALSA will error out the next time it tries to poll() if we don't give it a valid fh.
107
108 snd_pcm_ioplug_reinit_status(io);
109
110 // Stream is now active, yay.
111 self->stream_opened = 1;
112
113 ROAR_DBG("roar_pcm_start(*) = 0");
114 return 0;
115}
116
117void roar_plugin_reset(struct roar_alsa_pcm *self) {
118 if ( !self->stream_opened )
119  return;
120
121 roar_vs_close(self->vss, ROAR_VS_TRUE, NULL);
122 self->vss = NULL;
123 self->stream_opened = 0;
124 self->last_ptr = 0;
125}
126
127
128
129// Simply stopping the stream. Will need to be restarted to play more.
130// Will be called several times together with roar_pcm_start()
131///////////////////////////////////////////////////
132// Status: Still needs some error checking for the pthread calls, but
133// should work.
134//////////////////////////////////////////////////
135static int roar_pcm_stop (snd_pcm_ioplug_t *io) {
136 struct roar_alsa_pcm * self = io->private_data;
137
138 // If this is called several times in a row, just ignore.
139
140 ROAR_DBG("roar_pcm_stop(*) = 0");
141
142 roar_plugin_reset(self);
143
144 return 0;
145}
146
147///////////////////////////////
148// Status: Should be complete.
149///////////////////////////////
150static int roar_hw_constraint(struct roar_alsa_pcm * self) {
151 snd_pcm_ioplug_t *io = &(self->io);
152 static const snd_pcm_access_t access_list[] = {
153  SND_PCM_ACCESS_RW_INTERLEAVED
154 };
155 static const unsigned int formats[] = {
156  SND_PCM_FORMAT_S8,
157  SND_PCM_FORMAT_U8,
158  SND_PCM_FORMAT_A_LAW,
159  SND_PCM_FORMAT_MU_LAW,
160  SND_PCM_FORMAT_S16_LE,
161  SND_PCM_FORMAT_S16_BE,
162  SND_PCM_FORMAT_U16_LE,
163  SND_PCM_FORMAT_U16_BE,
164  SND_PCM_FORMAT_S32_LE,
165  SND_PCM_FORMAT_S32_BE,
166  SND_PCM_FORMAT_U32_LE,
167  SND_PCM_FORMAT_U32_BE,
168  SND_PCM_FORMAT_S24_3LE,
169  SND_PCM_FORMAT_S24_3BE,
170  SND_PCM_FORMAT_U24_3LE,
171  SND_PCM_FORMAT_U24_3BE,
172 };
173 int ret;
174
175 ROAR_DBG("roar_hw_constraint(*) = ?");
176
177 if ( (ret = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
178     _as(access_list), access_list)) < 0 )
179  return ret;
180
181 if ( (ret = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
182     _as(formats), formats)) < 0 )
183  return ret;
184
185 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
186     1, ROAR_MAX_CHANNELS)) < 0 )
187  return ret;
188
189 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 192000)) < 0 )
190  return ret;
191
192 // We shouldn't let ALSA use extremely low or high values, it will kill a kitty most likely. :v
193 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 1 << 6, 1 << 18)) < 0 )
194  return ret;
195
196 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 1, 1024)) < 0 )
197  return ret;
198
199 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, 1 << 13, 1 << 24)) < 0 )
200  return ret;
201
202 ROAR_DBG("roar_hw_constraint(*) = 0");
203
204 return 0;
205}
206
207// Referring to alsa-lib/src/pcm/pcm_ioplug.c : snd_pcm_ioplug_hw_ptr_update
208///////////////////////////////////////////////////////////
209// Status: Mostly complete, but uses a really nasty hack!
210///////////////////////////////////////////////////////////
211static snd_pcm_sframes_t roar_pcm_pointer(snd_pcm_ioplug_t *io) {
212 struct roar_alsa_pcm * self = io->private_data;
213 int ptr;
214 int buffered;
215 ssize_t avail;
216
217 ROAR_DBG("roar_pcm_pointer(*) = ?");
218
219 // Did ALSA just call snd_pcm_reset() or something like that without calling the plugin?
220 // We should restart our stream as well.
221 if ( io->appl_ptr < self->last_ptr ) {
222  roar_pcm_stop(io);
223  roar_pcm_start(io);
224 }
225
226 // ALSA expects return value to be equal to the amount of data written on the hardware side.
227 // It uses this to calculate writable amount. (Because hey, just calling a get_avail() is too hard.)
228 // Return value is thus io->appl_ptr - buffered_amount;
229
230 while ( roar_vs_iterate(self->vss, ROAR_VS_NOWAIT, NULL) == 2 );
231
232 avail = roar_vs_get_avail_write(self->vss, NULL);
233 ROAR_DBG("roar_pcm_pointer(*): avail=%lli", (long long int)avail);
234 buffered = snd_pcm_bytes_to_frames(io->pcm, self->bufsize - avail);
235
236 ptr = io->appl_ptr - buffered;
237 self->last_ptr = io->appl_ptr;
238
239 ROAR_DBG("roar_pcm_pointer(*) appl_ptr (frames): %i, buffered (frames): %i", (int)io->appl_ptr, (int)buffered);
240 ROAR_DBG("roar_pcm_pointer(*) = %i", ptr);
241 return ptr;
242}
243
244// TODO: FIXME: add support for reading data!
245//////////////////////////////////////////////////
246// Status: For writing, this should be complete.
247//////////////////////////////////////////////////
248static snd_pcm_sframes_t roar_pcm_transfer(snd_pcm_ioplug_t *io,
249  const snd_pcm_channel_area_t *areas,
250  snd_pcm_uframes_t offset,
251  snd_pcm_uframes_t size) {
252 struct roar_alsa_pcm * self = io->private_data;
253 char * buf;
254 size_t len = size * self->info.channels * self->info.bits / 8;
255 ssize_t ret;
256
257 ROAR_DBG("roar_pcm_transfer(*) = ?");
258 ROAR_DBG("roar_pcm_transfer(*): len=%lu", (long unsigned int) len);
259
260 // Weird ALSA stuff.
261 buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
262
263 while (len) {
264  ret = roar_vs_write(self->vss, buf, len, NULL);
265/*
266 * <ph3-der-loewe> is it supposed to be EIO?
267 * <ph3-der-loewe> or can we return the true error code from roar_vs_write()?
268 * <maister> ALSA tends to give -EIO errors
269 * <maister> It shouldn't cause problems to pass roar_vs errnos.
270 * <maister> But the error strings might be a bit confusing perhaps
271 */
272  if ( ret == -1 )
273   return -EIO;
274  len -= ret;
275  buf += ret;
276 }
277
278 while ( roar_vs_iterate(self->vss, ROAR_VS_NOWAIT, NULL) == 2 );
279
280 ROAR_DBG("roar_pcm_transfer(*) = %lli", (long long int)size);
281 return size;
282}
283
284///////////////////////////////////////////////////////////////////
285// Status: Still missing proper delay measurements from the roar server.
286// Only uses a blind timer now. In ideal conditions, this will work well.
287///////////////////////////////////////////////////////////////////
288static int roar_pcm_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) {
289 struct roar_alsa_pcm * self = io->private_data;
290 roar_mus_t lat;
291
292 ROAR_DBG("roar_pcm_delay(*) = ?");
293
294 lat = roar_vs_latency(self->vss, ROAR_VS_BACKEND_DEFAULT, ROAR_VS_NOWAIT, NULL);
295 *delayp = (lat * self->info.rate) / 1000000;
296
297 return 0;
298}
299
300////////////////////
301// Status: Complete
302////////////////////
303static int roar_pcm_prepare(snd_pcm_ioplug_t *io) {
304 ROAR_DBG("roar_pcm_prepare(*) = ?");
305
306 return roar_pcm_start(io);
307}
308
309///////////////////////////////////////////////////////////////////////////////////////////////////////////////
310// Status: This should be mostly complete.
311// I'm not sure if hw_params can be called several times during one stream without stop() start() in between.
312// This will mean a memory leak, and possibly breakage should the buffer size change itself.
313///////////////////////////////////////////////////////////////////////////////////////////////////////////////
314static int roar_pcm_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) {
315 struct roar_alsa_pcm * self = io->private_data;
316 snd_pcm_uframes_t buffersize;
317 int err;
318
319 ROAR_DBG("roar_pcm_hw_params(*) = ?");
320
321 self->info.channels = io->channels;
322 self->info.rate     = io->rate;
323
324 switch (io->format) {
325  case SND_PCM_FORMAT_S8:
326   self->info.codec = ROAR_CODEC_PCM_U_LE;
327   self->info.bits  = 8;
328   break;
329  case SND_PCM_FORMAT_U8:
330   self->info.codec = ROAR_CODEC_PCM_U_LE;
331   self->info.bits  = 8;
332   break;
333  case SND_PCM_FORMAT_A_LAW:
334   self->info.codec = ROAR_CODEC_ALAW;
335   self->info.bits  = 8;
336   break;
337  case SND_PCM_FORMAT_MU_LAW:
338   self->info.codec = ROAR_CODEC_MULAW;
339   self->info.bits  = 8;
340   break;
341  case SND_PCM_FORMAT_S16_LE:
342   self->info.codec = ROAR_CODEC_PCM_S_LE;
343   self->info.bits  = 16;
344   break;
345  case SND_PCM_FORMAT_S16_BE:
346   self->info.codec = ROAR_CODEC_PCM_S_BE;
347   self->info.bits  = 16;
348   break;
349  case SND_PCM_FORMAT_U16_LE:
350   self->info.codec = ROAR_CODEC_PCM_U_LE;
351   self->info.bits  = 16;
352   break;
353  case SND_PCM_FORMAT_U16_BE:
354   self->info.codec = ROAR_CODEC_PCM_U_BE;
355   self->info.bits  = 16;
356   break;
357  case SND_PCM_FORMAT_S32_LE:
358   self->info.codec = ROAR_CODEC_PCM_S_LE;
359   self->info.bits  = 32;
360   break;
361  case SND_PCM_FORMAT_S32_BE:
362   self->info.codec = ROAR_CODEC_PCM_S_BE;
363   self->info.bits  = 32;
364   break;
365  case SND_PCM_FORMAT_U32_LE:
366   self->info.codec = ROAR_CODEC_PCM_U_LE;
367   self->info.bits  = 32;
368   break;
369  case SND_PCM_FORMAT_U32_BE:
370   self->info.codec = ROAR_CODEC_PCM_U_BE;
371   self->info.bits  = 32;
372   break;
373  case SND_PCM_FORMAT_S24_3LE:
374   self->info.codec = ROAR_CODEC_PCM_S_LE;
375   self->info.bits  = 24;
376   break;
377  case SND_PCM_FORMAT_S24_3BE:
378   self->info.codec = ROAR_CODEC_PCM_S_BE;
379   self->info.bits  = 24;
380   break;
381  case SND_PCM_FORMAT_U24_3LE:
382   self->info.codec = ROAR_CODEC_PCM_U_LE;
383   self->info.bits  = 24;
384   break;
385  case SND_PCM_FORMAT_U24_3BE:
386   self->info.codec = ROAR_CODEC_PCM_U_BE;
387   self->info.bits  = 24;
388   break;
389  default:
390   return -EINVAL;
391 }
392
393 if ((err = snd_pcm_hw_params_get_buffer_size(params, &buffersize) < 0))
394  return err;
395
396 ROAR_DBG("roar_pcm_hw_params(*) buffersize (frames): %i", (int)buffersize);
397
398 self->bufsize = self->info.bits * self->info.channels * buffersize / 8;
399
400 ROAR_DBG("roar_pcm_hw_params(*) Setting buffersize (bytes): %i", (int)self->bufsize);
401 ROAR_DBG("roar_pcm_hw_params(*) = 0");
402 return 0;
403}
404
405///////////////////////////////////
406// Status: This should be complete.
407// This is the last cleanup function to be called by ALSA.
408///////////////////////////////////
409static int roar_pcm_close (snd_pcm_ioplug_t * io) {
410 struct roar_alsa_pcm * self = io->private_data;
411
412 ROAR_DBG("roar_pcm_close(*) = ?");
413
414 roar_plugin_reset(self);
415 roar_disconnect(&(self->roar.con));
416
417 free(self);
418
419 return 0;
420}
421
422static snd_pcm_ioplug_callback_t roar_pcm_callback = {
423 .start                  = roar_pcm_start,
424 .stop                   = roar_pcm_stop,
425 .pointer                = roar_pcm_pointer,
426 .transfer               = roar_pcm_transfer,
427 .delay                  = roar_pcm_delay,
428 .prepare                = roar_pcm_prepare,
429 .hw_params              = roar_pcm_hw_params,
430 .close                  = roar_pcm_close,
431};
432
433SND_PCM_PLUGIN_DEFINE_FUNC(roar) {
434 struct roar_alsa_pcm * self;
435 snd_config_iterator_t i, next;
436 snd_config_t * n;
437 const char   * para;
438 const char   * server = NULL;
439 const char   * tmp;
440 int            role = ROAR_ROLE_UNKNOWN;
441 int            ret;
442
443 (void)root;
444
445 ROAR_DBG("SND_PCM_PLUGIN_DEFINE_FUNC(roar) = ?");
446
447 snd_config_for_each(i, next, conf) {
448  n = snd_config_iterator_entry(i);
449  if ( snd_config_get_id(n, &para) < 0 )
450   continue;
451
452  if ( !strcmp(para, "type") || !strcmp(para, "comment") || !strcmp(para, "hint") )
453   continue;
454
455  if ( !strcmp(para, "server") ) {
456   if (snd_config_get_string(n, &server) < 0) {
457    return -EINVAL;
458   }
459  } else if ( !strcmp(para, "role") ) {
460   if (snd_config_get_string(n, &tmp) < 0) {
461    return -EINVAL;
462   }
463   if ( (role = roar_str2role(tmp)) == -1 ) {
464    return -EINVAL;
465   }
466  } else {
467   return -EINVAL;
468  }
469 }
470
471 errno = ENOSYS;
472
473 if ( (self = malloc(sizeof(struct roar_alsa_pcm))) == NULL )
474  return -errno;
475
476 memset(self, 0, sizeof(struct roar_alsa_pcm));
477
478 self->stream_role = role;
479
480 errno = ENOSYS;
481 if ( roar_simple_connect(&(self->roar.con), server, "ALSA Plugin") == -1 ) {
482  free(self);
483  return -errno;
484 }
485
486 self->io.version      = SND_PCM_IOPLUG_VERSION;
487 self->io.name         = "RoarAudio Plugin";
488 self->io.poll_fd      =  -1;
489 self->io.poll_events  =  POLLOUT;
490 self->io.mmap_rw      =  0;
491 self->io.callback     = &roar_pcm_callback;
492 self->io.private_data =  self;
493
494 if ( (ret = snd_pcm_ioplug_create(&(self->io), name, stream, mode)) < 0 ) {
495  roar_disconnect(&(self->roar.con));
496  free(self);
497  return ret;
498 }
499
500 if ( (ret = roar_hw_constraint(self)) < 0 ) {
501  snd_pcm_ioplug_delete(&(self->io));
502  roar_disconnect(&(self->roar.con));
503  free(self);
504  return ret;
505 }
506
507 *pcmp = self->io.pcm;
508
509 ROAR_DBG("SND_PCM_PLUGIN_DEFINE_FUNC(roar) = 0");
510
511 return 0;
512}
513
514SND_PCM_PLUGIN_SYMBOL(roar);
515
516//ll
Note: See TracBrowser for help on using the repository browser.